mirror of
https://github.com/andwati/zitadel.git
synced 2026-10-25 16:52:17 +00:00
feat: user profile requests in resource APIs (#10151)
# Which Problems Are Solved The commands for the resource based v2beta AuthorizationService API are added. Authorizations, previously knows as user grants, give a user in a specific organization and project context roles. The project can be owned or granted. The given roles can be used to restrict access within the projects applications. The commands for the resource based v2beta InteralPermissionService API are added. Administrators, previously knows as memberships, give a user in a specific organization and project context roles. The project can be owned or granted. The give roles give the user permissions to manage different resources in Zitadel. API definitions from https://github.com/zitadel/zitadel/issues/9165 are implemented. Contains endpoints for user metadata. # How the Problems Are Solved ### New Methods - CreateAuthorization - UpdateAuthorization - DeleteAuthorization - ActivateAuthorization - DeactivateAuthorization - ListAuthorizations - CreateAdministrator - UpdateAdministrator - DeleteAdministrator - ListAdministrators - SetUserMetadata to set metadata on a user - DeleteUserMetadata to delete metadata on a user - ListUserMetadata to query for metadata of a user ## Deprecated Methods ### v1.ManagementService - GetUserGrantByID - ListUserGrants - AddUserGrant - UpdateUserGrant - DeactivateUserGrant - ReactivateUserGrant - RemoveUserGrant - BulkRemoveUserGrant ### v1.AuthService - ListMyUserGrants - ListMyProjectPermissions # Additional Changes - Permission checks for metadata functionality on query and command side - correct existence checks for resources, for example you can only be an administrator on an existing project - combined all member tables to singular query for the administrators - add permission checks for command an query side functionality - combined functions on command side where necessary for easier maintainability # Additional Context Closes #9165 --------- Co-authored-by: Elio Bischof <[email protected]> Co-authored-by: Copilot <[email protected]> Co-authored-by: Livio Spring <[email protected]>
This commit is contained in:
co-authored by
Elio Bischof
Copilot
Livio Spring
parent
9ebf2316c6
commit
5403be7c4b
@@ -135,6 +135,8 @@ message CreateUserRequest {
|
||||
```
|
||||
|
||||
Only allow providing a context where it is required. The context MUST not be provided if not required.
|
||||
If the context is required but deferrable, the context can be defaulted.
|
||||
For example, creating an Authorization without an organization id will default the organization id to the projects resource owner.
|
||||
For example, when retrieving or updating a user, the `organization_id` is not required, since the user can be determined by the user's id.
|
||||
However, it is possible to provide the `organization_id` as a filter to retrieve a list of users of a specific organization.
|
||||
|
||||
|
||||
+9
-1
@@ -38,10 +38,12 @@ import (
|
||||
"github.com/zitadel/zitadel/internal/api/grpc/admin"
|
||||
app "github.com/zitadel/zitadel/internal/api/grpc/app/v2beta"
|
||||
"github.com/zitadel/zitadel/internal/api/grpc/auth"
|
||||
authorization_v2beta "github.com/zitadel/zitadel/internal/api/grpc/authorization/v2beta"
|
||||
feature_v2 "github.com/zitadel/zitadel/internal/api/grpc/feature/v2"
|
||||
feature_v2beta "github.com/zitadel/zitadel/internal/api/grpc/feature/v2beta"
|
||||
idp_v2 "github.com/zitadel/zitadel/internal/api/grpc/idp/v2"
|
||||
instance "github.com/zitadel/zitadel/internal/api/grpc/instance/v2beta"
|
||||
internal_permission_v2beta "github.com/zitadel/zitadel/internal/api/grpc/internal_permission/v2beta"
|
||||
"github.com/zitadel/zitadel/internal/api/grpc/management"
|
||||
oidc_v2 "github.com/zitadel/zitadel/internal/api/grpc/oidc/v2"
|
||||
oidc_v2beta "github.com/zitadel/zitadel/internal/api/grpc/oidc/v2beta"
|
||||
@@ -474,7 +476,7 @@ func startAPIs(
|
||||
if err := apis.RegisterService(ctx, user_v2beta.CreateServer(commands, queries, keys.User, keys.IDPConfig, idp.CallbackURL(), idp.SAMLRootURL(), assets.AssetAPI(), permissionCheck)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := apis.RegisterService(ctx, user_v2.CreateServer(config.SystemDefaults, commands, queries, keys.User, keys.IDPConfig, idp.CallbackURL(), idp.SAMLRootURL(), assets.AssetAPI(), permissionCheck)); err != nil {
|
||||
if err := apis.RegisterService(ctx, user_v2.CreateServer(commands, queries, config.SystemDefaults, keys.User, keys.IDPConfig, idp.CallbackURL(), idp.SAMLRootURL(), assets.AssetAPI(), permissionCheck)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := apis.RegisterService(ctx, session_v2beta.CreateServer(commands, queries, permissionCheck)); err != nil {
|
||||
@@ -510,6 +512,9 @@ func startAPIs(
|
||||
if err := apis.RegisterService(ctx, project_v2beta.CreateServer(config.SystemDefaults, commands, queries, permissionCheck)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := apis.RegisterService(ctx, internal_permission_v2beta.CreateServer(config.SystemDefaults, commands, queries, permissionCheck)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := apis.RegisterService(ctx, userschema_v3_alpha.CreateServer(config.SystemDefaults, commands, queries)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -525,6 +530,9 @@ func startAPIs(
|
||||
if err := apis.RegisterService(ctx, debug_events.CreateServer(commands, queries)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := apis.RegisterService(ctx, authorization_v2beta.CreateServer(config.SystemDefaults, commands, queries, permissionCheck)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := apis.RegisterService(ctx, app.CreateServer(commands, queries, permissionCheck)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -406,6 +406,24 @@ module.exports = {
|
||||
categoryLinkSource: "auto",
|
||||
},
|
||||
},
|
||||
authorization_v2: {
|
||||
specPath:
|
||||
".artifacts/openapi3/zitadel/authorization/v2beta/authorization_service.openapi.yaml",
|
||||
outputDir: "docs/apis/resources/authorization_service_v2",
|
||||
sidebarOptions: {
|
||||
groupPathsBy: "tag",
|
||||
categoryLinkSource: "auto",
|
||||
},
|
||||
},
|
||||
internal_permission_v2: {
|
||||
specPath:
|
||||
".artifacts/openapi3/zitadel/internal_permission/v2beta/internal_permission_service.openapi.yaml",
|
||||
outputDir: "docs/apis/resources/internal_permission_service_v2",
|
||||
sidebarOptions: {
|
||||
groupPathsBy: "tag",
|
||||
categoryLinkSource: "auto",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
|
||||
@@ -16,6 +16,8 @@ const sidebar_api_actions_v2 = require("./docs/apis/resources/action_service_v2/
|
||||
const sidebar_api_project_service_v2 = require("./docs/apis/resources/project_service_v2/sidebar.ts").default
|
||||
const sidebar_api_webkey_service_v2 = require("./docs/apis/resources/webkey_service_v2/sidebar.ts").default
|
||||
const sidebar_api_instance_service_v2 = require("./docs/apis/resources/instance_service_v2/sidebar.ts").default
|
||||
const sidebar_api_authorization_service_v2 = require("./docs/apis/resources/authorization_service_v2/sidebar.ts").default
|
||||
const sidebar_api_permission_service_v2 = require("./docs/apis/resources/internal_permission_service_v2/sidebar.ts").default
|
||||
const sidebar_api_app_v2 = require("./docs/apis/resources/application_service_v2/sidebar.ts").default
|
||||
|
||||
module.exports = {
|
||||
@@ -914,6 +916,37 @@ module.exports = {
|
||||
},
|
||||
items: sidebar_api_app_v2,
|
||||
},
|
||||
{
|
||||
type: "category",
|
||||
label: "Authorizations (Beta)",
|
||||
link: {
|
||||
type: "generated-index",
|
||||
title: "Authorization Service API (Beta)",
|
||||
slug: "/apis/resources/authorization_service_v2",
|
||||
description:
|
||||
"AuthorizationService provides methods to manage authorizations for users within your projects and applications.\n" +
|
||||
"\n" +
|
||||
"For managing permissions and roles for ZITADEL internal resources, like organizations, projects,\n" +
|
||||
"users, etc., please use the InternalPermissionService."+
|
||||
"\n"+
|
||||
"This API is in beta state. It can AND will continue breaking until a stable version is released.\n"
|
||||
},
|
||||
items: sidebar_api_authorization_service_v2,
|
||||
},
|
||||
{
|
||||
type: "category",
|
||||
label: "Permissions (Beta)",
|
||||
link: {
|
||||
type: "generated-index",
|
||||
title: "Permission Service API (Beta)",
|
||||
slug: "/apis/resources/permission_service_v2",
|
||||
description:
|
||||
"This API is intended to manage internal permissions in ZITADEL.\n" +
|
||||
"\n"+
|
||||
"This API is in beta state. It can AND will continue breaking until a stable version is released.\n"
|
||||
},
|
||||
items: sidebar_api_permission_service_v2,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
|
||||
@@ -782,12 +782,3 @@ func TestServer_ListExecutions(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func containExecution(t *assert.CollectT, executionList []*action.Execution, execution *action.Execution) bool {
|
||||
for _, exec := range executionList {
|
||||
if assert.EqualExportedValues(t, execution, exec) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -659,7 +659,7 @@ func (s *Server) getUsers(ctx context.Context, org string, withPasswords bool, w
|
||||
if err != nil {
|
||||
return nil, nil, nil, nil, err
|
||||
}
|
||||
metadataList, err := s.query.SearchUserMetadata(ctx, false, user.ID, &query.UserMetadataSearchQueries{Queries: []query.SearchQuery{metadataOrgSearch}}, false)
|
||||
metadataList, err := s.query.SearchUserMetadata(ctx, false, user.ID, &query.UserMetadataSearchQueries{Queries: []query.SearchQuery{metadataOrgSearch}}, nil)
|
||||
metaspan.EndWithError(err)
|
||||
if err != nil {
|
||||
return nil, nil, nil, nil, err
|
||||
@@ -984,7 +984,7 @@ func (s *Server) getNecessaryUserGrantsForOrg(ctx context.Context, org string, p
|
||||
return nil, err
|
||||
}
|
||||
|
||||
queriedUserGrants, err := s.query.UserGrants(ctx, &query.UserGrantsQueries{Queries: []query.SearchQuery{userGrantSearchOrg}}, true)
|
||||
queriedUserGrants, err := s.query.UserGrants(ctx, &query.UserGrantsQueries{Queries: []query.SearchQuery{userGrantSearchOrg}}, true, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/api/authz"
|
||||
"github.com/zitadel/zitadel/internal/api/grpc/member"
|
||||
"github.com/zitadel/zitadel/internal/api/grpc/object"
|
||||
admin_pb "github.com/zitadel/zitadel/pkg/grpc/admin"
|
||||
@@ -33,35 +34,35 @@ func (s *Server) ListIAMMembers(ctx context.Context, req *admin_pb.ListIAMMember
|
||||
}
|
||||
|
||||
func (s *Server) AddIAMMember(ctx context.Context, req *admin_pb.AddIAMMemberRequest) (*admin_pb.AddIAMMemberResponse, error) {
|
||||
member, err := s.command.AddInstanceMember(ctx, req.UserId, req.Roles...)
|
||||
member, err := s.command.AddInstanceMember(ctx, AddIAMMemberToCommand(req, authz.GetInstance(ctx).InstanceID()))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &admin_pb.AddIAMMemberResponse{
|
||||
Details: object.AddToDetailsPb(
|
||||
member.Sequence,
|
||||
member.ChangeDate,
|
||||
member.EventDate,
|
||||
member.ResourceOwner,
|
||||
),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *Server) UpdateIAMMember(ctx context.Context, req *admin_pb.UpdateIAMMemberRequest) (*admin_pb.UpdateIAMMemberResponse, error) {
|
||||
member, err := s.command.ChangeInstanceMember(ctx, UpdateIAMMemberToDomain(req))
|
||||
member, err := s.command.ChangeInstanceMember(ctx, UpdateIAMMemberToCommand(req, authz.GetInstance(ctx).InstanceID()))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &admin_pb.UpdateIAMMemberResponse{
|
||||
Details: object.ChangeToDetailsPb(
|
||||
member.Sequence,
|
||||
member.ChangeDate,
|
||||
member.EventDate,
|
||||
member.ResourceOwner,
|
||||
),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *Server) RemoveIAMMember(ctx context.Context, req *admin_pb.RemoveIAMMemberRequest) (*admin_pb.RemoveIAMMemberResponse, error) {
|
||||
objectDetails, err := s.command.RemoveInstanceMember(ctx, req.UserId)
|
||||
objectDetails, err := s.command.RemoveInstanceMember(ctx, authz.GetInstance(ctx).InstanceID(), req.UserId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -3,23 +3,25 @@ package admin
|
||||
import (
|
||||
member_grpc "github.com/zitadel/zitadel/internal/api/grpc/member"
|
||||
"github.com/zitadel/zitadel/internal/api/grpc/object"
|
||||
"github.com/zitadel/zitadel/internal/domain"
|
||||
"github.com/zitadel/zitadel/internal/command"
|
||||
"github.com/zitadel/zitadel/internal/query"
|
||||
admin_pb "github.com/zitadel/zitadel/pkg/grpc/admin"
|
||||
member_pb "github.com/zitadel/zitadel/pkg/grpc/member"
|
||||
)
|
||||
|
||||
func AddIAMMemberToDomain(req *admin_pb.AddIAMMemberRequest) *domain.Member {
|
||||
return &domain.Member{
|
||||
UserID: req.UserId,
|
||||
Roles: req.Roles,
|
||||
func AddIAMMemberToCommand(req *admin_pb.AddIAMMemberRequest, instanceID string) *command.AddInstanceMember {
|
||||
return &command.AddInstanceMember{
|
||||
InstanceID: instanceID,
|
||||
UserID: req.UserId,
|
||||
Roles: req.Roles,
|
||||
}
|
||||
}
|
||||
|
||||
func UpdateIAMMemberToDomain(req *admin_pb.UpdateIAMMemberRequest) *domain.Member {
|
||||
return &domain.Member{
|
||||
UserID: req.UserId,
|
||||
Roles: req.Roles,
|
||||
func UpdateIAMMemberToCommand(req *admin_pb.UpdateIAMMemberRequest, instanceID string) *command.ChangeInstanceMember {
|
||||
return &command.ChangeInstanceMember{
|
||||
InstanceID: instanceID,
|
||||
UserID: req.UserId,
|
||||
Roles: req.Roles,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -27,7 +27,7 @@ func TestAddIAMMemberToDomain(t *testing.T) {
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got := AddIAMMemberToDomain(tt.args.req)
|
||||
got := AddIAMMemberToCommand(tt.args.req, "INSTANCE")
|
||||
test.AssertFieldsMapped(t, got, "ObjectRoot")
|
||||
})
|
||||
}
|
||||
@@ -53,7 +53,7 @@ func TestUpdateIAMMemberToDomain(t *testing.T) {
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got := UpdateIAMMemberToDomain(tt.args.req)
|
||||
got := UpdateIAMMemberToCommand(tt.args.req, "INSTANCE")
|
||||
test.AssertFieldsMapped(t, got, "ObjectRoot")
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1046,7 +1046,7 @@ func importOrg2(ctx context.Context, s *Server, errors *[]*admin_pb.ImportDataEr
|
||||
if org.UserGrants != nil {
|
||||
for _, grant := range org.GetUserGrants() {
|
||||
logging.Debugf("import usergrant: %s", grant.GetProjectId()+"_"+grant.GetUserId())
|
||||
_, err := s.command.AddUserGrant(ctx, management.AddUserGrantRequestToDomain(grant), org.GetOrgId())
|
||||
_, err := s.command.AddUserGrant(ctx, management.AddUserGrantRequestToDomain(grant, org.GetOrgId()), nil)
|
||||
if err != nil {
|
||||
*errors = append(*errors, &admin_pb.ImportDataError{Type: "user_grant", Id: org.GetOrgId() + "_" + grant.GetProjectId() + "_" + grant.GetUserId(), Message: err.Error()})
|
||||
if isCtxTimeout(ctx) {
|
||||
@@ -1088,7 +1088,7 @@ func importOrgMembers(ctx context.Context, s *Server, errors *[]*admin_pb.Import
|
||||
}
|
||||
for _, member := range org.GetOrgMembers() {
|
||||
logging.Debugf("import orgmember: %s", member.GetUserId())
|
||||
_, err := s.command.AddOrgMember(ctx, org.GetOrgId(), member.GetUserId(), member.GetRoles()...)
|
||||
_, err := s.command.AddOrgMember(ctx, management.AddOrgMemberRequestToCommand(member, org.GetOrgId()))
|
||||
if err != nil {
|
||||
*errors = append(*errors, &admin_pb.ImportDataError{Type: "org_member", Id: org.GetOrgId() + "_" + member.GetUserId(), Message: err.Error()})
|
||||
if isCtxTimeout(ctx) {
|
||||
@@ -1112,7 +1112,7 @@ func importProjectGrantMembers(ctx context.Context, s *Server, errors *[]*admin_
|
||||
}
|
||||
for _, member := range org.GetProjectGrantMembers() {
|
||||
logging.Debugf("import projectgrantmember: %s", member.GetProjectId()+"_"+member.GetGrantId()+"_"+member.GetUserId())
|
||||
_, err := s.command.AddProjectGrantMember(ctx, management.AddProjectGrantMemberRequestToDomain(member))
|
||||
_, err := s.command.AddProjectGrantMember(ctx, management.AddProjectGrantMemberRequestToCommand(member, org.GetOrgId()))
|
||||
if err != nil {
|
||||
*errors = append(*errors, &admin_pb.ImportDataError{Type: "project_grant_member", Id: org.GetOrgId() + "_" + member.GetProjectId() + "_" + member.GetGrantId() + "_" + member.GetUserId(), Message: err.Error()})
|
||||
if isCtxTimeout(ctx) {
|
||||
@@ -1136,7 +1136,7 @@ func importProjectMembers(ctx context.Context, s *Server, errors *[]*admin_pb.Im
|
||||
}
|
||||
for _, member := range org.GetProjectMembers() {
|
||||
logging.Debugf("import orgmember: %s", member.GetProjectId()+"_"+member.GetUserId())
|
||||
_, err := s.command.AddProjectMember(ctx, management.AddProjectMemberRequestToDomain(member), org.GetOrgId())
|
||||
_, err := s.command.AddProjectMember(ctx, management.AddProjectMemberRequestToCommand(member, org.GetOrgId()))
|
||||
if err != nil {
|
||||
*errors = append(*errors, &admin_pb.ImportDataError{Type: "project_member", Id: org.GetOrgId() + "_" + member.GetProjectId() + "_" + member.GetUserId(), Message: err.Error()})
|
||||
if isCtxTimeout(ctx) {
|
||||
|
||||
@@ -457,7 +457,7 @@ func TestServer_ImportData(t *testing.T) {
|
||||
{
|
||||
Type: "project_grant_member",
|
||||
Id: orgIDs[5] + "_" + projectIDs[4] + "_" + grantIDs[5] + "_" + userIDs[2],
|
||||
Message: "ID=V3-DKcYh Message=Errors.Project.Member.AlreadyExists Parent=(ERROR: duplicate key value violates unique constraint \"unique_constraints_pkey\" (SQLSTATE 23505))",
|
||||
Message: "ID=PROJECT-37fug Message=Errors.AlreadyExists",
|
||||
},
|
||||
},
|
||||
Success: &admin.ImportDataSuccess{
|
||||
|
||||
@@ -32,7 +32,7 @@ func (s *Server) RemoveMyUser(ctx context.Context, _ *auth_pb.RemoveMyUserReques
|
||||
return nil, err
|
||||
}
|
||||
queries := &query.UserGrantsQueries{Queries: []query.SearchQuery{userGrantUserID}}
|
||||
grants, err := s.query.UserGrants(ctx, queries, true)
|
||||
grants, err := s.query.UserGrants(ctx, queries, true, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -97,7 +97,7 @@ func (s *Server) ListMyMetadata(ctx context.Context, req *auth_pb.ListMyMetadata
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res, err := s.query.SearchUserMetadata(ctx, true, authz.GetCtxData(ctx).UserID, queries, false)
|
||||
res, err := s.query.SearchUserMetadata(ctx, true, authz.GetCtxData(ctx).UserID, queries, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -151,7 +151,7 @@ func (s *Server) ListMyUserGrants(ctx context.Context, req *auth_pb.ListMyUserGr
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res, err := s.query.UserGrants(ctx, queries, false)
|
||||
res, err := s.query.UserGrants(ctx, queries, false, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -180,7 +180,7 @@ func (s *Server) ListMyProjectOrgs(ctx context.Context, req *auth_pb.ListMyProje
|
||||
return nil, err
|
||||
}
|
||||
|
||||
grants, err := s.query.UserGrants(ctx, &query.UserGrantsQueries{Queries: []query.SearchQuery{userGrantProjectID, userGrantUserID}}, false)
|
||||
grants, err := s.query.UserGrants(ctx, &query.UserGrantsQueries{Queries: []query.SearchQuery{userGrantProjectID, userGrantUserID}}, false, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
package authorization
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"connectrpc.com/connect"
|
||||
"google.golang.org/protobuf/types/known/timestamppb"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/domain"
|
||||
"github.com/zitadel/zitadel/internal/eventstore/v1/models"
|
||||
authorization "github.com/zitadel/zitadel/pkg/grpc/authorization/v2beta"
|
||||
)
|
||||
|
||||
func (s *Server) CreateAuthorization(ctx context.Context, req *connect.Request[authorization.CreateAuthorizationRequest]) (*connect.Response[authorization.CreateAuthorizationResponse], error) {
|
||||
grant := &domain.UserGrant{
|
||||
UserID: req.Msg.UserId,
|
||||
ProjectID: req.Msg.ProjectId,
|
||||
RoleKeys: req.Msg.RoleKeys,
|
||||
ObjectRoot: models.ObjectRoot{
|
||||
ResourceOwner: req.Msg.GetOrganizationId(),
|
||||
},
|
||||
}
|
||||
grant, err := s.command.AddUserGrant(ctx, grant, s.command.NewPermissionCheckUserGrantWrite(ctx))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return connect.NewResponse(&authorization.CreateAuthorizationResponse{
|
||||
Id: grant.AggregateID,
|
||||
CreationDate: timestamppb.New(grant.ChangeDate),
|
||||
}), nil
|
||||
}
|
||||
|
||||
func (s *Server) UpdateAuthorization(ctx context.Context, request *connect.Request[authorization.UpdateAuthorizationRequest]) (*connect.Response[authorization.UpdateAuthorizationResponse], error) {
|
||||
userGrant, err := s.command.ChangeUserGrant(ctx, &domain.UserGrant{
|
||||
ObjectRoot: models.ObjectRoot{
|
||||
AggregateID: request.Msg.Id,
|
||||
},
|
||||
RoleKeys: request.Msg.RoleKeys,
|
||||
}, true, true, s.command.NewPermissionCheckUserGrantWrite(ctx))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return connect.NewResponse(&authorization.UpdateAuthorizationResponse{
|
||||
ChangeDate: timestamppb.New(userGrant.ChangeDate),
|
||||
}), nil
|
||||
}
|
||||
|
||||
func (s *Server) DeleteAuthorization(ctx context.Context, request *connect.Request[authorization.DeleteAuthorizationRequest]) (*connect.Response[authorization.DeleteAuthorizationResponse], error) {
|
||||
details, err := s.command.RemoveUserGrant(ctx, request.Msg.Id, "", true, s.command.NewPermissionCheckUserGrantDelete(ctx))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return connect.NewResponse(&authorization.DeleteAuthorizationResponse{
|
||||
DeletionDate: timestamppb.New(details.EventDate),
|
||||
}), nil
|
||||
}
|
||||
|
||||
func (s *Server) ActivateAuthorization(ctx context.Context, request *connect.Request[authorization.ActivateAuthorizationRequest]) (*connect.Response[authorization.ActivateAuthorizationResponse], error) {
|
||||
details, err := s.command.ReactivateUserGrant(ctx, request.Msg.Id, "", s.command.NewPermissionCheckUserGrantWrite(ctx))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return connect.NewResponse(&authorization.ActivateAuthorizationResponse{
|
||||
ChangeDate: timestamppb.New(details.EventDate),
|
||||
}), nil
|
||||
}
|
||||
|
||||
func (s *Server) DeactivateAuthorization(ctx context.Context, request *connect.Request[authorization.DeactivateAuthorizationRequest]) (*connect.Response[authorization.DeactivateAuthorizationResponse], error) {
|
||||
details, err := s.command.DeactivateUserGrant(ctx, request.Msg.Id, "", s.command.NewPermissionCheckUserGrantWrite(ctx))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return connect.NewResponse(&authorization.DeactivateAuthorizationResponse{
|
||||
ChangeDate: timestamppb.New(details.EventDate),
|
||||
}), nil
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,971 @@
|
||||
//go:build integration
|
||||
|
||||
package authorization_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/brianvoe/gofakeit/v6"
|
||||
"github.com/muhlemmer/gu"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/integration"
|
||||
authorization "github.com/zitadel/zitadel/pkg/grpc/authorization/v2beta"
|
||||
filter "github.com/zitadel/zitadel/pkg/grpc/filter/v2beta"
|
||||
project "github.com/zitadel/zitadel/pkg/grpc/project/v2beta"
|
||||
"github.com/zitadel/zitadel/pkg/grpc/user/v2"
|
||||
)
|
||||
|
||||
func TestServer_ListAuthorizations(t *testing.T) {
|
||||
iamOwnerCtx := Instance.WithAuthorizationToken(EmptyCTX, integration.UserTypeIAMOwner)
|
||||
projectOwnerResp := Instance.CreateMachineUser(iamOwnerCtx)
|
||||
projectOwnerPatResp := Instance.CreatePersonalAccessToken(iamOwnerCtx, projectOwnerResp.GetUserId())
|
||||
projectResp := createProject(iamOwnerCtx, Instance, t, Instance.DefaultOrg.GetId(), false, false)
|
||||
Instance.CreateProjectMembership(t, iamOwnerCtx, projectResp.GetId(), projectOwnerResp.GetUserId())
|
||||
projectOwnerCtx := integration.WithAuthorizationToken(EmptyCTX, projectOwnerPatResp.Token)
|
||||
|
||||
projectGrantOwnerResp := Instance.CreateMachineUser(iamOwnerCtx)
|
||||
projectGrantOwnerPatResp := Instance.CreatePersonalAccessToken(iamOwnerCtx, projectGrantOwnerResp.GetUserId())
|
||||
grantedProjectResp := createGrantedProject(iamOwnerCtx, Instance, t, projectResp)
|
||||
Instance.CreateProjectGrantMembership(t, iamOwnerCtx, projectResp.GetId(), grantedProjectResp.GetGrantedOrganizationId(), projectGrantOwnerResp.GetUserId())
|
||||
projectGrantOwnerCtx := integration.WithAuthorizationToken(EmptyCTX, projectGrantOwnerPatResp.Token)
|
||||
|
||||
type args struct {
|
||||
ctx context.Context
|
||||
dep func(*authorization.ListAuthorizationsRequest, *authorization.ListAuthorizationsResponse)
|
||||
req *authorization.ListAuthorizationsRequest
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want *authorization.ListAuthorizationsResponse
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "list by user id, unauthenticated",
|
||||
args: args{
|
||||
ctx: EmptyCTX,
|
||||
dep: func(request *authorization.ListAuthorizationsRequest, response *authorization.ListAuthorizationsResponse) {
|
||||
userResp := Instance.CreateUserTypeHuman(iamOwnerCtx, gofakeit.Email())
|
||||
|
||||
request.Filters[0].Filter = &authorization.AuthorizationsSearchFilter_UserId{
|
||||
UserId: &filter.IDFilter{
|
||||
Id: userResp.GetId(),
|
||||
},
|
||||
}
|
||||
createAuthorization(iamOwnerCtx, Instance, t, Instance.DefaultOrg.GetId(), userResp.GetId(), false)
|
||||
},
|
||||
req: &authorization.ListAuthorizationsRequest{
|
||||
Filters: []*authorization.AuthorizationsSearchFilter{{}},
|
||||
},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "list by id, no permission",
|
||||
args: args{
|
||||
ctx: Instance.WithAuthorizationToken(EmptyCTX, integration.UserTypeNoPermission),
|
||||
dep: func(request *authorization.ListAuthorizationsRequest, response *authorization.ListAuthorizationsResponse) {
|
||||
userResp := Instance.CreateUserTypeHuman(iamOwnerCtx, gofakeit.Email())
|
||||
|
||||
request.Filters[0].Filter = &authorization.AuthorizationsSearchFilter_UserId{
|
||||
UserId: &filter.IDFilter{
|
||||
Id: userResp.GetId(),
|
||||
},
|
||||
}
|
||||
createAuthorization(iamOwnerCtx, Instance, t, Instance.DefaultOrg.GetId(), userResp.GetId(), false)
|
||||
},
|
||||
req: &authorization.ListAuthorizationsRequest{
|
||||
Filters: []*authorization.AuthorizationsSearchFilter{{}},
|
||||
},
|
||||
},
|
||||
want: &authorization.ListAuthorizationsResponse{
|
||||
Pagination: &filter.PaginationResponse{
|
||||
TotalResult: 1,
|
||||
AppliedLimit: 100,
|
||||
},
|
||||
Authorizations: []*authorization.Authorization{},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "list, not found",
|
||||
args: args{
|
||||
ctx: iamOwnerCtx,
|
||||
req: &authorization.ListAuthorizationsRequest{
|
||||
Filters: []*authorization.AuthorizationsSearchFilter{
|
||||
{Filter: &authorization.AuthorizationsSearchFilter_UserId{
|
||||
UserId: &filter.IDFilter{
|
||||
Id: "notexisting",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
want: &authorization.ListAuthorizationsResponse{
|
||||
Pagination: &filter.PaginationResponse{
|
||||
TotalResult: 0,
|
||||
AppliedLimit: 100,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "list single id, project",
|
||||
args: args{
|
||||
ctx: iamOwnerCtx,
|
||||
dep: func(request *authorization.ListAuthorizationsRequest, response *authorization.ListAuthorizationsResponse) {
|
||||
userResp := Instance.CreateUserTypeHuman(iamOwnerCtx, gofakeit.Email())
|
||||
|
||||
request.Filters[0].Filter = &authorization.AuthorizationsSearchFilter_UserId{
|
||||
UserId: &filter.IDFilter{
|
||||
Id: userResp.GetId(),
|
||||
},
|
||||
}
|
||||
response.Authorizations[0] = createAuthorization(iamOwnerCtx, Instance, t, Instance.DefaultOrg.GetId(), userResp.GetId(), false)
|
||||
},
|
||||
req: &authorization.ListAuthorizationsRequest{
|
||||
Filters: []*authorization.AuthorizationsSearchFilter{{}},
|
||||
},
|
||||
},
|
||||
want: &authorization.ListAuthorizationsResponse{
|
||||
Pagination: &filter.PaginationResponse{
|
||||
TotalResult: 1,
|
||||
AppliedLimit: 100,
|
||||
},
|
||||
Authorizations: []*authorization.Authorization{
|
||||
{},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "list single id",
|
||||
args: args{
|
||||
ctx: iamOwnerCtx,
|
||||
dep: func(request *authorization.ListAuthorizationsRequest, response *authorization.ListAuthorizationsResponse) {
|
||||
userResp := Instance.CreateUserTypeHuman(iamOwnerCtx, gofakeit.Email())
|
||||
|
||||
resp := createAuthorization(iamOwnerCtx, Instance, t, Instance.DefaultOrg.GetId(), userResp.GetId(), false)
|
||||
request.Filters[0].Filter = &authorization.AuthorizationsSearchFilter_AuthorizationIds{
|
||||
AuthorizationIds: &filter.InIDsFilter{
|
||||
Ids: []string{resp.GetId()},
|
||||
},
|
||||
}
|
||||
response.Authorizations[0] = resp
|
||||
},
|
||||
req: &authorization.ListAuthorizationsRequest{
|
||||
Filters: []*authorization.AuthorizationsSearchFilter{{}},
|
||||
},
|
||||
},
|
||||
want: &authorization.ListAuthorizationsResponse{
|
||||
Pagination: &filter.PaginationResponse{
|
||||
TotalResult: 1,
|
||||
AppliedLimit: 100,
|
||||
},
|
||||
Authorizations: []*authorization.Authorization{
|
||||
{},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "list single project id",
|
||||
args: args{
|
||||
ctx: iamOwnerCtx,
|
||||
dep: func(request *authorization.ListAuthorizationsRequest, response *authorization.ListAuthorizationsResponse) {
|
||||
userResp := Instance.CreateUserTypeHuman(iamOwnerCtx, gofakeit.Email())
|
||||
|
||||
resp := createAuthorization(iamOwnerCtx, Instance, t, Instance.DefaultOrg.GetId(), userResp.GetId(), false)
|
||||
request.Filters[0].Filter = &authorization.AuthorizationsSearchFilter_ProjectId{
|
||||
ProjectId: &filter.IDFilter{
|
||||
Id: resp.GetProjectId(),
|
||||
},
|
||||
}
|
||||
response.Authorizations[0] = resp
|
||||
},
|
||||
req: &authorization.ListAuthorizationsRequest{
|
||||
Filters: []*authorization.AuthorizationsSearchFilter{{}},
|
||||
},
|
||||
},
|
||||
want: &authorization.ListAuthorizationsResponse{
|
||||
Pagination: &filter.PaginationResponse{
|
||||
TotalResult: 1,
|
||||
AppliedLimit: 100,
|
||||
},
|
||||
Authorizations: []*authorization.Authorization{
|
||||
{},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "list single project name",
|
||||
args: args{
|
||||
ctx: iamOwnerCtx,
|
||||
dep: func(request *authorization.ListAuthorizationsRequest, response *authorization.ListAuthorizationsResponse) {
|
||||
userResp := Instance.CreateUserTypeHuman(iamOwnerCtx, gofakeit.Email())
|
||||
|
||||
resp := createAuthorization(iamOwnerCtx, Instance, t, Instance.DefaultOrg.GetId(), userResp.GetId(), false)
|
||||
request.Filters[0].Filter = &authorization.AuthorizationsSearchFilter_ProjectName{
|
||||
ProjectName: &authorization.ProjectNameQuery{
|
||||
Name: resp.GetProjectName(),
|
||||
Method: filter.TextFilterMethod_TEXT_FILTER_METHOD_EQUALS,
|
||||
},
|
||||
}
|
||||
response.Authorizations[0] = resp
|
||||
},
|
||||
req: &authorization.ListAuthorizationsRequest{
|
||||
Filters: []*authorization.AuthorizationsSearchFilter{{}},
|
||||
},
|
||||
},
|
||||
want: &authorization.ListAuthorizationsResponse{
|
||||
Pagination: &filter.PaginationResponse{
|
||||
TotalResult: 1,
|
||||
AppliedLimit: 100,
|
||||
},
|
||||
Authorizations: []*authorization.Authorization{
|
||||
{},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "list single id, project grant",
|
||||
args: args{
|
||||
ctx: iamOwnerCtx,
|
||||
dep: func(request *authorization.ListAuthorizationsRequest, response *authorization.ListAuthorizationsResponse) {
|
||||
userResp := Instance.CreateUserTypeHuman(iamOwnerCtx, gofakeit.Email())
|
||||
|
||||
request.Filters[0].Filter = &authorization.AuthorizationsSearchFilter_UserId{
|
||||
UserId: &filter.IDFilter{
|
||||
Id: userResp.GetId(),
|
||||
},
|
||||
}
|
||||
response.Authorizations[0] = createAuthorization(iamOwnerCtx, Instance, t, Instance.DefaultOrg.GetId(), userResp.GetId(), true)
|
||||
},
|
||||
req: &authorization.ListAuthorizationsRequest{
|
||||
Filters: []*authorization.AuthorizationsSearchFilter{{}},
|
||||
},
|
||||
},
|
||||
want: &authorization.ListAuthorizationsResponse{
|
||||
Pagination: &filter.PaginationResponse{
|
||||
TotalResult: 1,
|
||||
AppliedLimit: 100,
|
||||
},
|
||||
Authorizations: []*authorization.Authorization{
|
||||
{},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "list single grant id, project grant",
|
||||
args: args{
|
||||
ctx: iamOwnerCtx,
|
||||
dep: func(request *authorization.ListAuthorizationsRequest, response *authorization.ListAuthorizationsResponse) {
|
||||
userResp := Instance.CreateUserTypeHuman(iamOwnerCtx, gofakeit.Email())
|
||||
|
||||
resp := createAuthorization(iamOwnerCtx, Instance, t, Instance.DefaultOrg.GetId(), userResp.GetId(), true)
|
||||
request.Filters[0].Filter = &authorization.AuthorizationsSearchFilter_ProjectGrantId{
|
||||
ProjectGrantId: &filter.IDFilter{
|
||||
Id: resp.GetProjectGrantId(),
|
||||
},
|
||||
}
|
||||
response.Authorizations[0] = resp
|
||||
},
|
||||
req: &authorization.ListAuthorizationsRequest{
|
||||
Filters: []*authorization.AuthorizationsSearchFilter{{}},
|
||||
},
|
||||
},
|
||||
want: &authorization.ListAuthorizationsResponse{
|
||||
Pagination: &filter.PaginationResponse{
|
||||
TotalResult: 1,
|
||||
AppliedLimit: 100,
|
||||
},
|
||||
Authorizations: []*authorization.Authorization{
|
||||
{},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "list single id, project and project grant, multiple",
|
||||
args: args{
|
||||
ctx: iamOwnerCtx,
|
||||
dep: func(request *authorization.ListAuthorizationsRequest, response *authorization.ListAuthorizationsResponse) {
|
||||
userResp := Instance.CreateUserTypeHuman(iamOwnerCtx, gofakeit.Email())
|
||||
|
||||
request.Filters[0].Filter = &authorization.AuthorizationsSearchFilter_UserId{
|
||||
UserId: &filter.IDFilter{
|
||||
Id: userResp.GetId(),
|
||||
},
|
||||
}
|
||||
response.Authorizations[5] = createAuthorization(iamOwnerCtx, Instance, t, Instance.DefaultOrg.GetId(), userResp.GetId(), false)
|
||||
response.Authorizations[4] = createAuthorization(iamOwnerCtx, Instance, t, Instance.DefaultOrg.GetId(), userResp.GetId(), false)
|
||||
response.Authorizations[3] = createAuthorization(iamOwnerCtx, Instance, t, Instance.DefaultOrg.GetId(), userResp.GetId(), false)
|
||||
response.Authorizations[2] = createAuthorization(iamOwnerCtx, Instance, t, Instance.DefaultOrg.GetId(), userResp.GetId(), true)
|
||||
response.Authorizations[1] = createAuthorization(iamOwnerCtx, Instance, t, Instance.DefaultOrg.GetId(), userResp.GetId(), true)
|
||||
response.Authorizations[0] = createAuthorization(iamOwnerCtx, Instance, t, Instance.DefaultOrg.GetId(), userResp.GetId(), true)
|
||||
},
|
||||
req: &authorization.ListAuthorizationsRequest{
|
||||
Filters: []*authorization.AuthorizationsSearchFilter{{}},
|
||||
},
|
||||
},
|
||||
want: &authorization.ListAuthorizationsResponse{
|
||||
Pagination: &filter.PaginationResponse{
|
||||
TotalResult: 6,
|
||||
AppliedLimit: 100,
|
||||
},
|
||||
Authorizations: []*authorization.Authorization{
|
||||
{}, {}, {}, {}, {}, {},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "list single id, project and project grant, org owner",
|
||||
args: args{
|
||||
ctx: Instance.WithAuthorizationToken(EmptyCTX, integration.UserTypeOrgOwner),
|
||||
dep: func(request *authorization.ListAuthorizationsRequest, response *authorization.ListAuthorizationsResponse) {
|
||||
userResp := Instance.CreateUserTypeHuman(iamOwnerCtx, gofakeit.Email())
|
||||
|
||||
request.Filters[0].Filter = &authorization.AuthorizationsSearchFilter_UserId{
|
||||
UserId: &filter.IDFilter{
|
||||
Id: userResp.GetId(),
|
||||
},
|
||||
}
|
||||
|
||||
response.Authorizations[1] = createAuthorizationForProject(iamOwnerCtx, Instance, t, Instance.DefaultOrg.GetId(), userResp.GetId(), projectResp.GetName(), projectResp.GetId())
|
||||
response.Authorizations[0] = createAuthorizationWithProjectGrant(iamOwnerCtx, Instance, t, Instance.DefaultOrg.GetId(), userResp.GetId(), grantedProjectResp.GetName(), grantedProjectResp.GetId())
|
||||
},
|
||||
req: &authorization.ListAuthorizationsRequest{
|
||||
Filters: []*authorization.AuthorizationsSearchFilter{{}},
|
||||
},
|
||||
},
|
||||
want: &authorization.ListAuthorizationsResponse{
|
||||
Pagination: &filter.PaginationResponse{
|
||||
TotalResult: 2,
|
||||
AppliedLimit: 100,
|
||||
},
|
||||
Authorizations: []*authorization.Authorization{
|
||||
{}, {},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "list single id, project and project grant, project owner",
|
||||
args: args{
|
||||
ctx: projectOwnerCtx,
|
||||
dep: func(request *authorization.ListAuthorizationsRequest, response *authorization.ListAuthorizationsResponse) {
|
||||
userResp := Instance.CreateUserTypeHuman(iamOwnerCtx, gofakeit.Email())
|
||||
|
||||
request.Filters[0].Filter = &authorization.AuthorizationsSearchFilter_UserId{
|
||||
UserId: &filter.IDFilter{
|
||||
Id: userResp.GetId(),
|
||||
},
|
||||
}
|
||||
|
||||
response.Authorizations[0] = createAuthorizationForProject(iamOwnerCtx, Instance, t, Instance.DefaultOrg.GetId(), userResp.GetId(), projectResp.GetName(), projectResp.GetId())
|
||||
createAuthorizationWithProjectGrant(iamOwnerCtx, Instance, t, Instance.DefaultOrg.GetId(), userResp.GetId(), grantedProjectResp.GetName(), grantedProjectResp.GetId())
|
||||
},
|
||||
req: &authorization.ListAuthorizationsRequest{
|
||||
Filters: []*authorization.AuthorizationsSearchFilter{{}},
|
||||
},
|
||||
},
|
||||
want: &authorization.ListAuthorizationsResponse{
|
||||
Pagination: &filter.PaginationResponse{
|
||||
TotalResult: 2,
|
||||
AppliedLimit: 100,
|
||||
},
|
||||
Authorizations: []*authorization.Authorization{
|
||||
{},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "list single id, project and project grant, project grant owner",
|
||||
args: args{
|
||||
ctx: projectGrantOwnerCtx,
|
||||
dep: func(request *authorization.ListAuthorizationsRequest, response *authorization.ListAuthorizationsResponse) {
|
||||
userResp := Instance.CreateUserTypeHuman(iamOwnerCtx, gofakeit.Email())
|
||||
|
||||
request.Filters[0].Filter = &authorization.AuthorizationsSearchFilter_UserId{
|
||||
UserId: &filter.IDFilter{
|
||||
Id: userResp.GetId(),
|
||||
},
|
||||
}
|
||||
|
||||
createAuthorizationForProject(iamOwnerCtx, Instance, t, Instance.DefaultOrg.GetId(), userResp.GetId(), projectResp.GetName(), projectResp.GetId())
|
||||
response.Authorizations[0] = createAuthorizationForProjectGrant(iamOwnerCtx, Instance, t, Instance.DefaultOrg.GetId(), userResp.GetId(), grantedProjectResp.GetName(), grantedProjectResp.GetId(), grantedProjectResp.GetGrantedOrganizationId())
|
||||
},
|
||||
req: &authorization.ListAuthorizationsRequest{
|
||||
Filters: []*authorization.AuthorizationsSearchFilter{{}},
|
||||
},
|
||||
},
|
||||
want: &authorization.ListAuthorizationsResponse{
|
||||
Pagination: &filter.PaginationResponse{
|
||||
TotalResult: 2,
|
||||
AppliedLimit: 100,
|
||||
},
|
||||
Authorizations: []*authorization.Authorization{
|
||||
{},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if tt.args.dep != nil {
|
||||
tt.args.dep(tt.args.req, tt.want)
|
||||
}
|
||||
|
||||
retryDuration, tick := integration.WaitForAndTickWithMaxDuration(iamOwnerCtx, time.Minute)
|
||||
require.EventuallyWithT(t, func(ttt *assert.CollectT) {
|
||||
got, listErr := Instance.Client.AuthorizationV2Beta.ListAuthorizations(tt.args.ctx, tt.args.req)
|
||||
if tt.wantErr {
|
||||
require.Error(ttt, listErr)
|
||||
return
|
||||
}
|
||||
require.NoError(ttt, listErr)
|
||||
|
||||
// always first check length, otherwise its failed anyway
|
||||
if assert.Len(ttt, got.Authorizations, len(tt.want.Authorizations)) {
|
||||
for i := range tt.want.Authorizations {
|
||||
assert.EqualExportedValues(ttt, tt.want.Authorizations[i], got.Authorizations[i])
|
||||
}
|
||||
}
|
||||
assertPaginationResponse(ttt, tt.want.Pagination, got.Pagination)
|
||||
}, retryDuration, tick, "timeout waiting for expected execution result")
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func assertPaginationResponse(t *assert.CollectT, expected *filter.PaginationResponse, actual *filter.PaginationResponse) {
|
||||
assert.Equal(t, expected.AppliedLimit, actual.AppliedLimit)
|
||||
assert.Equal(t, expected.TotalResult, actual.TotalResult)
|
||||
}
|
||||
|
||||
func createAuthorization(ctx context.Context, instance *integration.Instance, t *testing.T, orgID, userID string, grant bool) *authorization.Authorization {
|
||||
projectName := gofakeit.AppName()
|
||||
projectResp := instance.CreateProject(ctx, t, orgID, projectName, false, false)
|
||||
|
||||
if grant {
|
||||
return createAuthorizationWithProjectGrant(ctx, instance, t, orgID, userID, projectName, projectResp.GetId())
|
||||
}
|
||||
return createAuthorizationForProject(ctx, instance, t, orgID, userID, projectName, projectResp.GetId())
|
||||
}
|
||||
|
||||
func createAuthorizationForProject(ctx context.Context, instance *integration.Instance, t *testing.T, orgID, userID, projectName, projectID string) *authorization.Authorization {
|
||||
userResp, err := instance.Client.UserV2.GetUserByID(ctx, &user.GetUserByIDRequest{UserId: userID})
|
||||
require.NoError(t, err)
|
||||
|
||||
userGrantResp := instance.CreateProjectUserGrant(t, ctx, projectID, userID)
|
||||
return &authorization.Authorization{
|
||||
Id: userGrantResp.GetUserGrantId(),
|
||||
ProjectId: projectID,
|
||||
ProjectName: projectName,
|
||||
ProjectOrganizationId: orgID,
|
||||
OrganizationId: orgID,
|
||||
CreationDate: userGrantResp.Details.GetCreationDate(),
|
||||
ChangeDate: userGrantResp.Details.GetCreationDate(),
|
||||
State: 1,
|
||||
User: &authorization.User{
|
||||
Id: userID,
|
||||
PreferredLoginName: userResp.User.GetPreferredLoginName(),
|
||||
DisplayName: userResp.User.GetHuman().GetProfile().GetDisplayName(),
|
||||
AvatarUrl: userResp.User.GetHuman().GetProfile().GetAvatarUrl(),
|
||||
OrganizationId: userResp.GetUser().GetDetails().GetResourceOwner(),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func createAuthorizationWithProjectGrant(ctx context.Context, instance *integration.Instance, t *testing.T, orgID, userID, projectName, projectID string) *authorization.Authorization {
|
||||
grantedOrgName := gofakeit.Company() + integration.RandString(10)
|
||||
grantedOrg := instance.CreateOrganization(ctx, grantedOrgName, gofakeit.Email())
|
||||
instance.CreateProjectGrant(ctx, t, projectID, grantedOrg.GetOrganizationId())
|
||||
|
||||
return createAuthorizationForProjectGrant(ctx, instance, t, orgID, userID, projectName, projectID, grantedOrg.GetOrganizationId())
|
||||
}
|
||||
|
||||
func createAuthorizationForProjectGrant(ctx context.Context, instance *integration.Instance, t *testing.T, orgID, userID, projectName, projectID, grantedOrgID string) *authorization.Authorization {
|
||||
userResp, err := instance.Client.UserV2.GetUserByID(ctx, &user.GetUserByIDRequest{UserId: userID})
|
||||
require.NoError(t, err)
|
||||
|
||||
userGrantResp := instance.CreateProjectGrantUserGrant(ctx, orgID, projectID, grantedOrgID, userID)
|
||||
return &authorization.Authorization{
|
||||
Id: userGrantResp.GetUserGrantId(),
|
||||
ProjectId: projectID,
|
||||
ProjectName: projectName,
|
||||
ProjectOrganizationId: orgID,
|
||||
ProjectGrantId: gu.Ptr(grantedOrgID),
|
||||
GrantedOrganizationId: gu.Ptr(grantedOrgID),
|
||||
OrganizationId: orgID,
|
||||
CreationDate: userGrantResp.Details.GetCreationDate(),
|
||||
ChangeDate: userGrantResp.Details.GetCreationDate(),
|
||||
State: 1,
|
||||
User: &authorization.User{
|
||||
Id: userID,
|
||||
PreferredLoginName: userResp.User.GetPreferredLoginName(),
|
||||
DisplayName: userResp.User.GetHuman().GetProfile().GetDisplayName(),
|
||||
AvatarUrl: userResp.User.GetHuman().GetProfile().GetAvatarUrl(),
|
||||
OrganizationId: userResp.GetUser().GetDetails().GetResourceOwner(),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func createProject(ctx context.Context, instance *integration.Instance, t *testing.T, orgID string, projectRoleCheck, hasProjectCheck bool) *project.Project {
|
||||
name := gofakeit.AppName()
|
||||
resp := instance.CreateProject(ctx, t, orgID, name, projectRoleCheck, hasProjectCheck)
|
||||
return &project.Project{
|
||||
Id: resp.GetId(),
|
||||
Name: name,
|
||||
OrganizationId: orgID,
|
||||
CreationDate: resp.GetCreationDate(),
|
||||
ChangeDate: resp.GetCreationDate(),
|
||||
State: 1,
|
||||
ProjectRoleAssertion: false,
|
||||
ProjectAccessRequired: hasProjectCheck,
|
||||
AuthorizationRequired: projectRoleCheck,
|
||||
PrivateLabelingSetting: project.PrivateLabelingSetting_PRIVATE_LABELING_SETTING_UNSPECIFIED,
|
||||
}
|
||||
}
|
||||
|
||||
func createGrantedProject(ctx context.Context, instance *integration.Instance, t *testing.T, projectToGrant *project.Project) *project.Project {
|
||||
grantedOrgName := gofakeit.AppName()
|
||||
grantedOrg := instance.CreateOrganization(ctx, grantedOrgName, gofakeit.Email())
|
||||
projectGrantResp := instance.CreateProjectGrant(ctx, t, projectToGrant.GetId(), grantedOrg.GetOrganizationId())
|
||||
|
||||
return &project.Project{
|
||||
Id: projectToGrant.GetId(),
|
||||
Name: projectToGrant.GetName(),
|
||||
OrganizationId: projectToGrant.GetOrganizationId(),
|
||||
CreationDate: projectGrantResp.GetCreationDate(),
|
||||
ChangeDate: projectGrantResp.GetCreationDate(),
|
||||
State: 1,
|
||||
ProjectRoleAssertion: false,
|
||||
ProjectAccessRequired: projectToGrant.GetProjectAccessRequired(),
|
||||
AuthorizationRequired: projectToGrant.GetAuthorizationRequired(),
|
||||
PrivateLabelingSetting: project.PrivateLabelingSetting_PRIVATE_LABELING_SETTING_UNSPECIFIED,
|
||||
GrantedOrganizationId: gu.Ptr(grantedOrg.GetOrganizationId()),
|
||||
GrantedOrganizationName: gu.Ptr(grantedOrgName),
|
||||
GrantedState: 1,
|
||||
}
|
||||
}
|
||||
|
||||
func TestServer_ListAuthorizations_PermissionsV2(t *testing.T) {
|
||||
ensureFeaturePermissionV2Enabled(t, InstancePermissionV2)
|
||||
iamOwnerCtx := InstancePermissionV2.WithAuthorizationToken(EmptyCTX, integration.UserTypeIAMOwner)
|
||||
|
||||
projectOwnerResp := InstancePermissionV2.CreateMachineUser(iamOwnerCtx)
|
||||
projectOwnerPatResp := InstancePermissionV2.CreatePersonalAccessToken(iamOwnerCtx, projectOwnerResp.GetUserId())
|
||||
projectResp := createProject(iamOwnerCtx, InstancePermissionV2, t, InstancePermissionV2.DefaultOrg.GetId(), false, false)
|
||||
InstancePermissionV2.CreateProjectMembership(t, iamOwnerCtx, projectResp.GetId(), projectOwnerResp.GetUserId())
|
||||
projectOwnerCtx := integration.WithAuthorizationToken(EmptyCTX, projectOwnerPatResp.Token)
|
||||
|
||||
//projectGrantOwnerResp := InstancePermissionV2.CreateMachineUser(iamOwnerCtx)
|
||||
//projectGrantOwnerPatResp := InstancePermissionV2.CreatePersonalAccessToken(iamOwnerCtx, projectGrantOwnerResp.GetUserId())
|
||||
grantedProjectResp := createGrantedProject(iamOwnerCtx, InstancePermissionV2, t, projectResp)
|
||||
//InstancePermissionV2.CreateProjectGrantMembership(t, iamOwnerCtx, projectResp.GetId(), grantedProjectResp.GetGrantedOrganizationId(), projectGrantOwnerResp.GetUserId())
|
||||
//projectGrantOwnerCtx := integration.WithAuthorizationToken(EmptyCTX, projectGrantOwnerPatResp.Token)
|
||||
|
||||
type args struct {
|
||||
ctx context.Context
|
||||
dep func(*authorization.ListAuthorizationsRequest, *authorization.ListAuthorizationsResponse)
|
||||
req *authorization.ListAuthorizationsRequest
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want *authorization.ListAuthorizationsResponse
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "list by user id, unauthenticated",
|
||||
args: args{
|
||||
ctx: EmptyCTX,
|
||||
dep: func(request *authorization.ListAuthorizationsRequest, response *authorization.ListAuthorizationsResponse) {
|
||||
userResp := InstancePermissionV2.CreateUserTypeHuman(iamOwnerCtx, gofakeit.Email())
|
||||
|
||||
request.Filters[0].Filter = &authorization.AuthorizationsSearchFilter_UserId{
|
||||
UserId: &filter.IDFilter{
|
||||
Id: userResp.GetId(),
|
||||
},
|
||||
}
|
||||
createAuthorization(iamOwnerCtx, InstancePermissionV2, t, InstancePermissionV2.DefaultOrg.GetId(), userResp.GetId(), false)
|
||||
},
|
||||
req: &authorization.ListAuthorizationsRequest{
|
||||
Filters: []*authorization.AuthorizationsSearchFilter{{}},
|
||||
},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "list by id, no permission",
|
||||
args: args{
|
||||
ctx: InstancePermissionV2.WithAuthorizationToken(EmptyCTX, integration.UserTypeNoPermission),
|
||||
dep: func(request *authorization.ListAuthorizationsRequest, response *authorization.ListAuthorizationsResponse) {
|
||||
userResp := InstancePermissionV2.CreateUserTypeHuman(iamOwnerCtx, gofakeit.Email())
|
||||
|
||||
request.Filters[0].Filter = &authorization.AuthorizationsSearchFilter_UserId{
|
||||
UserId: &filter.IDFilter{
|
||||
Id: userResp.GetId(),
|
||||
},
|
||||
}
|
||||
createAuthorization(iamOwnerCtx, InstancePermissionV2, t, InstancePermissionV2.DefaultOrg.GetId(), userResp.GetId(), false)
|
||||
},
|
||||
req: &authorization.ListAuthorizationsRequest{
|
||||
Filters: []*authorization.AuthorizationsSearchFilter{{}},
|
||||
},
|
||||
},
|
||||
want: &authorization.ListAuthorizationsResponse{
|
||||
Pagination: &filter.PaginationResponse{
|
||||
TotalResult: 0,
|
||||
AppliedLimit: 100,
|
||||
},
|
||||
Authorizations: []*authorization.Authorization{},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "list, not found",
|
||||
args: args{
|
||||
ctx: iamOwnerCtx,
|
||||
req: &authorization.ListAuthorizationsRequest{
|
||||
Filters: []*authorization.AuthorizationsSearchFilter{
|
||||
{Filter: &authorization.AuthorizationsSearchFilter_UserId{
|
||||
UserId: &filter.IDFilter{
|
||||
Id: "notexisting",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
want: &authorization.ListAuthorizationsResponse{
|
||||
Pagination: &filter.PaginationResponse{
|
||||
TotalResult: 0,
|
||||
AppliedLimit: 100,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "list single id, project",
|
||||
args: args{
|
||||
ctx: iamOwnerCtx,
|
||||
dep: func(request *authorization.ListAuthorizationsRequest, response *authorization.ListAuthorizationsResponse) {
|
||||
userResp := InstancePermissionV2.CreateUserTypeHuman(iamOwnerCtx, gofakeit.Email())
|
||||
|
||||
request.Filters[0].Filter = &authorization.AuthorizationsSearchFilter_UserId{
|
||||
UserId: &filter.IDFilter{
|
||||
Id: userResp.GetId(),
|
||||
},
|
||||
}
|
||||
response.Authorizations[0] = createAuthorization(iamOwnerCtx, InstancePermissionV2, t, InstancePermissionV2.DefaultOrg.GetId(), userResp.GetId(), false)
|
||||
},
|
||||
req: &authorization.ListAuthorizationsRequest{
|
||||
Filters: []*authorization.AuthorizationsSearchFilter{{}},
|
||||
},
|
||||
},
|
||||
want: &authorization.ListAuthorizationsResponse{
|
||||
Pagination: &filter.PaginationResponse{
|
||||
TotalResult: 1,
|
||||
AppliedLimit: 100,
|
||||
},
|
||||
Authorizations: []*authorization.Authorization{
|
||||
{},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "list single id",
|
||||
args: args{
|
||||
ctx: iamOwnerCtx,
|
||||
dep: func(request *authorization.ListAuthorizationsRequest, response *authorization.ListAuthorizationsResponse) {
|
||||
userResp := InstancePermissionV2.CreateUserTypeHuman(iamOwnerCtx, gofakeit.Email())
|
||||
|
||||
resp := createAuthorization(iamOwnerCtx, InstancePermissionV2, t, InstancePermissionV2.DefaultOrg.GetId(), userResp.GetId(), false)
|
||||
request.Filters[0].Filter = &authorization.AuthorizationsSearchFilter_AuthorizationIds{
|
||||
AuthorizationIds: &filter.InIDsFilter{
|
||||
Ids: []string{resp.GetId()},
|
||||
},
|
||||
}
|
||||
response.Authorizations[0] = resp
|
||||
},
|
||||
req: &authorization.ListAuthorizationsRequest{
|
||||
Filters: []*authorization.AuthorizationsSearchFilter{{}},
|
||||
},
|
||||
},
|
||||
want: &authorization.ListAuthorizationsResponse{
|
||||
Pagination: &filter.PaginationResponse{
|
||||
TotalResult: 1,
|
||||
AppliedLimit: 100,
|
||||
},
|
||||
Authorizations: []*authorization.Authorization{
|
||||
{},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "list single project id",
|
||||
args: args{
|
||||
ctx: iamOwnerCtx,
|
||||
dep: func(request *authorization.ListAuthorizationsRequest, response *authorization.ListAuthorizationsResponse) {
|
||||
userResp := InstancePermissionV2.CreateUserTypeHuman(iamOwnerCtx, gofakeit.Email())
|
||||
|
||||
resp := createAuthorization(iamOwnerCtx, InstancePermissionV2, t, InstancePermissionV2.DefaultOrg.GetId(), userResp.GetId(), false)
|
||||
request.Filters[0].Filter = &authorization.AuthorizationsSearchFilter_ProjectId{
|
||||
ProjectId: &filter.IDFilter{
|
||||
Id: resp.GetProjectId(),
|
||||
},
|
||||
}
|
||||
response.Authorizations[0] = resp
|
||||
},
|
||||
req: &authorization.ListAuthorizationsRequest{
|
||||
Filters: []*authorization.AuthorizationsSearchFilter{{}},
|
||||
},
|
||||
},
|
||||
want: &authorization.ListAuthorizationsResponse{
|
||||
Pagination: &filter.PaginationResponse{
|
||||
TotalResult: 1,
|
||||
AppliedLimit: 100,
|
||||
},
|
||||
Authorizations: []*authorization.Authorization{
|
||||
{},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "list single project name",
|
||||
args: args{
|
||||
ctx: iamOwnerCtx,
|
||||
dep: func(request *authorization.ListAuthorizationsRequest, response *authorization.ListAuthorizationsResponse) {
|
||||
userResp := InstancePermissionV2.CreateUserTypeHuman(iamOwnerCtx, gofakeit.Email())
|
||||
|
||||
resp := createAuthorization(iamOwnerCtx, InstancePermissionV2, t, InstancePermissionV2.DefaultOrg.GetId(), userResp.GetId(), false)
|
||||
request.Filters[0].Filter = &authorization.AuthorizationsSearchFilter_ProjectName{
|
||||
ProjectName: &authorization.ProjectNameQuery{
|
||||
Name: resp.GetProjectName(),
|
||||
Method: filter.TextFilterMethod_TEXT_FILTER_METHOD_EQUALS,
|
||||
},
|
||||
}
|
||||
response.Authorizations[0] = resp
|
||||
},
|
||||
req: &authorization.ListAuthorizationsRequest{
|
||||
Filters: []*authorization.AuthorizationsSearchFilter{{}},
|
||||
},
|
||||
},
|
||||
want: &authorization.ListAuthorizationsResponse{
|
||||
Pagination: &filter.PaginationResponse{
|
||||
TotalResult: 1,
|
||||
AppliedLimit: 100,
|
||||
},
|
||||
Authorizations: []*authorization.Authorization{
|
||||
{},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "list single id, project grant",
|
||||
args: args{
|
||||
ctx: iamOwnerCtx,
|
||||
dep: func(request *authorization.ListAuthorizationsRequest, response *authorization.ListAuthorizationsResponse) {
|
||||
userResp := InstancePermissionV2.CreateUserTypeHuman(iamOwnerCtx, gofakeit.Email())
|
||||
|
||||
request.Filters[0].Filter = &authorization.AuthorizationsSearchFilter_UserId{
|
||||
UserId: &filter.IDFilter{
|
||||
Id: userResp.GetId(),
|
||||
},
|
||||
}
|
||||
response.Authorizations[0] = createAuthorization(iamOwnerCtx, InstancePermissionV2, t, InstancePermissionV2.DefaultOrg.GetId(), userResp.GetId(), true)
|
||||
},
|
||||
req: &authorization.ListAuthorizationsRequest{
|
||||
Filters: []*authorization.AuthorizationsSearchFilter{{}},
|
||||
},
|
||||
},
|
||||
want: &authorization.ListAuthorizationsResponse{
|
||||
Pagination: &filter.PaginationResponse{
|
||||
TotalResult: 1,
|
||||
AppliedLimit: 100,
|
||||
},
|
||||
Authorizations: []*authorization.Authorization{
|
||||
{},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "list single grant id, project grant",
|
||||
args: args{
|
||||
ctx: iamOwnerCtx,
|
||||
dep: func(request *authorization.ListAuthorizationsRequest, response *authorization.ListAuthorizationsResponse) {
|
||||
userResp := InstancePermissionV2.CreateUserTypeHuman(iamOwnerCtx, gofakeit.Email())
|
||||
|
||||
resp := createAuthorization(iamOwnerCtx, InstancePermissionV2, t, InstancePermissionV2.DefaultOrg.GetId(), userResp.GetId(), true)
|
||||
request.Filters[0].Filter = &authorization.AuthorizationsSearchFilter_ProjectGrantId{
|
||||
ProjectGrantId: &filter.IDFilter{
|
||||
Id: resp.GetProjectGrantId(),
|
||||
},
|
||||
}
|
||||
response.Authorizations[0] = resp
|
||||
},
|
||||
req: &authorization.ListAuthorizationsRequest{
|
||||
Filters: []*authorization.AuthorizationsSearchFilter{{}},
|
||||
},
|
||||
},
|
||||
want: &authorization.ListAuthorizationsResponse{
|
||||
Pagination: &filter.PaginationResponse{
|
||||
TotalResult: 1,
|
||||
AppliedLimit: 100,
|
||||
},
|
||||
Authorizations: []*authorization.Authorization{
|
||||
{},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "list single id, project and project grant, multiple",
|
||||
args: args{
|
||||
ctx: iamOwnerCtx,
|
||||
dep: func(request *authorization.ListAuthorizationsRequest, response *authorization.ListAuthorizationsResponse) {
|
||||
userResp := InstancePermissionV2.CreateUserTypeHuman(iamOwnerCtx, gofakeit.Email())
|
||||
|
||||
request.Filters[0].Filter = &authorization.AuthorizationsSearchFilter_UserId{
|
||||
UserId: &filter.IDFilter{
|
||||
Id: userResp.GetId(),
|
||||
},
|
||||
}
|
||||
response.Authorizations[5] = createAuthorization(iamOwnerCtx, InstancePermissionV2, t, InstancePermissionV2.DefaultOrg.GetId(), userResp.GetId(), false)
|
||||
response.Authorizations[4] = createAuthorization(iamOwnerCtx, InstancePermissionV2, t, InstancePermissionV2.DefaultOrg.GetId(), userResp.GetId(), false)
|
||||
response.Authorizations[3] = createAuthorization(iamOwnerCtx, InstancePermissionV2, t, InstancePermissionV2.DefaultOrg.GetId(), userResp.GetId(), false)
|
||||
response.Authorizations[2] = createAuthorization(iamOwnerCtx, InstancePermissionV2, t, InstancePermissionV2.DefaultOrg.GetId(), userResp.GetId(), true)
|
||||
response.Authorizations[1] = createAuthorization(iamOwnerCtx, InstancePermissionV2, t, InstancePermissionV2.DefaultOrg.GetId(), userResp.GetId(), true)
|
||||
response.Authorizations[0] = createAuthorization(iamOwnerCtx, InstancePermissionV2, t, InstancePermissionV2.DefaultOrg.GetId(), userResp.GetId(), true)
|
||||
},
|
||||
req: &authorization.ListAuthorizationsRequest{
|
||||
Filters: []*authorization.AuthorizationsSearchFilter{{}},
|
||||
},
|
||||
},
|
||||
want: &authorization.ListAuthorizationsResponse{
|
||||
Pagination: &filter.PaginationResponse{
|
||||
TotalResult: 6,
|
||||
AppliedLimit: 100,
|
||||
},
|
||||
Authorizations: []*authorization.Authorization{
|
||||
{}, {}, {}, {}, {}, {},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "list single id, project and project grant, org owner",
|
||||
args: args{
|
||||
ctx: InstancePermissionV2.WithAuthorizationToken(EmptyCTX, integration.UserTypeOrgOwner),
|
||||
dep: func(request *authorization.ListAuthorizationsRequest, response *authorization.ListAuthorizationsResponse) {
|
||||
userResp := InstancePermissionV2.CreateUserTypeHuman(iamOwnerCtx, gofakeit.Email())
|
||||
|
||||
request.Filters[0].Filter = &authorization.AuthorizationsSearchFilter_UserId{
|
||||
UserId: &filter.IDFilter{
|
||||
Id: userResp.GetId(),
|
||||
},
|
||||
}
|
||||
|
||||
response.Authorizations[1] = createAuthorizationForProject(iamOwnerCtx, InstancePermissionV2, t, InstancePermissionV2.DefaultOrg.GetId(), userResp.GetId(), projectResp.GetName(), projectResp.GetId())
|
||||
response.Authorizations[0] = createAuthorizationWithProjectGrant(iamOwnerCtx, InstancePermissionV2, t, InstancePermissionV2.DefaultOrg.GetId(), userResp.GetId(), grantedProjectResp.GetName(), grantedProjectResp.GetId())
|
||||
},
|
||||
req: &authorization.ListAuthorizationsRequest{
|
||||
Filters: []*authorization.AuthorizationsSearchFilter{{}},
|
||||
},
|
||||
},
|
||||
want: &authorization.ListAuthorizationsResponse{
|
||||
Pagination: &filter.PaginationResponse{
|
||||
TotalResult: 2,
|
||||
AppliedLimit: 100,
|
||||
},
|
||||
Authorizations: []*authorization.Authorization{
|
||||
{}, {},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "list single id, project and project grant, project owner",
|
||||
args: args{
|
||||
ctx: projectOwnerCtx,
|
||||
dep: func(request *authorization.ListAuthorizationsRequest, response *authorization.ListAuthorizationsResponse) {
|
||||
userResp := InstancePermissionV2.CreateUserTypeHuman(iamOwnerCtx, gofakeit.Email())
|
||||
|
||||
request.Filters[0].Filter = &authorization.AuthorizationsSearchFilter_UserId{
|
||||
UserId: &filter.IDFilter{
|
||||
Id: userResp.GetId(),
|
||||
},
|
||||
}
|
||||
|
||||
response.Authorizations[1] = createAuthorizationForProject(iamOwnerCtx, InstancePermissionV2, t, InstancePermissionV2.DefaultOrg.GetId(), userResp.GetId(), projectResp.GetName(), projectResp.GetId())
|
||||
response.Authorizations[0] = createAuthorizationWithProjectGrant(iamOwnerCtx, InstancePermissionV2, t, InstancePermissionV2.DefaultOrg.GetId(), userResp.GetId(), grantedProjectResp.GetName(), grantedProjectResp.GetId())
|
||||
},
|
||||
req: &authorization.ListAuthorizationsRequest{
|
||||
Filters: []*authorization.AuthorizationsSearchFilter{{}},
|
||||
},
|
||||
},
|
||||
want: &authorization.ListAuthorizationsResponse{
|
||||
Pagination: &filter.PaginationResponse{
|
||||
TotalResult: 2,
|
||||
AppliedLimit: 100,
|
||||
},
|
||||
Authorizations: []*authorization.Authorization{
|
||||
{}, {},
|
||||
},
|
||||
},
|
||||
},
|
||||
/*
|
||||
TODO: correct when permission check is added for project grants https://github.com/zitadel/zitadel/issues/9972
|
||||
{
|
||||
name: "list single id, project and project grant, project grant owner",
|
||||
args: args{
|
||||
ctx: projectGrantOwnerCtx,
|
||||
dep: func(request *authorization.ListAuthorizationsRequest, response *authorization.ListAuthorizationsResponse) {
|
||||
userResp := InstancePermissionV2.CreateUserTypeHuman(iamOwnerCtx, gofakeit.Email())
|
||||
|
||||
request.Filters[0].Filter = &authorization.AuthorizationsSearchFilter_UserId{
|
||||
UserId: &filter.IDFilter{
|
||||
Id: userResp.GetId(),
|
||||
},
|
||||
}
|
||||
|
||||
createAuthorizationForProject(iamOwnerCtx, InstancePermissionV2, t, InstancePermissionV2.DefaultOrg.GetId(), userResp.GetId(), projectResp.GetName(), projectResp.GetId())
|
||||
response.Authorizations[0] = createAuthorizationForProjectGrant(iamOwnerCtx, InstancePermissionV2, t, InstancePermissionV2.DefaultOrg.GetId(), userResp.GetId(), grantedProjectResp.GetName(), grantedProjectResp.GetId())
|
||||
},
|
||||
req: &authorization.ListAuthorizationsRequest{
|
||||
Filters: []*authorization.AuthorizationsSearchFilter{{}},
|
||||
},
|
||||
},
|
||||
want: &authorization.ListAuthorizationsResponse{
|
||||
Pagination: &filter.PaginationResponse{
|
||||
TotalResult: 2,
|
||||
AppliedLimit: 100,
|
||||
},
|
||||
Authorizations: []*authorization.Authorization{
|
||||
{},
|
||||
},
|
||||
},
|
||||
},
|
||||
*/
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if tt.args.dep != nil {
|
||||
tt.args.dep(tt.args.req, tt.want)
|
||||
}
|
||||
|
||||
retryDuration, tick := integration.WaitForAndTickWithMaxDuration(iamOwnerCtx, time.Minute)
|
||||
require.EventuallyWithT(t, func(ttt *assert.CollectT) {
|
||||
got, listErr := InstancePermissionV2.Client.AuthorizationV2Beta.ListAuthorizations(tt.args.ctx, tt.args.req)
|
||||
if tt.wantErr {
|
||||
require.Error(ttt, listErr)
|
||||
return
|
||||
}
|
||||
require.NoError(ttt, listErr)
|
||||
|
||||
// always first check length, otherwise its failed anyway
|
||||
if assert.Len(ttt, got.Authorizations, len(tt.want.Authorizations)) {
|
||||
for i := range tt.want.Authorizations {
|
||||
assert.EqualExportedValues(ttt, tt.want.Authorizations[i], got.Authorizations[i])
|
||||
}
|
||||
}
|
||||
assertPaginationResponse(ttt, tt.want.Pagination, got.Pagination)
|
||||
}, retryDuration, tick, "timeout waiting for expected execution result")
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
//go:build integration
|
||||
|
||||
package authorization_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/muhlemmer/gu"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/integration"
|
||||
"github.com/zitadel/zitadel/pkg/grpc/feature/v2"
|
||||
)
|
||||
|
||||
var (
|
||||
EmptyCTX context.Context
|
||||
IAMCTX context.Context
|
||||
Instance *integration.Instance
|
||||
InstancePermissionV2 *integration.Instance
|
||||
)
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
os.Exit(func() int {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 15*time.Minute)
|
||||
defer cancel()
|
||||
EmptyCTX = ctx
|
||||
Instance = integration.NewInstance(ctx)
|
||||
IAMCTX = Instance.WithAuthorizationToken(ctx, integration.UserTypeIAMOwner)
|
||||
InstancePermissionV2 = integration.NewInstance(ctx)
|
||||
return m.Run()
|
||||
}())
|
||||
}
|
||||
|
||||
func ensureFeaturePermissionV2Enabled(t *testing.T, instance *integration.Instance) {
|
||||
ctx := instance.WithAuthorizationToken(EmptyCTX, integration.UserTypeIAMOwner)
|
||||
f, err := instance.Client.FeatureV2.GetInstanceFeatures(ctx, &feature.GetInstanceFeaturesRequest{
|
||||
Inheritance: true,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
if f.PermissionCheckV2.GetEnabled() {
|
||||
return
|
||||
}
|
||||
_, err = instance.Client.FeatureV2.SetInstanceFeatures(ctx, &feature.SetInstanceFeaturesRequest{
|
||||
PermissionCheckV2: gu.Ptr(true),
|
||||
})
|
||||
require.NoError(t, err)
|
||||
retryDuration, tick := integration.WaitForAndTickWithMaxDuration(ctx, 5*time.Minute)
|
||||
require.EventuallyWithT(t,
|
||||
func(ttt *assert.CollectT) {
|
||||
f, err := instance.Client.FeatureV2.GetInstanceFeatures(ctx, &feature.GetInstanceFeaturesRequest{
|
||||
Inheritance: true,
|
||||
})
|
||||
assert.NoError(ttt, err)
|
||||
if f.PermissionCheckV2.GetEnabled() {
|
||||
return
|
||||
}
|
||||
},
|
||||
retryDuration,
|
||||
tick,
|
||||
"timed out waiting for ensuring instance feature")
|
||||
}
|
||||
@@ -0,0 +1,208 @@
|
||||
package authorization
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
|
||||
"connectrpc.com/connect"
|
||||
"google.golang.org/protobuf/types/known/timestamppb"
|
||||
|
||||
filter "github.com/zitadel/zitadel/internal/api/grpc/filter/v2beta"
|
||||
"github.com/zitadel/zitadel/internal/domain"
|
||||
"github.com/zitadel/zitadel/internal/query"
|
||||
authorization "github.com/zitadel/zitadel/pkg/grpc/authorization/v2beta"
|
||||
filter_pb "github.com/zitadel/zitadel/pkg/grpc/filter/v2beta"
|
||||
)
|
||||
|
||||
func (s *Server) ListAuthorizations(ctx context.Context, req *connect.Request[authorization.ListAuthorizationsRequest]) (*connect.Response[authorization.ListAuthorizationsResponse], error) {
|
||||
queries, err := s.listAuthorizationsRequestToModel(req.Msg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
resp, err := s.query.UserGrants(ctx, queries, false, s.checkPermission)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return connect.NewResponse(&authorization.ListAuthorizationsResponse{
|
||||
Authorizations: userGrantsToPb(resp.UserGrants),
|
||||
Pagination: filter.QueryToPaginationPb(queries.SearchRequest, resp.SearchResponse),
|
||||
}), nil
|
||||
}
|
||||
|
||||
func (s *Server) listAuthorizationsRequestToModel(req *authorization.ListAuthorizationsRequest) (*query.UserGrantsQueries, error) {
|
||||
offset, limit, asc, err := filter.PaginationPbToQuery(s.systemDefaults, req.Pagination)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
queries, err := AuthorizationQueriesToQuery(req.Filters)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &query.UserGrantsQueries{
|
||||
SearchRequest: query.SearchRequest{
|
||||
Offset: offset,
|
||||
Limit: limit,
|
||||
Asc: asc,
|
||||
SortingColumn: authorizationFieldNameToSortingColumn(req.GetSortingColumn()),
|
||||
},
|
||||
Queries: queries,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func authorizationFieldNameToSortingColumn(field authorization.AuthorizationFieldName) query.Column {
|
||||
switch field {
|
||||
case authorization.AuthorizationFieldName_AUTHORIZATION_FIELD_NAME_UNSPECIFIED:
|
||||
return query.UserGrantCreationDate
|
||||
case authorization.AuthorizationFieldName_AUTHORIZATION_FIELD_NAME_CREATED_DATE:
|
||||
return query.UserGrantCreationDate
|
||||
case authorization.AuthorizationFieldName_AUTHORIZATION_FIELD_NAME_CHANGED_DATE:
|
||||
return query.UserGrantChangeDate
|
||||
case authorization.AuthorizationFieldName_AUTHORIZATION_FIELD_NAME_ID:
|
||||
return query.UserGrantID
|
||||
case authorization.AuthorizationFieldName_AUTHORIZATION_FIELD_NAME_USER_ID:
|
||||
return query.UserGrantUserID
|
||||
case authorization.AuthorizationFieldName_AUTHORIZATION_FIELD_NAME_PROJECT_ID:
|
||||
return query.UserGrantProjectID
|
||||
case authorization.AuthorizationFieldName_AUTHORIZATION_FIELD_NAME_ORGANIZATION_ID:
|
||||
return query.UserGrantResourceOwner
|
||||
case authorization.AuthorizationFieldName_AUTHORIZATION_FIELD_NAME_USER_ORGANIZATION_ID:
|
||||
return query.UserResourceOwnerCol
|
||||
default:
|
||||
return query.UserGrantCreationDate
|
||||
}
|
||||
}
|
||||
|
||||
func AuthorizationQueriesToQuery(queries []*authorization.AuthorizationsSearchFilter) (q []query.SearchQuery, err error) {
|
||||
q = make([]query.SearchQuery, len(queries))
|
||||
for i, query := range queries {
|
||||
q[i], err = AuthorizationSearchFilterToQuery(query)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return q, nil
|
||||
}
|
||||
|
||||
func AuthorizationSearchFilterToQuery(query *authorization.AuthorizationsSearchFilter) (query.SearchQuery, error) {
|
||||
switch q := query.Filter.(type) {
|
||||
case *authorization.AuthorizationsSearchFilter_AuthorizationIds:
|
||||
return AuthorizationIDQueryToModel(q.AuthorizationIds)
|
||||
case *authorization.AuthorizationsSearchFilter_OrganizationId:
|
||||
return AuthorizationOrganizationIDQueryToModel(q.OrganizationId)
|
||||
case *authorization.AuthorizationsSearchFilter_State:
|
||||
return AuthorizationStateQueryToModel(q.State)
|
||||
case *authorization.AuthorizationsSearchFilter_UserId:
|
||||
return AuthorizationUserUserIDQueryToModel(q.UserId)
|
||||
case *authorization.AuthorizationsSearchFilter_UserOrganizationId:
|
||||
return AuthorizationUserOrganizationIDQueryToModel(q.UserOrganizationId)
|
||||
case *authorization.AuthorizationsSearchFilter_UserPreferredLoginName:
|
||||
return AuthorizationUserNameQueryToModel(q.UserPreferredLoginName)
|
||||
case *authorization.AuthorizationsSearchFilter_UserDisplayName:
|
||||
return AuthorizationDisplayNameQueryToModel(q.UserDisplayName)
|
||||
case *authorization.AuthorizationsSearchFilter_ProjectId:
|
||||
return AuthorizationProjectIDQueryToModel(q.ProjectId)
|
||||
case *authorization.AuthorizationsSearchFilter_ProjectName:
|
||||
return AuthorizationProjectNameQueryToModel(q.ProjectName)
|
||||
case *authorization.AuthorizationsSearchFilter_RoleKey:
|
||||
return AuthorizationRoleKeyQueryToModel(q.RoleKey)
|
||||
case *authorization.AuthorizationsSearchFilter_ProjectGrantId:
|
||||
return AuthorizationProjectGrantIDQueryToModel(q.ProjectGrantId)
|
||||
default:
|
||||
return nil, errors.New("invalid query")
|
||||
}
|
||||
}
|
||||
|
||||
func AuthorizationIDQueryToModel(q *filter_pb.InIDsFilter) (query.SearchQuery, error) {
|
||||
return query.NewUserGrantInIDsSearchQuery(q.Ids)
|
||||
}
|
||||
|
||||
func AuthorizationDisplayNameQueryToModel(q *authorization.UserDisplayNameQuery) (query.SearchQuery, error) {
|
||||
return query.NewUserGrantDisplayNameQuery(q.DisplayName, filter.TextMethodPbToQuery(q.Method))
|
||||
}
|
||||
|
||||
func AuthorizationOrganizationIDQueryToModel(q *filter_pb.IDFilter) (query.SearchQuery, error) {
|
||||
return query.NewUserGrantResourceOwnerSearchQuery(q.Id)
|
||||
}
|
||||
|
||||
func AuthorizationProjectIDQueryToModel(q *filter_pb.IDFilter) (query.SearchQuery, error) {
|
||||
return query.NewUserGrantProjectIDSearchQuery(q.Id)
|
||||
}
|
||||
|
||||
func AuthorizationProjectNameQueryToModel(q *authorization.ProjectNameQuery) (query.SearchQuery, error) {
|
||||
return query.NewUserGrantProjectNameQuery(q.Name, filter.TextMethodPbToQuery(q.Method))
|
||||
}
|
||||
|
||||
func AuthorizationProjectGrantIDQueryToModel(q *filter_pb.IDFilter) (query.SearchQuery, error) {
|
||||
return query.NewUserGrantGrantIDSearchQuery(q.Id)
|
||||
}
|
||||
|
||||
func AuthorizationRoleKeyQueryToModel(q *authorization.RoleKeyQuery) (query.SearchQuery, error) {
|
||||
return query.NewUserGrantRoleQuery(q.Key)
|
||||
}
|
||||
|
||||
func AuthorizationUserNameQueryToModel(q *authorization.UserPreferredLoginNameQuery) (query.SearchQuery, error) {
|
||||
return query.NewUserGrantUsernameQuery(q.LoginName, filter.TextMethodPbToQuery(q.Method))
|
||||
}
|
||||
|
||||
func AuthorizationUserUserIDQueryToModel(q *filter_pb.IDFilter) (query.SearchQuery, error) {
|
||||
return query.NewUserGrantUserIDSearchQuery(q.Id)
|
||||
}
|
||||
|
||||
func AuthorizationUserOrganizationIDQueryToModel(q *filter_pb.IDFilter) (query.SearchQuery, error) {
|
||||
return query.NewUserGrantUserResourceOwnerSearchQuery(q.Id)
|
||||
}
|
||||
|
||||
func AuthorizationStateQueryToModel(q *authorization.StateQuery) (query.SearchQuery, error) {
|
||||
return query.NewUserGrantStateQuery(domain.UserGrantState(q.State))
|
||||
}
|
||||
|
||||
func userGrantsToPb(userGrants []*query.UserGrant) []*authorization.Authorization {
|
||||
o := make([]*authorization.Authorization, len(userGrants))
|
||||
for i, grant := range userGrants {
|
||||
o[i] = userGrantToPb(grant)
|
||||
}
|
||||
return o
|
||||
}
|
||||
|
||||
func userGrantToPb(userGrant *query.UserGrant) *authorization.Authorization {
|
||||
var grantID, grantedOrgID *string
|
||||
if userGrant.GrantID != "" {
|
||||
grantID = &userGrant.GrantID
|
||||
}
|
||||
if userGrant.GrantedOrgID != "" {
|
||||
grantedOrgID = &userGrant.GrantedOrgID
|
||||
}
|
||||
return &authorization.Authorization{
|
||||
Id: userGrant.ID,
|
||||
ProjectId: userGrant.ProjectID,
|
||||
ProjectName: userGrant.ProjectName,
|
||||
ProjectOrganizationId: userGrant.ProjectResourceOwner,
|
||||
ProjectGrantId: grantID,
|
||||
GrantedOrganizationId: grantedOrgID,
|
||||
OrganizationId: userGrant.ResourceOwner,
|
||||
CreationDate: timestamppb.New(userGrant.CreationDate),
|
||||
ChangeDate: timestamppb.New(userGrant.ChangeDate),
|
||||
State: userGrantStateToPb(userGrant.State),
|
||||
User: &authorization.User{
|
||||
Id: userGrant.UserID,
|
||||
PreferredLoginName: userGrant.PreferredLoginName,
|
||||
DisplayName: userGrant.DisplayName,
|
||||
AvatarUrl: userGrant.AvatarURL,
|
||||
OrganizationId: userGrant.UserResourceOwner,
|
||||
},
|
||||
Roles: userGrant.Roles,
|
||||
}
|
||||
}
|
||||
|
||||
func userGrantStateToPb(state domain.UserGrantState) authorization.State {
|
||||
switch state {
|
||||
case domain.UserGrantStateActive:
|
||||
return authorization.State_STATE_ACTIVE
|
||||
case domain.UserGrantStateInactive:
|
||||
return authorization.State_STATE_INACTIVE
|
||||
case domain.UserGrantStateUnspecified, domain.UserGrantStateRemoved:
|
||||
return authorization.State_STATE_UNSPECIFIED
|
||||
default:
|
||||
return authorization.State_STATE_UNSPECIFIED
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
package authorization
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"connectrpc.com/connect"
|
||||
"google.golang.org/protobuf/reflect/protoreflect"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/api/authz"
|
||||
"github.com/zitadel/zitadel/internal/api/grpc/server"
|
||||
"github.com/zitadel/zitadel/internal/command"
|
||||
"github.com/zitadel/zitadel/internal/config/systemdefaults"
|
||||
"github.com/zitadel/zitadel/internal/domain"
|
||||
"github.com/zitadel/zitadel/internal/query"
|
||||
authorization "github.com/zitadel/zitadel/pkg/grpc/authorization/v2beta"
|
||||
"github.com/zitadel/zitadel/pkg/grpc/authorization/v2beta/authorizationconnect"
|
||||
)
|
||||
|
||||
var _ authorizationconnect.AuthorizationServiceHandler = (*Server)(nil)
|
||||
|
||||
type Server struct {
|
||||
systemDefaults systemdefaults.SystemDefaults
|
||||
command *command.Commands
|
||||
query *query.Queries
|
||||
|
||||
checkPermission domain.PermissionCheck
|
||||
}
|
||||
|
||||
type Config struct{}
|
||||
|
||||
func CreateServer(
|
||||
systemDefaults systemdefaults.SystemDefaults,
|
||||
command *command.Commands,
|
||||
query *query.Queries,
|
||||
checkPermission domain.PermissionCheck,
|
||||
) *Server {
|
||||
return &Server{
|
||||
systemDefaults: systemDefaults,
|
||||
command: command,
|
||||
query: query,
|
||||
checkPermission: checkPermission,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Server) RegisterConnectServer(interceptors ...connect.Interceptor) (string, http.Handler) {
|
||||
return authorizationconnect.NewAuthorizationServiceHandler(s, connect.WithInterceptors(interceptors...))
|
||||
}
|
||||
|
||||
func (s *Server) FileDescriptor() protoreflect.FileDescriptor {
|
||||
return authorization.File_zitadel_authorization_v2beta_authorization_service_proto
|
||||
}
|
||||
|
||||
func (s *Server) AppName() string {
|
||||
return authorization.AuthorizationService_ServiceDesc.ServiceName
|
||||
}
|
||||
|
||||
func (s *Server) MethodPrefix() string {
|
||||
return authorization.AuthorizationService_ServiceDesc.ServiceName
|
||||
}
|
||||
|
||||
func (s *Server) AuthMethods() authz.MethodMapping {
|
||||
return authorization.AuthorizationService_AuthMethods
|
||||
}
|
||||
|
||||
func (s *Server) RegisterGateway() server.RegisterGatewayFunc {
|
||||
return authorization.RegisterAuthorizationServiceHandler
|
||||
}
|
||||
@@ -9,6 +9,29 @@ import (
|
||||
"github.com/zitadel/zitadel/pkg/grpc/filter/v2"
|
||||
)
|
||||
|
||||
func TextMethodPbToQuery(method filter.TextFilterMethod) query.TextComparison {
|
||||
switch method {
|
||||
case filter.TextFilterMethod_TEXT_FILTER_METHOD_EQUALS:
|
||||
return query.TextEquals
|
||||
case filter.TextFilterMethod_TEXT_FILTER_METHOD_EQUALS_IGNORE_CASE:
|
||||
return query.TextEqualsIgnoreCase
|
||||
case filter.TextFilterMethod_TEXT_FILTER_METHOD_STARTS_WITH:
|
||||
return query.TextStartsWith
|
||||
case filter.TextFilterMethod_TEXT_FILTER_METHOD_STARTS_WITH_IGNORE_CASE:
|
||||
return query.TextStartsWithIgnoreCase
|
||||
case filter.TextFilterMethod_TEXT_FILTER_METHOD_CONTAINS:
|
||||
return query.TextContains
|
||||
case filter.TextFilterMethod_TEXT_FILTER_METHOD_CONTAINS_IGNORE_CASE:
|
||||
return query.TextContainsIgnoreCase
|
||||
case filter.TextFilterMethod_TEXT_FILTER_METHOD_ENDS_WITH:
|
||||
return query.TextEndsWith
|
||||
case filter.TextFilterMethod_TEXT_FILTER_METHOD_ENDS_WITH_IGNORE_CASE:
|
||||
return query.TextEndsWithIgnoreCase
|
||||
default:
|
||||
return -1
|
||||
}
|
||||
}
|
||||
|
||||
func TimestampMethodPbToQuery(method filter.TimestampFilterMethod) query.TimestampComparison {
|
||||
switch method {
|
||||
case filter.TimestampFilterMethod_TIMESTAMP_FILTER_METHOD_EQUALS:
|
||||
@@ -48,26 +71,3 @@ func QueryToPaginationPb(request query.SearchRequest, response query.SearchRespo
|
||||
TotalResult: response.Count,
|
||||
}
|
||||
}
|
||||
|
||||
func TextMethodPbToQuery(method filter.TextFilterMethod) query.TextComparison {
|
||||
switch method {
|
||||
case filter.TextFilterMethod_TEXT_FILTER_METHOD_EQUALS:
|
||||
return query.TextEquals
|
||||
case filter.TextFilterMethod_TEXT_FILTER_METHOD_EQUALS_IGNORE_CASE:
|
||||
return query.TextEqualsIgnoreCase
|
||||
case filter.TextFilterMethod_TEXT_FILTER_METHOD_STARTS_WITH:
|
||||
return query.TextStartsWith
|
||||
case filter.TextFilterMethod_TEXT_FILTER_METHOD_STARTS_WITH_IGNORE_CASE:
|
||||
return query.TextStartsWithIgnoreCase
|
||||
case filter.TextFilterMethod_TEXT_FILTER_METHOD_CONTAINS:
|
||||
return query.TextContains
|
||||
case filter.TextFilterMethod_TEXT_FILTER_METHOD_CONTAINS_IGNORE_CASE:
|
||||
return query.TextContainsIgnoreCase
|
||||
case filter.TextFilterMethod_TEXT_FILTER_METHOD_ENDS_WITH:
|
||||
return query.TextEndsWith
|
||||
case filter.TextFilterMethod_TEXT_FILTER_METHOD_ENDS_WITH_IGNORE_CASE:
|
||||
return query.TextEndsWithIgnoreCase
|
||||
default:
|
||||
return -1
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,6 +32,23 @@ func TextMethodPbToQuery(method filter.TextFilterMethod) query.TextComparison {
|
||||
}
|
||||
}
|
||||
|
||||
func TimestampMethodPbToQuery(method filter.TimestampFilterMethod) query.TimestampComparison {
|
||||
switch method {
|
||||
case filter.TimestampFilterMethod_TIMESTAMP_FILTER_METHOD_EQUALS:
|
||||
return query.TimestampEquals
|
||||
case filter.TimestampFilterMethod_TIMESTAMP_FILTER_METHOD_LESS:
|
||||
return query.TimestampLess
|
||||
case filter.TimestampFilterMethod_TIMESTAMP_FILTER_METHOD_GREATER:
|
||||
return query.TimestampGreater
|
||||
case filter.TimestampFilterMethod_TIMESTAMP_FILTER_METHOD_LESS_OR_EQUALS:
|
||||
return query.TimestampLessOrEquals
|
||||
case filter.TimestampFilterMethod_TIMESTAMP_FILTER_METHOD_GREATER_OR_EQUALS:
|
||||
return query.TimestampGreaterOrEquals
|
||||
default:
|
||||
return -1
|
||||
}
|
||||
}
|
||||
|
||||
func PaginationPbToQuery(defaults systemdefaults.SystemDefaults, query *filter.PaginationRequest) (offset, limit uint64, asc bool, err error) {
|
||||
limit = defaults.DefaultQueryLimit
|
||||
if query == nil {
|
||||
|
||||
@@ -0,0 +1,220 @@
|
||||
package internal_permission
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"connectrpc.com/connect"
|
||||
"google.golang.org/protobuf/types/known/timestamppb"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/api/authz"
|
||||
"github.com/zitadel/zitadel/internal/command"
|
||||
"github.com/zitadel/zitadel/internal/zerrors"
|
||||
internal_permission "github.com/zitadel/zitadel/pkg/grpc/internal_permission/v2beta"
|
||||
)
|
||||
|
||||
func (s *Server) CreateAdministrator(ctx context.Context, req *connect.Request[internal_permission.CreateAdministratorRequest]) (*connect.Response[internal_permission.CreateAdministratorResponse], error) {
|
||||
var creationDate *timestamppb.Timestamp
|
||||
|
||||
switch resource := req.Msg.GetResource().GetResource().(type) {
|
||||
case *internal_permission.ResourceType_Instance:
|
||||
if resource.Instance {
|
||||
member, err := s.command.AddInstanceMember(ctx, createAdministratorInstanceToCommand(authz.GetInstance(ctx).InstanceID(), req.Msg.UserId, req.Msg.Roles))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !member.EventDate.IsZero() {
|
||||
creationDate = timestamppb.New(member.EventDate)
|
||||
}
|
||||
}
|
||||
case *internal_permission.ResourceType_OrganizationId:
|
||||
member, err := s.command.AddOrgMember(ctx, createAdministratorOrganizationToCommand(resource, req.Msg.UserId, req.Msg.Roles))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !member.EventDate.IsZero() {
|
||||
creationDate = timestamppb.New(member.EventDate)
|
||||
}
|
||||
case *internal_permission.ResourceType_ProjectId:
|
||||
member, err := s.command.AddProjectMember(ctx, createAdministratorProjectToCommand(resource, req.Msg.UserId, req.Msg.Roles))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !member.EventDate.IsZero() {
|
||||
creationDate = timestamppb.New(member.EventDate)
|
||||
}
|
||||
case *internal_permission.ResourceType_ProjectGrant_:
|
||||
member, err := s.command.AddProjectGrantMember(ctx, createAdministratorProjectGrantToCommand(resource, req.Msg.UserId, req.Msg.Roles))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !member.EventDate.IsZero() {
|
||||
creationDate = timestamppb.New(member.EventDate)
|
||||
}
|
||||
default:
|
||||
return nil, zerrors.ThrowInvalidArgument(nil, "ADMIN-IbPp47HDP5", "Errors.Invalid.Argument")
|
||||
}
|
||||
|
||||
return connect.NewResponse(&internal_permission.CreateAdministratorResponse{
|
||||
CreationDate: creationDate,
|
||||
}), nil
|
||||
}
|
||||
|
||||
func createAdministratorInstanceToCommand(instanceID, userID string, roles []string) *command.AddInstanceMember {
|
||||
return &command.AddInstanceMember{
|
||||
InstanceID: instanceID,
|
||||
UserID: userID,
|
||||
Roles: roles,
|
||||
}
|
||||
}
|
||||
|
||||
func createAdministratorOrganizationToCommand(req *internal_permission.ResourceType_OrganizationId, userID string, roles []string) *command.AddOrgMember {
|
||||
return &command.AddOrgMember{
|
||||
OrgID: req.OrganizationId,
|
||||
UserID: userID,
|
||||
Roles: roles,
|
||||
}
|
||||
}
|
||||
|
||||
func createAdministratorProjectToCommand(req *internal_permission.ResourceType_ProjectId, userID string, roles []string) *command.AddProjectMember {
|
||||
return &command.AddProjectMember{
|
||||
ProjectID: req.ProjectId,
|
||||
UserID: userID,
|
||||
Roles: roles,
|
||||
}
|
||||
}
|
||||
|
||||
func createAdministratorProjectGrantToCommand(req *internal_permission.ResourceType_ProjectGrant_, userID string, roles []string) *command.AddProjectGrantMember {
|
||||
return &command.AddProjectGrantMember{
|
||||
GrantID: req.ProjectGrant.ProjectGrantId,
|
||||
ProjectID: req.ProjectGrant.ProjectId,
|
||||
UserID: userID,
|
||||
Roles: roles,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Server) UpdateAdministrator(ctx context.Context, req *connect.Request[internal_permission.UpdateAdministratorRequest]) (*connect.Response[internal_permission.UpdateAdministratorResponse], error) {
|
||||
var changeDate *timestamppb.Timestamp
|
||||
|
||||
switch resource := req.Msg.GetResource().GetResource().(type) {
|
||||
case *internal_permission.ResourceType_Instance:
|
||||
if resource.Instance {
|
||||
member, err := s.command.ChangeInstanceMember(ctx, updateAdministratorInstanceToCommand(authz.GetInstance(ctx).InstanceID(), req.Msg.UserId, req.Msg.Roles))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !member.EventDate.IsZero() {
|
||||
changeDate = timestamppb.New(member.EventDate)
|
||||
}
|
||||
}
|
||||
case *internal_permission.ResourceType_OrganizationId:
|
||||
member, err := s.command.ChangeOrgMember(ctx, updateAdministratorOrganizationToCommand(resource, req.Msg.UserId, req.Msg.Roles))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !member.EventDate.IsZero() {
|
||||
changeDate = timestamppb.New(member.EventDate)
|
||||
}
|
||||
case *internal_permission.ResourceType_ProjectId:
|
||||
member, err := s.command.ChangeProjectMember(ctx, updateAdministratorProjectToCommand(resource, req.Msg.UserId, req.Msg.Roles))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !member.EventDate.IsZero() {
|
||||
changeDate = timestamppb.New(member.EventDate)
|
||||
}
|
||||
case *internal_permission.ResourceType_ProjectGrant_:
|
||||
member, err := s.command.ChangeProjectGrantMember(ctx, updateAdministratorProjectGrantToCommand(resource, req.Msg.UserId, req.Msg.Roles))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !member.EventDate.IsZero() {
|
||||
changeDate = timestamppb.New(member.EventDate)
|
||||
}
|
||||
default:
|
||||
return nil, zerrors.ThrowInvalidArgument(nil, "ADMIN-i0V2IbdloZ", "Errors.Invalid.Argument")
|
||||
}
|
||||
|
||||
return connect.NewResponse(&internal_permission.UpdateAdministratorResponse{
|
||||
ChangeDate: changeDate,
|
||||
}), nil
|
||||
}
|
||||
|
||||
func updateAdministratorInstanceToCommand(instanceID, userID string, roles []string) *command.ChangeInstanceMember {
|
||||
return &command.ChangeInstanceMember{
|
||||
InstanceID: instanceID,
|
||||
UserID: userID,
|
||||
Roles: roles,
|
||||
}
|
||||
}
|
||||
|
||||
func updateAdministratorOrganizationToCommand(req *internal_permission.ResourceType_OrganizationId, userID string, roles []string) *command.ChangeOrgMember {
|
||||
return &command.ChangeOrgMember{
|
||||
OrgID: req.OrganizationId,
|
||||
UserID: userID,
|
||||
Roles: roles,
|
||||
}
|
||||
}
|
||||
|
||||
func updateAdministratorProjectToCommand(req *internal_permission.ResourceType_ProjectId, userID string, roles []string) *command.ChangeProjectMember {
|
||||
return &command.ChangeProjectMember{
|
||||
ProjectID: req.ProjectId,
|
||||
UserID: userID,
|
||||
Roles: roles,
|
||||
}
|
||||
}
|
||||
|
||||
func updateAdministratorProjectGrantToCommand(req *internal_permission.ResourceType_ProjectGrant_, userID string, roles []string) *command.ChangeProjectGrantMember {
|
||||
return &command.ChangeProjectGrantMember{
|
||||
GrantID: req.ProjectGrant.ProjectGrantId,
|
||||
ProjectID: req.ProjectGrant.ProjectId,
|
||||
UserID: userID,
|
||||
Roles: roles,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Server) DeleteAdministrator(ctx context.Context, req *connect.Request[internal_permission.DeleteAdministratorRequest]) (*connect.Response[internal_permission.DeleteAdministratorResponse], error) {
|
||||
var deletionDate *timestamppb.Timestamp
|
||||
|
||||
switch resource := req.Msg.GetResource().GetResource().(type) {
|
||||
case *internal_permission.ResourceType_Instance:
|
||||
if resource.Instance {
|
||||
member, err := s.command.RemoveInstanceMember(ctx, authz.GetInstance(ctx).InstanceID(), req.Msg.UserId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !member.EventDate.IsZero() {
|
||||
deletionDate = timestamppb.New(member.EventDate)
|
||||
}
|
||||
}
|
||||
case *internal_permission.ResourceType_OrganizationId:
|
||||
member, err := s.command.RemoveOrgMember(ctx, resource.OrganizationId, req.Msg.UserId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !member.EventDate.IsZero() {
|
||||
deletionDate = timestamppb.New(member.EventDate)
|
||||
}
|
||||
case *internal_permission.ResourceType_ProjectId:
|
||||
member, err := s.command.RemoveProjectMember(ctx, resource.ProjectId, req.Msg.UserId, "")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !member.EventDate.IsZero() {
|
||||
deletionDate = timestamppb.New(member.EventDate)
|
||||
}
|
||||
case *internal_permission.ResourceType_ProjectGrant_:
|
||||
member, err := s.command.RemoveProjectGrantMember(ctx, resource.ProjectGrant.ProjectId, req.Msg.UserId, resource.ProjectGrant.ProjectGrantId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !member.EventDate.IsZero() {
|
||||
deletionDate = timestamppb.New(member.EventDate)
|
||||
}
|
||||
default:
|
||||
return nil, zerrors.ThrowInvalidArgument(nil, "ADMIN-3UOjLtuohh", "Errors.Invalid.Argument")
|
||||
}
|
||||
|
||||
return connect.NewResponse(&internal_permission.DeleteAdministratorResponse{
|
||||
DeletionDate: deletionDate,
|
||||
}), nil
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,63 @@
|
||||
//go:build integration
|
||||
|
||||
package project_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/muhlemmer/gu"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/integration"
|
||||
"github.com/zitadel/zitadel/pkg/grpc/feature/v2"
|
||||
)
|
||||
|
||||
var (
|
||||
CTX context.Context
|
||||
instance *integration.Instance
|
||||
instancePermissionV2 *integration.Instance
|
||||
)
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
os.Exit(func() int {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 15*time.Minute)
|
||||
defer cancel()
|
||||
CTX = ctx
|
||||
instance = integration.NewInstance(ctx)
|
||||
instancePermissionV2 = integration.NewInstance(CTX)
|
||||
return m.Run()
|
||||
}())
|
||||
}
|
||||
|
||||
func ensureFeaturePermissionV2Enabled(t *testing.T, instance *integration.Instance) {
|
||||
ctx := instance.WithAuthorization(CTX, integration.UserTypeIAMOwner)
|
||||
f, err := instance.Client.FeatureV2.GetInstanceFeatures(ctx, &feature.GetInstanceFeaturesRequest{
|
||||
Inheritance: true,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
if f.PermissionCheckV2.GetEnabled() {
|
||||
return
|
||||
}
|
||||
_, err = instance.Client.FeatureV2.SetInstanceFeatures(ctx, &feature.SetInstanceFeaturesRequest{
|
||||
PermissionCheckV2: gu.Ptr(true),
|
||||
})
|
||||
require.NoError(t, err)
|
||||
retryDuration, tick := integration.WaitForAndTickWithMaxDuration(ctx, 5*time.Minute)
|
||||
require.EventuallyWithT(t,
|
||||
func(ttt *assert.CollectT) {
|
||||
f, err := instance.Client.FeatureV2.GetInstanceFeatures(ctx, &feature.GetInstanceFeaturesRequest{
|
||||
Inheritance: true,
|
||||
})
|
||||
assert.NoError(ttt, err)
|
||||
if f.PermissionCheckV2.GetEnabled() {
|
||||
return
|
||||
}
|
||||
},
|
||||
retryDuration,
|
||||
tick,
|
||||
"timed out waiting for ensuring instance feature")
|
||||
}
|
||||
@@ -0,0 +1,192 @@
|
||||
package internal_permission
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"connectrpc.com/connect"
|
||||
"google.golang.org/protobuf/types/known/timestamppb"
|
||||
|
||||
filter "github.com/zitadel/zitadel/internal/api/grpc/filter/v2beta"
|
||||
"github.com/zitadel/zitadel/internal/query"
|
||||
"github.com/zitadel/zitadel/internal/zerrors"
|
||||
filter_pb "github.com/zitadel/zitadel/pkg/grpc/filter/v2beta"
|
||||
internal_permission "github.com/zitadel/zitadel/pkg/grpc/internal_permission/v2beta"
|
||||
)
|
||||
|
||||
func (s *Server) ListAdministrators(ctx context.Context, req *connect.Request[internal_permission.ListAdministratorsRequest]) (*connect.Response[internal_permission.ListAdministratorsResponse], error) {
|
||||
queries, err := s.listAdministratorsRequestToModel(req.Msg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
resp, err := s.query.SearchAdministrators(ctx, queries, s.checkPermission)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return connect.NewResponse(&internal_permission.ListAdministratorsResponse{
|
||||
Administrators: administratorsToPb(resp.Administrators),
|
||||
Pagination: filter.QueryToPaginationPb(queries.SearchRequest, resp.SearchResponse),
|
||||
}), nil
|
||||
}
|
||||
|
||||
func (s *Server) listAdministratorsRequestToModel(req *internal_permission.ListAdministratorsRequest) (*query.MembershipSearchQuery, error) {
|
||||
offset, limit, asc, err := filter.PaginationPbToQuery(s.systemDefaults, req.Pagination)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
queries, err := administratorSearchFiltersToQuery(req.Filters)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &query.MembershipSearchQuery{
|
||||
SearchRequest: query.SearchRequest{
|
||||
Offset: offset,
|
||||
Limit: limit,
|
||||
Asc: asc,
|
||||
SortingColumn: administratorFieldNameToSortingColumn(req.GetSortingColumn()),
|
||||
},
|
||||
Queries: queries,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func administratorFieldNameToSortingColumn(field internal_permission.AdministratorFieldName) query.Column {
|
||||
switch field {
|
||||
case internal_permission.AdministratorFieldName_ADMINISTRATOR_FIELD_NAME_CREATION_DATE:
|
||||
return query.MembershipCreationDate
|
||||
case internal_permission.AdministratorFieldName_ADMINISTRATOR_FIELD_NAME_USER_ID:
|
||||
return query.MembershipUserID
|
||||
case internal_permission.AdministratorFieldName_ADMINISTRATOR_FIELD_NAME_CHANGE_DATE:
|
||||
return query.MembershipChangeDate
|
||||
case internal_permission.AdministratorFieldName_ADMINISTRATOR_FIELD_NAME_UNSPECIFIED:
|
||||
return query.MembershipCreationDate
|
||||
default:
|
||||
return query.MembershipCreationDate
|
||||
}
|
||||
}
|
||||
|
||||
func administratorSearchFiltersToQuery(queries []*internal_permission.AdministratorSearchFilter) (_ []query.SearchQuery, err error) {
|
||||
q := make([]query.SearchQuery, len(queries))
|
||||
for i, qry := range queries {
|
||||
q[i], err = administratorFilterToModel(qry)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return q, nil
|
||||
}
|
||||
|
||||
func administratorFilterToModel(filter *internal_permission.AdministratorSearchFilter) (query.SearchQuery, error) {
|
||||
switch q := filter.Filter.(type) {
|
||||
case *internal_permission.AdministratorSearchFilter_InUserIdsFilter:
|
||||
return inUserIDsFilterToQuery(q.InUserIdsFilter)
|
||||
case *internal_permission.AdministratorSearchFilter_CreationDate:
|
||||
return creationDateFilterToQuery(q.CreationDate)
|
||||
case *internal_permission.AdministratorSearchFilter_ChangeDate:
|
||||
return changeDateFilterToQuery(q.ChangeDate)
|
||||
case *internal_permission.AdministratorSearchFilter_UserOrganizationId:
|
||||
return userResourceOwnerFilterToQuery(q.UserOrganizationId)
|
||||
case *internal_permission.AdministratorSearchFilter_UserPreferredLoginName:
|
||||
return userLoginNameFilterToQuery(q.UserPreferredLoginName)
|
||||
case *internal_permission.AdministratorSearchFilter_UserDisplayName:
|
||||
return userDisplayNameFilterToQuery(q.UserDisplayName)
|
||||
case *internal_permission.AdministratorSearchFilter_Resource:
|
||||
return resourceFilterToQuery(q.Resource)
|
||||
default:
|
||||
return nil, zerrors.ThrowInvalidArgument(nil, "ORG-vR9nC", "List.Query.Invalid")
|
||||
}
|
||||
}
|
||||
|
||||
func inUserIDsFilterToQuery(q *filter_pb.InIDsFilter) (query.SearchQuery, error) {
|
||||
return query.NewMemberInUserIDsSearchQuery(q.GetIds())
|
||||
}
|
||||
|
||||
func userResourceOwnerFilterToQuery(q *filter_pb.IDFilter) (query.SearchQuery, error) {
|
||||
return query.NewAdministratorUserResourceOwnerSearchQuery(q.GetId())
|
||||
}
|
||||
|
||||
func userLoginNameFilterToQuery(q *internal_permission.UserPreferredLoginNameFilter) (query.SearchQuery, error) {
|
||||
return query.NewAdministratorUserLoginNameSearchQuery(q.GetPreferredLoginName())
|
||||
}
|
||||
|
||||
func userDisplayNameFilterToQuery(q *internal_permission.UserDisplayNameFilter) (query.SearchQuery, error) {
|
||||
return query.NewAdministratorUserDisplayNameSearchQuery(q.GetDisplayName())
|
||||
}
|
||||
|
||||
func creationDateFilterToQuery(q *filter_pb.TimestampFilter) (query.SearchQuery, error) {
|
||||
return query.NewMembershipCreationDateQuery(q.GetTimestamp().AsTime(), filter.TimestampMethodPbToQuery(q.Method))
|
||||
}
|
||||
|
||||
func changeDateFilterToQuery(q *filter_pb.TimestampFilter) (query.SearchQuery, error) {
|
||||
return query.NewMembershipChangeDateQuery(q.GetTimestamp().AsTime(), filter.TimestampMethodPbToQuery(q.Method))
|
||||
}
|
||||
|
||||
func resourceFilterToQuery(q *internal_permission.ResourceFilter) (query.SearchQuery, error) {
|
||||
switch q.GetResource().(type) {
|
||||
case *internal_permission.ResourceFilter_Instance:
|
||||
if q.GetInstance() {
|
||||
return query.NewMembershipIsIAMQuery()
|
||||
}
|
||||
case *internal_permission.ResourceFilter_OrganizationId:
|
||||
return query.NewMembershipOrgIDQuery(q.GetOrganizationId())
|
||||
case *internal_permission.ResourceFilter_ProjectId:
|
||||
return query.NewMembershipProjectIDQuery(q.GetProjectId())
|
||||
case *internal_permission.ResourceFilter_ProjectGrantId:
|
||||
return query.NewMembershipProjectGrantIDQuery(q.GetProjectGrantId())
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func administratorsToPb(administrators []*query.Administrator) []*internal_permission.Administrator {
|
||||
a := make([]*internal_permission.Administrator, len(administrators))
|
||||
for i, admin := range administrators {
|
||||
a[i] = administratorToPb(admin)
|
||||
}
|
||||
return a
|
||||
}
|
||||
|
||||
func administratorToPb(admin *query.Administrator) *internal_permission.Administrator {
|
||||
var resource internal_permission.Resource
|
||||
if admin.Instance != nil {
|
||||
resource = &internal_permission.Administrator_Instance{Instance: true}
|
||||
}
|
||||
if admin.Org != nil {
|
||||
resource = &internal_permission.Administrator_Organization{
|
||||
Organization: &internal_permission.Organization{
|
||||
Id: admin.Org.OrgID,
|
||||
Name: admin.Org.Name,
|
||||
},
|
||||
}
|
||||
}
|
||||
if admin.Project != nil {
|
||||
resource = &internal_permission.Administrator_Project{
|
||||
Project: &internal_permission.Project{
|
||||
Id: admin.Project.ProjectID,
|
||||
Name: admin.Project.Name,
|
||||
OrganizationId: admin.Project.ResourceOwner,
|
||||
},
|
||||
}
|
||||
}
|
||||
if admin.ProjectGrant != nil {
|
||||
resource = &internal_permission.Administrator_ProjectGrant{
|
||||
ProjectGrant: &internal_permission.ProjectGrant{
|
||||
Id: admin.ProjectGrant.GrantID,
|
||||
ProjectId: admin.ProjectGrant.ProjectID,
|
||||
ProjectName: admin.ProjectGrant.ProjectName,
|
||||
OrganizationId: admin.ProjectGrant.ResourceOwner,
|
||||
GrantedOrganizationId: admin.ProjectGrant.GrantedOrgID,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
return &internal_permission.Administrator{
|
||||
CreationDate: timestamppb.New(admin.CreationDate),
|
||||
ChangeDate: timestamppb.New(admin.ChangeDate),
|
||||
User: &internal_permission.User{
|
||||
Id: admin.User.UserID,
|
||||
PreferredLoginName: admin.User.LoginName,
|
||||
DisplayName: admin.User.DisplayName,
|
||||
OrganizationId: admin.User.ResourceOwner,
|
||||
},
|
||||
Resource: resource,
|
||||
Roles: admin.Roles,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
package internal_permission
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"connectrpc.com/connect"
|
||||
"google.golang.org/protobuf/reflect/protoreflect"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/api/authz"
|
||||
"github.com/zitadel/zitadel/internal/api/grpc/server"
|
||||
"github.com/zitadel/zitadel/internal/command"
|
||||
"github.com/zitadel/zitadel/internal/config/systemdefaults"
|
||||
"github.com/zitadel/zitadel/internal/domain"
|
||||
"github.com/zitadel/zitadel/internal/query"
|
||||
internal_permission "github.com/zitadel/zitadel/pkg/grpc/internal_permission/v2beta"
|
||||
"github.com/zitadel/zitadel/pkg/grpc/internal_permission/v2beta/internal_permissionconnect"
|
||||
)
|
||||
|
||||
var _ internal_permissionconnect.InternalPermissionServiceHandler = (*Server)(nil)
|
||||
|
||||
type Server struct {
|
||||
systemDefaults systemdefaults.SystemDefaults
|
||||
command *command.Commands
|
||||
query *query.Queries
|
||||
checkPermission domain.PermissionCheck
|
||||
}
|
||||
|
||||
type Config struct{}
|
||||
|
||||
func CreateServer(
|
||||
systemDefaults systemdefaults.SystemDefaults,
|
||||
command *command.Commands,
|
||||
query *query.Queries,
|
||||
checkPermission domain.PermissionCheck,
|
||||
) *Server {
|
||||
return &Server{
|
||||
systemDefaults: systemDefaults,
|
||||
command: command,
|
||||
query: query,
|
||||
checkPermission: checkPermission,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Server) RegisterConnectServer(interceptors ...connect.Interceptor) (string, http.Handler) {
|
||||
return internal_permissionconnect.NewInternalPermissionServiceHandler(s, connect.WithInterceptors(interceptors...))
|
||||
}
|
||||
|
||||
func (s *Server) FileDescriptor() protoreflect.FileDescriptor {
|
||||
return internal_permission.File_zitadel_internal_permission_v2beta_internal_permission_service_proto
|
||||
}
|
||||
|
||||
func (s *Server) AppName() string {
|
||||
return internal_permission.InternalPermissionService_ServiceDesc.ServiceName
|
||||
}
|
||||
|
||||
func (s *Server) MethodPrefix() string {
|
||||
return internal_permission.InternalPermissionService_ServiceDesc.ServiceName
|
||||
}
|
||||
|
||||
func (s *Server) AuthMethods() authz.MethodMapping {
|
||||
return internal_permission.InternalPermissionService_AuthMethods
|
||||
}
|
||||
|
||||
func (s *Server) RegisterGateway() server.RegisterGatewayFunc {
|
||||
return internal_permission.RegisterInternalPermissionServiceHandler
|
||||
}
|
||||
@@ -278,28 +278,28 @@ func (s *Server) ListOrgMembers(ctx context.Context, req *mgmt_pb.ListOrgMembers
|
||||
}
|
||||
|
||||
func (s *Server) AddOrgMember(ctx context.Context, req *mgmt_pb.AddOrgMemberRequest) (*mgmt_pb.AddOrgMemberResponse, error) {
|
||||
addedMember, err := s.command.AddOrgMember(ctx, authz.GetCtxData(ctx).OrgID, req.UserId, req.Roles...)
|
||||
addedMember, err := s.command.AddOrgMember(ctx, AddOrgMemberRequestToCommand(req, authz.GetCtxData(ctx).OrgID))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &mgmt_pb.AddOrgMemberResponse{
|
||||
Details: object.AddToDetailsPb(
|
||||
addedMember.Sequence,
|
||||
addedMember.ChangeDate,
|
||||
addedMember.EventDate,
|
||||
addedMember.ResourceOwner,
|
||||
),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *Server) UpdateOrgMember(ctx context.Context, req *mgmt_pb.UpdateOrgMemberRequest) (*mgmt_pb.UpdateOrgMemberResponse, error) {
|
||||
changedMember, err := s.command.ChangeOrgMember(ctx, UpdateOrgMemberRequestToDomain(ctx, req))
|
||||
changedMember, err := s.command.ChangeOrgMember(ctx, UpdateOrgMemberRequestToCommand(req, authz.GetCtxData(ctx).OrgID))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &mgmt_pb.UpdateOrgMemberResponse{
|
||||
Details: object.ChangeToDetailsPb(
|
||||
changedMember.Sequence,
|
||||
changedMember.ChangeDate,
|
||||
changedMember.EventDate,
|
||||
changedMember.ResourceOwner,
|
||||
),
|
||||
}, nil
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
"github.com/zitadel/zitadel/internal/api/grpc/metadata"
|
||||
"github.com/zitadel/zitadel/internal/api/grpc/object"
|
||||
org_grpc "github.com/zitadel/zitadel/internal/api/grpc/org"
|
||||
"github.com/zitadel/zitadel/internal/command"
|
||||
"github.com/zitadel/zitadel/internal/domain"
|
||||
"github.com/zitadel/zitadel/internal/eventstore/v1/models"
|
||||
"github.com/zitadel/zitadel/internal/query"
|
||||
@@ -67,8 +68,20 @@ func SetPrimaryOrgDomainRequestToDomain(ctx context.Context, req *mgmt_pb.SetPri
|
||||
}
|
||||
}
|
||||
|
||||
func UpdateOrgMemberRequestToDomain(ctx context.Context, req *mgmt_pb.UpdateOrgMemberRequest) *domain.Member {
|
||||
return domain.NewMember(authz.GetCtxData(ctx).OrgID, req.UserId, req.Roles...)
|
||||
func AddOrgMemberRequestToCommand(req *mgmt_pb.AddOrgMemberRequest, orgID string) *command.AddOrgMember {
|
||||
return &command.AddOrgMember{
|
||||
OrgID: orgID,
|
||||
UserID: req.UserId,
|
||||
Roles: req.Roles,
|
||||
}
|
||||
}
|
||||
|
||||
func UpdateOrgMemberRequestToCommand(req *mgmt_pb.UpdateOrgMemberRequest, orgID string) *command.ChangeOrgMember {
|
||||
return &command.ChangeOrgMember{
|
||||
OrgID: orgID,
|
||||
UserID: req.UserId,
|
||||
Roles: req.Roles,
|
||||
}
|
||||
}
|
||||
|
||||
func ListOrgMembersRequestToModel(ctx context.Context, req *mgmt_pb.ListOrgMembersRequest) (*query.OrgMembersQuery, error) {
|
||||
|
||||
@@ -227,7 +227,7 @@ func (s *Server) RemoveProject(ctx context.Context, req *mgmt_pb.RemoveProjectRe
|
||||
}
|
||||
grants, err := s.query.UserGrants(ctx, &query.UserGrantsQueries{
|
||||
Queries: []query.SearchQuery{projectQuery},
|
||||
}, true)
|
||||
}, true, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -312,7 +312,7 @@ func (s *Server) RemoveProjectRole(ctx context.Context, req *mgmt_pb.RemoveProje
|
||||
}
|
||||
userGrants, err := s.query.UserGrants(ctx, &query.UserGrantsQueries{
|
||||
Queries: []query.SearchQuery{projectQuery, rolesQuery},
|
||||
}, false)
|
||||
}, false, nil)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -354,24 +354,28 @@ func (s *Server) ListProjectMembers(ctx context.Context, req *mgmt_pb.ListProjec
|
||||
}
|
||||
|
||||
func (s *Server) AddProjectMember(ctx context.Context, req *mgmt_pb.AddProjectMemberRequest) (*mgmt_pb.AddProjectMemberResponse, error) {
|
||||
member, err := s.command.AddProjectMember(ctx, AddProjectMemberRequestToDomain(req), authz.GetCtxData(ctx).OrgID)
|
||||
member, err := s.command.AddProjectMember(ctx, AddProjectMemberRequestToCommand(req, authz.GetCtxData(ctx).OrgID))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &mgmt_pb.AddProjectMemberResponse{
|
||||
Details: object_grpc.AddToDetailsPb(member.Sequence, member.ChangeDate, member.ResourceOwner),
|
||||
Details: object_grpc.AddToDetailsPb(
|
||||
member.Sequence,
|
||||
member.EventDate,
|
||||
member.ResourceOwner,
|
||||
),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *Server) UpdateProjectMember(ctx context.Context, req *mgmt_pb.UpdateProjectMemberRequest) (*mgmt_pb.UpdateProjectMemberResponse, error) {
|
||||
member, err := s.command.ChangeProjectMember(ctx, UpdateProjectMemberRequestToDomain(req), authz.GetCtxData(ctx).OrgID)
|
||||
member, err := s.command.ChangeProjectMember(ctx, UpdateProjectMemberRequestToCommand(req, authz.GetCtxData(ctx).OrgID))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &mgmt_pb.UpdateProjectMemberResponse{
|
||||
Details: object_grpc.ChangeToDetailsPb(
|
||||
member.Sequence,
|
||||
member.ChangeDate,
|
||||
member.EventDate,
|
||||
member.ResourceOwner,
|
||||
),
|
||||
}, nil
|
||||
|
||||
@@ -104,12 +104,22 @@ func ProjectGrantsToIDs(projectGrants *query.ProjectGrants) []string {
|
||||
return converted
|
||||
}
|
||||
|
||||
func AddProjectMemberRequestToDomain(req *mgmt_pb.AddProjectMemberRequest) *domain.Member {
|
||||
return domain.NewMember(req.ProjectId, req.UserId, req.Roles...)
|
||||
func AddProjectMemberRequestToCommand(req *mgmt_pb.AddProjectMemberRequest, orgID string) *command.AddProjectMember {
|
||||
return &command.AddProjectMember{
|
||||
ResourceOwner: orgID,
|
||||
ProjectID: req.ProjectId,
|
||||
UserID: req.UserId,
|
||||
Roles: req.Roles,
|
||||
}
|
||||
}
|
||||
|
||||
func UpdateProjectMemberRequestToDomain(req *mgmt_pb.UpdateProjectMemberRequest) *domain.Member {
|
||||
return domain.NewMember(req.ProjectId, req.UserId, req.Roles...)
|
||||
func UpdateProjectMemberRequestToCommand(req *mgmt_pb.UpdateProjectMemberRequest, orgID string) *command.ChangeProjectMember {
|
||||
return &command.ChangeProjectMember{
|
||||
ResourceOwner: orgID,
|
||||
ProjectID: req.ProjectId,
|
||||
UserID: req.UserId,
|
||||
Roles: req.Roles,
|
||||
}
|
||||
}
|
||||
|
||||
func listProjectRequestToModel(req *mgmt_pb.ListProjectsRequest) (*query.ProjectSearchQueries, error) {
|
||||
|
||||
@@ -91,7 +91,7 @@ func (s *Server) UpdateProjectGrant(ctx context.Context, req *mgmt_pb.UpdateProj
|
||||
}
|
||||
grants, err := s.query.UserGrants(ctx, &query.UserGrantsQueries{
|
||||
Queries: []query.SearchQuery{projectQuery, grantQuery},
|
||||
}, true)
|
||||
}, true, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -139,7 +139,7 @@ func (s *Server) RemoveProjectGrant(ctx context.Context, req *mgmt_pb.RemoveProj
|
||||
}
|
||||
userGrants, err := s.query.UserGrants(ctx, &query.UserGrantsQueries{
|
||||
Queries: []query.SearchQuery{projectQuery, grantQuery},
|
||||
}, false)
|
||||
}, false, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -176,28 +176,28 @@ func (s *Server) ListProjectGrantMembers(ctx context.Context, req *mgmt_pb.ListP
|
||||
}
|
||||
|
||||
func (s *Server) AddProjectGrantMember(ctx context.Context, req *mgmt_pb.AddProjectGrantMemberRequest) (*mgmt_pb.AddProjectGrantMemberResponse, error) {
|
||||
member, err := s.command.AddProjectGrantMember(ctx, AddProjectGrantMemberRequestToDomain(req))
|
||||
member, err := s.command.AddProjectGrantMember(ctx, AddProjectGrantMemberRequestToCommand(req, authz.GetCtxData(ctx).OrgID))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &mgmt_pb.AddProjectGrantMemberResponse{
|
||||
Details: object_grpc.AddToDetailsPb(
|
||||
member.Sequence,
|
||||
member.ChangeDate,
|
||||
member.EventDate,
|
||||
member.ResourceOwner,
|
||||
),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *Server) UpdateProjectGrantMember(ctx context.Context, req *mgmt_pb.UpdateProjectGrantMemberRequest) (*mgmt_pb.UpdateProjectGrantMemberResponse, error) {
|
||||
member, err := s.command.ChangeProjectGrantMember(ctx, UpdateProjectGrantMemberRequestToDomain(req))
|
||||
member, err := s.command.ChangeProjectGrantMember(ctx, UpdateProjectGrantMemberRequestToCommand(req))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &mgmt_pb.UpdateProjectGrantMemberResponse{
|
||||
Details: object_grpc.ChangeToDetailsPb(
|
||||
member.Sequence,
|
||||
member.ChangeDate,
|
||||
member.EventDate,
|
||||
member.ResourceOwner,
|
||||
),
|
||||
}, nil
|
||||
|
||||
@@ -7,7 +7,6 @@ import (
|
||||
member_grpc "github.com/zitadel/zitadel/internal/api/grpc/member"
|
||||
"github.com/zitadel/zitadel/internal/api/grpc/object"
|
||||
"github.com/zitadel/zitadel/internal/command"
|
||||
"github.com/zitadel/zitadel/internal/domain"
|
||||
"github.com/zitadel/zitadel/internal/eventstore/v1/models"
|
||||
"github.com/zitadel/zitadel/internal/query"
|
||||
"github.com/zitadel/zitadel/internal/zerrors"
|
||||
@@ -146,24 +145,21 @@ func ListProjectGrantMembersRequestToModel(ctx context.Context, req *mgmt_pb.Lis
|
||||
}, nil
|
||||
}
|
||||
|
||||
func AddProjectGrantMemberRequestToDomain(req *mgmt_pb.AddProjectGrantMemberRequest) *domain.ProjectGrantMember {
|
||||
return &domain.ProjectGrantMember{
|
||||
ObjectRoot: models.ObjectRoot{
|
||||
AggregateID: req.ProjectId,
|
||||
},
|
||||
GrantID: req.GrantId,
|
||||
UserID: req.UserId,
|
||||
Roles: req.Roles,
|
||||
func AddProjectGrantMemberRequestToCommand(req *mgmt_pb.AddProjectGrantMemberRequest, orgID string) *command.AddProjectGrantMember {
|
||||
return &command.AddProjectGrantMember{
|
||||
ResourceOwner: orgID,
|
||||
ProjectID: req.ProjectId,
|
||||
GrantID: req.GrantId,
|
||||
UserID: req.UserId,
|
||||
Roles: req.Roles,
|
||||
}
|
||||
}
|
||||
|
||||
func UpdateProjectGrantMemberRequestToDomain(req *mgmt_pb.UpdateProjectGrantMemberRequest) *domain.ProjectGrantMember {
|
||||
return &domain.ProjectGrantMember{
|
||||
ObjectRoot: models.ObjectRoot{
|
||||
AggregateID: req.ProjectId,
|
||||
},
|
||||
GrantID: req.GrantId,
|
||||
UserID: req.UserId,
|
||||
Roles: req.Roles,
|
||||
func UpdateProjectGrantMemberRequestToCommand(req *mgmt_pb.UpdateProjectGrantMemberRequest) *command.ChangeProjectGrantMember {
|
||||
return &command.ChangeProjectGrantMember{
|
||||
ProjectID: req.ProjectId,
|
||||
GrantID: req.GrantId,
|
||||
UserID: req.UserId,
|
||||
Roles: req.Roles,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -142,7 +142,7 @@ func (s *Server) ListUserMetadata(ctx context.Context, req *mgmt_pb.ListUserMeta
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res, err := s.query.SearchUserMetadata(ctx, true, req.Id, metadataQueries, false)
|
||||
res, err := s.query.SearchUserMetadata(ctx, true, req.Id, metadataQueries, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -369,7 +369,7 @@ func (s *Server) removeUserDependencies(ctx context.Context, userID string) ([]*
|
||||
}
|
||||
grants, err := s.query.UserGrants(ctx, &query.UserGrantsQueries{
|
||||
Queries: []query.SearchQuery{userGrantUserQuery},
|
||||
}, true)
|
||||
}, true, nil)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
@@ -33,7 +33,7 @@ func (s *Server) ListUserGrants(ctx context.Context, req *mgmt_pb.ListUserGrantR
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res, err := s.query.UserGrants(ctx, queries, false)
|
||||
res, err := s.query.UserGrants(ctx, queries, false, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -44,11 +44,11 @@ func (s *Server) ListUserGrants(ctx context.Context, req *mgmt_pb.ListUserGrantR
|
||||
}
|
||||
|
||||
func (s *Server) AddUserGrant(ctx context.Context, req *mgmt_pb.AddUserGrantRequest) (*mgmt_pb.AddUserGrantResponse, error) {
|
||||
grant := AddUserGrantRequestToDomain(req)
|
||||
grant := AddUserGrantRequestToDomain(req, authz.GetCtxData(ctx).OrgID)
|
||||
if err := checkExplicitProjectPermission(ctx, grant.ProjectGrantID, grant.ProjectID); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
grant, err := s.command.AddUserGrant(ctx, grant, authz.GetCtxData(ctx).OrgID)
|
||||
grant, err := s.command.AddUserGrant(ctx, grant, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -63,7 +63,7 @@ func (s *Server) AddUserGrant(ctx context.Context, req *mgmt_pb.AddUserGrantRequ
|
||||
}
|
||||
|
||||
func (s *Server) UpdateUserGrant(ctx context.Context, req *mgmt_pb.UpdateUserGrantRequest) (*mgmt_pb.UpdateUserGrantResponse, error) {
|
||||
grant, err := s.command.ChangeUserGrant(ctx, UpdateUserGrantRequestToDomain(req), authz.GetCtxData(ctx).OrgID)
|
||||
grant, err := s.command.ChangeUserGrant(ctx, UpdateUserGrantRequestToDomain(req, authz.GetCtxData(ctx).OrgID), false, false, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -77,7 +77,7 @@ func (s *Server) UpdateUserGrant(ctx context.Context, req *mgmt_pb.UpdateUserGra
|
||||
}
|
||||
|
||||
func (s *Server) DeactivateUserGrant(ctx context.Context, req *mgmt_pb.DeactivateUserGrantRequest) (*mgmt_pb.DeactivateUserGrantResponse, error) {
|
||||
objectDetails, err := s.command.DeactivateUserGrant(ctx, req.GrantId, authz.GetCtxData(ctx).OrgID)
|
||||
objectDetails, err := s.command.DeactivateUserGrant(ctx, req.GrantId, authz.GetCtxData(ctx).OrgID, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -87,7 +87,7 @@ func (s *Server) DeactivateUserGrant(ctx context.Context, req *mgmt_pb.Deactivat
|
||||
}
|
||||
|
||||
func (s *Server) ReactivateUserGrant(ctx context.Context, req *mgmt_pb.ReactivateUserGrantRequest) (*mgmt_pb.ReactivateUserGrantResponse, error) {
|
||||
objectDetails, err := s.command.ReactivateUserGrant(ctx, req.GrantId, authz.GetCtxData(ctx).OrgID)
|
||||
objectDetails, err := s.command.ReactivateUserGrant(ctx, req.GrantId, authz.GetCtxData(ctx).OrgID, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -97,7 +97,7 @@ func (s *Server) ReactivateUserGrant(ctx context.Context, req *mgmt_pb.Reactivat
|
||||
}
|
||||
|
||||
func (s *Server) RemoveUserGrant(ctx context.Context, req *mgmt_pb.RemoveUserGrantRequest) (*mgmt_pb.RemoveUserGrantResponse, error) {
|
||||
objectDetails, err := s.command.RemoveUserGrant(ctx, req.GrantId, authz.GetCtxData(ctx).OrgID)
|
||||
objectDetails, err := s.command.RemoveUserGrant(ctx, req.GrantId, authz.GetCtxData(ctx).OrgID, false, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -49,19 +49,23 @@ func shouldAppendUserGrantOwnerQuery(queries []*user.UserGrantQuery) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func AddUserGrantRequestToDomain(req *mgmt_pb.AddUserGrantRequest) *domain.UserGrant {
|
||||
func AddUserGrantRequestToDomain(req *mgmt_pb.AddUserGrantRequest, resourceowner string) *domain.UserGrant {
|
||||
return &domain.UserGrant{
|
||||
UserID: req.UserId,
|
||||
ProjectID: req.ProjectId,
|
||||
ProjectGrantID: req.ProjectGrantId,
|
||||
RoleKeys: req.RoleKeys,
|
||||
ObjectRoot: models.ObjectRoot{
|
||||
ResourceOwner: resourceowner,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func UpdateUserGrantRequestToDomain(req *mgmt_pb.UpdateUserGrantRequest) *domain.UserGrant {
|
||||
func UpdateUserGrantRequestToDomain(req *mgmt_pb.UpdateUserGrantRequest, resourceowner string) *domain.UserGrant {
|
||||
return &domain.UserGrant{
|
||||
ObjectRoot: models.ObjectRoot{
|
||||
AggregateID: req.GrantId,
|
||||
AggregateID: req.GrantId,
|
||||
ResourceOwner: resourceowner,
|
||||
},
|
||||
UserID: req.UserId,
|
||||
RoleKeys: req.RoleKeys,
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
package metadata
|
||||
|
||||
import (
|
||||
"google.golang.org/protobuf/types/known/timestamppb"
|
||||
|
||||
filter_v2 "github.com/zitadel/zitadel/internal/api/grpc/filter/v2"
|
||||
"github.com/zitadel/zitadel/internal/query"
|
||||
"github.com/zitadel/zitadel/internal/zerrors"
|
||||
meta_pb "github.com/zitadel/zitadel/pkg/grpc/metadata/v2"
|
||||
)
|
||||
|
||||
func UserMetadataListToPb(dataList []*query.UserMetadata) []*meta_pb.Metadata {
|
||||
mds := make([]*meta_pb.Metadata, len(dataList))
|
||||
for i, data := range dataList {
|
||||
mds[i] = UserMetadataToPb(data)
|
||||
}
|
||||
return mds
|
||||
}
|
||||
|
||||
func UserMetadataToPb(data *query.UserMetadata) *meta_pb.Metadata {
|
||||
return &meta_pb.Metadata{
|
||||
Key: data.Key,
|
||||
Value: data.Value,
|
||||
CreationDate: timestamppb.New(data.CreationDate),
|
||||
ChangeDate: timestamppb.New(data.ChangeDate),
|
||||
}
|
||||
}
|
||||
|
||||
func UserMetadataFiltersToQuery(queries []*meta_pb.MetadataSearchFilter) (_ []query.SearchQuery, err error) {
|
||||
q := make([]query.SearchQuery, len(queries))
|
||||
for i, query := range queries {
|
||||
q[i], err = UserMetadataFilterToQuery(query)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return q, nil
|
||||
}
|
||||
|
||||
func UserMetadataFilterToQuery(filter *meta_pb.MetadataSearchFilter) (query.SearchQuery, error) {
|
||||
switch q := filter.Filter.(type) {
|
||||
case *meta_pb.MetadataSearchFilter_KeyFilter:
|
||||
return query.NewUserMetadataKeySearchQuery(q.KeyFilter.Key, filter_v2.TextMethodPbToQuery(q.KeyFilter.Method))
|
||||
default:
|
||||
return nil, zerrors.ThrowInvalidArgument(nil, "METAD-fdg23", "List.Query.Invalid")
|
||||
}
|
||||
}
|
||||
@@ -389,7 +389,8 @@ func TestServer_ListProjects(t *testing.T) {
|
||||
resp2 := createProject(iamOwnerCtx, instance, t, orgID, true, false)
|
||||
resp3 := createProject(iamOwnerCtx, instance, t, orgResp.GetOrganizationId(), false, true)
|
||||
request.Filters[0].Filter = &project.ProjectSearchFilter_InProjectIdsFilter{
|
||||
InProjectIdsFilter: &filter.InIDsFilter{Ids: []string{resp1.GetId(), resp2.GetId(), resp3.GetId(), projectResp.GetId()}},
|
||||
InProjectIdsFilter: &filter.InIDsFilter{
|
||||
Ids: []string{resp1.GetId(), resp2.GetId(), resp3.GetId(), projectResp.GetId()}},
|
||||
}
|
||||
response.Projects[0] = grantedProjectResp
|
||||
response.Projects[1] = projectResp
|
||||
@@ -1225,7 +1226,8 @@ func TestServer_ListProjectGrants(t *testing.T) {
|
||||
project2Resp := instance.CreateProject(iamOwnerCtx, t, orgResp.GetOrganizationId(), name2, false, false)
|
||||
project3Resp := instance.CreateProject(iamOwnerCtx, t, orgResp.GetOrganizationId(), name3, false, false)
|
||||
request.Filters[0].Filter = &project.ProjectGrantSearchFilter_InProjectIdsFilter{
|
||||
InProjectIdsFilter: &filter.InIDsFilter{Ids: []string{project1Resp.GetId(), project2Resp.GetId(), project3Resp.GetId(), projectResp.GetId()}},
|
||||
InProjectIdsFilter: &filter.InIDsFilter{
|
||||
Ids: []string{project1Resp.GetId(), project2Resp.GetId(), project3Resp.GetId(), projectResp.GetId()}},
|
||||
}
|
||||
|
||||
createProjectGrant(iamOwnerCtx, instance, t, orgID, project1Resp.GetId(), name1)
|
||||
|
||||
@@ -118,7 +118,7 @@ func (s *Server) userGrantsFromProject(ctx context.Context, projectID string) ([
|
||||
}
|
||||
userGrants, err := s.query.UserGrants(ctx, &query.UserGrantsQueries{
|
||||
Queries: []query.SearchQuery{projectQuery},
|
||||
}, false)
|
||||
}, false, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -119,7 +119,7 @@ func (s *Server) userGrantsFromProjectGrant(ctx context.Context, projectID, gran
|
||||
}
|
||||
userGrants, err := s.query.UserGrants(ctx, &query.UserGrantsQueries{
|
||||
Queries: []query.SearchQuery{projectQuery, grantQuery},
|
||||
}, false)
|
||||
}, false, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -109,7 +109,7 @@ func (s *Server) userGrantsFromProjectAndRole(ctx context.Context, projectID, ro
|
||||
}
|
||||
userGrants, err := s.query.UserGrants(ctx, &query.UserGrantsQueries{
|
||||
Queries: []query.SearchQuery{projectQuery, rolesQuery},
|
||||
}, false)
|
||||
}, false, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -16,6 +16,13 @@ import (
|
||||
)
|
||||
|
||||
func (s *Server) createUserTypeHuman(ctx context.Context, humanPb *user.CreateUserRequest_Human, orgId string, userName, userId *string) (*connect.Response[user.CreateUserResponse], error) {
|
||||
metadataEntries := make([]*user.SetMetadataEntry, len(humanPb.Metadata))
|
||||
for i, metadataEntry := range humanPb.Metadata {
|
||||
metadataEntries[i] = &user.SetMetadataEntry{
|
||||
Key: metadataEntry.GetKey(),
|
||||
Value: metadataEntry.GetValue(),
|
||||
}
|
||||
}
|
||||
addHumanPb := &user.AddHumanUserRequest{
|
||||
Username: userName,
|
||||
UserId: userId,
|
||||
@@ -27,6 +34,7 @@ func (s *Server) createUserTypeHuman(ctx context.Context, humanPb *user.CreateUs
|
||||
Phone: humanPb.Phone,
|
||||
IdpLinks: humanPb.IdpLinks,
|
||||
TotpSecret: humanPb.TotpSecret,
|
||||
Metadata: metadataEntries,
|
||||
}
|
||||
switch pwType := humanPb.GetPasswordType().(type) {
|
||||
case *user.CreateUserRequest_Human_HashedPassword:
|
||||
|
||||
@@ -10,7 +10,7 @@ import (
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"google.golang.org/protobuf/types/known/timestamppb"
|
||||
|
||||
|
||||
"github.com/zitadel/zitadel/internal/integration"
|
||||
"github.com/zitadel/zitadel/pkg/grpc/object/v2"
|
||||
"github.com/zitadel/zitadel/pkg/grpc/user/v2"
|
||||
|
||||
@@ -22,7 +22,7 @@ import (
|
||||
)
|
||||
|
||||
func TestServer_AddKey(t *testing.T) {
|
||||
resp := Instance.CreateUserTypeMachine(IamCTX)
|
||||
resp := Instance.CreateUserTypeMachine(IamCTX, Instance.DefaultOrg.Id)
|
||||
userId := resp.GetId()
|
||||
expirationDate := timestamppb.New(time.Now().Add(time.Hour * 24))
|
||||
type args struct {
|
||||
@@ -108,7 +108,7 @@ abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789
|
||||
ExpirationDate: expirationDate,
|
||||
},
|
||||
func(request *user.AddKeyRequest) error {
|
||||
resp := Instance.CreateUserTypeHuman(IamCTX)
|
||||
resp := Instance.CreateUserTypeHuman(IamCTX, gofakeit.Email())
|
||||
request.UserId = resp.Id
|
||||
return nil
|
||||
},
|
||||
@@ -220,7 +220,7 @@ func TestServer_AddKey_Permission(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestServer_RemoveKey(t *testing.T) {
|
||||
resp := Instance.CreateUserTypeMachine(IamCTX)
|
||||
resp := Instance.CreateUserTypeMachine(IamCTX, Instance.DefaultOrg.Id)
|
||||
userId := resp.GetId()
|
||||
expirationDate := timestamppb.New(time.Now().Add(time.Hour * 24))
|
||||
type args struct {
|
||||
@@ -388,7 +388,7 @@ func TestServer_ListKeys(t *testing.T) {
|
||||
})
|
||||
require.NoError(t, err)
|
||||
otherOrgUserId := otherOrgUser.GetId()
|
||||
otherUserId := Instance.CreateUserTypeMachine(SystemCTX).GetId()
|
||||
otherUserId := Instance.CreateUserTypeMachine(SystemCTX, Instance.DefaultOrg.Id).GetId()
|
||||
onlySinceTestStartFilter := &user.KeysSearchFilter{Filter: &user.KeysSearchFilter_CreatedDateFilter{CreatedDateFilter: &filter.TimestampFilter{
|
||||
Timestamp: timestamppb.Now(),
|
||||
Method: filter.TimestampFilterMethod_TIMESTAMP_FILTER_METHOD_AFTER_OR_EQUALS,
|
||||
|
||||
@@ -0,0 +1,403 @@
|
||||
//go:build integration
|
||||
|
||||
package user_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/base64"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/brianvoe/gofakeit/v6"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/integration"
|
||||
"github.com/zitadel/zitadel/pkg/grpc/filter/v2"
|
||||
metadata "github.com/zitadel/zitadel/pkg/grpc/metadata/v2"
|
||||
"github.com/zitadel/zitadel/pkg/grpc/user/v2"
|
||||
)
|
||||
|
||||
func TestServer_SetUserMetadata(t *testing.T) {
|
||||
iamOwnerCTX := Instance.WithAuthorization(CTX, integration.UserTypeIAMOwner)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
ctx context.Context
|
||||
dep func(request *user.SetUserMetadataRequest)
|
||||
req *user.SetUserMetadataRequest
|
||||
setDate bool
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "missing permission",
|
||||
ctx: Instance.WithAuthorization(context.Background(), integration.UserTypeNoPermission),
|
||||
dep: func(req *user.SetUserMetadataRequest) {
|
||||
req.UserId = Instance.CreateUserTypeHuman(CTX, gofakeit.Email()).GetId()
|
||||
},
|
||||
req: &user.SetUserMetadataRequest{
|
||||
Metadata: []*user.Metadata{{Key: "key1", Value: []byte(base64.StdEncoding.EncodeToString([]byte("value1")))}},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "set user metadata",
|
||||
ctx: iamOwnerCTX,
|
||||
dep: func(req *user.SetUserMetadataRequest) {
|
||||
req.UserId = Instance.CreateUserTypeHuman(CTX, gofakeit.Email()).GetId()
|
||||
},
|
||||
req: &user.SetUserMetadataRequest{
|
||||
Metadata: []*user.Metadata{{Key: "key1", Value: []byte(base64.StdEncoding.EncodeToString([]byte("value1")))}},
|
||||
},
|
||||
setDate: true,
|
||||
},
|
||||
{
|
||||
name: "set user metadata, multiple",
|
||||
ctx: iamOwnerCTX,
|
||||
dep: func(req *user.SetUserMetadataRequest) {
|
||||
req.UserId = Instance.CreateUserTypeHuman(CTX, gofakeit.Email()).GetId()
|
||||
},
|
||||
req: &user.SetUserMetadataRequest{
|
||||
Metadata: []*user.Metadata{
|
||||
{Key: "key1", Value: []byte(base64.StdEncoding.EncodeToString([]byte("value1")))},
|
||||
{Key: "key2", Value: []byte(base64.StdEncoding.EncodeToString([]byte("value2")))},
|
||||
{Key: "key3", Value: []byte(base64.StdEncoding.EncodeToString([]byte("value3")))},
|
||||
},
|
||||
},
|
||||
setDate: true,
|
||||
},
|
||||
{
|
||||
name: "set user metadata on non existent user",
|
||||
ctx: iamOwnerCTX,
|
||||
req: &user.SetUserMetadataRequest{
|
||||
UserId: "notexisting",
|
||||
Metadata: []*user.Metadata{{Key: "key1", Value: []byte(base64.StdEncoding.EncodeToString([]byte("value1")))}},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "update user metadata",
|
||||
ctx: Instance.WithAuthorization(CTX, integration.UserTypeIAMOwner),
|
||||
dep: func(req *user.SetUserMetadataRequest) {
|
||||
req.UserId = Instance.CreateUserTypeHuman(iamOwnerCTX, gofakeit.Email()).GetId()
|
||||
Instance.SetUserMetadata(iamOwnerCTX, req.UserId, "key1", "value1")
|
||||
},
|
||||
req: &user.SetUserMetadataRequest{
|
||||
Metadata: []*user.Metadata{{Key: "key1", Value: []byte(base64.StdEncoding.EncodeToString([]byte("value2")))}},
|
||||
},
|
||||
setDate: true,
|
||||
},
|
||||
{
|
||||
name: "update user metadata with same value",
|
||||
ctx: Instance.WithAuthorization(CTX, integration.UserTypeIAMOwner),
|
||||
dep: func(req *user.SetUserMetadataRequest) {
|
||||
req.UserId = Instance.CreateUserTypeHuman(iamOwnerCTX, gofakeit.Email()).GetId()
|
||||
Instance.SetUserMetadata(iamOwnerCTX, req.UserId, "key1", "value1")
|
||||
},
|
||||
req: &user.SetUserMetadataRequest{
|
||||
Metadata: []*user.Metadata{{Key: "key1", Value: []byte(base64.StdEncoding.EncodeToString([]byte("value1")))}},
|
||||
},
|
||||
setDate: true,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
creationDate := time.Now().UTC()
|
||||
if tt.dep != nil {
|
||||
tt.dep(tt.req)
|
||||
}
|
||||
got, err := Client.SetUserMetadata(tt.ctx, tt.req)
|
||||
changeDate := time.Now().UTC()
|
||||
if tt.wantErr {
|
||||
require.Error(t, err)
|
||||
return
|
||||
}
|
||||
require.NoError(t, err)
|
||||
|
||||
assertSetUserMetadataResponse(t, creationDate, changeDate, tt.setDate, got)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func assertSetUserMetadataResponse(t *testing.T, creationDate, changeDate time.Time, expectedSetDat bool, actualResp *user.SetUserMetadataResponse) {
|
||||
if expectedSetDat {
|
||||
if !changeDate.IsZero() {
|
||||
assert.WithinRange(t, actualResp.GetSetDate().AsTime(), creationDate, changeDate)
|
||||
} else {
|
||||
assert.WithinRange(t, actualResp.GetSetDate().AsTime(), creationDate, time.Now().UTC())
|
||||
}
|
||||
} else {
|
||||
assert.Nil(t, actualResp.SetDate)
|
||||
}
|
||||
}
|
||||
|
||||
func TestServer_ListUserMetadata(t *testing.T) {
|
||||
iamOwnerCTX := Instance.WithAuthorization(CTX, integration.UserTypeIAMOwner)
|
||||
|
||||
type args struct {
|
||||
ctx context.Context
|
||||
dep func(context.Context, *user.ListUserMetadataRequest, *user.ListUserMetadataResponse)
|
||||
req *user.ListUserMetadataRequest
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want *user.ListUserMetadataResponse
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "missing permission",
|
||||
args: args{
|
||||
ctx: Instance.WithAuthorization(context.Background(), integration.UserTypeNoPermission),
|
||||
dep: func(ctx context.Context, request *user.ListUserMetadataRequest, response *user.ListUserMetadataResponse) {
|
||||
userID := Instance.CreateUserTypeHuman(iamOwnerCTX, gofakeit.Email()).GetId()
|
||||
request.UserId = userID
|
||||
Instance.SetUserMetadata(iamOwnerCTX, userID, "key1", "value1")
|
||||
},
|
||||
req: &user.ListUserMetadataRequest{},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "list request",
|
||||
args: args{
|
||||
ctx: iamOwnerCTX,
|
||||
dep: func(ctx context.Context, request *user.ListUserMetadataRequest, response *user.ListUserMetadataResponse) {
|
||||
userID := Instance.CreateUserTypeHuman(iamOwnerCTX, gofakeit.Email()).GetId()
|
||||
request.UserId = userID
|
||||
metadataResp := Instance.SetUserMetadata(iamOwnerCTX, userID, "key1", "value1")
|
||||
|
||||
response.Metadata[0] = &metadata.Metadata{
|
||||
CreationDate: metadataResp.GetSetDate(),
|
||||
ChangeDate: metadataResp.GetSetDate(),
|
||||
Key: "key1",
|
||||
Value: []byte(base64.StdEncoding.EncodeToString([]byte("value1"))),
|
||||
}
|
||||
},
|
||||
req: &user.ListUserMetadataRequest{},
|
||||
},
|
||||
want: &user.ListUserMetadataResponse{
|
||||
Pagination: &filter.PaginationResponse{
|
||||
TotalResult: 1,
|
||||
AppliedLimit: 100,
|
||||
},
|
||||
Metadata: []*metadata.Metadata{
|
||||
{},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "list request single key",
|
||||
args: args{
|
||||
ctx: iamOwnerCTX,
|
||||
dep: func(ctx context.Context, request *user.ListUserMetadataRequest, response *user.ListUserMetadataResponse) {
|
||||
userID := Instance.CreateUserTypeHuman(iamOwnerCTX, gofakeit.Email()).GetId()
|
||||
request.UserId = userID
|
||||
key := "key1"
|
||||
response.Metadata[0] = setUserMetadata(iamOwnerCTX, userID, key, "value1")
|
||||
Instance.SetUserMetadata(iamOwnerCTX, userID, "key2", "value2")
|
||||
Instance.SetUserMetadata(iamOwnerCTX, userID, "key3", "value3")
|
||||
request.Filters[0] = &metadata.MetadataSearchFilter{
|
||||
Filter: &metadata.MetadataSearchFilter_KeyFilter{KeyFilter: &metadata.MetadataKeyFilter{Key: key}},
|
||||
}
|
||||
},
|
||||
req: &user.ListUserMetadataRequest{
|
||||
Filters: []*metadata.MetadataSearchFilter{{}},
|
||||
},
|
||||
},
|
||||
want: &user.ListUserMetadataResponse{
|
||||
Pagination: &filter.PaginationResponse{
|
||||
TotalResult: 1,
|
||||
AppliedLimit: 100,
|
||||
},
|
||||
Metadata: []*metadata.Metadata{
|
||||
{},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "list multiple keys",
|
||||
args: args{
|
||||
ctx: iamOwnerCTX,
|
||||
dep: func(ctx context.Context, request *user.ListUserMetadataRequest, response *user.ListUserMetadataResponse) {
|
||||
userID := Instance.CreateUserTypeHuman(iamOwnerCTX, gofakeit.Email()).GetId()
|
||||
request.UserId = userID
|
||||
|
||||
response.Metadata[2] = setUserMetadata(iamOwnerCTX, userID, "key1", "value1")
|
||||
response.Metadata[1] = setUserMetadata(iamOwnerCTX, userID, "key2", "value2")
|
||||
response.Metadata[0] = setUserMetadata(iamOwnerCTX, userID, "key3", "value3")
|
||||
},
|
||||
req: &user.ListUserMetadataRequest{},
|
||||
},
|
||||
want: &user.ListUserMetadataResponse{
|
||||
Pagination: &filter.PaginationResponse{
|
||||
TotalResult: 3,
|
||||
AppliedLimit: 100,
|
||||
},
|
||||
Metadata: []*metadata.Metadata{
|
||||
{}, {}, {},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if tt.args.dep != nil {
|
||||
tt.args.dep(tt.args.ctx, tt.args.req, tt.want)
|
||||
}
|
||||
|
||||
retryDuration, tick := integration.WaitForAndTickWithMaxDuration(iamOwnerCTX, time.Minute)
|
||||
require.EventuallyWithT(t, func(ttt *assert.CollectT) {
|
||||
got, listErr := Instance.Client.UserV2.ListUserMetadata(tt.args.ctx, tt.args.req)
|
||||
if tt.wantErr {
|
||||
require.Error(ttt, listErr)
|
||||
return
|
||||
}
|
||||
require.NoError(ttt, listErr)
|
||||
// always first check length, otherwise its failed anyway
|
||||
if assert.Len(ttt, got.Metadata, len(tt.want.Metadata)) {
|
||||
assert.EqualExportedValues(ttt, got.Metadata, tt.want.Metadata)
|
||||
}
|
||||
assertPaginationResponse(ttt, tt.want.Pagination, got.Pagination)
|
||||
}, retryDuration, tick, "timeout waiting for expected execution result")
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func setUserMetadata(ctx context.Context, userID, key, value string) *metadata.Metadata {
|
||||
metadataResp := Instance.SetUserMetadata(ctx, userID, key, value)
|
||||
return &metadata.Metadata{
|
||||
CreationDate: metadataResp.GetSetDate(),
|
||||
ChangeDate: metadataResp.GetSetDate(),
|
||||
Key: key,
|
||||
Value: []byte(base64.StdEncoding.EncodeToString([]byte(value))),
|
||||
}
|
||||
}
|
||||
|
||||
func assertPaginationResponse(t *assert.CollectT, expected *filter.PaginationResponse, actual *filter.PaginationResponse) {
|
||||
assert.Equal(t, expected.AppliedLimit, actual.AppliedLimit)
|
||||
assert.Equal(t, expected.TotalResult, actual.TotalResult)
|
||||
}
|
||||
|
||||
func TestServer_DeleteUserMetadata(t *testing.T) {
|
||||
iamOwnerCTX := Instance.WithAuthorization(CTX, integration.UserTypeIAMOwner)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
ctx context.Context
|
||||
prepare func(request *user.DeleteUserMetadataRequest) (time.Time, time.Time)
|
||||
req *user.DeleteUserMetadataRequest
|
||||
wantDeletionDate bool
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "empty id",
|
||||
ctx: iamOwnerCTX,
|
||||
req: &user.DeleteUserMetadataRequest{
|
||||
UserId: "",
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "delete, user not existing",
|
||||
ctx: iamOwnerCTX,
|
||||
req: &user.DeleteUserMetadataRequest{
|
||||
UserId: "notexisting",
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "delete",
|
||||
ctx: iamOwnerCTX,
|
||||
prepare: func(request *user.DeleteUserMetadataRequest) (time.Time, time.Time) {
|
||||
creationDate := time.Now().UTC()
|
||||
userID := Instance.CreateUserTypeHuman(iamOwnerCTX, gofakeit.Email()).GetId()
|
||||
request.UserId = userID
|
||||
key := "key1"
|
||||
Instance.SetUserMetadata(iamOwnerCTX, userID, key, "value1")
|
||||
request.Keys = []string{key}
|
||||
return creationDate, time.Time{}
|
||||
},
|
||||
req: &user.DeleteUserMetadataRequest{},
|
||||
wantDeletionDate: true,
|
||||
},
|
||||
{
|
||||
name: "delete, empty list",
|
||||
ctx: iamOwnerCTX,
|
||||
prepare: func(request *user.DeleteUserMetadataRequest) (time.Time, time.Time) {
|
||||
creationDate := time.Now().UTC()
|
||||
userID := Instance.CreateUserTypeHuman(iamOwnerCTX, gofakeit.Email()).GetId()
|
||||
request.UserId = userID
|
||||
key := "key1"
|
||||
Instance.SetUserMetadata(iamOwnerCTX, userID, key, "value1")
|
||||
Instance.DeleteUserMetadata(iamOwnerCTX, userID, key)
|
||||
return creationDate, time.Now().UTC()
|
||||
},
|
||||
req: &user.DeleteUserMetadataRequest{},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "delete, already removed",
|
||||
ctx: iamOwnerCTX,
|
||||
prepare: func(request *user.DeleteUserMetadataRequest) (time.Time, time.Time) {
|
||||
creationDate := time.Now().UTC()
|
||||
userID := Instance.CreateUserTypeHuman(iamOwnerCTX, gofakeit.Email()).GetId()
|
||||
request.UserId = userID
|
||||
key := "key1"
|
||||
Instance.SetUserMetadata(iamOwnerCTX, userID, key, "value1")
|
||||
Instance.DeleteUserMetadata(iamOwnerCTX, userID, key)
|
||||
request.Keys = []string{key}
|
||||
return creationDate, time.Now().UTC()
|
||||
},
|
||||
req: &user.DeleteUserMetadataRequest{},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "delete, multiple",
|
||||
ctx: iamOwnerCTX,
|
||||
prepare: func(request *user.DeleteUserMetadataRequest) (time.Time, time.Time) {
|
||||
creationDate := time.Now().UTC()
|
||||
userID := Instance.CreateUserTypeHuman(iamOwnerCTX, gofakeit.Email()).GetId()
|
||||
request.UserId = userID
|
||||
key1 := "key1"
|
||||
Instance.SetUserMetadata(iamOwnerCTX, userID, key1, "value1")
|
||||
key2 := "key2"
|
||||
Instance.SetUserMetadata(iamOwnerCTX, userID, key2, "value1")
|
||||
key3 := "key3"
|
||||
Instance.SetUserMetadata(iamOwnerCTX, userID, key3, "value1")
|
||||
request.Keys = []string{key1, key2, key3}
|
||||
return creationDate, time.Time{}
|
||||
},
|
||||
req: &user.DeleteUserMetadataRequest{},
|
||||
wantDeletionDate: true,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
var creationDate, deletionDate time.Time
|
||||
if tt.prepare != nil {
|
||||
creationDate, deletionDate = tt.prepare(tt.req)
|
||||
}
|
||||
got, err := Instance.Client.UserV2.DeleteUserMetadata(tt.ctx, tt.req)
|
||||
if tt.wantErr {
|
||||
assert.Error(t, err)
|
||||
return
|
||||
}
|
||||
assert.NoError(t, err)
|
||||
assertDeleteProjectResponse(t, creationDate, deletionDate, tt.wantDeletionDate, got)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func assertDeleteProjectResponse(t *testing.T, creationDate, deletionDate time.Time, expectedDeletionDate bool, actualResp *user.DeleteUserMetadataResponse) {
|
||||
if expectedDeletionDate {
|
||||
if !deletionDate.IsZero() {
|
||||
assert.WithinRange(t, actualResp.GetDeletionDate().AsTime(), creationDate, deletionDate)
|
||||
} else {
|
||||
assert.WithinRange(t, actualResp.GetDeletionDate().AsTime(), creationDate, time.Now().UTC())
|
||||
}
|
||||
} else {
|
||||
assert.Nil(t, actualResp.DeletionDate)
|
||||
}
|
||||
}
|
||||
@@ -22,7 +22,7 @@ import (
|
||||
)
|
||||
|
||||
func TestServer_AddPersonalAccessToken(t *testing.T) {
|
||||
resp := Instance.CreateUserTypeMachine(IamCTX)
|
||||
resp := Instance.CreateUserTypeMachine(IamCTX, Instance.DefaultOrg.Id)
|
||||
userId := resp.GetId()
|
||||
expirationDate := timestamppb.New(time.Now().Add(time.Hour * 24))
|
||||
type args struct {
|
||||
@@ -64,7 +64,7 @@ func TestServer_AddPersonalAccessToken(t *testing.T) {
|
||||
ExpirationDate: expirationDate,
|
||||
},
|
||||
func(request *user.AddPersonalAccessTokenRequest) error {
|
||||
resp := Instance.CreateUserTypeHuman(IamCTX)
|
||||
resp := Instance.CreateUserTypeHuman(IamCTX, gofakeit.Email())
|
||||
request.UserId = resp.Id
|
||||
return nil
|
||||
},
|
||||
@@ -172,7 +172,7 @@ func TestServer_AddPersonalAccessToken_Permission(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestServer_RemovePersonalAccessToken(t *testing.T) {
|
||||
resp := Instance.CreateUserTypeMachine(IamCTX)
|
||||
resp := Instance.CreateUserTypeMachine(IamCTX, Instance.DefaultOrg.Id)
|
||||
userId := resp.GetId()
|
||||
expirationDate := timestamppb.New(time.Now().Add(time.Hour * 24))
|
||||
type args struct {
|
||||
@@ -339,7 +339,7 @@ func TestServer_ListPersonalAccessTokens(t *testing.T) {
|
||||
})
|
||||
require.NoError(t, err)
|
||||
otherOrgUserId := otherOrgUser.GetId()
|
||||
otherUserId := Instance.CreateUserTypeMachine(SystemCTX).GetId()
|
||||
otherUserId := Instance.CreateUserTypeMachine(SystemCTX, Instance.DefaultOrg.Id).GetId()
|
||||
onlySinceTestStartFilter := &user.PersonalAccessTokensSearchFilter{Filter: &user.PersonalAccessTokensSearchFilter_CreatedDateFilter{CreatedDateFilter: &filter.TimestampFilter{
|
||||
Timestamp: timestamppb.Now(),
|
||||
Method: filter.TimestampFilterMethod_TIMESTAMP_FILTER_METHOD_AFTER_OR_EQUALS,
|
||||
|
||||
@@ -43,7 +43,7 @@ func TestServer_AddSecret(t *testing.T) {
|
||||
CTX,
|
||||
&user.AddSecretRequest{},
|
||||
func(request *user.AddSecretRequest) error {
|
||||
resp := Instance.CreateUserTypeMachine(CTX)
|
||||
resp := Instance.CreateUserTypeMachine(CTX, Instance.DefaultOrg.Id)
|
||||
request.UserId = resp.GetId()
|
||||
return nil
|
||||
},
|
||||
@@ -55,7 +55,7 @@ func TestServer_AddSecret(t *testing.T) {
|
||||
CTX,
|
||||
&user.AddSecretRequest{},
|
||||
func(request *user.AddSecretRequest) error {
|
||||
resp := Instance.CreateUserTypeMachine(CTX)
|
||||
resp := Instance.CreateUserTypeMachine(CTX, Instance.DefaultOrg.Id)
|
||||
request.UserId = resp.GetId()
|
||||
return nil
|
||||
},
|
||||
@@ -67,7 +67,7 @@ func TestServer_AddSecret(t *testing.T) {
|
||||
CTX,
|
||||
&user.AddSecretRequest{},
|
||||
func(request *user.AddSecretRequest) error {
|
||||
resp := Instance.CreateUserTypeMachine(CTX)
|
||||
resp := Instance.CreateUserTypeMachine(CTX, Instance.DefaultOrg.Id)
|
||||
request.UserId = resp.GetId()
|
||||
_, err := Client.AddSecret(CTX, &user.AddSecretRequest{
|
||||
UserId: resp.GetId(),
|
||||
@@ -202,7 +202,7 @@ func TestServer_RemoveSecret(t *testing.T) {
|
||||
CTX,
|
||||
&user.RemoveSecretRequest{},
|
||||
func(request *user.RemoveSecretRequest) error {
|
||||
resp := Instance.CreateUserTypeMachine(CTX)
|
||||
resp := Instance.CreateUserTypeMachine(CTX, Instance.DefaultOrg.Id)
|
||||
request.UserId = resp.GetId()
|
||||
return nil
|
||||
},
|
||||
@@ -215,7 +215,7 @@ func TestServer_RemoveSecret(t *testing.T) {
|
||||
CTX,
|
||||
&user.RemoveSecretRequest{},
|
||||
func(request *user.RemoveSecretRequest) error {
|
||||
resp := Instance.CreateUserTypeMachine(CTX)
|
||||
resp := Instance.CreateUserTypeMachine(CTX, Instance.DefaultOrg.Id)
|
||||
request.UserId = resp.GetId()
|
||||
_, err := Instance.Client.UserV2.AddSecret(CTX, &user.AddSecretRequest{
|
||||
UserId: resp.GetId(),
|
||||
|
||||
@@ -4,6 +4,7 @@ package user_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"os"
|
||||
@@ -1818,7 +1819,7 @@ func TestServer_DeleteUser(t *testing.T) {
|
||||
request.UserId = resp.GetUserId()
|
||||
Instance.CreateProjectUserGrant(t, CTX, projectResp.GetId(), request.UserId)
|
||||
Instance.CreateProjectMembership(t, CTX, projectResp.GetId(), request.UserId)
|
||||
Instance.CreateOrgMembership(t, CTX, request.UserId)
|
||||
Instance.CreateOrgMembership(t, CTX, Instance.DefaultOrg.Id, request.UserId)
|
||||
return CTX
|
||||
},
|
||||
},
|
||||
@@ -3945,6 +3946,44 @@ func TestServer_CreateUser(t *testing.T) {
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "with metadata",
|
||||
testCase: func(runId string) testCase {
|
||||
username := fmt.Sprintf("donald.duck+%s", runId)
|
||||
email := username + "@example.com"
|
||||
return testCase{
|
||||
args: args{
|
||||
CTX,
|
||||
&user.CreateUserRequest{
|
||||
OrganizationId: Instance.DefaultOrg.Id,
|
||||
Username: &username,
|
||||
UserType: &user.CreateUserRequest_Human_{
|
||||
Human: &user.CreateUserRequest_Human{
|
||||
Profile: &user.SetHumanProfile{
|
||||
GivenName: "Donald",
|
||||
FamilyName: "Duck",
|
||||
},
|
||||
Email: &user.SetHumanEmail{
|
||||
Email: email,
|
||||
Verification: &user.SetHumanEmail_IsVerified{
|
||||
IsVerified: true,
|
||||
},
|
||||
},
|
||||
Metadata: []*user.Metadata{
|
||||
{Key: "key1", Value: []byte(base64.StdEncoding.EncodeToString([]byte("value1")))},
|
||||
{Key: "key2", Value: []byte(base64.StdEncoding.EncodeToString([]byte("value2")))},
|
||||
{Key: "key3", Value: []byte(base64.StdEncoding.EncodeToString([]byte("value3")))},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
want: &user.CreateUserResponse{
|
||||
Id: "is generated",
|
||||
},
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "with idp",
|
||||
testCase: func(runId string) testCase {
|
||||
@@ -4890,7 +4929,7 @@ func TestServer_UpdateUserTypeHuman(t *testing.T) {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
now := time.Now()
|
||||
runId := fmt.Sprint(now.UnixNano() + int64(i))
|
||||
userId := Instance.CreateUserTypeHuman(CTX).GetId()
|
||||
userId := Instance.CreateUserTypeHuman(CTX, gofakeit.Email()).GetId()
|
||||
test := tt.testCase(runId, userId)
|
||||
got, err := Client.UpdateUser(test.args.ctx, test.args.req)
|
||||
if test.wantErr {
|
||||
@@ -4972,7 +5011,7 @@ func TestServer_UpdateUserTypeMachine(t *testing.T) {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
now := time.Now()
|
||||
runId := fmt.Sprint(now.UnixNano() + int64(i))
|
||||
userId := Instance.CreateUserTypeMachine(CTX).GetId()
|
||||
userId := Instance.CreateUserTypeMachine(CTX, Instance.DefaultOrg.Id).GetId()
|
||||
test := tt.testCase(runId, userId)
|
||||
got, err := Client.UpdateUser(test.args.ctx, test.args.req)
|
||||
if test.wantErr {
|
||||
|
||||
@@ -0,0 +1,80 @@
|
||||
package user
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"connectrpc.com/connect"
|
||||
"google.golang.org/protobuf/types/known/timestamppb"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/api/grpc/filter/v2"
|
||||
"github.com/zitadel/zitadel/internal/api/grpc/metadata/v2"
|
||||
"github.com/zitadel/zitadel/internal/domain"
|
||||
"github.com/zitadel/zitadel/internal/query"
|
||||
"github.com/zitadel/zitadel/pkg/grpc/user/v2"
|
||||
)
|
||||
|
||||
func (s *Server) ListUserMetadata(ctx context.Context, req *connect.Request[user.ListUserMetadataRequest]) (*connect.Response[user.ListUserMetadataResponse], error) {
|
||||
metadataQueries, err := s.listUserMetadataRequestToModel(req.Msg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res, err := s.query.SearchUserMetadata(ctx, true, req.Msg.UserId, metadataQueries, s.checkPermission)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return connect.NewResponse(&user.ListUserMetadataResponse{
|
||||
Metadata: metadata.UserMetadataListToPb(res.Metadata),
|
||||
Pagination: filter.QueryToPaginationPb(metadataQueries.SearchRequest, res.SearchResponse),
|
||||
}), nil
|
||||
}
|
||||
|
||||
func (s *Server) listUserMetadataRequestToModel(req *user.ListUserMetadataRequest) (*query.UserMetadataSearchQueries, error) {
|
||||
offset, limit, asc, err := filter.PaginationPbToQuery(s.systemDefaults, req.Pagination)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
queries, err := metadata.UserMetadataFiltersToQuery(req.Filters)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &query.UserMetadataSearchQueries{
|
||||
SearchRequest: query.SearchRequest{
|
||||
Offset: offset,
|
||||
Limit: limit,
|
||||
Asc: asc,
|
||||
SortingColumn: query.UserMetadataCreationDateCol,
|
||||
},
|
||||
Queries: queries,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *Server) SetUserMetadata(ctx context.Context, req *connect.Request[user.SetUserMetadataRequest]) (*connect.Response[user.SetUserMetadataResponse], error) {
|
||||
result, err := s.command.BulkSetUserMetadata(ctx, req.Msg.UserId, "", setUserMetadataToDomain(req.Msg)...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return connect.NewResponse(&user.SetUserMetadataResponse{
|
||||
SetDate: timestamppb.New(result.EventDate),
|
||||
}), nil
|
||||
}
|
||||
|
||||
func setUserMetadataToDomain(req *user.SetUserMetadataRequest) []*domain.Metadata {
|
||||
metadata := make([]*domain.Metadata, len(req.Metadata))
|
||||
for i, data := range req.Metadata {
|
||||
metadata[i] = &domain.Metadata{
|
||||
Key: data.Key,
|
||||
Value: data.Value,
|
||||
}
|
||||
}
|
||||
return metadata
|
||||
}
|
||||
|
||||
func (s *Server) DeleteUserMetadata(ctx context.Context, req *connect.Request[user.DeleteUserMetadataRequest]) (*connect.Response[user.DeleteUserMetadataResponse], error) {
|
||||
result, err := s.command.BulkRemoveUserMetadata(ctx, req.Msg.UserId, "", req.Msg.Keys...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return connect.NewResponse(&user.DeleteUserMetadataResponse{
|
||||
DeletionDate: timestamppb.New(result.EventDate),
|
||||
}), nil
|
||||
}
|
||||
@@ -37,9 +37,9 @@ type Server struct {
|
||||
type Config struct{}
|
||||
|
||||
func CreateServer(
|
||||
systemDefaults systemdefaults.SystemDefaults,
|
||||
command *command.Commands,
|
||||
query *query.Queries,
|
||||
systemDefaults systemdefaults.SystemDefaults,
|
||||
userCodeAlg crypto.EncryptionAlgorithm,
|
||||
idpAlg crypto.EncryptionAlgorithm,
|
||||
idpCallback func(ctx context.Context) string,
|
||||
@@ -48,7 +48,6 @@ func CreateServer(
|
||||
checkPermission domain.PermissionCheck,
|
||||
) *Server {
|
||||
return &Server{
|
||||
systemDefaults: systemDefaults,
|
||||
command: command,
|
||||
query: query,
|
||||
userCodeAlg: userCodeAlg,
|
||||
@@ -57,6 +56,7 @@ func CreateServer(
|
||||
samlRootURL: samlRootURL,
|
||||
assetAPIPrefix: assetAPIPrefix,
|
||||
checkPermission: checkPermission,
|
||||
systemDefaults: systemDefaults,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -204,7 +204,7 @@ func (s *Server) removeUserDependencies(ctx context.Context, userID string) ([]*
|
||||
}
|
||||
grants, err := s.query.UserGrants(ctx, &query.UserGrantsQueries{
|
||||
Queries: []query.SearchQuery{userGrantUserQuery},
|
||||
}, true)
|
||||
}, true, nil)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
@@ -1773,7 +1773,7 @@ func TestServer_DeleteUser(t *testing.T) {
|
||||
request.UserId = resp.GetUserId()
|
||||
Instance.CreateProjectUserGrant(t, CTX, projectResp.GetId(), request.UserId)
|
||||
Instance.CreateProjectMembership(t, CTX, projectResp.GetId(), request.UserId)
|
||||
Instance.CreateOrgMembership(t, CTX, request.UserId)
|
||||
Instance.CreateOrgMembership(t, CTX, Instance.DefaultOrg.Id, request.UserId)
|
||||
},
|
||||
},
|
||||
want: &user.DeleteUserResponse{
|
||||
@@ -2125,7 +2125,7 @@ func TestServer_StartIdentityProviderIntent(t *testing.T) {
|
||||
if tt.want.url != "" && !tt.want.postForm {
|
||||
authUrl, err := url.Parse(got.GetAuthUrl())
|
||||
require.NoError(t, err)
|
||||
|
||||
|
||||
assert.Equal(t, tt.want.url, authUrl.Scheme+"://"+authUrl.Host+authUrl.Path)
|
||||
require.Len(t, authUrl.Query(), len(tt.want.parametersEqual)+len(tt.want.parametersExisting))
|
||||
|
||||
|
||||
@@ -296,7 +296,7 @@ func (s *Server) removeUserDependencies(ctx context.Context, userID string) ([]*
|
||||
}
|
||||
grants, err := s.query.UserGrants(ctx, &query.UserGrantsQueries{
|
||||
Queries: []query.SearchQuery{userGrantUserQuery},
|
||||
}, true)
|
||||
}, true, nil)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
@@ -309,7 +309,7 @@ func (p *Storage) getCustomAttributes(ctx context.Context, user *query.User, use
|
||||
true,
|
||||
user.ID,
|
||||
&query.UserMetadataSearchQueries{Queries: []query.SearchQuery{resourceOwnerQuery}},
|
||||
false,
|
||||
nil,
|
||||
)
|
||||
if err != nil {
|
||||
logging.WithError(err).Info("unable to get md in action")
|
||||
@@ -490,7 +490,7 @@ func (p *Storage) getGrants(ctx context.Context, userID, applicationID string) (
|
||||
userIDQuery,
|
||||
activeQuery,
|
||||
},
|
||||
}, true)
|
||||
}, true, nil)
|
||||
}
|
||||
|
||||
type customAttribute struct {
|
||||
|
||||
@@ -17,6 +17,8 @@ import (
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"golang.org/x/text/language"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/status"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/api/scim/resources"
|
||||
"github.com/zitadel/zitadel/internal/api/scim/schemas"
|
||||
@@ -478,7 +480,7 @@ func TestBulk(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "fail on errors",
|
||||
body: bulkFailOnErrorsJson,
|
||||
body: withUsername(bulkFailOnErrorsJson, gofakeit.Username()),
|
||||
want: &scim.BulkResponse{
|
||||
Schemas: []schemas.ScimSchemaType{schemas.IdBulkResponse},
|
||||
Operations: []*scim.BulkResponseOperation{
|
||||
@@ -579,7 +581,6 @@ func TestBulk(t *testing.T) {
|
||||
|
||||
response, err := Instance.Client.SCIM.Bulk(ctx, orgID, tt.body)
|
||||
createdUserIDs := buildCreatedIDs(response)
|
||||
defer deleteUsers(t, createdUserIDs)
|
||||
|
||||
if tt.wantErr != nil {
|
||||
statusCode := tt.wantErr.status
|
||||
@@ -656,17 +657,6 @@ func buildCreatedIDs(response *scim.BulkResponse) []string {
|
||||
return createdIds
|
||||
}
|
||||
|
||||
func deleteUsers(t require.TestingT, ids []string) {
|
||||
for _, id := range ids {
|
||||
err := Instance.Client.SCIM.Users.Delete(CTX, Instance.DefaultOrg.Id, id)
|
||||
|
||||
// only not found errors are ok (if the user is deleted in a later on bulk request)
|
||||
if err != nil {
|
||||
scim.RequireScimError(t, http.StatusNotFound, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func buildMinimalUpdateRequest(userID string) *scim.BulkRequest {
|
||||
return &scim.BulkRequest{
|
||||
Schemas: []schemas.ScimSchemaType{schemas.IdBulkRequest},
|
||||
@@ -690,7 +680,7 @@ func buildTooManyOperationsRequest() *scim.BulkRequest {
|
||||
req.Operations[i] = &scim.BulkRequestOperation{
|
||||
Method: http.MethodPost,
|
||||
Path: "/Users",
|
||||
Data: minimalUserJson,
|
||||
Data: withUsername(minimalUserJson, gofakeit.Username()),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -720,8 +710,11 @@ func ensureMetadataProjected(t require.TestingT, userID, key, value string) {
|
||||
Id: userID,
|
||||
Key: key,
|
||||
})
|
||||
require.NoError(tt, err)
|
||||
require.Equal(tt, value, string(md.Metadata.Value))
|
||||
if !assert.NoError(tt, err) {
|
||||
require.Equal(tt, status.Code(err), codes.NotFound)
|
||||
return
|
||||
}
|
||||
assert.Equal(tt, value, string(md.Metadata.Value))
|
||||
}, retryDuration, tick)
|
||||
}
|
||||
|
||||
|
||||
@@ -32,14 +32,14 @@
|
||||
"urn:ietf:params:scim:schemas:core:2.0:User"
|
||||
],
|
||||
"externalId": "scim-bulk-created-user-0",
|
||||
"userName": "scim-bulk-created-user-0",
|
||||
"userName": "{{ .Username }}",
|
||||
"name": {
|
||||
"familyName": "scim-bulk-created-user-0-family-name",
|
||||
"givenName": "scim-bulk-created-user-0-given-name"
|
||||
},
|
||||
"emails": [
|
||||
{
|
||||
"value": "scim-bulk-created-user-0@example.com",
|
||||
"value": "{{ .Username }}@example.com",
|
||||
"primary": true
|
||||
}
|
||||
],
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"schemas": ["urn:ietf:params:scim:schemas:core:2.0:User"],
|
||||
"externalId": "701984",
|
||||
"userName": "bjensen@example.com",
|
||||
"userName": "{{ .Username }}@example.com",
|
||||
"name": {
|
||||
"formatted": "Ms. Barbara J Jensen, III",
|
||||
"familyName": "Jensen",
|
||||
@@ -15,12 +15,12 @@
|
||||
"profileUrl": "http://login.example.com/bjensen",
|
||||
"emails": [
|
||||
{
|
||||
"value": "bjensen@example.com",
|
||||
"value": "{{ .Username }}@example.com",
|
||||
"type": "work",
|
||||
"primary": true
|
||||
},
|
||||
{
|
||||
"value": "[email protected]",
|
||||
"value": "{{ .Username }}[email protected]",
|
||||
"type": "home"
|
||||
}
|
||||
],
|
||||
|
||||
@@ -2,14 +2,14 @@
|
||||
"schemas": [
|
||||
"urn:ietf:params:scim:schemas:core:2.0:User"
|
||||
],
|
||||
"userName": "acmeUser1",
|
||||
"userName": "{{ .Username }}",
|
||||
"name": {
|
||||
"familyName": "Ross",
|
||||
"givenName": "Bethany"
|
||||
},
|
||||
"emails": [
|
||||
{
|
||||
"value": "user1@example.com",
|
||||
"value": "{{ .Username }}@example.com",
|
||||
"primary": true
|
||||
}
|
||||
]
|
||||
|
||||
+2
-2
@@ -2,14 +2,14 @@
|
||||
"schemas": [
|
||||
"urn:ietf:params:scim:schemas:core:2.0:User"
|
||||
],
|
||||
"userName": "acmeUser1",
|
||||
"userName": "acmeUser1-inactive",
|
||||
"name": {
|
||||
"familyName": "Ross",
|
||||
"givenName": "Bethany"
|
||||
},
|
||||
"emails": [
|
||||
{
|
||||
"value": "[email protected]",
|
||||
"value": "user1-inactive@example.com",
|
||||
"primary": true
|
||||
}
|
||||
],
|
||||
|
||||
+2
-2
@@ -2,14 +2,14 @@
|
||||
"schemas": [
|
||||
"urn:ietf:params:scim:schemas:core:2.0:User"
|
||||
],
|
||||
"userName": "acmeUser1",
|
||||
"userName": "acmeUser1-no-primary-email-phone",
|
||||
"name": {
|
||||
"familyName": "Ross",
|
||||
"givenName": "Bethany"
|
||||
},
|
||||
"emails": [
|
||||
{
|
||||
"value": "[email protected]"
|
||||
"value": "user1-no-primary-email-phone@example.com"
|
||||
}
|
||||
],
|
||||
"phoneNumbers": [
|
||||
|
||||
Vendored
+2
-2
@@ -2,14 +2,14 @@
|
||||
"schemas": [
|
||||
"urn:ietf:params:scim:schemas:core:2.0:User"
|
||||
],
|
||||
"userName": "acmeUser1-minimal-replaced",
|
||||
"userName": "{{ .Username }}",
|
||||
"name": {
|
||||
"familyName": "Ross-replaced",
|
||||
"givenName": "Bethany-replaced"
|
||||
},
|
||||
"emails": [
|
||||
{
|
||||
"value": "user1-minimal-replaced@example.com",
|
||||
"value": "{{ .Username }}@example.com",
|
||||
"primary": true,
|
||||
"type": "work"
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
"value": {
|
||||
"emails":[
|
||||
{
|
||||
"value":"babs@example.com",
|
||||
"value":"{{ .Username }}+2@example.com",
|
||||
"type":"home",
|
||||
"primary": true
|
||||
}
|
||||
|
||||
@@ -3,17 +3,22 @@
|
||||
package integration_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
_ "embed"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"path"
|
||||
"testing"
|
||||
"text/template"
|
||||
"time"
|
||||
|
||||
"github.com/brianvoe/gofakeit/v6"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"golang.org/x/text/language"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/status"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/api/scim/resources"
|
||||
"github.com/zitadel/zitadel/internal/api/scim/schemas"
|
||||
@@ -157,7 +162,18 @@ var (
|
||||
}
|
||||
)
|
||||
|
||||
func withUsername(fixture []byte, username string) []byte {
|
||||
buf := new(bytes.Buffer)
|
||||
template.Must(template.New("").Parse(string(fixture))).Execute(buf, &struct {
|
||||
Username string
|
||||
}{
|
||||
Username: username,
|
||||
})
|
||||
return buf.Bytes()
|
||||
}
|
||||
|
||||
func TestCreateUser(t *testing.T) {
|
||||
minimalUsername := gofakeit.Username()
|
||||
tests := []struct {
|
||||
name string
|
||||
body []byte
|
||||
@@ -171,16 +187,16 @@ func TestCreateUser(t *testing.T) {
|
||||
}{
|
||||
{
|
||||
name: "minimal user",
|
||||
body: minimalUserJson,
|
||||
body: withUsername(minimalUserJson, minimalUsername),
|
||||
want: &resources.ScimUser{
|
||||
UserName: "acmeUser1",
|
||||
UserName: minimalUsername,
|
||||
Name: &resources.ScimUserName{
|
||||
FamilyName: "Ross",
|
||||
GivenName: "Bethany",
|
||||
},
|
||||
Emails: []*resources.ScimEmail{
|
||||
{
|
||||
Value: "user1@example.com",
|
||||
Value: minimalUsername + "@example.com",
|
||||
Primary: true,
|
||||
},
|
||||
},
|
||||
@@ -195,7 +211,7 @@ func TestCreateUser(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "full user",
|
||||
body: fullUserJson,
|
||||
body: withUsername(fullUserJson, "bjensen"),
|
||||
want: fullUser,
|
||||
},
|
||||
{
|
||||
@@ -204,7 +220,7 @@ func TestCreateUser(t *testing.T) {
|
||||
want: &resources.ScimUser{
|
||||
Emails: []*resources.ScimEmail{
|
||||
{
|
||||
Value: "[email protected]",
|
||||
Value: "user1-no-primary-email-phone@example.com",
|
||||
Primary: true,
|
||||
},
|
||||
},
|
||||
@@ -262,21 +278,21 @@ func TestCreateUser(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "not authenticated",
|
||||
body: minimalUserJson,
|
||||
body: withUsername(minimalUserJson, gofakeit.Username()),
|
||||
ctx: context.Background(),
|
||||
wantErr: true,
|
||||
errorStatus: http.StatusUnauthorized,
|
||||
},
|
||||
{
|
||||
name: "no permissions",
|
||||
body: minimalUserJson,
|
||||
body: withUsername(minimalUserJson, gofakeit.Username()),
|
||||
ctx: Instance.WithAuthorization(CTX, integration.UserTypeNoPermission),
|
||||
wantErr: true,
|
||||
errorStatus: http.StatusNotFound,
|
||||
},
|
||||
{
|
||||
name: "another org",
|
||||
body: minimalUserJson,
|
||||
body: withUsername(minimalUserJson, gofakeit.Username()),
|
||||
orgID: SecondaryOrganization.OrganizationId,
|
||||
wantErr: true,
|
||||
errorStatus: http.StatusNotFound,
|
||||
@@ -315,11 +331,6 @@ func TestCreateUser(t *testing.T) {
|
||||
}
|
||||
|
||||
assert.NotEmpty(t, createdUser.ID)
|
||||
defer func() {
|
||||
_, err = Instance.Client.UserV2.DeleteUser(CTX, &user.DeleteUserRequest{UserId: createdUser.ID})
|
||||
assert.NoError(t, err)
|
||||
}()
|
||||
|
||||
assert.EqualValues(t, []schemas.ScimSchemaType{"urn:ietf:params:scim:schemas:core:2.0:User"}, createdUser.Resource.Schemas)
|
||||
assert.Equal(t, schemas.ScimResourceTypeSingular("User"), createdUser.Resource.Meta.ResourceType)
|
||||
assert.Equal(t, "http://"+Instance.Host()+path.Join(schemas.HandlerPrefix, orgID, "Users", createdUser.ID), createdUser.Resource.Meta.Location)
|
||||
@@ -345,10 +356,11 @@ func TestCreateUser(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestCreateUser_duplicate(t *testing.T) {
|
||||
createdUser, err := Instance.Client.SCIM.Users.Create(CTX, Instance.DefaultOrg.Id, minimalUserJson)
|
||||
parsedMinimalUserJson := withUsername(minimalUserJson, gofakeit.Username())
|
||||
createdUser, err := Instance.Client.SCIM.Users.Create(CTX, Instance.DefaultOrg.Id, parsedMinimalUserJson)
|
||||
require.NoError(t, err)
|
||||
|
||||
_, err = Instance.Client.SCIM.Users.Create(CTX, Instance.DefaultOrg.Id, minimalUserJson)
|
||||
_, err = Instance.Client.SCIM.Users.Create(CTX, Instance.DefaultOrg.Id, parsedMinimalUserJson)
|
||||
scimErr := scim.RequireScimError(t, http.StatusConflict, err)
|
||||
assert.Equal(t, "User already exists", scimErr.Error.Detail)
|
||||
assert.Equal(t, "uniqueness", scimErr.Error.ScimType)
|
||||
@@ -358,14 +370,10 @@ func TestCreateUser_duplicate(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestCreateUser_metadata(t *testing.T) {
|
||||
createdUser, err := Instance.Client.SCIM.Users.Create(CTX, Instance.DefaultOrg.Id, fullUserJson)
|
||||
username := gofakeit.Username()
|
||||
createdUser, err := Instance.Client.SCIM.Users.Create(CTX, Instance.DefaultOrg.Id, withUsername(fullUserJson, username))
|
||||
require.NoError(t, err)
|
||||
|
||||
defer func() {
|
||||
_, err = Instance.Client.UserV2.DeleteUser(CTX, &user.DeleteUserRequest{UserId: createdUser.ID})
|
||||
require.NoError(t, err)
|
||||
}()
|
||||
|
||||
retryDuration, tick := integration.WaitForAndTickWithMaxDuration(CTX, time.Minute)
|
||||
require.EventuallyWithT(t, func(tt *assert.CollectT) {
|
||||
md, err := Instance.Client.Mgmt.ListUserMetadata(CTX, &management.ListUserMetadataRequest{
|
||||
@@ -391,38 +399,36 @@ func TestCreateUser_metadata(t *testing.T) {
|
||||
test.AssertMapContains(tt, mdMap, "urn:zitadel:scim:locale", "en-US")
|
||||
test.AssertMapContains(tt, mdMap, "urn:zitadel:scim:ims", `[{"value":"someaimhandle","type":"aim"},{"value":"twitterhandle","type":"X"}]`)
|
||||
test.AssertMapContains(tt, mdMap, "urn:zitadel:scim:roles", `[{"value":"my-role-1","display":"Rolle 1","type":"main-role","primary":true},{"value":"my-role-2","display":"Rolle 2","type":"secondary-role"}]`)
|
||||
test.AssertMapContains(tt, mdMap, "urn:zitadel:scim:emails", `[{"value":"bjensen@example.com","primary":true,"type":"work"},{"value":"[email protected]","primary":false,"type":"home"}]`)
|
||||
test.AssertMapContains(tt, mdMap, "urn:zitadel:scim:emails", fmt.Sprintf(`[{"value":"%s@example.com","primary":true,"type":"work"},{"value":"%[email protected]","primary":false,"type":"home"}]`, username, username))
|
||||
}, retryDuration, tick)
|
||||
}
|
||||
|
||||
func TestCreateUser_scopedExternalID(t *testing.T) {
|
||||
setProvisioningDomain(t, Instance.Users.Get(integration.UserTypeOrgOwner).ID, "fooBar")
|
||||
|
||||
createdUser, err := Instance.Client.SCIM.Users.Create(CTX, Instance.DefaultOrg.Id, fullUserJson)
|
||||
callingUserId, callingUserPat, err := Instance.CreateMachineUserPATWithMembership(CTX, "ORG_OWNER")
|
||||
require.NoError(t, err)
|
||||
|
||||
defer func() {
|
||||
_, err = Instance.Client.UserV2.DeleteUser(CTX, &user.DeleteUserRequest{UserId: createdUser.ID})
|
||||
require.NoError(t, err)
|
||||
|
||||
removeProvisioningDomain(t, Instance.Users.Get(integration.UserTypeOrgOwner).ID)
|
||||
}()
|
||||
|
||||
retryDuration, tick := integration.WaitForAndTickWithMaxDuration(CTX, time.Minute)
|
||||
ctx := integration.WithAuthorizationToken(CTX, callingUserPat)
|
||||
setProvisioningDomain(t, callingUserId, "fooBar")
|
||||
createdUser, err := Instance.Client.SCIM.Users.Create(ctx, Instance.DefaultOrg.Id, withUsername(fullUserJson, gofakeit.Username()))
|
||||
require.NoError(t, err)
|
||||
retryDuration, tick := integration.WaitForAndTickWithMaxDuration(ctx, time.Minute)
|
||||
require.EventuallyWithT(t, func(tt *assert.CollectT) {
|
||||
// unscoped externalID should not exist
|
||||
_, err = Instance.Client.Mgmt.GetUserMetadata(CTX, &management.GetUserMetadataRequest{
|
||||
unscoped, err := Instance.Client.Mgmt.GetUserMetadata(ctx, &management.GetUserMetadataRequest{
|
||||
Id: createdUser.ID,
|
||||
Key: "urn:zitadel:scim:externalId",
|
||||
})
|
||||
integration.AssertGrpcStatus(tt, codes.NotFound, err)
|
||||
unscoped = unscoped
|
||||
|
||||
// scoped externalID should exist
|
||||
md, err := Instance.Client.Mgmt.GetUserMetadata(CTX, &management.GetUserMetadataRequest{
|
||||
md, err := Instance.Client.Mgmt.GetUserMetadata(ctx, &management.GetUserMetadataRequest{
|
||||
Id: createdUser.ID,
|
||||
Key: "urn:zitadel:scim:fooBar:externalId",
|
||||
})
|
||||
require.NoError(tt, err)
|
||||
if !assert.NoError(tt, err) {
|
||||
require.Equal(tt, status.Code(err), codes.NotFound)
|
||||
return
|
||||
}
|
||||
assert.Equal(tt, "701984", string(md.Metadata.Value))
|
||||
}, retryDuration, tick)
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/brianvoe/gofakeit/v6"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"golang.org/x/text/language"
|
||||
@@ -18,256 +19,260 @@ import (
|
||||
"github.com/zitadel/zitadel/internal/integration"
|
||||
"github.com/zitadel/zitadel/internal/integration/scim"
|
||||
"github.com/zitadel/zitadel/internal/test"
|
||||
"github.com/zitadel/zitadel/pkg/grpc/user/v2"
|
||||
)
|
||||
|
||||
func TestGetUser(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
orgID string
|
||||
buildUserID func() string
|
||||
cleanup func(userID string)
|
||||
type testCase struct {
|
||||
ctx context.Context
|
||||
orgID string
|
||||
userID string
|
||||
want *resources.ScimUser
|
||||
wantErr bool
|
||||
errorStatus int
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
setup func(t *testing.T) testCase
|
||||
}{
|
||||
{
|
||||
name: "not authenticated",
|
||||
ctx: context.Background(),
|
||||
errorStatus: http.StatusUnauthorized,
|
||||
wantErr: true,
|
||||
name: "not authenticated",
|
||||
setup: func(t *testing.T) testCase {
|
||||
return testCase{
|
||||
ctx: context.Background(),
|
||||
errorStatus: http.StatusUnauthorized,
|
||||
wantErr: true,
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "no permissions",
|
||||
ctx: Instance.WithAuthorization(CTX, integration.UserTypeNoPermission),
|
||||
errorStatus: http.StatusNotFound,
|
||||
wantErr: true,
|
||||
name: "no permissions",
|
||||
setup: func(t *testing.T) testCase {
|
||||
return testCase{
|
||||
ctx: Instance.WithAuthorization(CTX, integration.UserTypeNoPermission),
|
||||
errorStatus: http.StatusNotFound,
|
||||
wantErr: true,
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "another org",
|
||||
orgID: SecondaryOrganization.OrganizationId,
|
||||
errorStatus: http.StatusNotFound,
|
||||
wantErr: true,
|
||||
name: "another org",
|
||||
setup: func(t *testing.T) testCase {
|
||||
return testCase{
|
||||
orgID: SecondaryOrganization.OrganizationId,
|
||||
errorStatus: http.StatusNotFound,
|
||||
wantErr: true,
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "another org with permissions",
|
||||
orgID: SecondaryOrganization.OrganizationId,
|
||||
ctx: Instance.WithAuthorization(CTX, integration.UserTypeNoPermission),
|
||||
errorStatus: http.StatusNotFound,
|
||||
wantErr: true,
|
||||
name: "another org with permissions",
|
||||
setup: func(t *testing.T) testCase {
|
||||
return testCase{
|
||||
ctx: Instance.WithAuthorization(CTX, integration.UserTypeNoPermission),
|
||||
orgID: SecondaryOrganization.OrganizationId,
|
||||
errorStatus: http.StatusNotFound,
|
||||
wantErr: true,
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "unknown user id",
|
||||
buildUserID: func() string {
|
||||
return "unknown"
|
||||
setup: func(t *testing.T) testCase {
|
||||
return testCase{
|
||||
userID: "unknown",
|
||||
errorStatus: http.StatusNotFound,
|
||||
wantErr: true,
|
||||
}
|
||||
},
|
||||
errorStatus: http.StatusNotFound,
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "created via grpc",
|
||||
want: &resources.ScimUser{
|
||||
Name: &resources.ScimUserName{
|
||||
FamilyName: "Mouse",
|
||||
GivenName: "Mickey",
|
||||
},
|
||||
PreferredLanguage: language.MustParse("nl"),
|
||||
PhoneNumbers: []*resources.ScimPhoneNumber{
|
||||
{
|
||||
Value: "+41791234567",
|
||||
Primary: true,
|
||||
setup: func(t *testing.T) testCase {
|
||||
return testCase{
|
||||
want: &resources.ScimUser{
|
||||
Name: &resources.ScimUserName{
|
||||
FamilyName: "Mouse",
|
||||
GivenName: "Mickey",
|
||||
},
|
||||
PreferredLanguage: language.MustParse("nl"),
|
||||
PhoneNumbers: []*resources.ScimPhoneNumber{
|
||||
{
|
||||
Value: "+41791234567",
|
||||
Primary: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "created via scim",
|
||||
buildUserID: func() string {
|
||||
createdUser, err := Instance.Client.SCIM.Users.Create(CTX, Instance.DefaultOrg.Id, fullUserJson)
|
||||
setup: func(t *testing.T) testCase {
|
||||
username := gofakeit.Username()
|
||||
createdUser, err := Instance.Client.SCIM.Users.Create(CTX, Instance.DefaultOrg.Id, withUsername(fullUserJson, username))
|
||||
require.NoError(t, err)
|
||||
return createdUser.ID
|
||||
},
|
||||
cleanup: func(userID string) {
|
||||
_, err := Instance.Client.UserV2.DeleteUser(CTX, &user.DeleteUserRequest{UserId: userID})
|
||||
require.NoError(t, err)
|
||||
},
|
||||
want: &resources.ScimUser{
|
||||
ExternalID: "701984",
|
||||
UserName: "[email protected]",
|
||||
Name: &resources.ScimUserName{
|
||||
Formatted: "Babs Jensen", // DisplayName takes precedence
|
||||
FamilyName: "Jensen",
|
||||
GivenName: "Barbara",
|
||||
MiddleName: "Jane",
|
||||
HonorificPrefix: "Ms.",
|
||||
HonorificSuffix: "III",
|
||||
},
|
||||
DisplayName: "Babs Jensen",
|
||||
NickName: "Babs",
|
||||
ProfileUrl: test.Must(schemas.ParseHTTPURL("http://login.example.com/bjensen")),
|
||||
Title: "Tour Guide",
|
||||
PreferredLanguage: language.Make("en-US"),
|
||||
Locale: "en-US",
|
||||
Timezone: "America/Los_Angeles",
|
||||
Active: schemas.NewRelaxedBool(true),
|
||||
Emails: []*resources.ScimEmail{
|
||||
{
|
||||
Value: "[email protected]",
|
||||
Primary: true,
|
||||
Type: "work",
|
||||
return testCase{
|
||||
userID: createdUser.ID,
|
||||
want: &resources.ScimUser{
|
||||
ExternalID: "701984",
|
||||
UserName: username + "@example.com",
|
||||
Name: &resources.ScimUserName{
|
||||
Formatted: "Babs Jensen", // DisplayName takes precedence
|
||||
FamilyName: "Jensen",
|
||||
GivenName: "Barbara",
|
||||
MiddleName: "Jane",
|
||||
HonorificPrefix: "Ms.",
|
||||
HonorificSuffix: "III",
|
||||
},
|
||||
DisplayName: "Babs Jensen",
|
||||
NickName: "Babs",
|
||||
ProfileUrl: test.Must(schemas.ParseHTTPURL("http://login.example.com/bjensen")),
|
||||
Title: "Tour Guide",
|
||||
PreferredLanguage: language.Make("en-US"),
|
||||
Locale: "en-US",
|
||||
Timezone: "America/Los_Angeles",
|
||||
Active: schemas.NewRelaxedBool(true),
|
||||
Emails: []*resources.ScimEmail{
|
||||
{
|
||||
Value: username + "@example.com",
|
||||
Primary: true,
|
||||
Type: "work",
|
||||
},
|
||||
},
|
||||
PhoneNumbers: []*resources.ScimPhoneNumber{
|
||||
{
|
||||
Value: "+415555555555",
|
||||
Primary: true,
|
||||
},
|
||||
},
|
||||
Ims: []*resources.ScimIms{
|
||||
{
|
||||
Value: "someaimhandle",
|
||||
Type: "aim",
|
||||
},
|
||||
{
|
||||
Value: "twitterhandle",
|
||||
Type: "X",
|
||||
},
|
||||
},
|
||||
Addresses: []*resources.ScimAddress{
|
||||
{
|
||||
Type: "work",
|
||||
StreetAddress: "100 Universal City Plaza",
|
||||
Locality: "Hollywood",
|
||||
Region: "CA",
|
||||
PostalCode: "91608",
|
||||
Country: "USA",
|
||||
Formatted: "100 Universal City Plaza\nHollywood, CA 91608 USA",
|
||||
Primary: true,
|
||||
},
|
||||
{
|
||||
Type: "home",
|
||||
StreetAddress: "456 Hollywood Blvd",
|
||||
Locality: "Hollywood",
|
||||
Region: "CA",
|
||||
PostalCode: "91608",
|
||||
Country: "USA",
|
||||
Formatted: "456 Hollywood Blvd\nHollywood, CA 91608 USA",
|
||||
},
|
||||
},
|
||||
Photos: []*resources.ScimPhoto{
|
||||
{
|
||||
Value: *test.Must(schemas.ParseHTTPURL("https://photos.example.com/profilephoto/72930000000Ccne/F")),
|
||||
Type: "photo",
|
||||
},
|
||||
{
|
||||
Value: *test.Must(schemas.ParseHTTPURL("https://photos.example.com/profilephoto/72930000000Ccne/T")),
|
||||
Type: "thumbnail",
|
||||
},
|
||||
},
|
||||
Roles: []*resources.ScimRole{
|
||||
{
|
||||
Value: "my-role-1",
|
||||
Display: "Rolle 1",
|
||||
Type: "main-role",
|
||||
Primary: true,
|
||||
},
|
||||
{
|
||||
Value: "my-role-2",
|
||||
Display: "Rolle 2",
|
||||
Type: "secondary-role",
|
||||
Primary: false,
|
||||
},
|
||||
},
|
||||
Entitlements: []*resources.ScimEntitlement{
|
||||
{
|
||||
Value: "my-entitlement-1",
|
||||
Display: "Entitlement 1",
|
||||
Type: "main-entitlement",
|
||||
Primary: true,
|
||||
},
|
||||
{
|
||||
Value: "my-entitlement-2",
|
||||
Display: "Entitlement 2",
|
||||
Type: "secondary-entitlement",
|
||||
Primary: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
PhoneNumbers: []*resources.ScimPhoneNumber{
|
||||
{
|
||||
Value: "+415555555555",
|
||||
Primary: true,
|
||||
},
|
||||
},
|
||||
Ims: []*resources.ScimIms{
|
||||
{
|
||||
Value: "someaimhandle",
|
||||
Type: "aim",
|
||||
},
|
||||
{
|
||||
Value: "twitterhandle",
|
||||
Type: "X",
|
||||
},
|
||||
},
|
||||
Addresses: []*resources.ScimAddress{
|
||||
{
|
||||
Type: "work",
|
||||
StreetAddress: "100 Universal City Plaza",
|
||||
Locality: "Hollywood",
|
||||
Region: "CA",
|
||||
PostalCode: "91608",
|
||||
Country: "USA",
|
||||
Formatted: "100 Universal City Plaza\nHollywood, CA 91608 USA",
|
||||
Primary: true,
|
||||
},
|
||||
{
|
||||
Type: "home",
|
||||
StreetAddress: "456 Hollywood Blvd",
|
||||
Locality: "Hollywood",
|
||||
Region: "CA",
|
||||
PostalCode: "91608",
|
||||
Country: "USA",
|
||||
Formatted: "456 Hollywood Blvd\nHollywood, CA 91608 USA",
|
||||
},
|
||||
},
|
||||
Photos: []*resources.ScimPhoto{
|
||||
{
|
||||
Value: *test.Must(schemas.ParseHTTPURL("https://photos.example.com/profilephoto/72930000000Ccne/F")),
|
||||
Type: "photo",
|
||||
},
|
||||
{
|
||||
Value: *test.Must(schemas.ParseHTTPURL("https://photos.example.com/profilephoto/72930000000Ccne/T")),
|
||||
Type: "thumbnail",
|
||||
},
|
||||
},
|
||||
Roles: []*resources.ScimRole{
|
||||
{
|
||||
Value: "my-role-1",
|
||||
Display: "Rolle 1",
|
||||
Type: "main-role",
|
||||
Primary: true,
|
||||
},
|
||||
{
|
||||
Value: "my-role-2",
|
||||
Display: "Rolle 2",
|
||||
Type: "secondary-role",
|
||||
Primary: false,
|
||||
},
|
||||
},
|
||||
Entitlements: []*resources.ScimEntitlement{
|
||||
{
|
||||
Value: "my-entitlement-1",
|
||||
Display: "Entitlement 1",
|
||||
Type: "main-entitlement",
|
||||
Primary: true,
|
||||
},
|
||||
{
|
||||
Value: "my-entitlement-2",
|
||||
Display: "Entitlement 2",
|
||||
Type: "secondary-entitlement",
|
||||
Primary: false,
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "scoped externalID",
|
||||
buildUserID: func() string {
|
||||
// create user without provisioning domain
|
||||
createdUser, err := Instance.Client.SCIM.Users.Create(CTX, Instance.DefaultOrg.Id, fullUserJson)
|
||||
setup: func(t *testing.T) testCase {
|
||||
createdUser, err := Instance.Client.SCIM.Users.Create(CTX, Instance.DefaultOrg.Id, withUsername(fullUserJson, gofakeit.Username()))
|
||||
require.NoError(t, err)
|
||||
|
||||
// set provisioning domain of service user
|
||||
setProvisioningDomain(t, Instance.Users.Get(integration.UserTypeOrgOwner).ID, "fooBar")
|
||||
|
||||
// set externalID for provisioning domain
|
||||
callingUserId, callingUserPat, err := Instance.CreateMachineUserPATWithMembership(CTX, "ORG_OWNER")
|
||||
require.NoError(t, err)
|
||||
setProvisioningDomain(t, callingUserId, "fooBar")
|
||||
setAndEnsureMetadata(t, createdUser.ID, "urn:zitadel:scim:fooBar:externalId", "100-scopedExternalId")
|
||||
return createdUser.ID
|
||||
},
|
||||
cleanup: func(userID string) {
|
||||
_, err := Instance.Client.UserV2.DeleteUser(CTX, &user.DeleteUserRequest{UserId: userID})
|
||||
require.NoError(t, err)
|
||||
|
||||
removeProvisioningDomain(t, Instance.Users.Get(integration.UserTypeOrgOwner).ID)
|
||||
},
|
||||
want: &resources.ScimUser{
|
||||
ExternalID: "100-scopedExternalId",
|
||||
return testCase{
|
||||
ctx: integration.WithAuthorizationToken(CTX, callingUserPat),
|
||||
userID: createdUser.ID,
|
||||
want: &resources.ScimUser{
|
||||
ExternalID: "100-scopedExternalId",
|
||||
},
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
ctx := tt.ctx
|
||||
if ctx == nil {
|
||||
ctx = CTX
|
||||
ttt := tt.setup(t)
|
||||
if ttt.userID == "" {
|
||||
ttt.userID = Instance.CreateHumanUser(CTX).UserId
|
||||
}
|
||||
|
||||
var userID string
|
||||
if tt.buildUserID != nil {
|
||||
userID = tt.buildUserID()
|
||||
} else {
|
||||
createUserResp := Instance.CreateHumanUser(CTX)
|
||||
userID = createUserResp.UserId
|
||||
if ttt.ctx == nil {
|
||||
ttt.ctx = CTX
|
||||
}
|
||||
|
||||
orgID := tt.orgID
|
||||
if orgID == "" {
|
||||
orgID = Instance.DefaultOrg.Id
|
||||
if ttt.orgID == "" {
|
||||
ttt.orgID = Instance.DefaultOrg.Id
|
||||
}
|
||||
|
||||
retryDuration, tick := integration.WaitForAndTickWithMaxDuration(CTX, time.Minute)
|
||||
var fetchedUser *resources.ScimUser
|
||||
var err error
|
||||
require.EventuallyWithT(t, func(ttt *assert.CollectT) {
|
||||
fetchedUser, err = Instance.Client.SCIM.Users.Get(ctx, orgID, userID)
|
||||
if tt.wantErr {
|
||||
statusCode := tt.errorStatus
|
||||
require.EventuallyWithT(t, func(collect *assert.CollectT) {
|
||||
fetchedUser, err := Instance.Client.SCIM.Users.Get(ttt.ctx, ttt.orgID, ttt.userID)
|
||||
if ttt.wantErr {
|
||||
statusCode := ttt.errorStatus
|
||||
if statusCode == 0 {
|
||||
statusCode = http.StatusBadRequest
|
||||
}
|
||||
|
||||
scim.RequireScimError(ttt, statusCode, err)
|
||||
scim.RequireScimError(collect, statusCode, err)
|
||||
return
|
||||
}
|
||||
|
||||
assert.Equal(ttt, userID, fetchedUser.ID)
|
||||
assert.EqualValues(ttt, []schemas.ScimSchemaType{"urn:ietf:params:scim:schemas:core:2.0:User"}, fetchedUser.Schemas)
|
||||
assert.Equal(ttt, schemas.ScimResourceTypeSingular("User"), fetchedUser.Resource.Meta.ResourceType)
|
||||
assert.Equal(ttt, "http://"+Instance.Host()+path.Join(schemas.HandlerPrefix, orgID, "Users", fetchedUser.ID), fetchedUser.Resource.Meta.Location)
|
||||
assert.Nil(ttt, fetchedUser.Password)
|
||||
if !test.PartiallyDeepEqual(tt.want, fetchedUser) {
|
||||
ttt.Errorf("GetUser() got = %#v, want %#v", fetchedUser, tt.want)
|
||||
if !assert.NoError(collect, err) {
|
||||
scim.RequireScimError(collect, http.StatusNotFound, err)
|
||||
return
|
||||
}
|
||||
assert.Equal(collect, ttt.userID, fetchedUser.ID)
|
||||
assert.EqualValues(collect, []schemas.ScimSchemaType{"urn:ietf:params:scim:schemas:core:2.0:User"}, fetchedUser.Schemas)
|
||||
assert.Equal(collect, schemas.ScimResourceTypeSingular("User"), fetchedUser.Resource.Meta.ResourceType)
|
||||
assert.Equal(collect, "http://"+Instance.Host()+path.Join(schemas.HandlerPrefix, ttt.orgID, "Users", fetchedUser.ID), fetchedUser.Resource.Meta.Location)
|
||||
assert.Nil(collect, fetchedUser.Password)
|
||||
if !test.PartiallyDeepEqual(ttt.want, fetchedUser) {
|
||||
collect.Errorf("GetUser() got = %#v, want %#v", fetchedUser, ttt.want)
|
||||
}
|
||||
}, retryDuration, tick)
|
||||
|
||||
if tt.cleanup != nil {
|
||||
tt.cleanup(fetchedUser.ID)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,15 +23,6 @@ var totalCountOfHumanUsers = 13
|
||||
/*
|
||||
func TestListUser(t *testing.T) {
|
||||
createdUserIDs := createUsers(t, CTX, Instance.DefaultOrg.Id)
|
||||
defer func() {
|
||||
// only the full user needs to be deleted, all others have random identification data
|
||||
// fullUser is always the first one.
|
||||
_, err := Instance.Client.UserV2.DeleteUser(CTX, &user_v2.DeleteUserRequest{
|
||||
UserId: createdUserIDs[0],
|
||||
})
|
||||
require.NoError(t, err)
|
||||
}()
|
||||
|
||||
// secondary organization with same set of users,
|
||||
// these should never be modified.
|
||||
// This allows testing list requests without filters.
|
||||
@@ -451,7 +442,7 @@ func createUsers(t *testing.T, ctx context.Context, orgID string) []string {
|
||||
|
||||
// create the full scim user if on primary org
|
||||
if orgID == Instance.DefaultOrg.Id {
|
||||
fullUserCreatedResp, err := Instance.Client.SCIM.Users.Create(ctx, orgID, fullUserJson)
|
||||
fullUserCreatedResp, err := Instance.Client.SCIM.Users.Create(ctx, orgID, withUsername(fullUserJson, gofakeit.Username()))
|
||||
require.NoError(t, err)
|
||||
createdUserIDs = append(createdUserIDs, fullUserCreatedResp.ID)
|
||||
count--
|
||||
|
||||
@@ -5,11 +5,13 @@ package integration_test
|
||||
import (
|
||||
"context"
|
||||
_ "embed"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"path"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/brianvoe/gofakeit/v6"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"golang.org/x/text/language"
|
||||
@@ -20,7 +22,6 @@ import (
|
||||
"github.com/zitadel/zitadel/internal/integration/scim"
|
||||
"github.com/zitadel/zitadel/internal/test"
|
||||
"github.com/zitadel/zitadel/pkg/grpc/management"
|
||||
"github.com/zitadel/zitadel/pkg/grpc/user/v2"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -199,28 +200,28 @@ func TestReplaceUser(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "not authenticated",
|
||||
body: minimalUserJson,
|
||||
body: withUsername(minimalUserJson, gofakeit.Username()),
|
||||
ctx: context.Background(),
|
||||
wantErr: true,
|
||||
errorStatus: http.StatusUnauthorized,
|
||||
},
|
||||
{
|
||||
name: "no permissions",
|
||||
body: minimalUserJson,
|
||||
body: withUsername(minimalUserJson, gofakeit.Username()),
|
||||
ctx: Instance.WithAuthorization(CTX, integration.UserTypeNoPermission),
|
||||
wantErr: true,
|
||||
errorStatus: http.StatusNotFound,
|
||||
},
|
||||
{
|
||||
name: "another org",
|
||||
body: minimalUserJson,
|
||||
body: withUsername(minimalUserJson, gofakeit.Username()),
|
||||
replaceUserOrgID: SecondaryOrganization.OrganizationId,
|
||||
wantErr: true,
|
||||
errorStatus: http.StatusNotFound,
|
||||
},
|
||||
{
|
||||
name: "another org with permissions",
|
||||
body: minimalUserJson,
|
||||
body: withUsername(minimalUserJson, gofakeit.Username()),
|
||||
replaceUserOrgID: SecondaryOrganization.OrganizationId,
|
||||
ctx: Instance.WithAuthorization(CTX, integration.UserTypeIAMOwner),
|
||||
wantErr: true,
|
||||
@@ -230,14 +231,9 @@ func TestReplaceUser(t *testing.T) {
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
// use iam owner => we don't want to test permissions of the create endpoint.
|
||||
createdUser, err := Instance.Client.SCIM.Users.Create(Instance.WithAuthorization(CTX, integration.UserTypeIAMOwner), Instance.DefaultOrg.Id, fullUserJson)
|
||||
createdUser, err := Instance.Client.SCIM.Users.Create(Instance.WithAuthorization(CTX, integration.UserTypeIAMOwner), Instance.DefaultOrg.Id, withUsername(fullUserJson, gofakeit.Username()))
|
||||
require.NoError(t, err)
|
||||
|
||||
defer func() {
|
||||
_, err = Instance.Client.UserV2.DeleteUser(CTX, &user.DeleteUserRequest{UserId: createdUser.ID})
|
||||
assert.NoError(t, err)
|
||||
}()
|
||||
|
||||
ctx := tt.ctx
|
||||
if ctx == nil {
|
||||
ctx = CTX
|
||||
@@ -294,10 +290,11 @@ func TestReplaceUser(t *testing.T) {
|
||||
|
||||
func TestReplaceUser_removeOldMetadata(t *testing.T) {
|
||||
// ensure old metadata is removed correctly
|
||||
createdUser, err := Instance.Client.SCIM.Users.Create(CTX, Instance.DefaultOrg.Id, fullUserJson)
|
||||
username := gofakeit.Username()
|
||||
createdUser, err := Instance.Client.SCIM.Users.Create(CTX, Instance.DefaultOrg.Id, withUsername(fullUserJson, username))
|
||||
require.NoError(t, err)
|
||||
|
||||
_, err = Instance.Client.SCIM.Users.Replace(CTX, Instance.DefaultOrg.Id, createdUser.ID, minimalUserJson)
|
||||
_, err = Instance.Client.SCIM.Users.Replace(CTX, Instance.DefaultOrg.Id, createdUser.ID, withUsername(minimalUserJson, username))
|
||||
require.NoError(t, err)
|
||||
|
||||
retryDuration, tick := integration.WaitForAndTickWithMaxDuration(CTX, time.Minute)
|
||||
@@ -312,20 +309,17 @@ func TestReplaceUser_removeOldMetadata(t *testing.T) {
|
||||
for i := range md.Result {
|
||||
mdMap[md.Result[i].Key] = string(md.Result[i].Value)
|
||||
}
|
||||
|
||||
test.AssertMapContains(tt, mdMap, "urn:zitadel:scim:emails", "[{\"value\":\"[email protected]\",\"primary\":true}]")
|
||||
test.AssertMapContains(tt, mdMap, "urn:zitadel:scim:emails", fmt.Sprintf("[{\"value\":\"%[email protected]\",\"primary\":true}]", username))
|
||||
}, retryDuration, tick)
|
||||
|
||||
_, err = Instance.Client.UserV2.DeleteUser(CTX, &user.DeleteUserRequest{UserId: createdUser.ID})
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
func TestReplaceUser_emailType(t *testing.T) {
|
||||
// ensure old metadata is removed correctly
|
||||
createdUser, err := Instance.Client.SCIM.Users.Create(CTX, Instance.DefaultOrg.Id, fullUserJson)
|
||||
createdUser, err := Instance.Client.SCIM.Users.Create(CTX, Instance.DefaultOrg.Id, withUsername(fullUserJson, gofakeit.Username()))
|
||||
require.NoError(t, err)
|
||||
|
||||
_, err = Instance.Client.SCIM.Users.Replace(CTX, Instance.DefaultOrg.Id, createdUser.ID, minimalUserWithEmailTypeReplaceJson)
|
||||
replacedUsername := gofakeit.Username()
|
||||
_, err = Instance.Client.SCIM.Users.Replace(CTX, Instance.DefaultOrg.Id, createdUser.ID, withUsername(minimalUserWithEmailTypeReplaceJson, replacedUsername))
|
||||
require.NoError(t, err)
|
||||
|
||||
retryDuration, tick := integration.WaitForAndTickWithMaxDuration(CTX, time.Minute)
|
||||
@@ -341,28 +335,26 @@ func TestReplaceUser_emailType(t *testing.T) {
|
||||
mdMap[md.Result[i].Key] = string(md.Result[i].Value)
|
||||
}
|
||||
|
||||
test.AssertMapContains(tt, mdMap, "urn:zitadel:scim:emails", "[{\"value\":\"user1-minimal-replaced@example.com\",\"primary\":true,\"type\":\"work\"}]")
|
||||
test.AssertMapContains(tt, mdMap, "urn:zitadel:scim:emails", fmt.Sprintf("[{\"value\":\"%s@example.com\",\"primary\":true,\"type\":\"work\"}]", replacedUsername))
|
||||
}, retryDuration, tick)
|
||||
|
||||
_, err = Instance.Client.UserV2.DeleteUser(CTX, &user.DeleteUserRequest{UserId: createdUser.ID})
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
func TestReplaceUser_scopedExternalID(t *testing.T) {
|
||||
// create user without provisioning domain set
|
||||
createdUser, err := Instance.Client.SCIM.Users.Create(CTX, Instance.DefaultOrg.Id, fullUserJson)
|
||||
createdUser, err := Instance.Client.SCIM.Users.Create(CTX, Instance.DefaultOrg.Id, withUsername(fullUserJson, gofakeit.Username()))
|
||||
require.NoError(t, err)
|
||||
|
||||
callingUserId, callingUserPat, err := Instance.CreateMachineUserPATWithMembership(CTX, "ORG_OWNER")
|
||||
require.NoError(t, err)
|
||||
ctx := integration.WithAuthorizationToken(CTX, callingUserPat)
|
||||
// set provisioning domain of service user
|
||||
setProvisioningDomain(t, Instance.Users.Get(integration.UserTypeOrgOwner).ID, "fooBazz")
|
||||
setProvisioningDomain(t, callingUserId, "fooBazz")
|
||||
|
||||
// replace the user with provisioning domain set
|
||||
_, err = Instance.Client.SCIM.Users.Replace(CTX, Instance.DefaultOrg.Id, createdUser.ID, minimalUserWithExternalIDJson)
|
||||
_, err = Instance.Client.SCIM.Users.Replace(ctx, Instance.DefaultOrg.Id, createdUser.ID, minimalUserWithExternalIDJson)
|
||||
require.NoError(t, err)
|
||||
|
||||
retryDuration, tick := integration.WaitForAndTickWithMaxDuration(CTX, time.Minute)
|
||||
retryDuration, tick := integration.WaitForAndTickWithMaxDuration(ctx, time.Minute)
|
||||
require.EventuallyWithT(t, func(tt *assert.CollectT) {
|
||||
md, err := Instance.Client.Mgmt.ListUserMetadata(CTX, &management.ListUserMetadataRequest{
|
||||
md, err := Instance.Client.Mgmt.ListUserMetadata(ctx, &management.ListUserMetadataRequest{
|
||||
Id: createdUser.ID,
|
||||
})
|
||||
require.NoError(tt, err)
|
||||
@@ -376,9 +368,4 @@ func TestReplaceUser_scopedExternalID(t *testing.T) {
|
||||
test.AssertMapContains(tt, mdMap, "urn:zitadel:scim:externalId", "701984")
|
||||
test.AssertMapContains(tt, mdMap, "urn:zitadel:scim:fooBazz:externalId", "replaced-external-id")
|
||||
}, retryDuration, tick)
|
||||
|
||||
_, err = Instance.Client.UserV2.DeleteUser(CTX, &user.DeleteUserRequest{UserId: createdUser.ID})
|
||||
require.NoError(t, err)
|
||||
|
||||
removeProvisioningDomain(t, Instance.Users.Get(integration.UserTypeOrgOwner).ID)
|
||||
}
|
||||
|
||||
@@ -5,11 +5,13 @@ package integration_test
|
||||
import (
|
||||
"context"
|
||||
_ "embed"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/brianvoe/gofakeit/v6"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"golang.org/x/text/language"
|
||||
@@ -19,7 +21,6 @@ import (
|
||||
"github.com/zitadel/zitadel/internal/integration"
|
||||
"github.com/zitadel/zitadel/internal/integration/scim"
|
||||
"github.com/zitadel/zitadel/internal/test"
|
||||
"github.com/zitadel/zitadel/pkg/grpc/user/v2"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -34,15 +35,7 @@ func init() {
|
||||
}
|
||||
|
||||
func TestUpdateUser(t *testing.T) {
|
||||
fullUserCreated, err := Instance.Client.SCIM.Users.Create(CTX, Instance.DefaultOrg.Id, fullUserJson)
|
||||
require.NoError(t, err)
|
||||
|
||||
defer func() {
|
||||
_, err = Instance.Client.UserV2.DeleteUser(CTX, &user.DeleteUserRequest{UserId: fullUserCreated.ID})
|
||||
require.NoError(t, err)
|
||||
}()
|
||||
|
||||
tests := []struct {
|
||||
type testCase struct {
|
||||
name string
|
||||
body []byte
|
||||
ctx context.Context
|
||||
@@ -52,215 +45,297 @@ func TestUpdateUser(t *testing.T) {
|
||||
wantErr bool
|
||||
scimErrorType string
|
||||
errorStatus int
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
setup func(t *testing.T) testCase
|
||||
}{
|
||||
{
|
||||
name: "not authenticated",
|
||||
ctx: context.Background(),
|
||||
body: minimalUserUpdateJson,
|
||||
wantErr: true,
|
||||
errorStatus: http.StatusUnauthorized,
|
||||
name: "not authenticated",
|
||||
setup: func(t *testing.T) testCase {
|
||||
username := gofakeit.Username()
|
||||
created, err := Instance.Client.SCIM.Users.Create(CTX, Instance.DefaultOrg.Id, withUsername(fullUserJson, username))
|
||||
require.NoError(t, err)
|
||||
return testCase{
|
||||
userID: created.ID,
|
||||
ctx: context.Background(),
|
||||
body: minimalUserUpdateJson,
|
||||
wantErr: true,
|
||||
errorStatus: http.StatusUnauthorized,
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "no permissions",
|
||||
ctx: Instance.WithAuthorization(CTX, integration.UserTypeNoPermission),
|
||||
body: minimalUserUpdateJson,
|
||||
wantErr: true,
|
||||
errorStatus: http.StatusNotFound,
|
||||
name: "no permissions",
|
||||
setup: func(t *testing.T) testCase {
|
||||
username := gofakeit.Username()
|
||||
created, err := Instance.Client.SCIM.Users.Create(CTX, Instance.DefaultOrg.Id, withUsername(fullUserJson, username))
|
||||
require.NoError(t, err)
|
||||
return testCase{
|
||||
userID: created.ID,
|
||||
ctx: Instance.WithAuthorization(CTX, integration.UserTypeNoPermission),
|
||||
body: minimalUserUpdateJson,
|
||||
wantErr: true,
|
||||
errorStatus: http.StatusNotFound,
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "other org",
|
||||
orgID: SecondaryOrganization.OrganizationId,
|
||||
body: minimalUserUpdateJson,
|
||||
wantErr: true,
|
||||
errorStatus: http.StatusNotFound,
|
||||
name: "other org",
|
||||
setup: func(t *testing.T) testCase {
|
||||
username := gofakeit.Username()
|
||||
created, err := Instance.Client.SCIM.Users.Create(CTX, Instance.DefaultOrg.Id, withUsername(fullUserJson, username))
|
||||
require.NoError(t, err)
|
||||
return testCase{
|
||||
userID: created.ID,
|
||||
orgID: SecondaryOrganization.OrganizationId,
|
||||
body: minimalUserUpdateJson,
|
||||
wantErr: true,
|
||||
errorStatus: http.StatusNotFound,
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "other org with permissions",
|
||||
ctx: Instance.WithAuthorization(CTX, integration.UserTypeIAMOwner),
|
||||
orgID: SecondaryOrganization.OrganizationId,
|
||||
body: minimalUserUpdateJson,
|
||||
wantErr: true,
|
||||
errorStatus: http.StatusNotFound,
|
||||
name: "other org with permissions",
|
||||
setup: func(t *testing.T) testCase {
|
||||
username := gofakeit.Username()
|
||||
created, err := Instance.Client.SCIM.Users.Create(CTX, Instance.DefaultOrg.Id, withUsername(fullUserJson, username))
|
||||
require.NoError(t, err)
|
||||
return testCase{
|
||||
userID: created.ID,
|
||||
ctx: Instance.WithAuthorization(CTX, integration.UserTypeIAMOwner),
|
||||
orgID: SecondaryOrganization.OrganizationId,
|
||||
body: minimalUserUpdateJson,
|
||||
wantErr: true,
|
||||
errorStatus: http.StatusNotFound,
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "invalid patch json",
|
||||
body: simpleReplacePatchBody("nickname", "10"),
|
||||
wantErr: true,
|
||||
scimErrorType: "invalidValue",
|
||||
name: "invalid patch json",
|
||||
setup: func(t *testing.T) testCase {
|
||||
username := gofakeit.Username()
|
||||
created, err := Instance.Client.SCIM.Users.Create(CTX, Instance.DefaultOrg.Id, withUsername(fullUserJson, username))
|
||||
require.NoError(t, err)
|
||||
return testCase{
|
||||
userID: created.ID,
|
||||
body: simpleReplacePatchBody("nickname", "10"),
|
||||
wantErr: true,
|
||||
scimErrorType: "invalidValue",
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "password complexity violation",
|
||||
body: simpleReplacePatchBody("password", `"fooBar"`),
|
||||
wantErr: true,
|
||||
scimErrorType: "invalidValue",
|
||||
name: "password complexity violation",
|
||||
setup: func(t *testing.T) testCase {
|
||||
username := gofakeit.Username()
|
||||
created, err := Instance.Client.SCIM.Users.Create(CTX, Instance.DefaultOrg.Id, withUsername(fullUserJson, username))
|
||||
require.NoError(t, err)
|
||||
return testCase{
|
||||
userID: created.ID,
|
||||
body: simpleReplacePatchBody("password", `"fooBar"`),
|
||||
wantErr: true,
|
||||
scimErrorType: "invalidValue",
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "invalid profile url",
|
||||
body: simpleReplacePatchBody("profileUrl", `"ftp://example.com/profiles"`),
|
||||
wantErr: true,
|
||||
scimErrorType: "invalidValue",
|
||||
name: "invalid profile url",
|
||||
setup: func(t *testing.T) testCase {
|
||||
username := gofakeit.Username()
|
||||
created, err := Instance.Client.SCIM.Users.Create(CTX, Instance.DefaultOrg.Id, withUsername(fullUserJson, username))
|
||||
require.NoError(t, err)
|
||||
return testCase{
|
||||
userID: created.ID,
|
||||
body: simpleReplacePatchBody("profileUrl", `"ftp://example.com/profiles"`),
|
||||
wantErr: true,
|
||||
scimErrorType: "invalidValue",
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "invalid time zone",
|
||||
body: simpleReplacePatchBody("timezone", `"foobar"`),
|
||||
wantErr: true,
|
||||
scimErrorType: "invalidValue",
|
||||
name: "invalid time zone",
|
||||
setup: func(t *testing.T) testCase {
|
||||
username := gofakeit.Username()
|
||||
created, err := Instance.Client.SCIM.Users.Create(CTX, Instance.DefaultOrg.Id, withUsername(fullUserJson, username))
|
||||
require.NoError(t, err)
|
||||
return testCase{
|
||||
userID: created.ID,
|
||||
body: simpleReplacePatchBody("timezone", `"foobar"`),
|
||||
wantErr: true,
|
||||
scimErrorType: "invalidValue",
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "invalid locale",
|
||||
body: simpleReplacePatchBody("locale", `"foobar"`),
|
||||
wantErr: true,
|
||||
scimErrorType: "invalidValue",
|
||||
name: "invalid locale",
|
||||
setup: func(t *testing.T) testCase {
|
||||
username := gofakeit.Username()
|
||||
created, err := Instance.Client.SCIM.Users.Create(CTX, Instance.DefaultOrg.Id, withUsername(fullUserJson, username))
|
||||
require.NoError(t, err)
|
||||
return testCase{
|
||||
userID: created.ID,
|
||||
body: simpleReplacePatchBody("locale", `"foobar"`),
|
||||
wantErr: true,
|
||||
scimErrorType: "invalidValue",
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "unknown user id",
|
||||
body: simpleReplacePatchBody("nickname", `"foo"`),
|
||||
userID: "fooBar",
|
||||
wantErr: true,
|
||||
errorStatus: http.StatusNotFound,
|
||||
name: "unknown user id",
|
||||
setup: func(t *testing.T) testCase {
|
||||
return testCase{
|
||||
body: simpleReplacePatchBody("nickname", `"foo"`),
|
||||
userID: "fooBar",
|
||||
wantErr: true,
|
||||
errorStatus: http.StatusNotFound,
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "full",
|
||||
body: fullUserUpdateJson,
|
||||
want: &resources.ScimUser{
|
||||
ExternalID: "fooBAR",
|
||||
UserName: "[email protected]",
|
||||
Name: &resources.ScimUserName{
|
||||
Formatted: "replaced-display-name",
|
||||
FamilyName: "added-family-name",
|
||||
GivenName: "added-given-name",
|
||||
MiddleName: "added-middle-name-2",
|
||||
HonorificPrefix: "added-honorific-prefix",
|
||||
HonorificSuffix: "replaced-honorific-suffix",
|
||||
},
|
||||
DisplayName: "replaced-display-name",
|
||||
NickName: "",
|
||||
ProfileUrl: test.Must(schemas.ParseHTTPURL("http://login.example.com/bjensen")),
|
||||
Emails: []*resources.ScimEmail{
|
||||
{
|
||||
Value: "[email protected]",
|
||||
Type: "work",
|
||||
setup: func(t *testing.T) testCase {
|
||||
username := gofakeit.Username()
|
||||
created, err := Instance.Client.SCIM.Users.Create(CTX, Instance.DefaultOrg.Id, withUsername(fullUserJson, username))
|
||||
require.NoError(t, err)
|
||||
return testCase{
|
||||
userID: created.ID,
|
||||
body: withUsername(fullUserUpdateJson, username),
|
||||
want: &resources.ScimUser{
|
||||
ExternalID: "fooBAR",
|
||||
UserName: username + "@example.com",
|
||||
Name: &resources.ScimUserName{
|
||||
Formatted: "replaced-display-name",
|
||||
FamilyName: "added-family-name",
|
||||
GivenName: "added-given-name",
|
||||
MiddleName: "added-middle-name-2",
|
||||
HonorificPrefix: "added-honorific-prefix",
|
||||
HonorificSuffix: "replaced-honorific-suffix",
|
||||
},
|
||||
DisplayName: "replaced-display-name",
|
||||
NickName: "",
|
||||
ProfileUrl: test.Must(schemas.ParseHTTPURL("http://login.example.com/bjensen")),
|
||||
Emails: []*resources.ScimEmail{
|
||||
{
|
||||
Value: username + "@example.com",
|
||||
Type: "work",
|
||||
},
|
||||
{
|
||||
Value: username + "[email protected]",
|
||||
Type: "home",
|
||||
},
|
||||
{
|
||||
Value: username + "[email protected]",
|
||||
Primary: true,
|
||||
Type: "home",
|
||||
},
|
||||
},
|
||||
Addresses: []*resources.ScimAddress{
|
||||
{
|
||||
Type: "replaced-work",
|
||||
StreetAddress: "replaced-100 Universal City Plaza",
|
||||
Locality: "replaced-Hollywood",
|
||||
Region: "replaced-CA",
|
||||
PostalCode: "replaced-91608",
|
||||
Country: "replaced-USA",
|
||||
Formatted: "replaced-100 Universal City Plaza\nHollywood, CA 91608 USA",
|
||||
Primary: true,
|
||||
},
|
||||
},
|
||||
PhoneNumbers: []*resources.ScimPhoneNumber{
|
||||
{
|
||||
Value: "+41711234567",
|
||||
Primary: true,
|
||||
},
|
||||
},
|
||||
Ims: []*resources.ScimIms{
|
||||
{
|
||||
Value: "someaimhandle",
|
||||
Type: "aim",
|
||||
},
|
||||
{
|
||||
Value: "twitterhandle",
|
||||
Type: "",
|
||||
},
|
||||
},
|
||||
Photos: []*resources.ScimPhoto{
|
||||
{
|
||||
Value: *test.Must(schemas.ParseHTTPURL("https://photos.example.com/profilephoto/72930000000Ccne/F")),
|
||||
Type: "photo",
|
||||
},
|
||||
},
|
||||
Roles: nil,
|
||||
Entitlements: []*resources.ScimEntitlement{
|
||||
{
|
||||
Value: "my-entitlement-1",
|
||||
Display: "added-entitlement-1",
|
||||
Type: "added-entitlement-1",
|
||||
Primary: false,
|
||||
},
|
||||
{
|
||||
Value: "my-entitlement-2",
|
||||
Display: "Entitlement 2",
|
||||
Type: "secondary-entitlement",
|
||||
Primary: false,
|
||||
},
|
||||
{
|
||||
Value: "added-entitlement-1",
|
||||
Primary: false,
|
||||
},
|
||||
{
|
||||
Value: "added-entitlement-2",
|
||||
Primary: false,
|
||||
},
|
||||
{
|
||||
Value: "added-entitlement-3",
|
||||
Primary: true,
|
||||
},
|
||||
},
|
||||
Title: "Tour Guide",
|
||||
PreferredLanguage: language.MustParse("en"),
|
||||
Locale: "en-US",
|
||||
Timezone: "America/Los_Angeles",
|
||||
Active: schemas.NewRelaxedBool(true),
|
||||
},
|
||||
{
|
||||
Value: "[email protected]",
|
||||
Type: "home",
|
||||
},
|
||||
{
|
||||
Value: "[email protected]",
|
||||
Primary: true,
|
||||
Type: "home",
|
||||
},
|
||||
},
|
||||
Addresses: []*resources.ScimAddress{
|
||||
{
|
||||
Type: "replaced-work",
|
||||
StreetAddress: "replaced-100 Universal City Plaza",
|
||||
Locality: "replaced-Hollywood",
|
||||
Region: "replaced-CA",
|
||||
PostalCode: "replaced-91608",
|
||||
Country: "replaced-USA",
|
||||
Formatted: "replaced-100 Universal City Plaza\nHollywood, CA 91608 USA",
|
||||
Primary: true,
|
||||
},
|
||||
},
|
||||
PhoneNumbers: []*resources.ScimPhoneNumber{
|
||||
{
|
||||
Value: "+41711234567",
|
||||
Primary: true,
|
||||
},
|
||||
},
|
||||
Ims: []*resources.ScimIms{
|
||||
{
|
||||
Value: "someaimhandle",
|
||||
Type: "aim",
|
||||
},
|
||||
{
|
||||
Value: "twitterhandle",
|
||||
Type: "",
|
||||
},
|
||||
},
|
||||
Photos: []*resources.ScimPhoto{
|
||||
{
|
||||
Value: *test.Must(schemas.ParseHTTPURL("https://photos.example.com/profilephoto/72930000000Ccne/F")),
|
||||
Type: "photo",
|
||||
},
|
||||
},
|
||||
Roles: nil,
|
||||
Entitlements: []*resources.ScimEntitlement{
|
||||
{
|
||||
Value: "my-entitlement-1",
|
||||
Display: "added-entitlement-1",
|
||||
Type: "added-entitlement-1",
|
||||
Primary: false,
|
||||
},
|
||||
{
|
||||
Value: "my-entitlement-2",
|
||||
Display: "Entitlement 2",
|
||||
Type: "secondary-entitlement",
|
||||
Primary: false,
|
||||
},
|
||||
{
|
||||
Value: "added-entitlement-1",
|
||||
Primary: false,
|
||||
},
|
||||
{
|
||||
Value: "added-entitlement-2",
|
||||
Primary: false,
|
||||
},
|
||||
{
|
||||
Value: "added-entitlement-3",
|
||||
Primary: true,
|
||||
},
|
||||
},
|
||||
Title: "Tour Guide",
|
||||
PreferredLanguage: language.MustParse("en-US"),
|
||||
Locale: "en-US",
|
||||
Timezone: "America/Los_Angeles",
|
||||
Active: schemas.NewRelaxedBool(true),
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if tt.ctx == nil {
|
||||
tt.ctx = CTX
|
||||
ttt := tt.setup(t)
|
||||
if ttt.orgID == "" {
|
||||
ttt.orgID = Instance.DefaultOrg.Id
|
||||
}
|
||||
|
||||
if tt.orgID == "" {
|
||||
tt.orgID = Instance.DefaultOrg.Id
|
||||
if ttt.ctx == nil {
|
||||
ttt.ctx = CTX
|
||||
}
|
||||
|
||||
if tt.userID == "" {
|
||||
tt.userID = fullUserCreated.ID
|
||||
}
|
||||
|
||||
err := Instance.Client.SCIM.Users.Update(tt.ctx, tt.orgID, tt.userID, tt.body)
|
||||
|
||||
if tt.wantErr {
|
||||
err := Instance.Client.SCIM.Users.Update(ttt.ctx, ttt.orgID, ttt.userID, ttt.body)
|
||||
if ttt.wantErr {
|
||||
require.Error(t, err)
|
||||
|
||||
statusCode := tt.errorStatus
|
||||
statusCode := ttt.errorStatus
|
||||
if statusCode == 0 {
|
||||
statusCode = http.StatusBadRequest
|
||||
}
|
||||
|
||||
scimErr := scim.RequireScimError(t, statusCode, err)
|
||||
assert.Equal(t, tt.scimErrorType, scimErr.Error.ScimType)
|
||||
assert.Equal(t, ttt.scimErrorType, scimErr.Error.ScimType)
|
||||
return
|
||||
}
|
||||
|
||||
require.NoError(t, err)
|
||||
|
||||
retryDuration, tick := integration.WaitForAndTickWithMaxDuration(CTX, time.Minute)
|
||||
require.EventuallyWithT(t, func(ttt *assert.CollectT) {
|
||||
fetchedUser, err := Instance.Client.SCIM.Users.Get(tt.ctx, tt.orgID, fullUserCreated.ID)
|
||||
require.NoError(ttt, err)
|
||||
|
||||
require.EventuallyWithT(t, func(collect *assert.CollectT) {
|
||||
fetchedUser, err := Instance.Client.SCIM.Users.Get(ttt.ctx, ttt.orgID, ttt.userID)
|
||||
if !assert.NoError(collect, err) {
|
||||
return
|
||||
}
|
||||
fetchedUser.Resource = nil
|
||||
fetchedUser.ID = ""
|
||||
if tt.want != nil && !test.PartiallyDeepEqual(tt.want, fetchedUser) {
|
||||
ttt.Errorf("got = %#v, want = %#v", fetchedUser, tt.want)
|
||||
}
|
||||
fetched, err := json.Marshal(fetchedUser)
|
||||
require.NoError(collect, err)
|
||||
want, err := json.Marshal(ttt.want)
|
||||
require.NoError(collect, err)
|
||||
assert.JSONEq(collect, string(want), string(fetched))
|
||||
}, retryDuration, tick)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -262,7 +262,7 @@ func (h *UsersHandler) queryUserDependencies(ctx context.Context, userID string)
|
||||
|
||||
grants, err := h.query.UserGrants(ctx, &query.UserGrantsQueries{
|
||||
Queries: []query.SearchQuery{userGrantUserQuery},
|
||||
}, true)
|
||||
}, true, nil)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
@@ -45,7 +45,7 @@ func (h *UsersHandler) queryMetadataForUsers(ctx context.Context, userIds []stri
|
||||
func (h *UsersHandler) queryMetadataForUser(ctx context.Context, id string) (map[metadata.ScopedKey][]byte, error) {
|
||||
queries := h.buildMetadataQueries(ctx)
|
||||
|
||||
md, err := h.query.SearchUserMetadata(ctx, false, id, queries, false)
|
||||
md, err := h.query.SearchUserMetadata(ctx, false, id, queries, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -1260,7 +1260,8 @@ func (l *Login) appendUserGrants(ctx context.Context, userGrants []*domain.UserG
|
||||
return nil
|
||||
}
|
||||
for _, grant := range userGrants {
|
||||
_, err := l.command.AddUserGrant(setContext(ctx, resourceOwner), grant, resourceOwner)
|
||||
grant.ResourceOwner = resourceOwner
|
||||
_, err := l.command.AddUserGrant(setContext(ctx, resourceOwner), grant, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -125,7 +125,7 @@ func (q queryViewWrapper) UserGrantsByProjectAndUserID(ctx context.Context, proj
|
||||
return nil, err
|
||||
}
|
||||
queries := &query.UserGrantsQueries{Queries: []query.SearchQuery{userGrantUserID, userGrantProjectID, activeQuery}}
|
||||
grants, err := q.Queries.UserGrants(ctx, queries, true)
|
||||
grants, err := q.Queries.UserGrants(ctx, queries, true, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -690,7 +690,7 @@ func setupLoginClient(commands *Commands, validations *[]preparation.Validation,
|
||||
|
||||
func setupAdminMembers(commands *Commands, validations *[]preparation.Validation, instanceAgg *instance.Aggregate, orgAgg *org.Aggregate, userID string) {
|
||||
*validations = append(*validations,
|
||||
commands.AddOrgMemberCommand(orgAgg, userID, domain.RoleOrgOwner),
|
||||
commands.AddOrgMemberCommand(&AddOrgMember{orgAgg.ID, userID, []string{domain.RoleOrgOwner}}),
|
||||
commands.AddInstanceMemberCommand(instanceAgg, userID, domain.RoleIAMOwner),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ package command
|
||||
|
||||
import (
|
||||
"context"
|
||||
"reflect"
|
||||
"slices"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/api/authz"
|
||||
"github.com/zitadel/zitadel/internal/command/preparation"
|
||||
@@ -69,9 +69,19 @@ func IsInstanceMember(ctx context.Context, filter preparation.FilterToQueryReduc
|
||||
return isMember, nil
|
||||
}
|
||||
|
||||
func (c *Commands) AddInstanceMember(ctx context.Context, userID string, roles ...string) (*domain.Member, error) {
|
||||
instanceAgg := instance.NewAggregate(authz.GetInstance(ctx).InstanceID())
|
||||
cmds, err := preparation.PrepareCommands(ctx, c.eventstore.Filter, c.AddInstanceMemberCommand(instanceAgg, userID, roles...))
|
||||
type AddInstanceMember struct {
|
||||
InstanceID string
|
||||
UserID string
|
||||
Roles []string
|
||||
}
|
||||
|
||||
func (c *Commands) AddInstanceMember(ctx context.Context, member *AddInstanceMember) (*domain.ObjectDetails, error) {
|
||||
instanceAgg := instance.NewAggregate(member.InstanceID)
|
||||
if err := c.checkPermissionUpdateInstanceMember(ctx, member.InstanceID); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
//nolint:staticcheck
|
||||
cmds, err := preparation.PrepareCommands(ctx, c.eventstore.Filter, c.AddInstanceMemberCommand(instanceAgg, member.UserID, member.Roles...))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -79,33 +89,56 @@ func (c *Commands) AddInstanceMember(ctx context.Context, userID string, roles .
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
addedMember := NewInstanceMemberWriteModel(ctx, userID)
|
||||
addedMember := NewInstanceMemberWriteModel(member.InstanceID, member.UserID)
|
||||
err = AppendAndReduce(addedMember, events...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return memberWriteModelToMember(&addedMember.MemberWriteModel), nil
|
||||
return writeModelToObjectDetails(&addedMember.WriteModel), nil
|
||||
}
|
||||
|
||||
type ChangeInstanceMember struct {
|
||||
InstanceID string
|
||||
UserID string
|
||||
Roles []string
|
||||
}
|
||||
|
||||
func (i *ChangeInstanceMember) IsValid(zitadelRoles []authz.RoleMapping) error {
|
||||
if i.InstanceID == "" || i.UserID == "" || len(i.Roles) == 0 {
|
||||
return zerrors.ThrowInvalidArgument(nil, "INSTANCE-LiaZi", "Errors.IAM.MemberInvalid")
|
||||
}
|
||||
if len(domain.CheckForInvalidRoles(i.Roles, domain.IAMRolePrefix, zitadelRoles)) > 0 {
|
||||
return zerrors.ThrowInvalidArgument(nil, "INSTANCE-3m9fs", "Errors.IAM.MemberInvalid")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ChangeInstanceMember updates an existing member
|
||||
func (c *Commands) ChangeInstanceMember(ctx context.Context, member *domain.Member) (*domain.Member, error) {
|
||||
if !member.IsIAMValid() {
|
||||
return nil, zerrors.ThrowInvalidArgument(nil, "INSTANCE-LiaZi", "Errors.IAM.MemberInvalid")
|
||||
}
|
||||
if len(domain.CheckForInvalidRoles(member.Roles, domain.IAMRolePrefix, c.zitadelRoles)) > 0 {
|
||||
return nil, zerrors.ThrowInvalidArgument(nil, "INSTANCE-3m9fs", "Errors.IAM.MemberInvalid")
|
||||
}
|
||||
|
||||
existingMember, err := c.instanceMemberWriteModelByID(ctx, member.UserID)
|
||||
if err != nil {
|
||||
func (c *Commands) ChangeInstanceMember(ctx context.Context, member *ChangeInstanceMember) (*domain.ObjectDetails, error) {
|
||||
if err := member.IsValid(c.zitadelRoles); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if reflect.DeepEqual(existingMember.Roles, member.Roles) {
|
||||
return nil, zerrors.ThrowPreconditionFailed(nil, "INSTANCE-LiaZi", "Errors.IAM.Member.RolesNotChanged")
|
||||
existingMember, err := c.instanceMemberWriteModelByID(ctx, member.InstanceID, member.UserID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
instanceAgg := InstanceAggregateFromWriteModel(&existingMember.MemberWriteModel.WriteModel)
|
||||
pushedEvents, err := c.eventstore.Push(ctx, instance.NewMemberChangedEvent(ctx, instanceAgg, member.UserID, member.Roles...))
|
||||
if !existingMember.State.Exists() {
|
||||
return nil, zerrors.ThrowNotFound(nil, "INSTANCE-D8JxR", "Errors.NotFound")
|
||||
}
|
||||
if err := c.checkPermissionUpdateInstanceMember(ctx, existingMember.AggregateID); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if slices.Compare(existingMember.Roles, member.Roles) == 0 {
|
||||
return writeModelToObjectDetails(&existingMember.WriteModel), nil
|
||||
}
|
||||
pushedEvents, err := c.eventstore.Push(ctx,
|
||||
instance.NewMemberChangedEvent(ctx,
|
||||
InstanceAggregateFromWriteModel(&existingMember.WriteModel),
|
||||
member.UserID,
|
||||
member.Roles...,
|
||||
),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -114,34 +147,40 @@ func (c *Commands) ChangeInstanceMember(ctx context.Context, member *domain.Memb
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return memberWriteModelToMember(&existingMember.MemberWriteModel), nil
|
||||
return writeModelToObjectDetails(&existingMember.WriteModel), nil
|
||||
}
|
||||
|
||||
func (c *Commands) RemoveInstanceMember(ctx context.Context, userID string) (*domain.ObjectDetails, error) {
|
||||
func (c *Commands) RemoveInstanceMember(ctx context.Context, instanceID, userID string) (*domain.ObjectDetails, error) {
|
||||
if userID == "" {
|
||||
return nil, zerrors.ThrowInvalidArgument(nil, "INSTANCE-LiaZi", "Errors.IDMissing")
|
||||
}
|
||||
memberWriteModel, err := c.instanceMemberWriteModelByID(ctx, userID)
|
||||
if err != nil && !zerrors.IsNotFound(err) {
|
||||
return nil, err
|
||||
}
|
||||
if zerrors.IsNotFound(err) {
|
||||
// empty response because we have no data that match the request
|
||||
return &domain.ObjectDetails{}, nil
|
||||
}
|
||||
|
||||
instanceAgg := InstanceAggregateFromWriteModel(&memberWriteModel.MemberWriteModel.WriteModel)
|
||||
removeEvent := c.removeInstanceMember(ctx, instanceAgg, userID, false)
|
||||
pushedEvents, err := c.eventstore.Push(ctx, removeEvent)
|
||||
existingMember, err := c.instanceMemberWriteModelByID(ctx, instanceID, userID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = AppendAndReduce(memberWriteModel, pushedEvents...)
|
||||
if err := c.checkPermissionDeleteInstanceMember(ctx, instanceID); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !existingMember.State.Exists() {
|
||||
return writeModelToObjectDetails(&existingMember.WriteModel), nil
|
||||
}
|
||||
|
||||
pushedEvents, err := c.eventstore.Push(ctx,
|
||||
c.removeInstanceMember(ctx,
|
||||
InstanceAggregateFromWriteModel(&existingMember.WriteModel),
|
||||
userID,
|
||||
false,
|
||||
),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = AppendAndReduce(existingMember, pushedEvents...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return writeModelToObjectDetails(&memberWriteModel.MemberWriteModel.WriteModel), nil
|
||||
return writeModelToObjectDetails(&existingMember.WriteModel), nil
|
||||
}
|
||||
|
||||
func (c *Commands) removeInstanceMember(ctx context.Context, instanceAgg *eventstore.Aggregate, userID string, cascade bool) eventstore.Command {
|
||||
@@ -155,19 +194,15 @@ func (c *Commands) removeInstanceMember(ctx context.Context, instanceAgg *events
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Commands) instanceMemberWriteModelByID(ctx context.Context, userID string) (member *InstanceMemberWriteModel, err error) {
|
||||
func (c *Commands) instanceMemberWriteModelByID(ctx context.Context, instanceID, userID string) (member *InstanceMemberWriteModel, err error) {
|
||||
ctx, span := tracing.NewSpan(ctx)
|
||||
defer func() { span.EndWithError(err) }()
|
||||
|
||||
writeModel := NewInstanceMemberWriteModel(ctx, userID)
|
||||
writeModel := NewInstanceMemberWriteModel(instanceID, userID)
|
||||
err = c.eventstore.FilterToQueryReducer(ctx, writeModel)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if writeModel.State == domain.MemberStateUnspecified || writeModel.State == domain.MemberStateRemoved {
|
||||
return nil, zerrors.ThrowNotFound(nil, "INSTANCE-D8JxR", "Errors.NotFound")
|
||||
}
|
||||
|
||||
return writeModel, nil
|
||||
}
|
||||
|
||||
@@ -1,9 +1,6 @@
|
||||
package command
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/api/authz"
|
||||
"github.com/zitadel/zitadel/internal/eventstore"
|
||||
"github.com/zitadel/zitadel/internal/repository/instance"
|
||||
)
|
||||
@@ -12,12 +9,12 @@ type InstanceMemberWriteModel struct {
|
||||
MemberWriteModel
|
||||
}
|
||||
|
||||
func NewInstanceMemberWriteModel(ctx context.Context, userID string) *InstanceMemberWriteModel {
|
||||
func NewInstanceMemberWriteModel(instanceID, userID string) *InstanceMemberWriteModel {
|
||||
return &InstanceMemberWriteModel{
|
||||
MemberWriteModel{
|
||||
WriteModel: eventstore.WriteModel{
|
||||
AggregateID: authz.GetInstance(ctx).InstanceID(),
|
||||
ResourceOwner: authz.GetInstance(ctx).InstanceID(),
|
||||
AggregateID: instanceID,
|
||||
ResourceOwner: instanceID,
|
||||
},
|
||||
UserID: userID,
|
||||
},
|
||||
|
||||
@@ -10,24 +10,22 @@ import (
|
||||
"github.com/zitadel/zitadel/internal/api/authz"
|
||||
"github.com/zitadel/zitadel/internal/domain"
|
||||
"github.com/zitadel/zitadel/internal/eventstore"
|
||||
"github.com/zitadel/zitadel/internal/eventstore/v1/models"
|
||||
"github.com/zitadel/zitadel/internal/repository/instance"
|
||||
"github.com/zitadel/zitadel/internal/repository/user"
|
||||
"github.com/zitadel/zitadel/internal/zerrors"
|
||||
)
|
||||
|
||||
func TestCommandSide_AddIAMMember(t *testing.T) {
|
||||
func TestCommandSide_AddInstanceMember(t *testing.T) {
|
||||
type fields struct {
|
||||
eventstore *eventstore.Eventstore
|
||||
zitadelRoles []authz.RoleMapping
|
||||
eventstore func(t *testing.T) *eventstore.Eventstore
|
||||
zitadelRoles []authz.RoleMapping
|
||||
checkPermission domain.PermissionCheck
|
||||
}
|
||||
type args struct {
|
||||
ctx context.Context
|
||||
userID string
|
||||
roles []string
|
||||
member *AddInstanceMember
|
||||
}
|
||||
type res struct {
|
||||
want *domain.Member
|
||||
want *domain.ObjectDetails
|
||||
err func(error) bool
|
||||
}
|
||||
tests := []struct {
|
||||
@@ -39,28 +37,28 @@ func TestCommandSide_AddIAMMember(t *testing.T) {
|
||||
{
|
||||
name: "invalid member, error",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
),
|
||||
eventstore: expectEventstore(),
|
||||
checkPermission: newMockPermissionCheckAllowed(),
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
member: &AddInstanceMember{},
|
||||
},
|
||||
res: res{
|
||||
err: zerrors.IsErrorInvalidArgument,
|
||||
err: zerrors.IsInternal,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "invalid roles, error",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
),
|
||||
eventstore: expectEventstore(),
|
||||
checkPermission: newMockPermissionCheckAllowed(),
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
userID: "user1",
|
||||
roles: []string{"IAM_OWNER"},
|
||||
member: &AddInstanceMember{
|
||||
InstanceID: "INSTANCE",
|
||||
UserID: "user1",
|
||||
Roles: []string{"IAM_OWNER"},
|
||||
},
|
||||
},
|
||||
res: res{
|
||||
err: zerrors.IsErrorInvalidArgument,
|
||||
@@ -69,10 +67,10 @@ func TestCommandSide_AddIAMMember(t *testing.T) {
|
||||
{
|
||||
name: "user not existing, precondition error",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(),
|
||||
),
|
||||
checkPermission: newMockPermissionCheckAllowed(),
|
||||
zitadelRoles: []authz.RoleMapping{
|
||||
{
|
||||
Role: "IAM_OWNER",
|
||||
@@ -80,9 +78,11 @@ func TestCommandSide_AddIAMMember(t *testing.T) {
|
||||
},
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
userID: "user1",
|
||||
roles: []string{"IAM_OWNER"},
|
||||
member: &AddInstanceMember{
|
||||
InstanceID: "INSTANCE",
|
||||
UserID: "user1",
|
||||
Roles: []string{"IAM_OWNER"},
|
||||
},
|
||||
},
|
||||
res: res{
|
||||
err: zerrors.IsPreconditionFailed,
|
||||
@@ -91,8 +91,7 @@ func TestCommandSide_AddIAMMember(t *testing.T) {
|
||||
{
|
||||
name: "member already exists, precondition error",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
user.NewHumanAddedEvent(context.Background(),
|
||||
@@ -118,6 +117,7 @@ func TestCommandSide_AddIAMMember(t *testing.T) {
|
||||
),
|
||||
),
|
||||
),
|
||||
checkPermission: newMockPermissionCheckAllowed(),
|
||||
zitadelRoles: []authz.RoleMapping{
|
||||
{
|
||||
Role: "IAM_OWNER",
|
||||
@@ -125,9 +125,11 @@ func TestCommandSide_AddIAMMember(t *testing.T) {
|
||||
},
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
userID: "user1",
|
||||
roles: []string{"IAM_OWNER"},
|
||||
member: &AddInstanceMember{
|
||||
InstanceID: "INSTANCE",
|
||||
UserID: "user1",
|
||||
Roles: []string{"IAM_OWNER"},
|
||||
},
|
||||
},
|
||||
res: res{
|
||||
err: zerrors.IsErrorAlreadyExists,
|
||||
@@ -136,8 +138,7 @@ func TestCommandSide_AddIAMMember(t *testing.T) {
|
||||
{
|
||||
name: "member add uniqueconstraint err, already exists",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
user.NewHumanAddedEvent(context.Background(),
|
||||
@@ -163,6 +164,7 @@ func TestCommandSide_AddIAMMember(t *testing.T) {
|
||||
),
|
||||
),
|
||||
),
|
||||
checkPermission: newMockPermissionCheckAllowed(),
|
||||
zitadelRoles: []authz.RoleMapping{
|
||||
{
|
||||
Role: "IAM_OWNER",
|
||||
@@ -170,9 +172,11 @@ func TestCommandSide_AddIAMMember(t *testing.T) {
|
||||
},
|
||||
},
|
||||
args: args{
|
||||
ctx: authz.WithInstanceID(context.Background(), "INSTANCE"),
|
||||
userID: "user1",
|
||||
roles: []string{"IAM_OWNER"},
|
||||
member: &AddInstanceMember{
|
||||
InstanceID: "INSTANCE",
|
||||
UserID: "user1",
|
||||
Roles: []string{"IAM_OWNER"},
|
||||
},
|
||||
},
|
||||
res: res{
|
||||
err: zerrors.IsErrorAlreadyExists,
|
||||
@@ -181,8 +185,7 @@ func TestCommandSide_AddIAMMember(t *testing.T) {
|
||||
{
|
||||
name: "member add, ok",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(
|
||||
eventFromEventPusherWithInstanceID(
|
||||
"INSTANCE",
|
||||
@@ -209,6 +212,7 @@ func TestCommandSide_AddIAMMember(t *testing.T) {
|
||||
),
|
||||
),
|
||||
),
|
||||
checkPermission: newMockPermissionCheckAllowed(),
|
||||
zitadelRoles: []authz.RoleMapping{
|
||||
{
|
||||
Role: "IAM_OWNER",
|
||||
@@ -216,30 +220,49 @@ func TestCommandSide_AddIAMMember(t *testing.T) {
|
||||
},
|
||||
},
|
||||
args: args{
|
||||
ctx: authz.WithInstanceID(context.Background(), "INSTANCE"),
|
||||
userID: "user1",
|
||||
roles: []string{"IAM_OWNER"},
|
||||
member: &AddInstanceMember{
|
||||
InstanceID: "INSTANCE",
|
||||
UserID: "user1",
|
||||
Roles: []string{"IAM_OWNER"},
|
||||
},
|
||||
},
|
||||
res: res{
|
||||
want: &domain.Member{
|
||||
ObjectRoot: models.ObjectRoot{
|
||||
InstanceID: "INSTANCE",
|
||||
ResourceOwner: "INSTANCE",
|
||||
AggregateID: "INSTANCE",
|
||||
},
|
||||
UserID: "user1",
|
||||
Roles: []string{"IAM_OWNER"},
|
||||
want: &domain.ObjectDetails{
|
||||
ResourceOwner: "INSTANCE",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "member add, no permission",
|
||||
fields: fields{
|
||||
eventstore: expectEventstore(),
|
||||
checkPermission: newMockPermissionCheckNotAllowed(),
|
||||
zitadelRoles: []authz.RoleMapping{
|
||||
{
|
||||
Role: "IAM_OWNER",
|
||||
},
|
||||
},
|
||||
},
|
||||
args: args{
|
||||
member: &AddInstanceMember{
|
||||
InstanceID: "INSTANCE",
|
||||
UserID: "user1",
|
||||
Roles: []string{"IAM_OWNER"},
|
||||
},
|
||||
},
|
||||
res: res{
|
||||
err: zerrors.IsPermissionDenied,
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
r := &Commands{
|
||||
eventstore: tt.fields.eventstore,
|
||||
zitadelRoles: tt.fields.zitadelRoles,
|
||||
eventstore: tt.fields.eventstore(t),
|
||||
zitadelRoles: tt.fields.zitadelRoles,
|
||||
checkPermission: tt.fields.checkPermission,
|
||||
}
|
||||
got, err := r.AddInstanceMember(tt.args.ctx, tt.args.userID, tt.args.roles...)
|
||||
got, err := r.AddInstanceMember(context.Background(), tt.args.member)
|
||||
if tt.res.err == nil {
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
@@ -247,24 +270,23 @@ func TestCommandSide_AddIAMMember(t *testing.T) {
|
||||
t.Errorf("got wrong err: %v ", err)
|
||||
}
|
||||
if tt.res.err == nil {
|
||||
assert.Equal(t, tt.res.want, got)
|
||||
assertObjectDetails(t, tt.res.want, got)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCommandSide_ChangeIAMMember(t *testing.T) {
|
||||
func TestCommandSide_ChangeInstanceMember(t *testing.T) {
|
||||
type fields struct {
|
||||
eventstore *eventstore.Eventstore
|
||||
zitadelRoles []authz.RoleMapping
|
||||
eventstore func(t *testing.T) *eventstore.Eventstore
|
||||
zitadelRoles []authz.RoleMapping
|
||||
checkPermission domain.PermissionCheck
|
||||
}
|
||||
type args struct {
|
||||
ctx context.Context
|
||||
instanceID string
|
||||
member *domain.Member
|
||||
member *ChangeInstanceMember
|
||||
}
|
||||
type res struct {
|
||||
want *domain.Member
|
||||
want *domain.ObjectDetails
|
||||
err func(error) bool
|
||||
}
|
||||
tests := []struct {
|
||||
@@ -276,13 +298,11 @@ func TestCommandSide_ChangeIAMMember(t *testing.T) {
|
||||
{
|
||||
name: "invalid member, error",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
),
|
||||
eventstore: expectEventstore(),
|
||||
checkPermission: newMockPermissionCheckAllowed(),
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
member: &domain.Member{},
|
||||
member: &ChangeInstanceMember{},
|
||||
},
|
||||
res: res{
|
||||
err: zerrors.IsErrorInvalidArgument,
|
||||
@@ -291,15 +311,14 @@ func TestCommandSide_ChangeIAMMember(t *testing.T) {
|
||||
{
|
||||
name: "invalid roles, error",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
),
|
||||
eventstore: expectEventstore(),
|
||||
checkPermission: newMockPermissionCheckAllowed(),
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
member: &domain.Member{
|
||||
UserID: "user1",
|
||||
Roles: []string{"IAM_OWNER"},
|
||||
member: &ChangeInstanceMember{
|
||||
InstanceID: "INSTANCE",
|
||||
UserID: "user1",
|
||||
Roles: []string{"IAM_OWNER"},
|
||||
},
|
||||
},
|
||||
res: res{
|
||||
@@ -309,10 +328,10 @@ func TestCommandSide_ChangeIAMMember(t *testing.T) {
|
||||
{
|
||||
name: "member not existing, not found error",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(),
|
||||
),
|
||||
checkPermission: newMockPermissionCheckAllowed(),
|
||||
zitadelRoles: []authz.RoleMapping{
|
||||
{
|
||||
Role: "IAM_OWNER",
|
||||
@@ -320,10 +339,10 @@ func TestCommandSide_ChangeIAMMember(t *testing.T) {
|
||||
},
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
member: &domain.Member{
|
||||
UserID: "user1",
|
||||
Roles: []string{"IAM_OWNER"},
|
||||
member: &ChangeInstanceMember{
|
||||
InstanceID: "INSTANCE",
|
||||
UserID: "user1",
|
||||
Roles: []string{"IAM_OWNER"},
|
||||
},
|
||||
},
|
||||
res: res{
|
||||
@@ -333,8 +352,7 @@ func TestCommandSide_ChangeIAMMember(t *testing.T) {
|
||||
{
|
||||
name: "member not changed, precondition error",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
instance.NewMemberAddedEvent(context.Background(),
|
||||
@@ -345,6 +363,7 @@ func TestCommandSide_ChangeIAMMember(t *testing.T) {
|
||||
),
|
||||
),
|
||||
),
|
||||
checkPermission: newMockPermissionCheckAllowed(),
|
||||
zitadelRoles: []authz.RoleMapping{
|
||||
{
|
||||
Role: domain.RoleIAMOwner,
|
||||
@@ -352,21 +371,22 @@ func TestCommandSide_ChangeIAMMember(t *testing.T) {
|
||||
},
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
member: &domain.Member{
|
||||
UserID: "user1",
|
||||
Roles: []string{"IAM_OWNER"},
|
||||
member: &ChangeInstanceMember{
|
||||
InstanceID: "INSTANCE",
|
||||
UserID: "user1",
|
||||
Roles: []string{"IAM_OWNER"},
|
||||
},
|
||||
},
|
||||
res: res{
|
||||
err: zerrors.IsPreconditionFailed,
|
||||
want: &domain.ObjectDetails{
|
||||
ResourceOwner: "INSTANCE",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "member change, ok",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
instance.NewMemberAddedEvent(context.Background(),
|
||||
@@ -384,6 +404,7 @@ func TestCommandSide_ChangeIAMMember(t *testing.T) {
|
||||
),
|
||||
),
|
||||
),
|
||||
checkPermission: newMockPermissionCheckAllowed(),
|
||||
zitadelRoles: []authz.RoleMapping{
|
||||
{
|
||||
Role: "IAM_OWNER",
|
||||
@@ -394,32 +415,62 @@ func TestCommandSide_ChangeIAMMember(t *testing.T) {
|
||||
},
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
member: &domain.Member{
|
||||
UserID: "user1",
|
||||
Roles: []string{"IAM_OWNER", "IAM_OWNER_VIEWER"},
|
||||
member: &ChangeInstanceMember{
|
||||
InstanceID: "INSTANCE",
|
||||
UserID: "user1",
|
||||
Roles: []string{"IAM_OWNER", "IAM_OWNER_VIEWER"},
|
||||
},
|
||||
},
|
||||
res: res{
|
||||
want: &domain.Member{
|
||||
ObjectRoot: models.ObjectRoot{
|
||||
ResourceOwner: "INSTANCE",
|
||||
AggregateID: "INSTANCE",
|
||||
InstanceID: "INSTANCE",
|
||||
},
|
||||
UserID: "user1",
|
||||
Roles: []string{"IAM_OWNER", "IAM_OWNER_VIEWER"},
|
||||
want: &domain.ObjectDetails{
|
||||
ResourceOwner: "INSTANCE",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "member change, ok",
|
||||
fields: fields{
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
instance.NewMemberAddedEvent(context.Background(),
|
||||
&instance.NewAggregate("INSTANCE").Aggregate,
|
||||
"user1",
|
||||
[]string{"IAM_OWNER"}...,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
checkPermission: newMockPermissionCheckNotAllowed(),
|
||||
zitadelRoles: []authz.RoleMapping{
|
||||
{
|
||||
Role: "IAM_OWNER",
|
||||
},
|
||||
{
|
||||
Role: "IAM_OWNER_VIEWER",
|
||||
},
|
||||
},
|
||||
},
|
||||
args: args{
|
||||
member: &ChangeInstanceMember{
|
||||
InstanceID: "INSTANCE",
|
||||
UserID: "user1",
|
||||
Roles: []string{"IAM_OWNER"},
|
||||
},
|
||||
},
|
||||
res: res{
|
||||
err: zerrors.IsPermissionDenied,
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
r := &Commands{
|
||||
eventstore: tt.fields.eventstore,
|
||||
zitadelRoles: tt.fields.zitadelRoles,
|
||||
eventstore: tt.fields.eventstore(t),
|
||||
zitadelRoles: tt.fields.zitadelRoles,
|
||||
checkPermission: tt.fields.checkPermission,
|
||||
}
|
||||
got, err := r.ChangeInstanceMember(tt.args.ctx, tt.args.member)
|
||||
got, err := r.ChangeInstanceMember(context.Background(), tt.args.member)
|
||||
if tt.res.err == nil {
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
@@ -427,18 +478,18 @@ func TestCommandSide_ChangeIAMMember(t *testing.T) {
|
||||
t.Errorf("got wrong err: %v ", err)
|
||||
}
|
||||
if tt.res.err == nil {
|
||||
assert.Equal(t, tt.res.want, got)
|
||||
assertObjectDetails(t, tt.res.want, got)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCommandSide_RemoveIAMMember(t *testing.T) {
|
||||
func TestCommandSide_RemoveInstanceMember(t *testing.T) {
|
||||
type fields struct {
|
||||
eventstore *eventstore.Eventstore
|
||||
eventstore func(t *testing.T) *eventstore.Eventstore
|
||||
checkPermission domain.PermissionCheck
|
||||
}
|
||||
type args struct {
|
||||
ctx context.Context
|
||||
instanceID string
|
||||
userID string
|
||||
}
|
||||
@@ -455,13 +506,12 @@ func TestCommandSide_RemoveIAMMember(t *testing.T) {
|
||||
{
|
||||
name: "invalid member userid missing, error",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
),
|
||||
eventstore: expectEventstore(),
|
||||
checkPermission: newMockPermissionCheckAllowed(),
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
userID: "",
|
||||
instanceID: "INSTANCE",
|
||||
userID: "",
|
||||
},
|
||||
res: res{
|
||||
err: zerrors.IsErrorInvalidArgument,
|
||||
@@ -470,24 +520,25 @@ func TestCommandSide_RemoveIAMMember(t *testing.T) {
|
||||
{
|
||||
name: "member not existing, empty object details result",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(),
|
||||
),
|
||||
checkPermission: newMockPermissionCheckAllowed(),
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
userID: "user1",
|
||||
instanceID: "INSTANCE",
|
||||
userID: "user1",
|
||||
},
|
||||
res: res{
|
||||
want: &domain.ObjectDetails{},
|
||||
want: &domain.ObjectDetails{
|
||||
ResourceOwner: "INSTANCE",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "member remove, ok",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
instance.NewMemberAddedEvent(context.Background(),
|
||||
@@ -504,10 +555,11 @@ func TestCommandSide_RemoveIAMMember(t *testing.T) {
|
||||
),
|
||||
),
|
||||
),
|
||||
checkPermission: newMockPermissionCheckAllowed(),
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
userID: "user1",
|
||||
instanceID: "INSTANCE",
|
||||
userID: "user1",
|
||||
},
|
||||
res: res{
|
||||
want: &domain.ObjectDetails{
|
||||
@@ -515,13 +567,38 @@ func TestCommandSide_RemoveIAMMember(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "member remove, no permission",
|
||||
fields: fields{
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
instance.NewMemberAddedEvent(context.Background(),
|
||||
&instance.NewAggregate("INSTANCE").Aggregate,
|
||||
"user1",
|
||||
[]string{"IAM_OWNER"}...,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
checkPermission: newMockPermissionCheckNotAllowed(),
|
||||
},
|
||||
args: args{
|
||||
instanceID: "INSTANCE",
|
||||
userID: "user1",
|
||||
},
|
||||
res: res{
|
||||
err: zerrors.IsPermissionDenied,
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
r := &Commands{
|
||||
eventstore: tt.fields.eventstore,
|
||||
eventstore: tt.fields.eventstore(t),
|
||||
checkPermission: tt.fields.checkPermission,
|
||||
}
|
||||
got, err := r.RemoveInstanceMember(tt.args.ctx, tt.args.userID)
|
||||
got, err := r.RemoveInstanceMember(context.Background(), tt.args.instanceID, tt.args.userID)
|
||||
if tt.res.err == nil {
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
@@ -123,7 +123,7 @@ func (c *Commands) newOrgSetupCommands(ctx context.Context, orgID string, orgSet
|
||||
|
||||
func (c *orgSetupCommands) setupOrgAdmin(admin *OrgSetupAdmin, allowInitialMail bool) error {
|
||||
if admin.ID != "" {
|
||||
c.validations = append(c.validations, c.commands.AddOrgMemberCommand(c.aggregate, admin.ID, orgAdminRoles(admin.Roles)...))
|
||||
c.validations = append(c.validations, c.commands.AddOrgMemberCommand(&AddOrgMember{OrgID: c.aggregate.ID, UserID: admin.ID, Roles: orgAdminRoles(admin.Roles)}))
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -147,7 +147,7 @@ func (c *orgSetupCommands) setupOrgAdmin(admin *OrgSetupAdmin, allowInitialMail
|
||||
return err
|
||||
}
|
||||
}
|
||||
c.validations = append(c.validations, c.commands.AddOrgMemberCommand(c.aggregate, userID, orgAdminRoles(admin.Roles)...))
|
||||
c.validations = append(c.validations, c.commands.AddOrgMemberCommand(&AddOrgMember{OrgID: c.aggregate.ID, UserID: userID, Roles: orgAdminRoles(admin.Roles)}))
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -359,16 +359,15 @@ func (c *Commands) addOrgWithIDAndMember(ctx context.Context, name, userID, reso
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = c.checkUserExists(ctx, userID, resourceOwner)
|
||||
_, err = c.checkUserExists(ctx, userID, resourceOwner)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
addedMember := NewOrgMemberWriteModel(addedOrg.AggregateID, userID)
|
||||
orgMemberEvent, err := c.addOrgMember(ctx, orgAgg, addedMember, domain.NewMember(orgAgg.ID, userID, domain.RoleOrgOwner))
|
||||
if err != nil {
|
||||
addMember := &AddOrgMember{OrgID: orgAgg.ID, UserID: userID, Roles: []string{domain.RoleOrgOwner}}
|
||||
if err := addMember.IsValid(c.zitadelRoles); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
events = append(events, orgMemberEvent)
|
||||
events = append(events, org.NewMemberAddedEvent(ctx, orgAgg, addMember.UserID, addMember.Roles...))
|
||||
if setOrgInactive {
|
||||
deactivateOrgEvent := org.NewOrgDeactivatedEvent(ctx, orgAgg)
|
||||
events = append(events, deactivateOrgEvent)
|
||||
|
||||
@@ -2,8 +2,9 @@ package command
|
||||
|
||||
import (
|
||||
"context"
|
||||
"reflect"
|
||||
"slices"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/api/authz"
|
||||
"github.com/zitadel/zitadel/internal/command/preparation"
|
||||
"github.com/zitadel/zitadel/internal/domain"
|
||||
"github.com/zitadel/zitadel/internal/eventstore"
|
||||
@@ -12,29 +13,22 @@ import (
|
||||
"github.com/zitadel/zitadel/internal/zerrors"
|
||||
)
|
||||
|
||||
func (c *Commands) AddOrgMemberCommand(a *org.Aggregate, userID string, roles ...string) preparation.Validation {
|
||||
func (c *Commands) AddOrgMemberCommand(member *AddOrgMember) preparation.Validation {
|
||||
return func() (preparation.CreateCommands, error) {
|
||||
if userID == "" {
|
||||
return nil, zerrors.ThrowInvalidArgument(nil, "ORG-4Mlfs", "Errors.Invalid.Argument")
|
||||
}
|
||||
if len(roles) == 0 {
|
||||
return nil, zerrors.ThrowInvalidArgument(nil, "V2-PfYhb", "Errors.Invalid.Argument")
|
||||
}
|
||||
|
||||
if len(domain.CheckForInvalidRoles(roles, domain.OrgRolePrefix, c.zitadelRoles)) > 0 && len(domain.CheckForInvalidRoles(roles, domain.RoleSelfManagementGlobal, c.zitadelRoles)) > 0 {
|
||||
return nil, zerrors.ThrowInvalidArgument(nil, "Org-4N8es", "Errors.Org.MemberInvalid")
|
||||
if err := member.IsValid(c.zitadelRoles); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return func(ctx context.Context, filter preparation.FilterToQueryReducer) (_ []eventstore.Command, err error) {
|
||||
ctx, span := tracing.NewSpan(ctx)
|
||||
defer func() { span.EndWithError(err) }()
|
||||
|
||||
if exists, err := ExistsUser(ctx, filter, userID, "", false); err != nil || !exists {
|
||||
if exists, err := ExistsUser(ctx, filter, member.UserID, "", false); err != nil || !exists {
|
||||
return nil, zerrors.ThrowPreconditionFailed(err, "ORG-GoXOn", "Errors.User.NotFound")
|
||||
}
|
||||
if isMember, err := IsOrgMember(ctx, filter, a.ID, userID); err != nil || isMember {
|
||||
if isMember, err := IsOrgMember(ctx, filter, member.OrgID, member.UserID); err != nil || isMember {
|
||||
return nil, zerrors.ThrowAlreadyExists(err, "ORG-poWwe", "Errors.Org.Member.AlreadyExists")
|
||||
}
|
||||
return []eventstore.Command{org.NewMemberAddedEvent(ctx, &a.Aggregate, userID, roles...)}, nil
|
||||
return []eventstore.Command{org.NewMemberAddedEvent(ctx, &org.NewAggregate(member.OrgID).Aggregate, member.UserID, member.Roles...)}, nil
|
||||
},
|
||||
nil
|
||||
}
|
||||
@@ -76,12 +70,33 @@ func IsOrgMember(ctx context.Context, filter preparation.FilterToQueryReducer, o
|
||||
return isMember, nil
|
||||
}
|
||||
|
||||
func (c *Commands) AddOrgMember(ctx context.Context, orgID, userID string, roles ...string) (_ *domain.Member, err error) {
|
||||
type AddOrgMember struct {
|
||||
OrgID string
|
||||
UserID string
|
||||
Roles []string
|
||||
}
|
||||
|
||||
func (m *AddOrgMember) IsValid(zitadelRoles []authz.RoleMapping) error {
|
||||
if m.UserID == "" || m.OrgID == "" || len(m.Roles) == 0 {
|
||||
return zerrors.ThrowInvalidArgument(nil, "ORG-4Mlfs", "Errors.Invalid.Argument")
|
||||
}
|
||||
if len(domain.CheckForInvalidRoles(m.Roles, domain.OrgRolePrefix, zitadelRoles)) > 0 && len(domain.CheckForInvalidRoles(m.Roles, domain.RoleSelfManagementGlobal, zitadelRoles)) > 0 {
|
||||
return zerrors.ThrowInvalidArgument(nil, "Org-4N8es", "Errors.Org.MemberInvalid")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Commands) AddOrgMember(ctx context.Context, member *AddOrgMember) (_ *domain.ObjectDetails, err error) {
|
||||
ctx, span := tracing.NewSpan(ctx)
|
||||
defer func() { span.EndWithError(err) }()
|
||||
|
||||
orgAgg := org.NewAggregate(orgID)
|
||||
cmds, err := preparation.PrepareCommands(ctx, c.eventstore.Filter, c.AddOrgMemberCommand(orgAgg, userID, roles...))
|
||||
if err := c.checkOrgExists(ctx, member.OrgID); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := c.checkPermissionUpdateOrgMember(ctx, member.OrgID, member.OrgID); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
//nolint:staticcheck
|
||||
cmds, err := preparation.PrepareCommands(ctx, c.eventstore.Filter, c.AddOrgMemberCommand(member))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -89,51 +104,59 @@ func (c *Commands) AddOrgMember(ctx context.Context, orgID, userID string, roles
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
addedMember := NewOrgMemberWriteModel(orgID, userID)
|
||||
addedMember := NewOrgMemberWriteModel(member.OrgID, member.UserID)
|
||||
err = AppendAndReduce(addedMember, events...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return memberWriteModelToMember(&addedMember.MemberWriteModel), nil
|
||||
return writeModelToObjectDetails(&addedMember.WriteModel), nil
|
||||
}
|
||||
|
||||
func (c *Commands) addOrgMember(ctx context.Context, orgAgg *eventstore.Aggregate, addedMember *OrgMemberWriteModel, member *domain.Member) (eventstore.Command, error) {
|
||||
if !member.IsValid() {
|
||||
return nil, zerrors.ThrowInvalidArgument(nil, "Org-W8m4l", "Errors.Org.MemberInvalid")
|
||||
type ChangeOrgMember struct {
|
||||
OrgID string
|
||||
UserID string
|
||||
Roles []string
|
||||
}
|
||||
|
||||
func (c *ChangeOrgMember) IsValid(zitadelRoles []authz.RoleMapping) error {
|
||||
if c.OrgID == "" || c.UserID == "" || len(c.Roles) == 0 {
|
||||
return zerrors.ThrowInvalidArgument(nil, "Org-LiaZi", "Errors.Org.MemberInvalid")
|
||||
}
|
||||
if len(domain.CheckForInvalidRoles(member.Roles, domain.OrgRolePrefix, c.zitadelRoles)) > 0 && len(domain.CheckForInvalidRoles(member.Roles, domain.RoleSelfManagementGlobal, c.zitadelRoles)) > 0 {
|
||||
return nil, zerrors.ThrowInvalidArgument(nil, "Org-4N8es", "Errors.Org.MemberInvalid")
|
||||
}
|
||||
err := c.eventstore.FilterToQueryReducer(ctx, addedMember)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if addedMember.State == domain.MemberStateActive {
|
||||
return nil, zerrors.ThrowAlreadyExists(nil, "Org-PtXi1", "Errors.Org.Member.AlreadyExists")
|
||||
if len(domain.CheckForInvalidRoles(c.Roles, domain.OrgRolePrefix, zitadelRoles)) > 0 {
|
||||
return zerrors.ThrowInvalidArgument(nil, "IAM-m9fG8", "Errors.Org.MemberInvalid")
|
||||
}
|
||||
|
||||
return org.NewMemberAddedEvent(ctx, orgAgg, member.UserID, member.Roles...), nil
|
||||
return nil
|
||||
}
|
||||
|
||||
// ChangeOrgMember updates an existing member
|
||||
func (c *Commands) ChangeOrgMember(ctx context.Context, member *domain.Member) (*domain.Member, error) {
|
||||
if !member.IsValid() {
|
||||
return nil, zerrors.ThrowInvalidArgument(nil, "Org-LiaZi", "Errors.Org.MemberInvalid")
|
||||
}
|
||||
if len(domain.CheckForInvalidRoles(member.Roles, domain.OrgRolePrefix, c.zitadelRoles)) > 0 {
|
||||
return nil, zerrors.ThrowInvalidArgument(nil, "IAM-m9fG8", "Errors.Org.MemberInvalid")
|
||||
}
|
||||
|
||||
existingMember, err := c.orgMemberWriteModelByID(ctx, member.AggregateID, member.UserID)
|
||||
if err != nil {
|
||||
func (c *Commands) ChangeOrgMember(ctx context.Context, member *ChangeOrgMember) (*domain.ObjectDetails, error) {
|
||||
if err := member.IsValid(c.zitadelRoles); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if reflect.DeepEqual(existingMember.Roles, member.Roles) {
|
||||
return nil, zerrors.ThrowPreconditionFailed(nil, "Org-LiaZi", "Errors.Org.Member.RolesNotChanged")
|
||||
existingMember, err := c.orgMemberWriteModelByID(ctx, member.OrgID, member.UserID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
orgAgg := OrgAggregateFromWriteModel(&existingMember.MemberWriteModel.WriteModel)
|
||||
pushedEvents, err := c.eventstore.Push(ctx, org.NewMemberChangedEvent(ctx, orgAgg, member.UserID, member.Roles...))
|
||||
if !existingMember.State.Exists() {
|
||||
return nil, zerrors.ThrowNotFound(nil, "Org-D8JxR", "Errors.NotFound")
|
||||
}
|
||||
if err := c.checkPermissionUpdateOrgMember(ctx, existingMember.ResourceOwner, existingMember.AggregateID); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if slices.Compare(existingMember.Roles, member.Roles) == 0 {
|
||||
return writeModelToObjectDetails(&existingMember.WriteModel), nil
|
||||
}
|
||||
|
||||
pushedEvents, err := c.eventstore.Push(ctx,
|
||||
org.NewMemberChangedEvent(ctx,
|
||||
OrgAggregateFromWriteModelWithCTX(ctx, &existingMember.WriteModel),
|
||||
member.UserID,
|
||||
member.Roles...,
|
||||
),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -142,30 +165,39 @@ func (c *Commands) ChangeOrgMember(ctx context.Context, member *domain.Member) (
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return memberWriteModelToMember(&existingMember.MemberWriteModel), nil
|
||||
return writeModelToObjectDetails(&existingMember.WriteModel), nil
|
||||
}
|
||||
|
||||
func (c *Commands) RemoveOrgMember(ctx context.Context, orgID, userID string) (*domain.ObjectDetails, error) {
|
||||
m, err := c.orgMemberWriteModelByID(ctx, orgID, userID)
|
||||
if err != nil && !zerrors.IsNotFound(err) {
|
||||
if orgID == "" || userID == "" {
|
||||
return nil, zerrors.ThrowInvalidArgument(nil, "Org-LiaZi", "Errors.Org.MemberInvalid")
|
||||
}
|
||||
existingMember, err := c.orgMemberWriteModelByID(ctx, orgID, userID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if zerrors.IsNotFound(err) {
|
||||
// empty response because we have no data that match the request
|
||||
return &domain.ObjectDetails{}, nil
|
||||
if !existingMember.State.Exists() {
|
||||
return writeModelToObjectDetails(&existingMember.WriteModel), nil
|
||||
}
|
||||
if err := c.checkPermissionDeleteOrgMember(ctx, existingMember.ResourceOwner, existingMember.AggregateID); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
orgAgg := OrgAggregateFromWriteModel(&m.MemberWriteModel.WriteModel)
|
||||
removeEvent := c.removeOrgMember(ctx, orgAgg, userID, false)
|
||||
pushedEvents, err := c.eventstore.Push(ctx, removeEvent)
|
||||
pushedEvents, err := c.eventstore.Push(ctx,
|
||||
c.removeOrgMember(ctx,
|
||||
OrgAggregateFromWriteModelWithCTX(ctx, &existingMember.WriteModel),
|
||||
userID,
|
||||
false,
|
||||
),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = AppendAndReduce(m, pushedEvents...)
|
||||
err = AppendAndReduce(existingMember, pushedEvents...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return writeModelToObjectDetails(&m.WriteModel), nil
|
||||
return writeModelToObjectDetails(&existingMember.WriteModel), nil
|
||||
}
|
||||
|
||||
func (c *Commands) removeOrgMember(ctx context.Context, orgAgg *eventstore.Aggregate, userID string, cascade bool) eventstore.Command {
|
||||
@@ -189,9 +221,5 @@ func (c *Commands) orgMemberWriteModelByID(ctx context.Context, orgID, userID st
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if writeModel.State == domain.MemberStateUnspecified || writeModel.State == domain.MemberStateRemoved {
|
||||
return nil, zerrors.ThrowNotFound(nil, "Org-D8JxR", "Errors.NotFound")
|
||||
}
|
||||
|
||||
return writeModel, nil
|
||||
}
|
||||
|
||||
+379
-255
@@ -11,24 +11,19 @@ import (
|
||||
"github.com/zitadel/zitadel/internal/command/preparation"
|
||||
"github.com/zitadel/zitadel/internal/domain"
|
||||
"github.com/zitadel/zitadel/internal/eventstore"
|
||||
"github.com/zitadel/zitadel/internal/eventstore/v1/models"
|
||||
"github.com/zitadel/zitadel/internal/repository/org"
|
||||
"github.com/zitadel/zitadel/internal/repository/project"
|
||||
"github.com/zitadel/zitadel/internal/repository/user"
|
||||
"github.com/zitadel/zitadel/internal/zerrors"
|
||||
)
|
||||
|
||||
func TestAddMember(t *testing.T) {
|
||||
type args struct {
|
||||
a *org.Aggregate
|
||||
userID string
|
||||
roles []string
|
||||
member *AddOrgMember
|
||||
zitadelRoles []authz.RoleMapping
|
||||
filter preparation.FilterToQueryReducer
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
agg := org.NewAggregate("test")
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
@@ -38,8 +33,10 @@ func TestAddMember(t *testing.T) {
|
||||
{
|
||||
name: "no user id",
|
||||
args: args{
|
||||
a: agg,
|
||||
userID: "",
|
||||
member: &AddOrgMember{
|
||||
OrgID: "test",
|
||||
UserID: "",
|
||||
},
|
||||
},
|
||||
want: Want{
|
||||
ValidationErr: zerrors.ThrowInvalidArgument(nil, "ORG-4Mlfs", "Errors.Invalid.Argument"),
|
||||
@@ -48,19 +45,23 @@ func TestAddMember(t *testing.T) {
|
||||
{
|
||||
name: "no roles",
|
||||
args: args{
|
||||
a: agg,
|
||||
userID: "12342",
|
||||
member: &AddOrgMember{
|
||||
OrgID: "test",
|
||||
UserID: "12342",
|
||||
},
|
||||
},
|
||||
want: Want{
|
||||
ValidationErr: zerrors.ThrowInvalidArgument(nil, "V2-PfYhb", "Errors.Invalid.Argument"),
|
||||
ValidationErr: zerrors.ThrowInvalidArgument(nil, "ORG-4Mlfs", "Errors.Invalid.Argument"),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "TODO: invalid roles",
|
||||
args: args{
|
||||
a: agg,
|
||||
userID: "123",
|
||||
roles: []string{"ORG_OWNER"},
|
||||
member: &AddOrgMember{
|
||||
OrgID: "test",
|
||||
UserID: "12342",
|
||||
Roles: []string{"ORG_OWNER"},
|
||||
},
|
||||
},
|
||||
want: Want{
|
||||
ValidationErr: zerrors.ThrowInvalidArgument(nil, "Org-4N8es", ""),
|
||||
@@ -69,9 +70,11 @@ func TestAddMember(t *testing.T) {
|
||||
{
|
||||
name: "user not exists",
|
||||
args: args{
|
||||
a: agg,
|
||||
userID: "userID",
|
||||
roles: []string{"ORG_OWNER"},
|
||||
member: &AddOrgMember{
|
||||
OrgID: "test",
|
||||
UserID: "userID",
|
||||
Roles: []string{"ORG_OWNER"},
|
||||
},
|
||||
zitadelRoles: []authz.RoleMapping{
|
||||
{
|
||||
Role: "ORG_OWNER",
|
||||
@@ -89,9 +92,11 @@ func TestAddMember(t *testing.T) {
|
||||
{
|
||||
name: "already member",
|
||||
args: args{
|
||||
a: agg,
|
||||
userID: "userID",
|
||||
roles: []string{"ORG_OWNER"},
|
||||
member: &AddOrgMember{
|
||||
OrgID: "test",
|
||||
UserID: "userID",
|
||||
Roles: []string{"ORG_OWNER"},
|
||||
},
|
||||
zitadelRoles: []authz.RoleMapping{
|
||||
{
|
||||
Role: "ORG_OWNER",
|
||||
@@ -129,9 +134,11 @@ func TestAddMember(t *testing.T) {
|
||||
{
|
||||
name: "correct",
|
||||
args: args{
|
||||
a: agg,
|
||||
userID: "userID",
|
||||
roles: []string{"ORG_OWNER"},
|
||||
member: &AddOrgMember{
|
||||
OrgID: "test",
|
||||
UserID: "userID",
|
||||
Roles: []string{"ORG_OWNER"},
|
||||
},
|
||||
zitadelRoles: []authz.RoleMapping{
|
||||
{
|
||||
Role: "ORG_OWNER",
|
||||
@@ -158,14 +165,14 @@ func TestAddMember(t *testing.T) {
|
||||
},
|
||||
want: Want{
|
||||
Commands: []eventstore.Command{
|
||||
org.NewMemberAddedEvent(ctx, &agg.Aggregate, "userID", "ORG_OWNER"),
|
||||
org.NewMemberAddedEvent(ctx, &org.NewAggregate("test").Aggregate, "userID", "ORG_OWNER"),
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
AssertValidation(t, context.Background(), (&Commands{zitadelRoles: tt.args.zitadelRoles}).AddOrgMemberCommand(tt.args.a, tt.args.userID, tt.args.roles...), tt.args.filter, tt.want)
|
||||
AssertValidation(t, context.Background(), (&Commands{zitadelRoles: tt.args.zitadelRoles}).AddOrgMemberCommand(tt.args.member), tt.args.filter, tt.want)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -287,17 +294,15 @@ func TestIsMember(t *testing.T) {
|
||||
|
||||
func TestCommandSide_AddOrgMember(t *testing.T) {
|
||||
type fields struct {
|
||||
eventstore *eventstore.Eventstore
|
||||
zitadelRoles []authz.RoleMapping
|
||||
checkPermission domain.PermissionCheck
|
||||
eventstore func(t *testing.T) *eventstore.Eventstore
|
||||
zitadelRoles []authz.RoleMapping
|
||||
}
|
||||
type args struct {
|
||||
ctx context.Context
|
||||
userID string
|
||||
orgID string
|
||||
roles []string
|
||||
member *AddOrgMember
|
||||
}
|
||||
type res struct {
|
||||
want *domain.Member
|
||||
want *domain.ObjectDetails
|
||||
err func(error) bool
|
||||
}
|
||||
tests := []struct {
|
||||
@@ -309,13 +314,22 @@ func TestCommandSide_AddOrgMember(t *testing.T) {
|
||||
{
|
||||
name: "invalid member, error",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
org.NewOrgAddedEvent(context.Background(),
|
||||
&org.NewAggregate("org1").Aggregate,
|
||||
"org",
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
checkPermission: newMockPermissionCheckAllowed(),
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
orgID: "org1",
|
||||
member: &AddOrgMember{
|
||||
OrgID: "org1",
|
||||
},
|
||||
},
|
||||
res: res{
|
||||
err: zerrors.IsErrorInvalidArgument,
|
||||
@@ -324,15 +338,24 @@ func TestCommandSide_AddOrgMember(t *testing.T) {
|
||||
{
|
||||
name: "invalid roles, error",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
org.NewOrgAddedEvent(context.Background(),
|
||||
&org.NewAggregate("org1").Aggregate,
|
||||
"org",
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
checkPermission: newMockPermissionCheckAllowed(),
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
orgID: "org1",
|
||||
userID: "user1",
|
||||
roles: []string{"ORG_OWNER"},
|
||||
member: &AddOrgMember{
|
||||
OrgID: "org1",
|
||||
UserID: "user1",
|
||||
Roles: []string{"ORG_OWNER"},
|
||||
},
|
||||
},
|
||||
res: res{
|
||||
err: zerrors.IsErrorInvalidArgument,
|
||||
@@ -341,10 +364,18 @@ func TestCommandSide_AddOrgMember(t *testing.T) {
|
||||
{
|
||||
name: "user not existing, precondition error",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
org.NewOrgAddedEvent(context.Background(),
|
||||
&org.NewAggregate("org1").Aggregate,
|
||||
"org",
|
||||
),
|
||||
),
|
||||
),
|
||||
expectFilter(),
|
||||
),
|
||||
checkPermission: newMockPermissionCheckAllowed(),
|
||||
zitadelRoles: []authz.RoleMapping{
|
||||
{
|
||||
Role: domain.RoleOrgOwner,
|
||||
@@ -352,10 +383,11 @@ func TestCommandSide_AddOrgMember(t *testing.T) {
|
||||
},
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
orgID: "org1",
|
||||
userID: "user1",
|
||||
roles: []string{domain.RoleOrgOwner},
|
||||
member: &AddOrgMember{
|
||||
OrgID: "org1",
|
||||
UserID: "user1",
|
||||
Roles: []string{"ORG_OWNER"},
|
||||
},
|
||||
},
|
||||
res: res{
|
||||
err: zerrors.IsPreconditionFailed,
|
||||
@@ -364,8 +396,15 @@ func TestCommandSide_AddOrgMember(t *testing.T) {
|
||||
{
|
||||
name: "member already exists, precondition error",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
org.NewOrgAddedEvent(context.Background(),
|
||||
&org.NewAggregate("org1").Aggregate,
|
||||
"org",
|
||||
),
|
||||
),
|
||||
),
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
user.NewHumanAddedEvent(context.Background(),
|
||||
@@ -391,6 +430,7 @@ func TestCommandSide_AddOrgMember(t *testing.T) {
|
||||
),
|
||||
),
|
||||
),
|
||||
checkPermission: newMockPermissionCheckAllowed(),
|
||||
zitadelRoles: []authz.RoleMapping{
|
||||
{
|
||||
Role: domain.RoleOrgOwner,
|
||||
@@ -398,10 +438,11 @@ func TestCommandSide_AddOrgMember(t *testing.T) {
|
||||
},
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
orgID: "org1",
|
||||
userID: "user1",
|
||||
roles: []string{"ORG_OWNER"},
|
||||
member: &AddOrgMember{
|
||||
OrgID: "org1",
|
||||
UserID: "user1",
|
||||
Roles: []string{"ORG_OWNER"},
|
||||
},
|
||||
},
|
||||
res: res{
|
||||
err: zerrors.IsErrorAlreadyExists,
|
||||
@@ -410,8 +451,15 @@ func TestCommandSide_AddOrgMember(t *testing.T) {
|
||||
{
|
||||
name: "member add uniqueconstraint err, already exists",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
org.NewOrgAddedEvent(context.Background(),
|
||||
&org.NewAggregate("org1").Aggregate,
|
||||
"org",
|
||||
),
|
||||
),
|
||||
),
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
user.NewHumanAddedEvent(context.Background(),
|
||||
@@ -437,6 +485,7 @@ func TestCommandSide_AddOrgMember(t *testing.T) {
|
||||
),
|
||||
),
|
||||
),
|
||||
checkPermission: newMockPermissionCheckAllowed(),
|
||||
zitadelRoles: []authz.RoleMapping{
|
||||
{
|
||||
Role: domain.RoleOrgOwner,
|
||||
@@ -444,10 +493,11 @@ func TestCommandSide_AddOrgMember(t *testing.T) {
|
||||
},
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
orgID: "org1",
|
||||
userID: "user1",
|
||||
roles: []string{"ORG_OWNER"},
|
||||
member: &AddOrgMember{
|
||||
OrgID: "org1",
|
||||
UserID: "user1",
|
||||
Roles: []string{"ORG_OWNER"},
|
||||
},
|
||||
},
|
||||
res: res{
|
||||
err: zerrors.IsErrorAlreadyExists,
|
||||
@@ -456,8 +506,15 @@ func TestCommandSide_AddOrgMember(t *testing.T) {
|
||||
{
|
||||
name: "member add, ok",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
org.NewOrgAddedEvent(context.Background(),
|
||||
&org.NewAggregate("org1").Aggregate,
|
||||
"org",
|
||||
),
|
||||
),
|
||||
),
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
user.NewHumanAddedEvent(context.Background(),
|
||||
@@ -483,6 +540,7 @@ func TestCommandSide_AddOrgMember(t *testing.T) {
|
||||
),
|
||||
),
|
||||
),
|
||||
checkPermission: newMockPermissionCheckAllowed(),
|
||||
zitadelRoles: []authz.RoleMapping{
|
||||
{
|
||||
Role: domain.RoleOrgOwner,
|
||||
@@ -490,30 +548,58 @@ func TestCommandSide_AddOrgMember(t *testing.T) {
|
||||
},
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
orgID: "org1",
|
||||
userID: "user1",
|
||||
roles: []string{"ORG_OWNER"},
|
||||
member: &AddOrgMember{
|
||||
OrgID: "org1",
|
||||
UserID: "user1",
|
||||
Roles: []string{"ORG_OWNER"},
|
||||
},
|
||||
},
|
||||
res: res{
|
||||
want: &domain.Member{
|
||||
ObjectRoot: models.ObjectRoot{
|
||||
ResourceOwner: "org1",
|
||||
AggregateID: "org1",
|
||||
},
|
||||
UserID: "user1",
|
||||
Roles: []string{domain.RoleOrgOwner},
|
||||
want: &domain.ObjectDetails{
|
||||
ResourceOwner: "org1",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "member add, no permission",
|
||||
fields: fields{
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
org.NewOrgAddedEvent(context.Background(),
|
||||
&org.NewAggregate("org1").Aggregate,
|
||||
"org",
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
checkPermission: newMockPermissionCheckNotAllowed(),
|
||||
zitadelRoles: []authz.RoleMapping{
|
||||
{
|
||||
Role: domain.RoleOrgOwner,
|
||||
},
|
||||
},
|
||||
},
|
||||
args: args{
|
||||
member: &AddOrgMember{
|
||||
OrgID: "org1",
|
||||
UserID: "user1",
|
||||
Roles: []string{"ORG_OWNER"},
|
||||
},
|
||||
},
|
||||
res: res{
|
||||
err: zerrors.IsPermissionDenied,
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
r := &Commands{
|
||||
eventstore: tt.fields.eventstore,
|
||||
zitadelRoles: tt.fields.zitadelRoles,
|
||||
eventstore: tt.fields.eventstore(t),
|
||||
zitadelRoles: tt.fields.zitadelRoles,
|
||||
checkPermission: tt.fields.checkPermission,
|
||||
}
|
||||
got, err := r.AddOrgMember(tt.args.ctx, tt.args.orgID, tt.args.userID, tt.args.roles...)
|
||||
got, err := r.AddOrgMember(context.Background(), tt.args.member)
|
||||
if tt.res.err == nil {
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
@@ -521,7 +607,7 @@ func TestCommandSide_AddOrgMember(t *testing.T) {
|
||||
t.Errorf("got wrong err: %v ", err)
|
||||
}
|
||||
if tt.res.err == nil {
|
||||
assert.Equal(t, tt.res.want, got)
|
||||
assertObjectDetails(t, tt.res.want, got)
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -529,15 +615,15 @@ func TestCommandSide_AddOrgMember(t *testing.T) {
|
||||
|
||||
func TestCommandSide_ChangeOrgMember(t *testing.T) {
|
||||
type fields struct {
|
||||
eventstore *eventstore.Eventstore
|
||||
zitadelRoles []authz.RoleMapping
|
||||
checkPermission domain.PermissionCheck
|
||||
eventstore func(t *testing.T) *eventstore.Eventstore
|
||||
zitadelRoles []authz.RoleMapping
|
||||
}
|
||||
type args struct {
|
||||
ctx context.Context
|
||||
member *domain.Member
|
||||
member *ChangeOrgMember
|
||||
}
|
||||
type res struct {
|
||||
want *domain.Member
|
||||
want *domain.ObjectDetails
|
||||
err func(error) bool
|
||||
}
|
||||
tests := []struct {
|
||||
@@ -549,16 +635,12 @@ func TestCommandSide_ChangeOrgMember(t *testing.T) {
|
||||
{
|
||||
name: "invalid member, error",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
),
|
||||
eventstore: expectEventstore(),
|
||||
checkPermission: newMockPermissionCheckAllowed(),
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
member: &domain.Member{
|
||||
ObjectRoot: models.ObjectRoot{
|
||||
AggregateID: "org1",
|
||||
},
|
||||
member: &ChangeOrgMember{
|
||||
OrgID: "org1",
|
||||
},
|
||||
},
|
||||
res: res{
|
||||
@@ -568,16 +650,12 @@ func TestCommandSide_ChangeOrgMember(t *testing.T) {
|
||||
{
|
||||
name: "invalid roles, error",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
),
|
||||
eventstore: expectEventstore(),
|
||||
checkPermission: newMockPermissionCheckAllowed(),
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
member: &domain.Member{
|
||||
ObjectRoot: models.ObjectRoot{
|
||||
AggregateID: "org1",
|
||||
},
|
||||
member: &ChangeOrgMember{
|
||||
OrgID: "org1",
|
||||
UserID: "user1",
|
||||
Roles: []string{"PROJECT_OWNER"},
|
||||
},
|
||||
@@ -589,10 +667,10 @@ func TestCommandSide_ChangeOrgMember(t *testing.T) {
|
||||
{
|
||||
name: "member not existing, not found error",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(),
|
||||
),
|
||||
checkPermission: newMockPermissionCheckAllowed(),
|
||||
zitadelRoles: []authz.RoleMapping{
|
||||
{
|
||||
Role: domain.RoleOrgOwner,
|
||||
@@ -600,11 +678,8 @@ func TestCommandSide_ChangeOrgMember(t *testing.T) {
|
||||
},
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
member: &domain.Member{
|
||||
ObjectRoot: models.ObjectRoot{
|
||||
AggregateID: "org1",
|
||||
},
|
||||
member: &ChangeOrgMember{
|
||||
OrgID: "org1",
|
||||
UserID: "user1",
|
||||
Roles: []string{"ORG_OWNER"},
|
||||
},
|
||||
@@ -614,10 +689,9 @@ func TestCommandSide_ChangeOrgMember(t *testing.T) {
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "member not changed, precondition error",
|
||||
name: "member not changed, no change",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
org.NewMemberAddedEvent(context.Background(),
|
||||
@@ -628,6 +702,7 @@ func TestCommandSide_ChangeOrgMember(t *testing.T) {
|
||||
),
|
||||
),
|
||||
),
|
||||
checkPermission: newMockPermissionCheckAllowed(),
|
||||
zitadelRoles: []authz.RoleMapping{
|
||||
{
|
||||
Role: domain.RoleOrgOwner,
|
||||
@@ -635,24 +710,22 @@ func TestCommandSide_ChangeOrgMember(t *testing.T) {
|
||||
},
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
member: &domain.Member{
|
||||
ObjectRoot: models.ObjectRoot{
|
||||
AggregateID: "org1",
|
||||
},
|
||||
member: &ChangeOrgMember{
|
||||
OrgID: "org1",
|
||||
UserID: "user1",
|
||||
Roles: []string{"ORG_OWNER"},
|
||||
},
|
||||
},
|
||||
res: res{
|
||||
err: zerrors.IsPreconditionFailed,
|
||||
want: &domain.ObjectDetails{
|
||||
ResourceOwner: "org1",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "member change, ok",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
org.NewMemberAddedEvent(context.Background(),
|
||||
@@ -670,6 +743,7 @@ func TestCommandSide_ChangeOrgMember(t *testing.T) {
|
||||
),
|
||||
),
|
||||
),
|
||||
checkPermission: newMockPermissionCheckAllowed(),
|
||||
zitadelRoles: []authz.RoleMapping{
|
||||
{
|
||||
Role: "ORG_OWNER",
|
||||
@@ -680,160 +754,210 @@ func TestCommandSide_ChangeOrgMember(t *testing.T) {
|
||||
},
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
member: &domain.Member{
|
||||
ObjectRoot: models.ObjectRoot{
|
||||
AggregateID: "org1",
|
||||
},
|
||||
member: &ChangeOrgMember{
|
||||
OrgID: "org1",
|
||||
UserID: "user1",
|
||||
Roles: []string{"ORG_OWNER", "ORG_OWNER_VIEWER"},
|
||||
},
|
||||
},
|
||||
res: res{
|
||||
want: &domain.Member{
|
||||
ObjectRoot: models.ObjectRoot{
|
||||
ResourceOwner: "org1",
|
||||
AggregateID: "org1",
|
||||
},
|
||||
UserID: "user1",
|
||||
Roles: []string{"ORG_OWNER", "ORG_OWNER_VIEWER"},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
r := &Commands{
|
||||
eventstore: tt.fields.eventstore,
|
||||
zitadelRoles: tt.fields.zitadelRoles,
|
||||
}
|
||||
got, err := r.ChangeOrgMember(tt.args.ctx, tt.args.member)
|
||||
if tt.res.err == nil {
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
if tt.res.err != nil && !tt.res.err(err) {
|
||||
t.Errorf("got wrong err: %v ", err)
|
||||
}
|
||||
if tt.res.err == nil {
|
||||
assert.Equal(t, tt.res.want, got)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCommandSide_RemoveOrgMember(t *testing.T) {
|
||||
type fields struct {
|
||||
eventstore *eventstore.Eventstore
|
||||
}
|
||||
type args struct {
|
||||
ctx context.Context
|
||||
projectID string
|
||||
userID string
|
||||
resourceOwner string
|
||||
}
|
||||
type res struct {
|
||||
want *domain.ObjectDetails
|
||||
err func(error) bool
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
fields fields
|
||||
args args
|
||||
res res
|
||||
}{
|
||||
{
|
||||
name: "invalid member projectid missing, error",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
),
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
projectID: "",
|
||||
userID: "user1",
|
||||
resourceOwner: "org1",
|
||||
},
|
||||
res: res{
|
||||
err: zerrors.IsErrorInvalidArgument,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "invalid member userid missing, error",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
),
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
projectID: "project1",
|
||||
userID: "",
|
||||
resourceOwner: "org1",
|
||||
},
|
||||
res: res{
|
||||
err: zerrors.IsErrorInvalidArgument,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "member not existing, empty object details result",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
expectFilter(),
|
||||
),
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
projectID: "project1",
|
||||
userID: "user1",
|
||||
resourceOwner: "org1",
|
||||
},
|
||||
res: res{
|
||||
want: &domain.ObjectDetails{},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "member remove, ok",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
project.NewProjectMemberAddedEvent(context.Background(),
|
||||
&project.NewAggregate("project1", "org1").Aggregate,
|
||||
"user1",
|
||||
[]string{"PROJECT_OWNER"}...,
|
||||
),
|
||||
),
|
||||
),
|
||||
expectPush(
|
||||
project.NewProjectMemberRemovedEvent(context.Background(),
|
||||
&project.NewAggregate("project1", "org1").Aggregate,
|
||||
"user1",
|
||||
),
|
||||
),
|
||||
),
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
projectID: "project1",
|
||||
userID: "user1",
|
||||
resourceOwner: "org1",
|
||||
},
|
||||
res: res{
|
||||
want: &domain.ObjectDetails{
|
||||
ResourceOwner: "org1",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "member change, no permission",
|
||||
fields: fields{
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
org.NewMemberAddedEvent(context.Background(),
|
||||
&org.NewAggregate("org1").Aggregate,
|
||||
"user1",
|
||||
[]string{"ORG_OWNER"}...,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
checkPermission: newMockPermissionCheckNotAllowed(),
|
||||
zitadelRoles: []authz.RoleMapping{
|
||||
{
|
||||
Role: "ORG_OWNER",
|
||||
},
|
||||
{
|
||||
Role: "ORG_OWNER_VIEWER",
|
||||
},
|
||||
},
|
||||
},
|
||||
args: args{
|
||||
member: &ChangeOrgMember{
|
||||
OrgID: "org1",
|
||||
UserID: "user1",
|
||||
Roles: []string{"ORG_OWNER", "ORG_OWNER_VIEWER"},
|
||||
},
|
||||
},
|
||||
res: res{
|
||||
err: zerrors.IsPermissionDenied,
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
r := &Commands{
|
||||
eventstore: tt.fields.eventstore,
|
||||
eventstore: tt.fields.eventstore(t),
|
||||
zitadelRoles: tt.fields.zitadelRoles,
|
||||
checkPermission: tt.fields.checkPermission,
|
||||
}
|
||||
got, err := r.RemoveProjectMember(tt.args.ctx, tt.args.projectID, tt.args.userID, tt.args.resourceOwner)
|
||||
got, err := r.ChangeOrgMember(context.Background(), tt.args.member)
|
||||
if tt.res.err == nil {
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
if tt.res.err != nil && !tt.res.err(err) {
|
||||
t.Errorf("got wrong err: %v ", err)
|
||||
}
|
||||
if tt.res.err == nil {
|
||||
assertObjectDetails(t, tt.res.want, got)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCommandSide_RemoveOrgMember(t *testing.T) {
|
||||
type fields struct {
|
||||
eventstore func(t *testing.T) *eventstore.Eventstore
|
||||
checkPermission domain.PermissionCheck
|
||||
}
|
||||
type args struct {
|
||||
ctx context.Context
|
||||
orgID string
|
||||
userID string
|
||||
}
|
||||
type res struct {
|
||||
want *domain.ObjectDetails
|
||||
err func(error) bool
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
fields fields
|
||||
args args
|
||||
res res
|
||||
}{
|
||||
{
|
||||
name: "invalid member orgID missing, error",
|
||||
fields: fields{
|
||||
eventstore: expectEventstore(),
|
||||
checkPermission: newMockPermissionCheckAllowed(),
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
orgID: "",
|
||||
userID: "user1",
|
||||
},
|
||||
res: res{
|
||||
err: zerrors.IsErrorInvalidArgument,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "invalid member userid missing, error",
|
||||
fields: fields{
|
||||
eventstore: expectEventstore(),
|
||||
checkPermission: newMockPermissionCheckAllowed(),
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
orgID: "org1",
|
||||
userID: "",
|
||||
},
|
||||
res: res{
|
||||
err: zerrors.IsErrorInvalidArgument,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "member not existing, empty object details result",
|
||||
fields: fields{
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(),
|
||||
),
|
||||
checkPermission: newMockPermissionCheckAllowed(),
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
orgID: "org1",
|
||||
userID: "user1",
|
||||
},
|
||||
res: res{
|
||||
want: &domain.ObjectDetails{
|
||||
ResourceOwner: "org1",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "member remove, ok",
|
||||
fields: fields{
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
org.NewMemberAddedEvent(context.Background(),
|
||||
&org.NewAggregate("org1").Aggregate,
|
||||
"user1",
|
||||
[]string{"PROJECT_OWNER"}...,
|
||||
),
|
||||
),
|
||||
),
|
||||
expectPush(
|
||||
org.NewMemberRemovedEvent(context.Background(),
|
||||
&org.NewAggregate("org1").Aggregate,
|
||||
"user1",
|
||||
),
|
||||
),
|
||||
),
|
||||
checkPermission: newMockPermissionCheckAllowed(),
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
orgID: "org1",
|
||||
userID: "user1",
|
||||
},
|
||||
res: res{
|
||||
want: &domain.ObjectDetails{
|
||||
ResourceOwner: "org1",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "member remove, no permission",
|
||||
fields: fields{
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
org.NewMemberAddedEvent(context.Background(),
|
||||
&org.NewAggregate("org1").Aggregate,
|
||||
"user1",
|
||||
[]string{"PROJECT_OWNER"}...,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
checkPermission: newMockPermissionCheckNotAllowed(),
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
orgID: "org1",
|
||||
userID: "user1",
|
||||
},
|
||||
res: res{
|
||||
err: zerrors.IsPermissionDenied,
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
r := &Commands{
|
||||
eventstore: tt.fields.eventstore(t),
|
||||
checkPermission: tt.fields.checkPermission,
|
||||
}
|
||||
got, err := r.RemoveOrgMember(tt.args.ctx, tt.args.orgID, tt.args.userID)
|
||||
if tt.res.err == nil {
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
package command
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/domain"
|
||||
"github.com/zitadel/zitadel/internal/eventstore"
|
||||
"github.com/zitadel/zitadel/internal/repository/org"
|
||||
@@ -63,3 +65,7 @@ func (wm *OrgWriteModel) Query() *eventstore.SearchQueryBuilder {
|
||||
func OrgAggregateFromWriteModel(wm *eventstore.WriteModel) *eventstore.Aggregate {
|
||||
return eventstore.AggregateFromWriteModel(wm, org.AggregateType, org.AggregateVersion)
|
||||
}
|
||||
|
||||
func OrgAggregateFromWriteModelWithCTX(ctx context.Context, wm *eventstore.WriteModel) *eventstore.Aggregate {
|
||||
return org.AggregateFromWriteModel(ctx, wm)
|
||||
}
|
||||
|
||||
@@ -73,7 +73,7 @@ func TestAddOrg(t *testing.T) {
|
||||
|
||||
func TestCommandSide_AddOrg(t *testing.T) {
|
||||
type fields struct {
|
||||
eventstore *eventstore.Eventstore
|
||||
eventstore func(t *testing.T) *eventstore.Eventstore
|
||||
idGenerator id.Generator
|
||||
zitadelRoles []authz.RoleMapping
|
||||
}
|
||||
@@ -97,9 +97,7 @@ func TestCommandSide_AddOrg(t *testing.T) {
|
||||
{
|
||||
name: "invalid org, error",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
),
|
||||
eventstore: expectEventstore(),
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
@@ -113,9 +111,7 @@ func TestCommandSide_AddOrg(t *testing.T) {
|
||||
{
|
||||
name: "invalid org (spaces), error",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
),
|
||||
eventstore: expectEventstore(),
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
@@ -130,8 +126,7 @@ func TestCommandSide_AddOrg(t *testing.T) {
|
||||
{
|
||||
name: "user removed, error",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
eventstore: expectEventstore(
|
||||
expectFilterOrgDomainNotFound(),
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
@@ -174,8 +169,7 @@ func TestCommandSide_AddOrg(t *testing.T) {
|
||||
{
|
||||
name: "push failed unique constraint, error",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
eventstore: expectEventstore(
|
||||
expectFilterOrgDomainNotFound(),
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
@@ -193,7 +187,6 @@ func TestCommandSide_AddOrg(t *testing.T) {
|
||||
),
|
||||
),
|
||||
),
|
||||
expectFilterOrgMemberNotFound(),
|
||||
expectPushFailed(zerrors.ThrowAlreadyExists(nil, "id", "internal"),
|
||||
org.NewOrgAddedEvent(
|
||||
context.Background(),
|
||||
@@ -242,8 +235,7 @@ func TestCommandSide_AddOrg(t *testing.T) {
|
||||
{
|
||||
name: "push failed, error",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
eventstore: expectEventstore(
|
||||
expectFilterOrgDomainNotFound(),
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
@@ -261,7 +253,6 @@ func TestCommandSide_AddOrg(t *testing.T) {
|
||||
),
|
||||
),
|
||||
),
|
||||
expectFilterOrgMemberNotFound(),
|
||||
expectPushFailed(zerrors.ThrowInternal(nil, "id", "internal"),
|
||||
org.NewOrgAddedEvent(
|
||||
context.Background(),
|
||||
@@ -310,8 +301,7 @@ func TestCommandSide_AddOrg(t *testing.T) {
|
||||
{
|
||||
name: "add org, no error",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
eventstore: expectEventstore(
|
||||
expectFilterOrgDomainNotFound(),
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
@@ -329,7 +319,6 @@ func TestCommandSide_AddOrg(t *testing.T) {
|
||||
),
|
||||
),
|
||||
),
|
||||
expectFilterOrgMemberNotFound(),
|
||||
expectPush(
|
||||
org.NewOrgAddedEvent(context.Background(),
|
||||
&org.NewAggregate("org2").Aggregate,
|
||||
@@ -381,8 +370,7 @@ func TestCommandSide_AddOrg(t *testing.T) {
|
||||
{
|
||||
name: "add org (remove spaces), no error",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
eventstore: expectEventstore(
|
||||
expectFilterOrgDomainNotFound(),
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
@@ -400,7 +388,6 @@ func TestCommandSide_AddOrg(t *testing.T) {
|
||||
),
|
||||
),
|
||||
),
|
||||
expectFilterOrgMemberNotFound(),
|
||||
expectPush(
|
||||
org.NewOrgAddedEvent(context.Background(),
|
||||
&org.NewAggregate("org2").Aggregate,
|
||||
@@ -453,7 +440,7 @@ func TestCommandSide_AddOrg(t *testing.T) {
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
r := &Commands{
|
||||
eventstore: tt.fields.eventstore,
|
||||
eventstore: tt.fields.eventstore(t),
|
||||
idGenerator: tt.fields.idGenerator,
|
||||
zitadelRoles: tt.fields.zitadelRoles,
|
||||
}
|
||||
@@ -473,7 +460,7 @@ func TestCommandSide_AddOrg(t *testing.T) {
|
||||
|
||||
func TestCommandSide_ChangeOrg(t *testing.T) {
|
||||
type fields struct {
|
||||
eventstore *eventstore.Eventstore
|
||||
eventstore func(t *testing.T) *eventstore.Eventstore
|
||||
}
|
||||
type args struct {
|
||||
ctx context.Context
|
||||
@@ -492,9 +479,7 @@ func TestCommandSide_ChangeOrg(t *testing.T) {
|
||||
{
|
||||
name: "empty name, invalid argument error",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
),
|
||||
eventstore: expectEventstore(),
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
@@ -507,9 +492,7 @@ func TestCommandSide_ChangeOrg(t *testing.T) {
|
||||
{
|
||||
name: "empty name (spaces), invalid argument error",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
),
|
||||
eventstore: expectEventstore(),
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
@@ -523,8 +506,7 @@ func TestCommandSide_ChangeOrg(t *testing.T) {
|
||||
{
|
||||
name: "org not found, error",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(),
|
||||
),
|
||||
},
|
||||
@@ -540,8 +522,7 @@ func TestCommandSide_ChangeOrg(t *testing.T) {
|
||||
{
|
||||
name: "no change (spaces), error",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
org.NewOrgAddedEvent(context.Background(),
|
||||
@@ -563,8 +544,7 @@ func TestCommandSide_ChangeOrg(t *testing.T) {
|
||||
{
|
||||
name: "push failed, error",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
org.NewOrgAddedEvent(context.Background(),
|
||||
@@ -593,8 +573,7 @@ func TestCommandSide_ChangeOrg(t *testing.T) {
|
||||
{
|
||||
name: "change org name verified, not primary",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
org.NewOrgAddedEvent(context.Background(),
|
||||
@@ -645,8 +624,7 @@ func TestCommandSide_ChangeOrg(t *testing.T) {
|
||||
{
|
||||
name: "change org name verified, with primary",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
org.NewOrgAddedEvent(context.Background(),
|
||||
@@ -705,8 +683,7 @@ func TestCommandSide_ChangeOrg(t *testing.T) {
|
||||
{
|
||||
name: "change org name case verified, with primary",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
org.NewOrgAddedEvent(context.Background(),
|
||||
@@ -754,7 +731,7 @@ func TestCommandSide_ChangeOrg(t *testing.T) {
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
r := &Commands{
|
||||
eventstore: tt.fields.eventstore,
|
||||
eventstore: tt.fields.eventstore(t),
|
||||
}
|
||||
_, err := r.ChangeOrg(tt.args.ctx, tt.args.orgID, tt.args.name)
|
||||
if tt.res.err == nil {
|
||||
@@ -769,7 +746,7 @@ func TestCommandSide_ChangeOrg(t *testing.T) {
|
||||
|
||||
func TestCommandSide_DeactivateOrg(t *testing.T) {
|
||||
type fields struct {
|
||||
eventstore *eventstore.Eventstore
|
||||
eventstore func(t *testing.T) *eventstore.Eventstore
|
||||
idGenerator id.Generator
|
||||
iamDomain string
|
||||
}
|
||||
@@ -790,8 +767,7 @@ func TestCommandSide_DeactivateOrg(t *testing.T) {
|
||||
{
|
||||
name: "org not found, error",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(),
|
||||
),
|
||||
},
|
||||
@@ -806,8 +782,7 @@ func TestCommandSide_DeactivateOrg(t *testing.T) {
|
||||
{
|
||||
name: "org already inactive, error",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
org.NewOrgAddedEvent(context.Background(),
|
||||
@@ -832,8 +807,7 @@ func TestCommandSide_DeactivateOrg(t *testing.T) {
|
||||
{
|
||||
name: "push failed, error",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
org.NewOrgAddedEvent(context.Background(),
|
||||
@@ -860,8 +834,7 @@ func TestCommandSide_DeactivateOrg(t *testing.T) {
|
||||
{
|
||||
name: "deactivate org",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
org.NewOrgAddedEvent(context.Background(),
|
||||
@@ -886,7 +859,7 @@ func TestCommandSide_DeactivateOrg(t *testing.T) {
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
r := &Commands{
|
||||
eventstore: tt.fields.eventstore,
|
||||
eventstore: tt.fields.eventstore(t),
|
||||
idGenerator: tt.fields.idGenerator,
|
||||
}
|
||||
_, err := r.DeactivateOrg(tt.args.ctx, tt.args.orgID)
|
||||
@@ -902,7 +875,7 @@ func TestCommandSide_DeactivateOrg(t *testing.T) {
|
||||
|
||||
func TestCommandSide_ReactivateOrg(t *testing.T) {
|
||||
type fields struct {
|
||||
eventstore *eventstore.Eventstore
|
||||
eventstore func(t *testing.T) *eventstore.Eventstore
|
||||
idGenerator id.Generator
|
||||
iamDomain string
|
||||
}
|
||||
@@ -923,8 +896,7 @@ func TestCommandSide_ReactivateOrg(t *testing.T) {
|
||||
{
|
||||
name: "org not found, error",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(),
|
||||
),
|
||||
},
|
||||
@@ -939,8 +911,7 @@ func TestCommandSide_ReactivateOrg(t *testing.T) {
|
||||
{
|
||||
name: "org already active, error",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
org.NewOrgAddedEvent(context.Background(),
|
||||
@@ -961,8 +932,7 @@ func TestCommandSide_ReactivateOrg(t *testing.T) {
|
||||
{
|
||||
name: "push failed, error",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
org.NewOrgAddedEvent(context.Background(),
|
||||
@@ -994,8 +964,7 @@ func TestCommandSide_ReactivateOrg(t *testing.T) {
|
||||
{
|
||||
name: "reactivate org",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
org.NewOrgAddedEvent(context.Background(),
|
||||
@@ -1024,7 +993,7 @@ func TestCommandSide_ReactivateOrg(t *testing.T) {
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
r := &Commands{
|
||||
eventstore: tt.fields.eventstore,
|
||||
eventstore: tt.fields.eventstore(t),
|
||||
idGenerator: tt.fields.idGenerator,
|
||||
}
|
||||
_, err := r.ReactivateOrg(tt.args.ctx, tt.args.orgID)
|
||||
@@ -1040,7 +1009,7 @@ func TestCommandSide_ReactivateOrg(t *testing.T) {
|
||||
|
||||
func TestCommandSide_RemoveOrg(t *testing.T) {
|
||||
type fields struct {
|
||||
eventstore *eventstore.Eventstore
|
||||
eventstore func(t *testing.T) *eventstore.Eventstore
|
||||
idGenerator id.Generator
|
||||
}
|
||||
type args struct {
|
||||
@@ -1059,9 +1028,7 @@ func TestCommandSide_RemoveOrg(t *testing.T) {
|
||||
{
|
||||
name: "default org, error",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
),
|
||||
eventstore: expectEventstore(),
|
||||
},
|
||||
args: args{
|
||||
ctx: authz.WithInstance(context.Background(), &mockInstance{}),
|
||||
@@ -1074,8 +1041,7 @@ func TestCommandSide_RemoveOrg(t *testing.T) {
|
||||
{
|
||||
name: "zitadel org, error",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
project.NewProjectAddedEvent(context.Background(),
|
||||
@@ -1100,8 +1066,7 @@ func TestCommandSide_RemoveOrg(t *testing.T) {
|
||||
{
|
||||
name: "org not found, error",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(), // zitadel project check
|
||||
expectFilter(),
|
||||
),
|
||||
@@ -1117,8 +1082,7 @@ func TestCommandSide_RemoveOrg(t *testing.T) {
|
||||
{
|
||||
name: "push failed, error",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(), // zitadel project check
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
@@ -1160,8 +1124,7 @@ func TestCommandSide_RemoveOrg(t *testing.T) {
|
||||
{
|
||||
name: "remove org",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(), // zitadel project check
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
@@ -1200,8 +1163,7 @@ func TestCommandSide_RemoveOrg(t *testing.T) {
|
||||
{
|
||||
name: "remove org with usernames and domains",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(), // zitadel project check
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
@@ -1291,7 +1253,7 @@ func TestCommandSide_RemoveOrg(t *testing.T) {
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
r := &Commands{
|
||||
eventstore: tt.fields.eventstore,
|
||||
eventstore: tt.fields.eventstore(t),
|
||||
idGenerator: tt.fields.idGenerator,
|
||||
}
|
||||
_, err := r.RemoveOrg(tt.args.ctx, tt.args.orgID)
|
||||
|
||||
@@ -6,6 +6,8 @@ import (
|
||||
"github.com/zitadel/zitadel/internal/api/authz"
|
||||
"github.com/zitadel/zitadel/internal/domain"
|
||||
"github.com/zitadel/zitadel/internal/eventstore"
|
||||
"github.com/zitadel/zitadel/internal/repository/instance"
|
||||
"github.com/zitadel/zitadel/internal/repository/org"
|
||||
"github.com/zitadel/zitadel/internal/repository/project"
|
||||
"github.com/zitadel/zitadel/internal/v2/user"
|
||||
"github.com/zitadel/zitadel/internal/zerrors"
|
||||
@@ -13,6 +15,8 @@ import (
|
||||
|
||||
type PermissionCheck func(resourceOwner, aggregateID string) error
|
||||
|
||||
type UserGrantPermissionCheck func(projectID, projectGrantID string) PermissionCheck
|
||||
|
||||
func (c *Commands) newPermissionCheck(ctx context.Context, permission string, aggregateType eventstore.AggregateType) PermissionCheck {
|
||||
return func(resourceOwner, aggregateID string) error {
|
||||
if aggregateID == "" {
|
||||
@@ -93,3 +97,62 @@ func (c *Commands) checkPermissionUpdateApplication(ctx context.Context, resourc
|
||||
func (c *Commands) checkPermissionDeleteApp(ctx context.Context, resourceOwner, appID string) error {
|
||||
return c.newPermissionCheck(ctx, domain.PermissionProjectAppDelete, project.AggregateType)(resourceOwner, appID)
|
||||
}
|
||||
|
||||
func (c *Commands) checkPermissionUpdateInstanceMember(ctx context.Context, instanceID string) error {
|
||||
return c.newPermissionCheck(ctx, domain.PermissionInstanceMemberWrite, instance.AggregateType)(instanceID, instanceID)
|
||||
}
|
||||
|
||||
func (c *Commands) checkPermissionDeleteInstanceMember(ctx context.Context, instanceID string) error {
|
||||
return c.newPermissionCheck(ctx, domain.PermissionInstanceMemberDelete, instance.AggregateType)(instanceID, instanceID)
|
||||
}
|
||||
|
||||
func (c *Commands) checkPermissionUpdateOrgMember(ctx context.Context, instanceID, orgID string) error {
|
||||
return c.newPermissionCheck(ctx, domain.PermissionOrgMemberWrite, org.AggregateType)(instanceID, orgID)
|
||||
}
|
||||
func (c *Commands) checkPermissionDeleteOrgMember(ctx context.Context, instanceID, orgID string) error {
|
||||
return c.newPermissionCheck(ctx, domain.PermissionOrgMemberDelete, org.AggregateType)(instanceID, orgID)
|
||||
}
|
||||
|
||||
func (c *Commands) checkPermissionUpdateProjectMember(ctx context.Context, resourceOwner, projectID string) error {
|
||||
return c.newPermissionCheck(ctx, domain.PermissionProjectMemberWrite, project.AggregateType)(resourceOwner, projectID)
|
||||
}
|
||||
|
||||
func (c *Commands) checkPermissionDeleteProjectMember(ctx context.Context, resourceOwner, projectID string) error {
|
||||
return c.newPermissionCheck(ctx, domain.PermissionProjectMemberDelete, project.AggregateType)(resourceOwner, projectID)
|
||||
}
|
||||
|
||||
func (c *Commands) checkPermissionUpdateProjectGrantMember(ctx context.Context, grantedOrgID, projectGrantID string) (err error) {
|
||||
// TODO: add permission check for project grant owners
|
||||
//if err := c.newPermissionCheck(ctx, domain.PermissionProjectGrantMemberWrite, project.AggregateType)(resourceOwner, projectGrantID); err != nil {
|
||||
return c.newPermissionCheck(ctx, domain.PermissionProjectGrantMemberWrite, project.AggregateType)(grantedOrgID, projectGrantID)
|
||||
//}
|
||||
//return nil
|
||||
}
|
||||
|
||||
func (c *Commands) checkPermissionDeleteProjectGrantMember(ctx context.Context, grantedOrgID, projectGrantID string) (err error) {
|
||||
// TODO: add permission check for project grant owners
|
||||
//if err := c.newPermissionCheck(ctx, domain.PermissionProjectGrantMemberDelete, project.AggregateType)(resourceOwner, projectGrantID); err != nil {
|
||||
return c.newPermissionCheck(ctx, domain.PermissionProjectGrantMemberDelete, project.AggregateType)(grantedOrgID, projectGrantID)
|
||||
//}
|
||||
//return nil
|
||||
}
|
||||
|
||||
func (c *Commands) newUserGrantPermissionCheck(ctx context.Context, permission string) UserGrantPermissionCheck {
|
||||
check := c.newPermissionCheck(ctx, permission, project.AggregateType)
|
||||
return func(projectID, projectGrantID string) PermissionCheck {
|
||||
return func(resourceOwner, _ string) error {
|
||||
if projectGrantID != "" {
|
||||
return check(resourceOwner, projectGrantID)
|
||||
}
|
||||
return check(resourceOwner, projectID)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Commands) NewPermissionCheckUserGrantWrite(ctx context.Context) UserGrantPermissionCheck {
|
||||
return c.newUserGrantPermissionCheck(ctx, domain.PermissionUserGrantWrite)
|
||||
}
|
||||
|
||||
func (c *Commands) NewPermissionCheckUserGrantDelete(ctx context.Context) UserGrantPermissionCheck {
|
||||
return c.newUserGrantPermissionCheck(ctx, domain.PermissionUserGrantDelete)
|
||||
}
|
||||
|
||||
@@ -346,7 +346,7 @@ func (c *Commands) RemoveProject(ctx context.Context, projectID, resourceOwner s
|
||||
}
|
||||
|
||||
for _, grantID := range cascadingUserGrantIDs {
|
||||
event, _, err := c.removeUserGrant(ctx, grantID, "", true)
|
||||
event, _, err := c.removeUserGrant(ctx, grantID, "", true, false, nil)
|
||||
if err != nil {
|
||||
logging.WithFields("id", "COMMAND-b8Djf", "usergrantid", grantID).WithError(err).Warn("could not cascade remove user grant")
|
||||
continue
|
||||
@@ -398,7 +398,7 @@ func (c *Commands) DeleteProject(ctx context.Context, id, resourceOwner string,
|
||||
),
|
||||
}
|
||||
for _, grantID := range cascadingUserGrantIDs {
|
||||
event, _, err := c.removeUserGrant(ctx, grantID, "", true)
|
||||
event, _, err := c.removeUserGrant(ctx, grantID, "", true, false, nil)
|
||||
if err != nil {
|
||||
logging.WithFields("id", "COMMAND-b8Djf", "usergrantid", grantID).WithError(err).Warn("could not cascade remove user grant")
|
||||
continue
|
||||
|
||||
@@ -234,6 +234,17 @@ func (c *Commands) DeactivateProjectGrant(ctx context.Context, projectID, grantI
|
||||
return writeModelToObjectDetails(&existingGrant.WriteModel), nil
|
||||
}
|
||||
|
||||
func (c *Commands) checkProjectGrantExists(ctx context.Context, grantID, grantedOrgID, projectID, resourceOwner string) (string, string, error) {
|
||||
existingGrant, err := c.projectGrantWriteModelByID(ctx, grantID, grantedOrgID, projectID, resourceOwner)
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
if !existingGrant.State.Exists() {
|
||||
return "", "", zerrors.ThrowNotFound(nil, "PROJECT-D8JxR", "Errors.Project.Grant.NotFound")
|
||||
}
|
||||
return existingGrant.GrantedOrgID, existingGrant.ResourceOwner, nil
|
||||
}
|
||||
|
||||
func (c *Commands) ReactivateProjectGrant(ctx context.Context, projectID, grantID, grantedOrgID, resourceOwner string) (details *domain.ObjectDetails, err error) {
|
||||
if (grantID == "" && grantedOrgID == "") || projectID == "" {
|
||||
return details, zerrors.ThrowInvalidArgument(nil, "PROJECT-p0s4V", "Errors.IDMissing")
|
||||
@@ -302,16 +313,17 @@ func (c *Commands) RemoveProjectGrant(ctx context.Context, projectID, grantID, r
|
||||
ProjectAggregateFromWriteModelWithCTX(ctx, &existingGrant.WriteModel),
|
||||
existingGrant.GrantID,
|
||||
existingGrant.GrantedOrgID,
|
||||
),
|
||||
)
|
||||
))
|
||||
|
||||
for _, userGrantID := range cascadeUserGrantIDs {
|
||||
event, _, err := c.removeUserGrant(ctx, userGrantID, "", true)
|
||||
event, _, err := c.removeUserGrant(ctx, userGrantID, "", true, true, nil)
|
||||
if err != nil {
|
||||
logging.WithFields("id", "COMMAND-3m8sG", "usergrantid", grantID).WithError(err).Warn("could not cascade remove user grant")
|
||||
continue
|
||||
}
|
||||
events = append(events, event)
|
||||
if event != nil {
|
||||
events = append(events, event)
|
||||
}
|
||||
}
|
||||
pushedEvents, err := c.eventstore.Push(ctx, events...)
|
||||
if err != nil {
|
||||
@@ -348,12 +360,14 @@ func (c *Commands) DeleteProjectGrant(ctx context.Context, projectID, grantID, g
|
||||
)
|
||||
|
||||
for _, userGrantID := range cascadeUserGrantIDs {
|
||||
event, _, err := c.removeUserGrant(ctx, userGrantID, "", true)
|
||||
event, _, err := c.removeUserGrant(ctx, userGrantID, "", true, true, nil)
|
||||
if err != nil {
|
||||
logging.WithFields("id", "COMMAND-3m8sG", "usergrantid", grantID).WithError(err).Warn("could not cascade remove user grant")
|
||||
continue
|
||||
}
|
||||
events = append(events, event)
|
||||
if event != nil {
|
||||
events = append(events, event)
|
||||
}
|
||||
}
|
||||
pushedEvents, err := c.eventstore.Push(ctx, events...)
|
||||
if err != nil {
|
||||
|
||||
@@ -2,8 +2,9 @@ package command
|
||||
|
||||
import (
|
||||
"context"
|
||||
"reflect"
|
||||
"slices"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/api/authz"
|
||||
"github.com/zitadel/zitadel/internal/domain"
|
||||
"github.com/zitadel/zitadel/internal/eventstore"
|
||||
"github.com/zitadel/zitadel/internal/repository/project"
|
||||
@@ -11,25 +12,66 @@ import (
|
||||
"github.com/zitadel/zitadel/internal/zerrors"
|
||||
)
|
||||
|
||||
func (c *Commands) AddProjectGrantMember(ctx context.Context, member *domain.ProjectGrantMember) (_ *domain.ProjectGrantMember, err error) {
|
||||
type AddProjectGrantMember struct {
|
||||
ResourceOwner string
|
||||
UserID string
|
||||
GrantID string
|
||||
ProjectID string
|
||||
Roles []string
|
||||
}
|
||||
|
||||
func (i *AddProjectGrantMember) IsValid(zitadelRoles []authz.RoleMapping) error {
|
||||
if i.ProjectID == "" || i.GrantID == "" || i.UserID == "" || len(i.Roles) == 0 {
|
||||
return zerrors.ThrowInvalidArgument(nil, "PROJECT-8fi7G", "Errors.Project.Grant.Member.Invalid")
|
||||
}
|
||||
if len(domain.CheckForInvalidRoles(i.Roles, domain.ProjectGrantRolePrefix, zitadelRoles)) > 0 {
|
||||
return zerrors.ThrowInvalidArgument(nil, "PROJECT-m9gKK", "Errors.Project.Grant.Member.Invalid")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Commands) AddProjectGrantMember(ctx context.Context, member *AddProjectGrantMember) (_ *domain.ObjectDetails, err error) {
|
||||
ctx, span := tracing.NewSpan(ctx)
|
||||
defer func() { span.EndWithError(err) }()
|
||||
|
||||
if !member.IsValid() {
|
||||
return nil, zerrors.ThrowInvalidArgument(nil, "PROJECT-8fi7G", "Errors.Project.Grant.Member.Invalid")
|
||||
if err := member.IsValid(c.zitadelRoles); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(domain.CheckForInvalidRoles(member.Roles, domain.ProjectGrantRolePrefix, c.zitadelRoles)) > 0 {
|
||||
return nil, zerrors.ThrowInvalidArgument(nil, "PROJECT-m9gKK", "Errors.Project.Grant.Member.Invalid")
|
||||
}
|
||||
err = c.checkUserExists(ctx, member.UserID, "")
|
||||
_, err = c.checkUserExists(ctx, member.UserID, "")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
addedMember := NewProjectGrantMemberWriteModel(member.AggregateID, member.UserID, member.GrantID)
|
||||
projectAgg := ProjectAggregateFromWriteModel(&addedMember.WriteModel)
|
||||
grantedOrgID, projectGrantResourceOwner, err := c.checkProjectGrantExists(ctx, member.GrantID, "", member.ProjectID, "")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if member.ResourceOwner == "" {
|
||||
member.ResourceOwner = projectGrantResourceOwner
|
||||
}
|
||||
addedMember, err := c.projectGrantMemberWriteModelByID(ctx, member.ProjectID, member.UserID, member.GrantID, member.ResourceOwner)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// TODO: change e2e tests to use correct resourceowner, wrong resource owner is corrected through aggregate
|
||||
// error if provided resourceowner is not equal to the resourceowner of the project grant
|
||||
//if projectGrantResourceOwner != addedMember.ResourceOwner {
|
||||
// return nil, zerrors.ThrowPreconditionFailed(nil, "PROJECT-0l10S9OmZV", "Errors.Project.Grant.Invalid")
|
||||
//}
|
||||
if addedMember.State.Exists() {
|
||||
return nil, zerrors.ThrowNotFound(nil, "PROJECT-37fug", "Errors.AlreadyExists")
|
||||
}
|
||||
if err := c.checkPermissionUpdateProjectGrantMember(ctx, grantedOrgID, addedMember.GrantID); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
pushedEvents, err := c.eventstore.Push(
|
||||
ctx,
|
||||
project.NewProjectGrantMemberAddedEvent(ctx, projectAgg, member.UserID, member.GrantID, member.Roles...))
|
||||
project.NewProjectGrantMemberAddedEvent(ctx,
|
||||
ProjectAggregateFromWriteModelWithCTX(ctx, &addedMember.WriteModel),
|
||||
member.UserID,
|
||||
member.GrantID,
|
||||
member.Roles...,
|
||||
))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -38,30 +80,58 @@ func (c *Commands) AddProjectGrantMember(ctx context.Context, member *domain.Pro
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return memberWriteModelToProjectGrantMember(addedMember), nil
|
||||
return writeModelToObjectDetails(&addedMember.WriteModel), nil
|
||||
}
|
||||
|
||||
type ChangeProjectGrantMember struct {
|
||||
UserID string
|
||||
GrantID string
|
||||
ProjectID string
|
||||
Roles []string
|
||||
}
|
||||
|
||||
func (i *ChangeProjectGrantMember) IsValid(zitadelRoles []authz.RoleMapping) error {
|
||||
if i.ProjectID == "" || i.GrantID == "" || i.UserID == "" || len(i.Roles) == 0 {
|
||||
return zerrors.ThrowInvalidArgument(nil, "PROJECT-109fs", "Errors.Project.Grant.Member.Invalid")
|
||||
}
|
||||
if len(domain.CheckForInvalidRoles(i.Roles, domain.ProjectGrantRolePrefix, zitadelRoles)) > 0 {
|
||||
return zerrors.ThrowInvalidArgument(nil, "PROJECT-m0sDf", "Errors.Project.Grant.Member.Invalid")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ChangeProjectGrantMember updates an existing member
|
||||
func (c *Commands) ChangeProjectGrantMember(ctx context.Context, member *domain.ProjectGrantMember) (*domain.ProjectGrantMember, error) {
|
||||
if !member.IsValid() {
|
||||
return nil, zerrors.ThrowInvalidArgument(nil, "PROJECT-109fs", "Errors.Project.Member.Invalid")
|
||||
func (c *Commands) ChangeProjectGrantMember(ctx context.Context, member *ChangeProjectGrantMember) (*domain.ObjectDetails, error) {
|
||||
if err := member.IsValid(c.zitadelRoles); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(domain.CheckForInvalidRoles(member.Roles, domain.ProjectGrantRolePrefix, c.zitadelRoles)) > 0 {
|
||||
return nil, zerrors.ThrowInvalidArgument(nil, "PROJECT-m0sDf", "Errors.Project.Member.Invalid")
|
||||
}
|
||||
|
||||
existingMember, err := c.projectGrantMemberWriteModelByID(ctx, member.AggregateID, member.UserID, member.GrantID)
|
||||
existingGrant, err := c.projectGrantWriteModelByID(ctx, member.GrantID, "", member.ProjectID, "")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if reflect.DeepEqual(existingMember.Roles, member.Roles) {
|
||||
return nil, zerrors.ThrowPreconditionFailed(nil, "PROJECT-2n8vx", "Errors.Project.Member.RolesNotChanged")
|
||||
existingMember, err := c.projectGrantMemberWriteModelByID(ctx, member.ProjectID, member.UserID, member.GrantID, existingGrant.ResourceOwner)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
projectAgg := ProjectAggregateFromWriteModel(&existingMember.WriteModel)
|
||||
if !existingMember.State.Exists() {
|
||||
return nil, zerrors.ThrowNotFound(nil, "PROJECT-37fug", "Errors.NotFound")
|
||||
}
|
||||
|
||||
if err := c.checkPermissionUpdateProjectGrantMember(ctx, existingGrant.GrantedOrgID, existingMember.GrantID); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if slices.Compare(existingMember.Roles, member.Roles) == 0 {
|
||||
return writeModelToObjectDetails(&existingMember.WriteModel), nil
|
||||
}
|
||||
|
||||
pushedEvents, err := c.eventstore.Push(
|
||||
ctx,
|
||||
project.NewProjectGrantMemberChangedEvent(ctx, projectAgg, member.UserID, member.GrantID, member.Roles...))
|
||||
project.NewProjectGrantMemberChangedEvent(ctx,
|
||||
ProjectAggregateFromWriteModelWithCTX(ctx, &existingMember.WriteModel),
|
||||
member.UserID,
|
||||
member.GrantID,
|
||||
member.Roles...,
|
||||
))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -70,29 +140,43 @@ func (c *Commands) ChangeProjectGrantMember(ctx context.Context, member *domain.
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return memberWriteModelToProjectGrantMember(existingMember), nil
|
||||
return writeModelToObjectDetails(&existingMember.WriteModel), nil
|
||||
}
|
||||
|
||||
func (c *Commands) RemoveProjectGrantMember(ctx context.Context, projectID, userID, grantID string) (*domain.ObjectDetails, error) {
|
||||
if projectID == "" || userID == "" || grantID == "" {
|
||||
return nil, zerrors.ThrowInvalidArgument(nil, "PROJECT-66mHd", "Errors.Project.Member.Invalid")
|
||||
}
|
||||
m, err := c.projectGrantMemberWriteModelByID(ctx, projectID, userID, grantID)
|
||||
existingGrant, err := c.projectGrantWriteModelByID(ctx, grantID, "", projectID, "")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
existingMember, err := c.projectGrantMemberWriteModelByID(ctx, projectID, userID, grantID, existingGrant.ResourceOwner)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !existingMember.State.Exists() {
|
||||
return writeModelToObjectDetails(&existingMember.WriteModel), nil
|
||||
}
|
||||
if err := c.checkPermissionDeleteProjectGrantMember(ctx, existingGrant.GrantedOrgID, existingMember.GrantID); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
projectAgg := ProjectAggregateFromWriteModel(&m.WriteModel)
|
||||
removeEvent := c.removeProjectGrantMember(ctx, projectAgg, userID, grantID, false)
|
||||
removeEvent := c.removeProjectGrantMember(ctx,
|
||||
ProjectAggregateFromWriteModelWithCTX(ctx, &existingMember.WriteModel),
|
||||
userID,
|
||||
grantID,
|
||||
false,
|
||||
)
|
||||
pushedEvents, err := c.eventstore.Push(ctx, removeEvent)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = AppendAndReduce(m, pushedEvents...)
|
||||
err = AppendAndReduce(existingMember, pushedEvents...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return writeModelToObjectDetails(&m.WriteModel), nil
|
||||
return writeModelToObjectDetails(&existingMember.WriteModel), nil
|
||||
}
|
||||
|
||||
func (c *Commands) removeProjectGrantMember(ctx context.Context, projectAgg *eventstore.Aggregate, userID, grantID string, cascade bool) eventstore.Command {
|
||||
@@ -107,19 +191,15 @@ func (c *Commands) removeProjectGrantMember(ctx context.Context, projectAgg *eve
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Commands) projectGrantMemberWriteModelByID(ctx context.Context, projectID, userID, grantID string) (member *ProjectGrantMemberWriteModel, err error) {
|
||||
func (c *Commands) projectGrantMemberWriteModelByID(ctx context.Context, projectID, userID, grantID, resourceOwner string) (member *ProjectGrantMemberWriteModel, err error) {
|
||||
ctx, span := tracing.NewSpan(ctx)
|
||||
defer func() { span.EndWithError(err) }()
|
||||
|
||||
writeModel := NewProjectGrantMemberWriteModel(projectID, userID, grantID)
|
||||
writeModel := NewProjectGrantMemberWriteModel(projectID, userID, grantID, resourceOwner)
|
||||
err = c.eventstore.FilterToQueryReducer(ctx, writeModel)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if writeModel.State == domain.MemberStateUnspecified || writeModel.State == domain.MemberStateRemoved {
|
||||
return nil, zerrors.ThrowNotFound(nil, "PROJECT-37fug", "Errors.NotFound")
|
||||
}
|
||||
|
||||
return writeModel, nil
|
||||
}
|
||||
|
||||
@@ -16,10 +16,11 @@ type ProjectGrantMemberWriteModel struct {
|
||||
State domain.MemberState
|
||||
}
|
||||
|
||||
func NewProjectGrantMemberWriteModel(projectID, userID, grantID string) *ProjectGrantMemberWriteModel {
|
||||
func NewProjectGrantMemberWriteModel(projectID, userID, grantID, resourceOwner string) *ProjectGrantMemberWriteModel {
|
||||
return &ProjectGrantMemberWriteModel{
|
||||
WriteModel: eventstore.WriteModel{
|
||||
AggregateID: projectID,
|
||||
AggregateID: projectID,
|
||||
ResourceOwner: resourceOwner,
|
||||
},
|
||||
UserID: userID,
|
||||
GrantID: grantID,
|
||||
@@ -66,6 +67,7 @@ func (wm *ProjectGrantMemberWriteModel) Reduce() error {
|
||||
case *project.GrantMemberAddedEvent:
|
||||
wm.Roles = e.Roles
|
||||
wm.State = domain.MemberStateActive
|
||||
wm.ResourceOwner = e.Aggregate().ResourceOwner
|
||||
case *project.GrantMemberChangedEvent:
|
||||
wm.Roles = e.Roles
|
||||
case *project.GrantMemberRemovedEvent:
|
||||
@@ -80,7 +82,8 @@ func (wm *ProjectGrantMemberWriteModel) Reduce() error {
|
||||
}
|
||||
|
||||
func (wm *ProjectGrantMemberWriteModel) Query() *eventstore.SearchQueryBuilder {
|
||||
return eventstore.NewSearchQueryBuilder(eventstore.ColumnsEvent).
|
||||
query := eventstore.NewSearchQueryBuilder(eventstore.ColumnsEvent).
|
||||
ResourceOwner(wm.ResourceOwner).
|
||||
AddQuery().
|
||||
AggregateTypes(project.AggregateType).
|
||||
AggregateIDs(wm.AggregateID).
|
||||
@@ -92,4 +95,5 @@ func (wm *ProjectGrantMemberWriteModel) Query() *eventstore.SearchQueryBuilder {
|
||||
project.GrantRemovedType,
|
||||
project.ProjectRemovedType).
|
||||
Builder()
|
||||
return query
|
||||
}
|
||||
|
||||
@@ -10,7 +10,6 @@ import (
|
||||
"github.com/zitadel/zitadel/internal/api/authz"
|
||||
"github.com/zitadel/zitadel/internal/domain"
|
||||
"github.com/zitadel/zitadel/internal/eventstore"
|
||||
"github.com/zitadel/zitadel/internal/eventstore/v1/models"
|
||||
"github.com/zitadel/zitadel/internal/repository/project"
|
||||
"github.com/zitadel/zitadel/internal/repository/user"
|
||||
"github.com/zitadel/zitadel/internal/zerrors"
|
||||
@@ -18,15 +17,15 @@ import (
|
||||
|
||||
func TestCommandSide_AddProjectGrantMember(t *testing.T) {
|
||||
type fields struct {
|
||||
eventstore *eventstore.Eventstore
|
||||
zitadelRoles []authz.RoleMapping
|
||||
eventstore func(t *testing.T) *eventstore.Eventstore
|
||||
zitadelRoles []authz.RoleMapping
|
||||
checkPermission domain.PermissionCheck
|
||||
}
|
||||
type args struct {
|
||||
ctx context.Context
|
||||
member *domain.ProjectGrantMember
|
||||
member *AddProjectGrantMember
|
||||
}
|
||||
type res struct {
|
||||
want *domain.ProjectGrantMember
|
||||
want *domain.ObjectDetails
|
||||
err func(error) bool
|
||||
}
|
||||
tests := []struct {
|
||||
@@ -38,16 +37,12 @@ func TestCommandSide_AddProjectGrantMember(t *testing.T) {
|
||||
{
|
||||
name: "invalid member, error",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
),
|
||||
eventstore: expectEventstore(),
|
||||
checkPermission: newMockPermissionCheckAllowed(),
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
member: &domain.ProjectGrantMember{
|
||||
ObjectRoot: models.ObjectRoot{
|
||||
AggregateID: "project1",
|
||||
},
|
||||
member: &AddProjectGrantMember{
|
||||
ProjectID: "project1",
|
||||
},
|
||||
},
|
||||
res: res{
|
||||
@@ -57,19 +52,15 @@ func TestCommandSide_AddProjectGrantMember(t *testing.T) {
|
||||
{
|
||||
name: "invalid roles, error",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
),
|
||||
eventstore: expectEventstore(),
|
||||
checkPermission: newMockPermissionCheckAllowed(),
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
member: &domain.ProjectGrantMember{
|
||||
ObjectRoot: models.ObjectRoot{
|
||||
AggregateID: "project1",
|
||||
},
|
||||
GrantID: "projectgrant1",
|
||||
UserID: "user1",
|
||||
Roles: []string{"PROJECT_GRANT_OWNER"},
|
||||
member: &AddProjectGrantMember{
|
||||
ProjectID: "project1",
|
||||
GrantID: "projectgrant1",
|
||||
UserID: "user1",
|
||||
Roles: []string{"PROJECT_GRANT_OWNER"},
|
||||
},
|
||||
},
|
||||
res: res{
|
||||
@@ -79,10 +70,10 @@ func TestCommandSide_AddProjectGrantMember(t *testing.T) {
|
||||
{
|
||||
name: "user not existing, precondition error",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(),
|
||||
),
|
||||
checkPermission: newMockPermissionCheckAllowed(),
|
||||
zitadelRoles: []authz.RoleMapping{
|
||||
{
|
||||
Role: "PROJECT_GRANT_OWNER",
|
||||
@@ -90,14 +81,11 @@ func TestCommandSide_AddProjectGrantMember(t *testing.T) {
|
||||
},
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
member: &domain.ProjectGrantMember{
|
||||
ObjectRoot: models.ObjectRoot{
|
||||
AggregateID: "project1",
|
||||
},
|
||||
GrantID: "projectgrant1",
|
||||
UserID: "user1",
|
||||
Roles: []string{"PROJECT_GRANT_OWNER"},
|
||||
member: &AddProjectGrantMember{
|
||||
ProjectID: "project1",
|
||||
GrantID: "projectgrant1",
|
||||
UserID: "user1",
|
||||
Roles: []string{"PROJECT_GRANT_OWNER"},
|
||||
},
|
||||
},
|
||||
res: res{
|
||||
@@ -107,8 +95,7 @@ func TestCommandSide_AddProjectGrantMember(t *testing.T) {
|
||||
{
|
||||
name: "member add uniqueconstraint err, already exists",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
user.NewHumanAddedEvent(context.Background(),
|
||||
@@ -125,15 +112,28 @@ func TestCommandSide_AddProjectGrantMember(t *testing.T) {
|
||||
),
|
||||
),
|
||||
),
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
eventFromEventPusher(project.NewGrantAddedEvent(context.Background(),
|
||||
&project.NewAggregate("project1", "org1").Aggregate,
|
||||
"projectgrant1",
|
||||
"grantedorg1",
|
||||
[]string{"key1"},
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
expectFilter(),
|
||||
expectPushFailed(zerrors.ThrowAlreadyExists(nil, "ERROR", "internal"),
|
||||
project.NewProjectGrantMemberAddedEvent(context.Background(),
|
||||
&project.NewAggregate("project1", "").Aggregate,
|
||||
&project.NewAggregate("project1", "org1").Aggregate,
|
||||
"user1",
|
||||
"projectgrant1",
|
||||
[]string{"PROJECT_GRANT_OWNER"}...,
|
||||
),
|
||||
),
|
||||
),
|
||||
checkPermission: newMockPermissionCheckAllowed(),
|
||||
zitadelRoles: []authz.RoleMapping{
|
||||
{
|
||||
Role: "PROJECT_GRANT_OWNER",
|
||||
@@ -141,14 +141,11 @@ func TestCommandSide_AddProjectGrantMember(t *testing.T) {
|
||||
},
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
member: &domain.ProjectGrantMember{
|
||||
ObjectRoot: models.ObjectRoot{
|
||||
AggregateID: "project1",
|
||||
},
|
||||
GrantID: "projectgrant1",
|
||||
UserID: "user1",
|
||||
Roles: []string{"PROJECT_GRANT_OWNER"},
|
||||
member: &AddProjectGrantMember{
|
||||
ProjectID: "project1",
|
||||
GrantID: "projectgrant1",
|
||||
UserID: "user1",
|
||||
Roles: []string{"PROJECT_GRANT_OWNER"},
|
||||
},
|
||||
},
|
||||
res: res{
|
||||
@@ -158,8 +155,7 @@ func TestCommandSide_AddProjectGrantMember(t *testing.T) {
|
||||
{
|
||||
name: "member add, ok",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
user.NewHumanAddedEvent(context.Background(),
|
||||
@@ -176,15 +172,28 @@ func TestCommandSide_AddProjectGrantMember(t *testing.T) {
|
||||
),
|
||||
),
|
||||
),
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
eventFromEventPusher(project.NewGrantAddedEvent(context.Background(),
|
||||
&project.NewAggregate("project1", "org1").Aggregate,
|
||||
"projectgrant1",
|
||||
"grantedorg1",
|
||||
[]string{"key1"},
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
expectFilter(),
|
||||
expectPush(
|
||||
project.NewProjectGrantMemberAddedEvent(context.Background(),
|
||||
&project.NewAggregate("project1", "").Aggregate,
|
||||
&project.NewAggregate("project1", "org1").Aggregate,
|
||||
"user1",
|
||||
"projectgrant1",
|
||||
[]string{"PROJECT_GRANT_OWNER"}...,
|
||||
),
|
||||
),
|
||||
),
|
||||
checkPermission: newMockPermissionCheckAllowed(),
|
||||
zitadelRoles: []authz.RoleMapping{
|
||||
{
|
||||
Role: "PROJECT_GRANT_OWNER",
|
||||
@@ -192,35 +201,81 @@ func TestCommandSide_AddProjectGrantMember(t *testing.T) {
|
||||
},
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
member: &domain.ProjectGrantMember{
|
||||
ObjectRoot: models.ObjectRoot{
|
||||
AggregateID: "project1",
|
||||
},
|
||||
UserID: "user1",
|
||||
GrantID: "projectgrant1",
|
||||
Roles: []string{"PROJECT_GRANT_OWNER"},
|
||||
member: &AddProjectGrantMember{
|
||||
ProjectID: "project1",
|
||||
GrantID: "projectgrant1",
|
||||
UserID: "user1",
|
||||
Roles: []string{"PROJECT_GRANT_OWNER"},
|
||||
},
|
||||
},
|
||||
res: res{
|
||||
want: &domain.ProjectGrantMember{
|
||||
ObjectRoot: models.ObjectRoot{
|
||||
AggregateID: "project1",
|
||||
},
|
||||
GrantID: "projectgrant1",
|
||||
UserID: "user1",
|
||||
Roles: []string{"PROJECT_GRANT_OWNER"},
|
||||
want: &domain.ObjectDetails{
|
||||
ResourceOwner: "org1",
|
||||
ID: "project1",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "member add, no permission",
|
||||
fields: fields{
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
user.NewHumanAddedEvent(context.Background(),
|
||||
&user.NewAggregate("user1", "org1").Aggregate,
|
||||
"username1",
|
||||
"firstname1",
|
||||
"lastname1",
|
||||
"nickname1",
|
||||
"displayname1",
|
||||
language.German,
|
||||
domain.GenderMale,
|
||||
"email1",
|
||||
true,
|
||||
),
|
||||
),
|
||||
),
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
eventFromEventPusher(project.NewGrantAddedEvent(context.Background(),
|
||||
&project.NewAggregate("project1", "org1").Aggregate,
|
||||
"projectgrant1",
|
||||
"grantedorg1",
|
||||
[]string{"key1"},
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
expectFilter(),
|
||||
),
|
||||
checkPermission: newMockPermissionCheckNotAllowed(),
|
||||
zitadelRoles: []authz.RoleMapping{
|
||||
{
|
||||
Role: "PROJECT_GRANT_OWNER",
|
||||
},
|
||||
},
|
||||
},
|
||||
args: args{
|
||||
member: &AddProjectGrantMember{
|
||||
ProjectID: "project1",
|
||||
GrantID: "projectgrant1",
|
||||
UserID: "user1",
|
||||
Roles: []string{"PROJECT_GRANT_OWNER"},
|
||||
},
|
||||
},
|
||||
res: res{
|
||||
err: zerrors.IsPermissionDenied,
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
r := &Commands{
|
||||
eventstore: tt.fields.eventstore,
|
||||
zitadelRoles: tt.fields.zitadelRoles,
|
||||
eventstore: tt.fields.eventstore(t),
|
||||
zitadelRoles: tt.fields.zitadelRoles,
|
||||
checkPermission: tt.fields.checkPermission,
|
||||
}
|
||||
got, err := r.AddProjectGrantMember(tt.args.ctx, tt.args.member)
|
||||
got, err := r.AddProjectGrantMember(context.Background(), tt.args.member)
|
||||
if tt.res.err == nil {
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
@@ -236,15 +291,15 @@ func TestCommandSide_AddProjectGrantMember(t *testing.T) {
|
||||
|
||||
func TestCommandSide_ChangeProjectGrantMember(t *testing.T) {
|
||||
type fields struct {
|
||||
eventstore *eventstore.Eventstore
|
||||
zitadelRoles []authz.RoleMapping
|
||||
eventstore func(t *testing.T) *eventstore.Eventstore
|
||||
zitadelRoles []authz.RoleMapping
|
||||
checkPermission domain.PermissionCheck
|
||||
}
|
||||
type args struct {
|
||||
ctx context.Context
|
||||
member *domain.ProjectGrantMember
|
||||
member *ChangeProjectGrantMember
|
||||
}
|
||||
type res struct {
|
||||
want *domain.ProjectGrantMember
|
||||
want *domain.ObjectDetails
|
||||
err func(error) bool
|
||||
}
|
||||
tests := []struct {
|
||||
@@ -256,16 +311,12 @@ func TestCommandSide_ChangeProjectGrantMember(t *testing.T) {
|
||||
{
|
||||
name: "invalid member, error",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
),
|
||||
eventstore: expectEventstore(),
|
||||
checkPermission: newMockPermissionCheckAllowed(),
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
member: &domain.ProjectGrantMember{
|
||||
ObjectRoot: models.ObjectRoot{
|
||||
AggregateID: "project1",
|
||||
},
|
||||
member: &ChangeProjectGrantMember{
|
||||
ProjectID: "project1",
|
||||
},
|
||||
},
|
||||
res: res{
|
||||
@@ -275,19 +326,15 @@ func TestCommandSide_ChangeProjectGrantMember(t *testing.T) {
|
||||
{
|
||||
name: "invalid roles, error",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
),
|
||||
eventstore: expectEventstore(),
|
||||
checkPermission: newMockPermissionCheckAllowed(),
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
member: &domain.ProjectGrantMember{
|
||||
ObjectRoot: models.ObjectRoot{
|
||||
AggregateID: "project1",
|
||||
},
|
||||
GrantID: "projectgrant1",
|
||||
UserID: "user1",
|
||||
Roles: []string{"PROJECT_OWNER"},
|
||||
member: &ChangeProjectGrantMember{
|
||||
ProjectID: "project1",
|
||||
GrantID: "projectgrant1",
|
||||
UserID: "user1",
|
||||
Roles: []string{"PROJECT_OWNER"},
|
||||
},
|
||||
},
|
||||
res: res{
|
||||
@@ -297,10 +344,20 @@ func TestCommandSide_ChangeProjectGrantMember(t *testing.T) {
|
||||
{
|
||||
name: "member not existing, not found error",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
project.NewGrantAddedEvent(context.Background(),
|
||||
&project.NewAggregate("project1", "org1").Aggregate,
|
||||
"projectgrant1",
|
||||
"org2",
|
||||
[]string{"rol1", "role2"},
|
||||
),
|
||||
),
|
||||
),
|
||||
expectFilter(),
|
||||
),
|
||||
checkPermission: newMockPermissionCheckAllowed(),
|
||||
zitadelRoles: []authz.RoleMapping{
|
||||
{
|
||||
Role: "PROJECT_GRANT_OWNER",
|
||||
@@ -308,14 +365,11 @@ func TestCommandSide_ChangeProjectGrantMember(t *testing.T) {
|
||||
},
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
member: &domain.ProjectGrantMember{
|
||||
ObjectRoot: models.ObjectRoot{
|
||||
AggregateID: "project1",
|
||||
},
|
||||
GrantID: "projectgrant1",
|
||||
UserID: "user1",
|
||||
Roles: []string{"PROJECT_GRANT_OWNER"},
|
||||
member: &ChangeProjectGrantMember{
|
||||
ProjectID: "project1",
|
||||
GrantID: "projectgrant1",
|
||||
UserID: "user1",
|
||||
Roles: []string{"PROJECT_GRANT_OWNER"},
|
||||
},
|
||||
},
|
||||
res: res{
|
||||
@@ -325,8 +379,17 @@ func TestCommandSide_ChangeProjectGrantMember(t *testing.T) {
|
||||
{
|
||||
name: "member not changed, precondition error",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
project.NewGrantAddedEvent(context.Background(),
|
||||
&project.NewAggregate("project1", "org1").Aggregate,
|
||||
"projectgrant1",
|
||||
"org2",
|
||||
[]string{"rol1", "role2"},
|
||||
),
|
||||
),
|
||||
),
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
project.NewProjectGrantMemberAddedEvent(context.Background(),
|
||||
@@ -338,6 +401,7 @@ func TestCommandSide_ChangeProjectGrantMember(t *testing.T) {
|
||||
),
|
||||
),
|
||||
),
|
||||
checkPermission: newMockPermissionCheckAllowed(),
|
||||
zitadelRoles: []authz.RoleMapping{
|
||||
{
|
||||
Role: "PROJECT_GRANT_OWNER",
|
||||
@@ -345,25 +409,33 @@ func TestCommandSide_ChangeProjectGrantMember(t *testing.T) {
|
||||
},
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
member: &domain.ProjectGrantMember{
|
||||
ObjectRoot: models.ObjectRoot{
|
||||
AggregateID: "project1",
|
||||
},
|
||||
GrantID: "projectgrant1",
|
||||
UserID: "user1",
|
||||
Roles: []string{"PROJECT_GRANT_OWNER"},
|
||||
member: &ChangeProjectGrantMember{
|
||||
ProjectID: "project1",
|
||||
GrantID: "projectgrant1",
|
||||
UserID: "user1",
|
||||
Roles: []string{"PROJECT_GRANT_OWNER"},
|
||||
},
|
||||
},
|
||||
res: res{
|
||||
err: zerrors.IsPreconditionFailed,
|
||||
want: &domain.ObjectDetails{
|
||||
ResourceOwner: "org1",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "member change, ok",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
project.NewGrantAddedEvent(context.Background(),
|
||||
&project.NewAggregate("project1", "org1").Aggregate,
|
||||
"projectgrant1",
|
||||
"org2",
|
||||
[]string{"rol1", "role2"},
|
||||
),
|
||||
),
|
||||
),
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
project.NewProjectGrantMemberAddedEvent(context.Background(),
|
||||
@@ -383,6 +455,7 @@ func TestCommandSide_ChangeProjectGrantMember(t *testing.T) {
|
||||
),
|
||||
),
|
||||
),
|
||||
checkPermission: newMockPermissionCheckAllowed(),
|
||||
zitadelRoles: []authz.RoleMapping{
|
||||
{
|
||||
Role: "PROJECT_GRANT_OWNER",
|
||||
@@ -393,36 +466,75 @@ func TestCommandSide_ChangeProjectGrantMember(t *testing.T) {
|
||||
},
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
member: &domain.ProjectGrantMember{
|
||||
ObjectRoot: models.ObjectRoot{
|
||||
AggregateID: "project1",
|
||||
},
|
||||
GrantID: "projectgrant1",
|
||||
UserID: "user1",
|
||||
Roles: []string{"PROJECT_GRANT_OWNER", "PROJECT_GRANT_VIEWER"},
|
||||
member: &ChangeProjectGrantMember{
|
||||
ProjectID: "project1",
|
||||
GrantID: "projectgrant1",
|
||||
UserID: "user1",
|
||||
Roles: []string{"PROJECT_GRANT_OWNER", "PROJECT_GRANT_VIEWER"},
|
||||
},
|
||||
},
|
||||
res: res{
|
||||
want: &domain.ProjectGrantMember{
|
||||
ObjectRoot: models.ObjectRoot{
|
||||
ResourceOwner: "org1",
|
||||
AggregateID: "project1",
|
||||
},
|
||||
GrantID: "projectgrant1",
|
||||
UserID: "user1",
|
||||
Roles: []string{"PROJECT_GRANT_OWNER", "PROJECT_GRANT_VIEWER"},
|
||||
want: &domain.ObjectDetails{
|
||||
ResourceOwner: "org1",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "member change, no permission",
|
||||
fields: fields{
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
project.NewGrantAddedEvent(context.Background(),
|
||||
&project.NewAggregate("project1", "org1").Aggregate,
|
||||
"projectgrant1",
|
||||
"org2",
|
||||
[]string{"rol1", "role2"},
|
||||
),
|
||||
),
|
||||
),
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
project.NewProjectGrantMemberAddedEvent(context.Background(),
|
||||
&project.NewAggregate("project1", "org1").Aggregate,
|
||||
"user1",
|
||||
"projectgrant1",
|
||||
[]string{"PROJECT_GRANT_OWNER"}...,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
checkPermission: newMockPermissionCheckNotAllowed(),
|
||||
zitadelRoles: []authz.RoleMapping{
|
||||
{
|
||||
Role: "PROJECT_GRANT_OWNER",
|
||||
},
|
||||
{
|
||||
Role: "PROJECT_GRANT_VIEWER",
|
||||
},
|
||||
},
|
||||
},
|
||||
args: args{
|
||||
member: &ChangeProjectGrantMember{
|
||||
ProjectID: "project1",
|
||||
GrantID: "projectgrant1",
|
||||
UserID: "user1",
|
||||
Roles: []string{"PROJECT_GRANT_OWNER", "PROJECT_GRANT_VIEWER"},
|
||||
},
|
||||
},
|
||||
res: res{
|
||||
err: zerrors.IsPermissionDenied,
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
r := &Commands{
|
||||
eventstore: tt.fields.eventstore,
|
||||
zitadelRoles: tt.fields.zitadelRoles,
|
||||
eventstore: tt.fields.eventstore(t),
|
||||
zitadelRoles: tt.fields.zitadelRoles,
|
||||
checkPermission: tt.fields.checkPermission,
|
||||
}
|
||||
got, err := r.ChangeProjectGrantMember(tt.args.ctx, tt.args.member)
|
||||
got, err := r.ChangeProjectGrantMember(context.Background(), tt.args.member)
|
||||
if tt.res.err == nil {
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
@@ -430,7 +542,7 @@ func TestCommandSide_ChangeProjectGrantMember(t *testing.T) {
|
||||
t.Errorf("got wrong err: %v ", err)
|
||||
}
|
||||
if tt.res.err == nil {
|
||||
assert.Equal(t, tt.res.want, got)
|
||||
assertObjectDetails(t, tt.res.want, got)
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -438,7 +550,8 @@ func TestCommandSide_ChangeProjectGrantMember(t *testing.T) {
|
||||
|
||||
func TestCommandSide_RemoveProjectGrantMember(t *testing.T) {
|
||||
type fields struct {
|
||||
eventstore *eventstore.Eventstore
|
||||
eventstore func(t *testing.T) *eventstore.Eventstore
|
||||
checkPermission domain.PermissionCheck
|
||||
}
|
||||
type args struct {
|
||||
ctx context.Context
|
||||
@@ -459,9 +572,8 @@ func TestCommandSide_RemoveProjectGrantMember(t *testing.T) {
|
||||
{
|
||||
name: "invalid member projectid missing, error",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
),
|
||||
eventstore: expectEventstore(),
|
||||
checkPermission: newMockPermissionCheckAllowed(),
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
@@ -476,9 +588,8 @@ func TestCommandSide_RemoveProjectGrantMember(t *testing.T) {
|
||||
{
|
||||
name: "invalid member userid missing, error",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
),
|
||||
eventstore: expectEventstore(),
|
||||
checkPermission: newMockPermissionCheckAllowed(),
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
@@ -493,9 +604,8 @@ func TestCommandSide_RemoveProjectGrantMember(t *testing.T) {
|
||||
{
|
||||
name: "invalid member grantid missing, error",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
),
|
||||
eventstore: expectEventstore(),
|
||||
checkPermission: newMockPermissionCheckAllowed(),
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
@@ -508,12 +618,22 @@ func TestCommandSide_RemoveProjectGrantMember(t *testing.T) {
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "member not existing, not found err",
|
||||
name: "member not existing, not found ok",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
project.NewGrantAddedEvent(context.Background(),
|
||||
&project.NewAggregate("project1", "org1").Aggregate,
|
||||
"projectgrant1",
|
||||
"org2",
|
||||
[]string{"rol1", "role2"},
|
||||
),
|
||||
),
|
||||
),
|
||||
expectFilter(),
|
||||
),
|
||||
checkPermission: newMockPermissionCheckAllowed(),
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
@@ -522,14 +642,25 @@ func TestCommandSide_RemoveProjectGrantMember(t *testing.T) {
|
||||
grantID: "projectgrant1",
|
||||
},
|
||||
res: res{
|
||||
err: zerrors.IsNotFound,
|
||||
want: &domain.ObjectDetails{
|
||||
ResourceOwner: "org1",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "member remove, ok",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
project.NewGrantAddedEvent(context.Background(),
|
||||
&project.NewAggregate("project1", "org1").Aggregate,
|
||||
"projectgrant1",
|
||||
"org2",
|
||||
[]string{"rol1", "role2"},
|
||||
),
|
||||
),
|
||||
),
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
project.NewProjectGrantMemberAddedEvent(context.Background(),
|
||||
@@ -548,6 +679,7 @@ func TestCommandSide_RemoveProjectGrantMember(t *testing.T) {
|
||||
),
|
||||
),
|
||||
),
|
||||
checkPermission: newMockPermissionCheckAllowed(),
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
@@ -561,11 +693,49 @@ func TestCommandSide_RemoveProjectGrantMember(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "member remove, no permission",
|
||||
fields: fields{
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
project.NewGrantAddedEvent(context.Background(),
|
||||
&project.NewAggregate("project1", "org1").Aggregate,
|
||||
"projectgrant1",
|
||||
"org2",
|
||||
[]string{"rol1", "role2"},
|
||||
),
|
||||
),
|
||||
),
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
project.NewProjectGrantMemberAddedEvent(context.Background(),
|
||||
&project.NewAggregate("project1", "org1").Aggregate,
|
||||
"user1",
|
||||
"projectgrant1",
|
||||
[]string{"PROJECT_OWNER"}...,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
checkPermission: newMockPermissionCheckNotAllowed(),
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
projectID: "project1",
|
||||
userID: "user1",
|
||||
grantID: "projectgrant1",
|
||||
},
|
||||
res: res{
|
||||
err: zerrors.IsPermissionDenied,
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
r := &Commands{
|
||||
eventstore: tt.fields.eventstore,
|
||||
eventstore: tt.fields.eventstore(t),
|
||||
checkPermission: tt.fields.checkPermission,
|
||||
}
|
||||
got, err := r.RemoveProjectGrantMember(tt.args.ctx, tt.args.projectID, tt.args.userID, tt.args.grantID)
|
||||
if tt.res.err == nil {
|
||||
|
||||
@@ -2,8 +2,9 @@ package command
|
||||
|
||||
import (
|
||||
"context"
|
||||
"reflect"
|
||||
"slices"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/api/authz"
|
||||
"github.com/zitadel/zitadel/internal/domain"
|
||||
"github.com/zitadel/zitadel/internal/eventstore"
|
||||
"github.com/zitadel/zitadel/internal/repository/project"
|
||||
@@ -11,18 +12,64 @@ import (
|
||||
"github.com/zitadel/zitadel/internal/zerrors"
|
||||
)
|
||||
|
||||
func (c *Commands) AddProjectMember(ctx context.Context, member *domain.Member, resourceOwner string) (_ *domain.Member, err error) {
|
||||
type AddProjectMember struct {
|
||||
ResourceOwner string
|
||||
ProjectID string
|
||||
UserID string
|
||||
Roles []string
|
||||
}
|
||||
|
||||
func (i *AddProjectMember) IsValid(zitadelRoles []authz.RoleMapping) error {
|
||||
if i.ProjectID == "" || i.UserID == "" || len(i.Roles) == 0 {
|
||||
return zerrors.ThrowInvalidArgument(nil, "PROJECT-W8m4l", "Errors.Project.Member.Invalid")
|
||||
}
|
||||
if len(domain.CheckForInvalidRoles(i.Roles, domain.ProjectRolePrefix, zitadelRoles)) > 0 {
|
||||
return zerrors.ThrowInvalidArgument(nil, "PROJECT-3m9ds", "Errors.Project.Member.Invalid")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Commands) AddProjectMember(ctx context.Context, member *AddProjectMember) (_ *domain.ObjectDetails, err error) {
|
||||
ctx, span := tracing.NewSpan(ctx)
|
||||
defer func() { span.EndWithError(err) }()
|
||||
|
||||
addedMember := NewProjectMemberWriteModel(member.AggregateID, member.UserID, resourceOwner)
|
||||
projectAgg := ProjectAggregateFromWriteModel(&addedMember.WriteModel)
|
||||
event, err := c.addProjectMember(ctx, projectAgg, addedMember, member)
|
||||
if err := member.IsValid(c.zitadelRoles); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
_, err = c.checkUserExists(ctx, member.UserID, "")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
projectResourceOwner, err := c.checkProjectExists(ctx, member.ProjectID, member.ResourceOwner)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// resourceowner of the member if not provided is the resourceowner of the project
|
||||
if member.ResourceOwner == "" {
|
||||
member.ResourceOwner = projectResourceOwner
|
||||
}
|
||||
addedMember, err := c.projectMemberWriteModelByID(ctx, member.ProjectID, member.UserID, member.ResourceOwner)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// error if provided resourceowner is not equal to the resourceowner of the project
|
||||
if projectResourceOwner != addedMember.ResourceOwner {
|
||||
return nil, zerrors.ThrowPreconditionFailed(nil, "PROJECT-0l10S9OmZV", "Errors.Project.Member.Invalid")
|
||||
}
|
||||
if err := c.checkPermissionUpdateProjectMember(ctx, addedMember.ResourceOwner, addedMember.AggregateID); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if addedMember.State.Exists() {
|
||||
return nil, zerrors.ThrowAlreadyExists(nil, "PROJECT-PtXi1", "Errors.Project.Member.AlreadyExists")
|
||||
}
|
||||
|
||||
pushedEvents, err := c.eventstore.Push(ctx, event)
|
||||
pushedEvents, err := c.eventstore.Push(ctx,
|
||||
project.NewProjectMemberAddedEvent(ctx,
|
||||
ProjectAggregateFromWriteModelWithCTX(ctx, &addedMember.WriteModel),
|
||||
member.UserID,
|
||||
member.Roles...,
|
||||
),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -31,53 +78,46 @@ func (c *Commands) AddProjectMember(ctx context.Context, member *domain.Member,
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return memberWriteModelToMember(&addedMember.MemberWriteModel), nil
|
||||
return writeModelToObjectDetails(&addedMember.WriteModel), nil
|
||||
}
|
||||
|
||||
func (c *Commands) addProjectMember(ctx context.Context, projectAgg *eventstore.Aggregate, addedMember *ProjectMemberWriteModel, member *domain.Member) (_ eventstore.Command, err error) {
|
||||
ctx, span := tracing.NewSpan(ctx)
|
||||
defer func() { span.EndWithError(err) }()
|
||||
type ChangeProjectMember struct {
|
||||
ResourceOwner string
|
||||
ProjectID string
|
||||
UserID string
|
||||
Roles []string
|
||||
}
|
||||
|
||||
if !member.IsValid() {
|
||||
return nil, zerrors.ThrowInvalidArgument(nil, "PROJECT-W8m4l", "Errors.Project.Member.Invalid")
|
||||
func (i *ChangeProjectMember) IsValid(zitadelRoles []authz.RoleMapping) error {
|
||||
if i.ProjectID == "" || i.UserID == "" || len(i.Roles) == 0 {
|
||||
return zerrors.ThrowInvalidArgument(nil, "PROJECT-LiaZi", "Errors.Project.Member.Invalid")
|
||||
}
|
||||
if len(domain.CheckForInvalidRoles(member.Roles, domain.ProjectRolePrefix, c.zitadelRoles)) > 0 {
|
||||
return nil, zerrors.ThrowInvalidArgument(nil, "PROJECT-3m9ds", "Errors.Project.Member.Invalid")
|
||||
if len(domain.CheckForInvalidRoles(i.Roles, domain.ProjectRolePrefix, zitadelRoles)) > 0 {
|
||||
return zerrors.ThrowInvalidArgument(nil, "PROJECT-3m9d", "Errors.Project.Member.Invalid")
|
||||
}
|
||||
|
||||
err = c.checkUserExists(ctx, addedMember.UserID, "")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = c.eventstore.FilterToQueryReducer(ctx, addedMember)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if addedMember.State == domain.MemberStateActive {
|
||||
return nil, zerrors.ThrowAlreadyExists(nil, "PROJECT-PtXi1", "Errors.Project.Member.AlreadyExists")
|
||||
}
|
||||
|
||||
return project.NewProjectMemberAddedEvent(ctx, projectAgg, member.UserID, member.Roles...), nil
|
||||
return nil
|
||||
}
|
||||
|
||||
// ChangeProjectMember updates an existing member
|
||||
func (c *Commands) ChangeProjectMember(ctx context.Context, member *domain.Member, resourceOwner string) (*domain.Member, error) {
|
||||
if !member.IsValid() {
|
||||
return nil, zerrors.ThrowInvalidArgument(nil, "PROJECT-LiaZi", "Errors.Project.Member.Invalid")
|
||||
}
|
||||
if len(domain.CheckForInvalidRoles(member.Roles, domain.ProjectRolePrefix, c.zitadelRoles)) > 0 {
|
||||
return nil, zerrors.ThrowInvalidArgument(nil, "PROJECT-3m9d", "Errors.Project.Member.Invalid")
|
||||
}
|
||||
|
||||
existingMember, err := c.projectMemberWriteModelByID(ctx, member.AggregateID, member.UserID, resourceOwner)
|
||||
if err != nil {
|
||||
func (c *Commands) ChangeProjectMember(ctx context.Context, member *ChangeProjectMember) (*domain.ObjectDetails, error) {
|
||||
if err := member.IsValid(c.zitadelRoles); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if reflect.DeepEqual(existingMember.Roles, member.Roles) {
|
||||
return nil, zerrors.ThrowPreconditionFailed(nil, "PROJECT-LiaZi", "Errors.Project.Member.RolesNotChanged")
|
||||
existingMember, err := c.projectMemberWriteModelByID(ctx, member.ProjectID, member.UserID, member.ResourceOwner)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
projectAgg := ProjectAggregateFromWriteModel(&existingMember.MemberWriteModel.WriteModel)
|
||||
if !existingMember.State.Exists() {
|
||||
return nil, zerrors.ThrowNotFound(nil, "PROJECT-D8JxR", "Errors.NotFound")
|
||||
}
|
||||
if err := c.checkPermissionUpdateProjectMember(ctx, existingMember.ResourceOwner, existingMember.AggregateID); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if slices.Compare(existingMember.Roles, member.Roles) == 0 {
|
||||
return writeModelToObjectDetails(&existingMember.WriteModel), nil
|
||||
}
|
||||
projectAgg := ProjectAggregateFromWriteModelWithCTX(ctx, &existingMember.WriteModel)
|
||||
pushedEvents, err := c.eventstore.Push(ctx, project.NewProjectMemberChangedEvent(ctx, projectAgg, member.UserID, member.Roles...))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -88,33 +128,35 @@ func (c *Commands) ChangeProjectMember(ctx context.Context, member *domain.Membe
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return memberWriteModelToMember(&existingMember.MemberWriteModel), nil
|
||||
return writeModelToObjectDetails(&existingMember.WriteModel), nil
|
||||
}
|
||||
|
||||
func (c *Commands) RemoveProjectMember(ctx context.Context, projectID, userID, resourceOwner string) (*domain.ObjectDetails, error) {
|
||||
if projectID == "" || userID == "" {
|
||||
return nil, zerrors.ThrowInvalidArgument(nil, "PROJECT-66mHd", "Errors.Project.Member.Invalid")
|
||||
}
|
||||
m, err := c.projectMemberWriteModelByID(ctx, projectID, userID, resourceOwner)
|
||||
if err != nil && !zerrors.IsNotFound(err) {
|
||||
existingMember, err := c.projectMemberWriteModelByID(ctx, projectID, userID, resourceOwner)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if zerrors.IsNotFound(err) {
|
||||
// empty response because we have no data that match the request
|
||||
return &domain.ObjectDetails{}, nil
|
||||
if !existingMember.State.Exists() {
|
||||
return writeModelToObjectDetails(&existingMember.WriteModel), nil
|
||||
}
|
||||
if err := c.checkPermissionDeleteProjectMember(ctx, existingMember.ResourceOwner, existingMember.AggregateID); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
projectAgg := ProjectAggregateFromWriteModel(&m.MemberWriteModel.WriteModel)
|
||||
projectAgg := ProjectAggregateFromWriteModelWithCTX(ctx, &existingMember.WriteModel)
|
||||
removeEvent := c.removeProjectMember(ctx, projectAgg, userID, false)
|
||||
pushedEvents, err := c.eventstore.Push(ctx, removeEvent)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = AppendAndReduce(m, pushedEvents...)
|
||||
err = AppendAndReduce(existingMember, pushedEvents...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return writeModelToObjectDetails(&m.WriteModel), nil
|
||||
return writeModelToObjectDetails(&existingMember.WriteModel), nil
|
||||
}
|
||||
|
||||
func (c *Commands) removeProjectMember(ctx context.Context, projectAgg *eventstore.Aggregate, userID string, cascade bool) eventstore.Command {
|
||||
@@ -138,9 +180,5 @@ func (c *Commands) projectMemberWriteModelByID(ctx context.Context, projectID, u
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if writeModel.State == domain.MemberStateUnspecified || writeModel.State == domain.MemberStateRemoved {
|
||||
return nil, zerrors.ThrowNotFound(nil, "PROJECT-D8JxR", "Errors.NotFound")
|
||||
}
|
||||
|
||||
return writeModel, nil
|
||||
}
|
||||
|
||||
@@ -10,7 +10,6 @@ import (
|
||||
"github.com/zitadel/zitadel/internal/api/authz"
|
||||
"github.com/zitadel/zitadel/internal/domain"
|
||||
"github.com/zitadel/zitadel/internal/eventstore"
|
||||
"github.com/zitadel/zitadel/internal/eventstore/v1/models"
|
||||
"github.com/zitadel/zitadel/internal/repository/project"
|
||||
"github.com/zitadel/zitadel/internal/repository/user"
|
||||
"github.com/zitadel/zitadel/internal/zerrors"
|
||||
@@ -18,16 +17,15 @@ import (
|
||||
|
||||
func TestCommandSide_AddProjectMember(t *testing.T) {
|
||||
type fields struct {
|
||||
eventstore *eventstore.Eventstore
|
||||
zitadelRoles []authz.RoleMapping
|
||||
eventstore func(t *testing.T) *eventstore.Eventstore
|
||||
zitadelRoles []authz.RoleMapping
|
||||
checkPermission domain.PermissionCheck
|
||||
}
|
||||
type args struct {
|
||||
ctx context.Context
|
||||
member *domain.Member
|
||||
resourceOwner string
|
||||
member *AddProjectMember
|
||||
}
|
||||
type res struct {
|
||||
want *domain.Member
|
||||
want *domain.ObjectDetails
|
||||
err func(error) bool
|
||||
}
|
||||
tests := []struct {
|
||||
@@ -39,18 +37,14 @@ func TestCommandSide_AddProjectMember(t *testing.T) {
|
||||
{
|
||||
name: "invalid member, error",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
),
|
||||
eventstore: expectEventstore(),
|
||||
checkPermission: newMockPermissionCheckAllowed(),
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
member: &domain.Member{
|
||||
ObjectRoot: models.ObjectRoot{
|
||||
AggregateID: "project1",
|
||||
},
|
||||
member: &AddProjectMember{
|
||||
ResourceOwner: "org1",
|
||||
ProjectID: "project1",
|
||||
},
|
||||
resourceOwner: "org1",
|
||||
},
|
||||
res: res{
|
||||
err: zerrors.IsErrorInvalidArgument,
|
||||
@@ -59,20 +53,16 @@ func TestCommandSide_AddProjectMember(t *testing.T) {
|
||||
{
|
||||
name: "invalid roles, error",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
),
|
||||
eventstore: expectEventstore(),
|
||||
checkPermission: newMockPermissionCheckAllowed(),
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
member: &domain.Member{
|
||||
ObjectRoot: models.ObjectRoot{
|
||||
AggregateID: "project1",
|
||||
},
|
||||
UserID: "user1",
|
||||
Roles: []string{"PROJECT_OWNER"},
|
||||
member: &AddProjectMember{
|
||||
ResourceOwner: "org1",
|
||||
ProjectID: "project1",
|
||||
UserID: "user1",
|
||||
Roles: []string{"PROJECT_OWNER"},
|
||||
},
|
||||
resourceOwner: "org1",
|
||||
},
|
||||
res: res{
|
||||
err: zerrors.IsErrorInvalidArgument,
|
||||
@@ -81,10 +71,10 @@ func TestCommandSide_AddProjectMember(t *testing.T) {
|
||||
{
|
||||
name: "user not existing, precondition error",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(),
|
||||
),
|
||||
checkPermission: newMockPermissionCheckAllowed(),
|
||||
zitadelRoles: []authz.RoleMapping{
|
||||
{
|
||||
Role: domain.RoleProjectOwner,
|
||||
@@ -92,15 +82,12 @@ func TestCommandSide_AddProjectMember(t *testing.T) {
|
||||
},
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
member: &domain.Member{
|
||||
ObjectRoot: models.ObjectRoot{
|
||||
AggregateID: "project1",
|
||||
},
|
||||
UserID: "user1",
|
||||
Roles: []string{"PROJECT_OWNER"},
|
||||
member: &AddProjectMember{
|
||||
ResourceOwner: "org1",
|
||||
ProjectID: "project1",
|
||||
UserID: "user1",
|
||||
Roles: []string{"PROJECT_OWNER"},
|
||||
},
|
||||
resourceOwner: "org1",
|
||||
},
|
||||
res: res{
|
||||
err: zerrors.IsPreconditionFailed,
|
||||
@@ -109,8 +96,7 @@ func TestCommandSide_AddProjectMember(t *testing.T) {
|
||||
{
|
||||
name: "member already exists, precondition error",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
user.NewHumanAddedEvent(context.Background(),
|
||||
@@ -127,6 +113,19 @@ func TestCommandSide_AddProjectMember(t *testing.T) {
|
||||
),
|
||||
),
|
||||
),
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
project.NewProjectAddedEvent(
|
||||
context.Background(),
|
||||
&project.NewAggregate("project1", "org1").Aggregate,
|
||||
"project",
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
domain.PrivateLabelingSettingUnspecified,
|
||||
),
|
||||
),
|
||||
),
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
project.NewProjectMemberAddedEvent(context.Background(),
|
||||
@@ -136,6 +135,7 @@ func TestCommandSide_AddProjectMember(t *testing.T) {
|
||||
),
|
||||
),
|
||||
),
|
||||
checkPermission: newMockPermissionCheckAllowed(),
|
||||
zitadelRoles: []authz.RoleMapping{
|
||||
{
|
||||
Role: domain.RoleProjectOwner,
|
||||
@@ -143,15 +143,12 @@ func TestCommandSide_AddProjectMember(t *testing.T) {
|
||||
},
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
member: &domain.Member{
|
||||
ObjectRoot: models.ObjectRoot{
|
||||
AggregateID: "project1",
|
||||
},
|
||||
UserID: "user1",
|
||||
Roles: []string{"PROJECT_OWNER"},
|
||||
member: &AddProjectMember{
|
||||
ResourceOwner: "org1",
|
||||
ProjectID: "project1",
|
||||
UserID: "user1",
|
||||
Roles: []string{"PROJECT_OWNER"},
|
||||
},
|
||||
resourceOwner: "org1",
|
||||
},
|
||||
res: res{
|
||||
err: zerrors.IsErrorAlreadyExists,
|
||||
@@ -160,8 +157,7 @@ func TestCommandSide_AddProjectMember(t *testing.T) {
|
||||
{
|
||||
name: "member add uniqueconstraint err, already exists",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
user.NewHumanAddedEvent(context.Background(),
|
||||
@@ -178,6 +174,19 @@ func TestCommandSide_AddProjectMember(t *testing.T) {
|
||||
),
|
||||
),
|
||||
),
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
project.NewProjectAddedEvent(
|
||||
context.Background(),
|
||||
&project.NewAggregate("project1", "org1").Aggregate,
|
||||
"project",
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
domain.PrivateLabelingSettingUnspecified,
|
||||
),
|
||||
),
|
||||
),
|
||||
expectFilter(),
|
||||
expectPushFailed(zerrors.ThrowAlreadyExists(nil, "ERROR", "internal"),
|
||||
project.NewProjectMemberAddedEvent(context.Background(),
|
||||
@@ -187,6 +196,7 @@ func TestCommandSide_AddProjectMember(t *testing.T) {
|
||||
),
|
||||
),
|
||||
),
|
||||
checkPermission: newMockPermissionCheckAllowed(),
|
||||
zitadelRoles: []authz.RoleMapping{
|
||||
{
|
||||
Role: domain.RoleProjectOwner,
|
||||
@@ -194,15 +204,12 @@ func TestCommandSide_AddProjectMember(t *testing.T) {
|
||||
},
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
member: &domain.Member{
|
||||
ObjectRoot: models.ObjectRoot{
|
||||
AggregateID: "project1",
|
||||
},
|
||||
UserID: "user1",
|
||||
Roles: []string{"PROJECT_OWNER"},
|
||||
member: &AddProjectMember{
|
||||
ResourceOwner: "org1",
|
||||
ProjectID: "project1",
|
||||
UserID: "user1",
|
||||
Roles: []string{"PROJECT_OWNER"},
|
||||
},
|
||||
resourceOwner: "org1",
|
||||
},
|
||||
res: res{
|
||||
err: zerrors.IsErrorAlreadyExists,
|
||||
@@ -211,8 +218,7 @@ func TestCommandSide_AddProjectMember(t *testing.T) {
|
||||
{
|
||||
name: "member add, ok",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
user.NewHumanAddedEvent(context.Background(),
|
||||
@@ -229,6 +235,19 @@ func TestCommandSide_AddProjectMember(t *testing.T) {
|
||||
),
|
||||
),
|
||||
),
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
project.NewProjectAddedEvent(
|
||||
context.Background(),
|
||||
&project.NewAggregate("project1", "org1").Aggregate,
|
||||
"project",
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
domain.PrivateLabelingSettingUnspecified,
|
||||
),
|
||||
),
|
||||
),
|
||||
expectFilter(),
|
||||
expectPush(
|
||||
project.NewProjectMemberAddedEvent(context.Background(),
|
||||
@@ -238,6 +257,7 @@ func TestCommandSide_AddProjectMember(t *testing.T) {
|
||||
),
|
||||
),
|
||||
),
|
||||
checkPermission: newMockPermissionCheckAllowed(),
|
||||
zitadelRoles: []authz.RoleMapping{
|
||||
{
|
||||
Role: domain.RoleProjectOwner,
|
||||
@@ -245,35 +265,81 @@ func TestCommandSide_AddProjectMember(t *testing.T) {
|
||||
},
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
member: &domain.Member{
|
||||
ObjectRoot: models.ObjectRoot{
|
||||
AggregateID: "project1",
|
||||
},
|
||||
UserID: "user1",
|
||||
Roles: []string{"PROJECT_OWNER"},
|
||||
member: &AddProjectMember{
|
||||
ResourceOwner: "org1",
|
||||
ProjectID: "project1",
|
||||
UserID: "user1",
|
||||
Roles: []string{"PROJECT_OWNER"},
|
||||
},
|
||||
resourceOwner: "org1",
|
||||
},
|
||||
res: res{
|
||||
want: &domain.Member{
|
||||
ObjectRoot: models.ObjectRoot{
|
||||
ResourceOwner: "org1",
|
||||
AggregateID: "project1",
|
||||
},
|
||||
UserID: "user1",
|
||||
Roles: []string{domain.RoleProjectOwner},
|
||||
want: &domain.ObjectDetails{
|
||||
ResourceOwner: "org1",
|
||||
},
|
||||
},
|
||||
}, {
|
||||
name: "member add, no permission",
|
||||
fields: fields{
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
user.NewHumanAddedEvent(context.Background(),
|
||||
&user.NewAggregate("user1", "org1").Aggregate,
|
||||
"username1",
|
||||
"firstname1",
|
||||
"lastname1",
|
||||
"nickname1",
|
||||
"displayname1",
|
||||
language.German,
|
||||
domain.GenderMale,
|
||||
"email1",
|
||||
true,
|
||||
),
|
||||
),
|
||||
),
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
project.NewProjectAddedEvent(
|
||||
context.Background(),
|
||||
&project.NewAggregate("project1", "org1").Aggregate,
|
||||
"project",
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
domain.PrivateLabelingSettingUnspecified,
|
||||
),
|
||||
),
|
||||
),
|
||||
expectFilter(),
|
||||
),
|
||||
checkPermission: newMockPermissionCheckNotAllowed(),
|
||||
zitadelRoles: []authz.RoleMapping{
|
||||
{
|
||||
Role: domain.RoleProjectOwner,
|
||||
},
|
||||
},
|
||||
},
|
||||
args: args{
|
||||
member: &AddProjectMember{
|
||||
ResourceOwner: "org1",
|
||||
ProjectID: "project1",
|
||||
UserID: "user1",
|
||||
Roles: []string{"PROJECT_OWNER"},
|
||||
},
|
||||
},
|
||||
res: res{
|
||||
err: zerrors.IsPermissionDenied,
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
r := &Commands{
|
||||
eventstore: tt.fields.eventstore,
|
||||
zitadelRoles: tt.fields.zitadelRoles,
|
||||
eventstore: tt.fields.eventstore(t),
|
||||
zitadelRoles: tt.fields.zitadelRoles,
|
||||
checkPermission: tt.fields.checkPermission,
|
||||
}
|
||||
got, err := r.AddProjectMember(tt.args.ctx, tt.args.member, tt.args.resourceOwner)
|
||||
got, err := r.AddProjectMember(context.Background(), tt.args.member)
|
||||
if tt.res.err == nil {
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
@@ -281,7 +347,7 @@ func TestCommandSide_AddProjectMember(t *testing.T) {
|
||||
t.Errorf("got wrong err: %v ", err)
|
||||
}
|
||||
if tt.res.err == nil {
|
||||
assert.Equal(t, tt.res.want, got)
|
||||
assertObjectDetails(t, tt.res.want, got)
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -289,16 +355,15 @@ func TestCommandSide_AddProjectMember(t *testing.T) {
|
||||
|
||||
func TestCommandSide_ChangeProjectMember(t *testing.T) {
|
||||
type fields struct {
|
||||
eventstore *eventstore.Eventstore
|
||||
zitadelRoles []authz.RoleMapping
|
||||
eventstore func(t *testing.T) *eventstore.Eventstore
|
||||
zitadelRoles []authz.RoleMapping
|
||||
checkPermission domain.PermissionCheck
|
||||
}
|
||||
type args struct {
|
||||
ctx context.Context
|
||||
member *domain.Member
|
||||
resourceOwner string
|
||||
member *ChangeProjectMember
|
||||
}
|
||||
type res struct {
|
||||
want *domain.Member
|
||||
want *domain.ObjectDetails
|
||||
err func(error) bool
|
||||
}
|
||||
tests := []struct {
|
||||
@@ -310,18 +375,14 @@ func TestCommandSide_ChangeProjectMember(t *testing.T) {
|
||||
{
|
||||
name: "invalid member, error",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
),
|
||||
eventstore: expectEventstore(),
|
||||
checkPermission: newMockPermissionCheckAllowed(),
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
member: &domain.Member{
|
||||
ObjectRoot: models.ObjectRoot{
|
||||
AggregateID: "project1",
|
||||
},
|
||||
member: &ChangeProjectMember{
|
||||
ResourceOwner: "org1",
|
||||
ProjectID: "project1",
|
||||
},
|
||||
resourceOwner: "org1",
|
||||
},
|
||||
res: res{
|
||||
err: zerrors.IsErrorInvalidArgument,
|
||||
@@ -330,20 +391,16 @@ func TestCommandSide_ChangeProjectMember(t *testing.T) {
|
||||
{
|
||||
name: "invalid roles, error",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
),
|
||||
eventstore: expectEventstore(),
|
||||
checkPermission: newMockPermissionCheckAllowed(),
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
member: &domain.Member{
|
||||
ObjectRoot: models.ObjectRoot{
|
||||
AggregateID: "project1",
|
||||
},
|
||||
UserID: "user1",
|
||||
Roles: []string{"PROJECT_OWNER"},
|
||||
member: &ChangeProjectMember{
|
||||
ResourceOwner: "org1",
|
||||
ProjectID: "project1",
|
||||
UserID: "user1",
|
||||
Roles: []string{"PROJECT_OWNER"},
|
||||
},
|
||||
resourceOwner: "org1",
|
||||
},
|
||||
res: res{
|
||||
err: zerrors.IsErrorInvalidArgument,
|
||||
@@ -352,10 +409,10 @@ func TestCommandSide_ChangeProjectMember(t *testing.T) {
|
||||
{
|
||||
name: "member not existing, not found error",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(),
|
||||
),
|
||||
checkPermission: newMockPermissionCheckAllowed(),
|
||||
zitadelRoles: []authz.RoleMapping{
|
||||
{
|
||||
Role: domain.RoleProjectOwner,
|
||||
@@ -363,15 +420,12 @@ func TestCommandSide_ChangeProjectMember(t *testing.T) {
|
||||
},
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
member: &domain.Member{
|
||||
ObjectRoot: models.ObjectRoot{
|
||||
AggregateID: "project1",
|
||||
},
|
||||
UserID: "user1",
|
||||
Roles: []string{"PROJECT_OWNER"},
|
||||
member: &ChangeProjectMember{
|
||||
ResourceOwner: "org1",
|
||||
ProjectID: "project1",
|
||||
UserID: "user1",
|
||||
Roles: []string{"PROJECT_OWNER"},
|
||||
},
|
||||
resourceOwner: "org1",
|
||||
},
|
||||
res: res{
|
||||
err: zerrors.IsNotFound,
|
||||
@@ -380,8 +434,7 @@ func TestCommandSide_ChangeProjectMember(t *testing.T) {
|
||||
{
|
||||
name: "member not changed, precondition error",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
project.NewProjectMemberAddedEvent(context.Background(),
|
||||
@@ -392,6 +445,7 @@ func TestCommandSide_ChangeProjectMember(t *testing.T) {
|
||||
),
|
||||
),
|
||||
),
|
||||
checkPermission: newMockPermissionCheckAllowed(),
|
||||
zitadelRoles: []authz.RoleMapping{
|
||||
{
|
||||
Role: domain.RoleProjectOwner,
|
||||
@@ -399,25 +453,23 @@ func TestCommandSide_ChangeProjectMember(t *testing.T) {
|
||||
},
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
member: &domain.Member{
|
||||
ObjectRoot: models.ObjectRoot{
|
||||
AggregateID: "project1",
|
||||
},
|
||||
UserID: "user1",
|
||||
Roles: []string{"PROJECT_OWNER"},
|
||||
member: &ChangeProjectMember{
|
||||
ResourceOwner: "org1",
|
||||
ProjectID: "project1",
|
||||
UserID: "user1",
|
||||
Roles: []string{"PROJECT_OWNER"},
|
||||
},
|
||||
resourceOwner: "org1",
|
||||
},
|
||||
res: res{
|
||||
err: zerrors.IsPreconditionFailed,
|
||||
want: &domain.ObjectDetails{
|
||||
ResourceOwner: "org1",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "member change, ok",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
project.NewProjectMemberAddedEvent(context.Background(),
|
||||
@@ -435,6 +487,7 @@ func TestCommandSide_ChangeProjectMember(t *testing.T) {
|
||||
),
|
||||
),
|
||||
),
|
||||
checkPermission: newMockPermissionCheckAllowed(),
|
||||
zitadelRoles: []authz.RoleMapping{
|
||||
{
|
||||
Role: domain.RoleProjectOwner,
|
||||
@@ -445,35 +498,64 @@ func TestCommandSide_ChangeProjectMember(t *testing.T) {
|
||||
},
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
member: &domain.Member{
|
||||
ObjectRoot: models.ObjectRoot{
|
||||
AggregateID: "project1",
|
||||
},
|
||||
UserID: "user1",
|
||||
Roles: []string{"PROJECT_OWNER", "PROJECT_VIEWER"},
|
||||
member: &ChangeProjectMember{
|
||||
ResourceOwner: "org1",
|
||||
ProjectID: "project1",
|
||||
UserID: "user1",
|
||||
Roles: []string{"PROJECT_OWNER", "PROJECT_VIEWER"},
|
||||
},
|
||||
resourceOwner: "org1",
|
||||
},
|
||||
res: res{
|
||||
want: &domain.Member{
|
||||
ObjectRoot: models.ObjectRoot{
|
||||
ResourceOwner: "org1",
|
||||
AggregateID: "project1",
|
||||
},
|
||||
UserID: "user1",
|
||||
Roles: []string{domain.RoleProjectOwner, "PROJECT_VIEWER"},
|
||||
want: &domain.ObjectDetails{
|
||||
ResourceOwner: "org1",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "member change, no permission",
|
||||
fields: fields{
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
project.NewProjectMemberAddedEvent(context.Background(),
|
||||
&project.NewAggregate("project1", "org1").Aggregate,
|
||||
"user1",
|
||||
[]string{"PROJECT_OWNER"}...,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
checkPermission: newMockPermissionCheckNotAllowed(),
|
||||
zitadelRoles: []authz.RoleMapping{
|
||||
{
|
||||
Role: domain.RoleProjectOwner,
|
||||
},
|
||||
{
|
||||
Role: "PROJECT_VIEWER",
|
||||
},
|
||||
},
|
||||
},
|
||||
args: args{
|
||||
member: &ChangeProjectMember{
|
||||
ResourceOwner: "org1",
|
||||
ProjectID: "project1",
|
||||
UserID: "user1",
|
||||
Roles: []string{"PROJECT_OWNER", "PROJECT_VIEWER"},
|
||||
},
|
||||
},
|
||||
res: res{
|
||||
err: zerrors.IsPermissionDenied,
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
r := &Commands{
|
||||
eventstore: tt.fields.eventstore,
|
||||
zitadelRoles: tt.fields.zitadelRoles,
|
||||
eventstore: tt.fields.eventstore(t),
|
||||
zitadelRoles: tt.fields.zitadelRoles,
|
||||
checkPermission: tt.fields.checkPermission,
|
||||
}
|
||||
got, err := r.ChangeProjectMember(tt.args.ctx, tt.args.member, tt.args.resourceOwner)
|
||||
got, err := r.ChangeProjectMember(context.Background(), tt.args.member)
|
||||
if tt.res.err == nil {
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
@@ -481,7 +563,7 @@ func TestCommandSide_ChangeProjectMember(t *testing.T) {
|
||||
t.Errorf("got wrong err: %v ", err)
|
||||
}
|
||||
if tt.res.err == nil {
|
||||
assert.Equal(t, tt.res.want, got)
|
||||
assertObjectDetails(t, tt.res.want, got)
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -489,10 +571,10 @@ func TestCommandSide_ChangeProjectMember(t *testing.T) {
|
||||
|
||||
func TestCommandSide_RemoveProjectMember(t *testing.T) {
|
||||
type fields struct {
|
||||
eventstore *eventstore.Eventstore
|
||||
eventstore func(t *testing.T) *eventstore.Eventstore
|
||||
checkPermission domain.PermissionCheck
|
||||
}
|
||||
type args struct {
|
||||
ctx context.Context
|
||||
projectID string
|
||||
userID string
|
||||
resourceOwner string
|
||||
@@ -510,12 +592,10 @@ func TestCommandSide_RemoveProjectMember(t *testing.T) {
|
||||
{
|
||||
name: "invalid member projectid missing, error",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
),
|
||||
eventstore: expectEventstore(),
|
||||
checkPermission: newMockPermissionCheckAllowed(),
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
projectID: "",
|
||||
userID: "user1",
|
||||
resourceOwner: "org1",
|
||||
@@ -527,12 +607,10 @@ func TestCommandSide_RemoveProjectMember(t *testing.T) {
|
||||
{
|
||||
name: "invalid member userid missing, error",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
),
|
||||
eventstore: expectEventstore(),
|
||||
checkPermission: newMockPermissionCheckAllowed(),
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
projectID: "project1",
|
||||
userID: "",
|
||||
resourceOwner: "org1",
|
||||
@@ -544,26 +622,26 @@ func TestCommandSide_RemoveProjectMember(t *testing.T) {
|
||||
{
|
||||
name: "member not existing, empty object details result",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(),
|
||||
),
|
||||
checkPermission: newMockPermissionCheckAllowed(),
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
projectID: "project1",
|
||||
userID: "user1",
|
||||
resourceOwner: "org1",
|
||||
},
|
||||
res: res{
|
||||
want: &domain.ObjectDetails{},
|
||||
want: &domain.ObjectDetails{
|
||||
ResourceOwner: "org1",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "member remove, ok",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
project.NewProjectMemberAddedEvent(context.Background(),
|
||||
@@ -580,9 +658,9 @@ func TestCommandSide_RemoveProjectMember(t *testing.T) {
|
||||
),
|
||||
),
|
||||
),
|
||||
checkPermission: newMockPermissionCheckAllowed(),
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
projectID: "project1",
|
||||
userID: "user1",
|
||||
resourceOwner: "org1",
|
||||
@@ -593,13 +671,39 @@ func TestCommandSide_RemoveProjectMember(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "member remove, no permission",
|
||||
fields: fields{
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
project.NewProjectMemberAddedEvent(context.Background(),
|
||||
&project.NewAggregate("project1", "org1").Aggregate,
|
||||
"user1",
|
||||
[]string{"PROJECT_OWNER"}...,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
checkPermission: newMockPermissionCheckNotAllowed(),
|
||||
},
|
||||
args: args{
|
||||
projectID: "project1",
|
||||
userID: "user1",
|
||||
resourceOwner: "org1",
|
||||
},
|
||||
res: res{
|
||||
err: zerrors.IsPermissionDenied,
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
r := &Commands{
|
||||
eventstore: tt.fields.eventstore,
|
||||
eventstore: tt.fields.eventstore(t),
|
||||
checkPermission: tt.fields.checkPermission,
|
||||
}
|
||||
got, err := r.RemoveProjectMember(tt.args.ctx, tt.args.projectID, tt.args.userID, tt.args.resourceOwner)
|
||||
got, err := r.RemoveProjectMember(context.Background(), tt.args.projectID, tt.args.userID, tt.args.resourceOwner)
|
||||
if tt.res.err == nil {
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
@@ -194,7 +194,7 @@ func (c *Commands) RemoveUser(ctx context.Context, userID, resourceOwner string,
|
||||
events = append(events, user.NewUserRemovedEvent(ctx, userAgg, existingUser.UserName, existingUser.IDPLinks, domainPolicy.UserLoginMustBeDomain))
|
||||
|
||||
for _, grantID := range cascadingGrantIDs {
|
||||
removeEvent, _, err := c.removeUserGrant(ctx, grantID, "", true)
|
||||
removeEvent, _, err := c.removeUserGrant(ctx, grantID, "", true, false, nil)
|
||||
if err != nil {
|
||||
logging.WithFields("usergrantid", grantID).WithError(err).Warn("could not cascade remove role on user grant")
|
||||
continue
|
||||
@@ -327,18 +327,18 @@ func (c *Commands) UserDomainClaimedSent(ctx context.Context, orgID, userID stri
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *Commands) checkUserExists(ctx context.Context, userID, resourceOwner string) (err error) {
|
||||
func (c *Commands) checkUserExists(ctx context.Context, userID, resourceOwner string) (_ string, err error) {
|
||||
ctx, span := tracing.NewSpan(ctx)
|
||||
defer func() { span.EndWithError(err) }()
|
||||
|
||||
existingUser, err := c.userWriteModelByID(ctx, userID, resourceOwner)
|
||||
if err != nil {
|
||||
return err
|
||||
return "", err
|
||||
}
|
||||
if !isUserStateExists(existingUser.UserState) {
|
||||
return zerrors.ThrowPreconditionFailed(nil, "COMMAND-uXHNj", "Errors.User.NotFound")
|
||||
return "", zerrors.ThrowPreconditionFailed(nil, "COMMAND-uXHNj", "Errors.User.NotFound")
|
||||
}
|
||||
return nil
|
||||
return existingUser.ResourceOwner, nil
|
||||
}
|
||||
|
||||
func (c *Commands) userWriteModelByID(ctx context.Context, userID, resourceOwner string) (writeModel *UserWriteModel, err error) {
|
||||
|
||||
+144
-76
@@ -2,7 +2,7 @@ package command
|
||||
|
||||
import (
|
||||
"context"
|
||||
"reflect"
|
||||
"slices"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/api/authz"
|
||||
"github.com/zitadel/zitadel/internal/domain"
|
||||
@@ -15,11 +15,14 @@ import (
|
||||
"github.com/zitadel/zitadel/internal/zerrors"
|
||||
)
|
||||
|
||||
func (c *Commands) AddUserGrant(ctx context.Context, usergrant *domain.UserGrant, resourceOwner string) (_ *domain.UserGrant, err error) {
|
||||
// AddUserGrant authorizes a user for a project with the given role keys.
|
||||
// The project must be owned by or granted to the resourceOwner.
|
||||
// If the resourceOwner is nil, the project must be owned by the project that belongs to usergrant.ProjectID.
|
||||
func (c *Commands) AddUserGrant(ctx context.Context, usergrant *domain.UserGrant, check UserGrantPermissionCheck) (_ *domain.UserGrant, err error) {
|
||||
ctx, span := tracing.NewSpan(ctx)
|
||||
defer func() { span.EndWithError(err) }()
|
||||
|
||||
event, addedUserGrant, err := c.addUserGrant(ctx, usergrant, resourceOwner)
|
||||
event, addedUserGrant, err := c.addUserGrant(ctx, usergrant, check)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -35,11 +38,11 @@ func (c *Commands) AddUserGrant(ctx context.Context, usergrant *domain.UserGrant
|
||||
return userGrantWriteModelToUserGrant(addedUserGrant), nil
|
||||
}
|
||||
|
||||
func (c *Commands) addUserGrant(ctx context.Context, userGrant *domain.UserGrant, resourceOwner string) (command eventstore.Command, _ *UserGrantWriteModel, err error) {
|
||||
func (c *Commands) addUserGrant(ctx context.Context, userGrant *domain.UserGrant, check UserGrantPermissionCheck) (command eventstore.Command, _ *UserGrantWriteModel, err error) {
|
||||
if !userGrant.IsValid() {
|
||||
return nil, nil, zerrors.ThrowInvalidArgument(nil, "COMMAND-kVfMa", "Errors.UserGrant.Invalid")
|
||||
}
|
||||
err = c.checkUserGrantPreCondition(ctx, userGrant, resourceOwner)
|
||||
err = c.checkUserGrantPreCondition(ctx, userGrant, check)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
@@ -48,7 +51,7 @@ func (c *Commands) addUserGrant(ctx context.Context, userGrant *domain.UserGrant
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
addedUserGrant := NewUserGrantWriteModel(userGrant.AggregateID, resourceOwner)
|
||||
addedUserGrant := NewUserGrantWriteModel(userGrant.AggregateID, userGrant.ResourceOwner)
|
||||
userGrantAgg := UserGrantAggregateFromWriteModel(&addedUserGrant.WriteModel)
|
||||
command = usergrant.NewUserGrantAddedEvent(
|
||||
ctx,
|
||||
@@ -61,54 +64,51 @@ func (c *Commands) addUserGrant(ctx context.Context, userGrant *domain.UserGrant
|
||||
return command, addedUserGrant, nil
|
||||
}
|
||||
|
||||
func (c *Commands) ChangeUserGrant(ctx context.Context, userGrant *domain.UserGrant, resourceOwner string) (_ *domain.UserGrant, err error) {
|
||||
event, changedUserGrant, err := c.changeUserGrant(ctx, userGrant, resourceOwner, false)
|
||||
func (c *Commands) ChangeUserGrant(ctx context.Context, userGrant *domain.UserGrant, cascade, ignoreUnchanged bool, check UserGrantPermissionCheck) (_ *domain.UserGrant, err error) {
|
||||
if userGrant.AggregateID == "" {
|
||||
return nil, zerrors.ThrowInvalidArgument(nil, "COMMAND-3M0sd", "Errors.UserGrant.Invalid")
|
||||
}
|
||||
existingUserGrant, err := c.userGrantWriteModelByID(ctx, userGrant.AggregateID, "")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if existingUserGrant.State == domain.UserGrantStateUnspecified || existingUserGrant.State == domain.UserGrantStateRemoved {
|
||||
return nil, zerrors.ThrowNotFound(nil, "COMMAND-3M9sd", "Errors.UserGrant.NotFound")
|
||||
}
|
||||
|
||||
grantUnchanged := slices.Equal(existingUserGrant.RoleKeys, userGrant.RoleKeys)
|
||||
if grantUnchanged {
|
||||
if ignoreUnchanged {
|
||||
return userGrantWriteModelToUserGrant(existingUserGrant), nil
|
||||
}
|
||||
return nil, zerrors.ThrowPreconditionFailed(nil, "COMMAND-Rs8fy", "Errors.UserGrant.NotChanged")
|
||||
}
|
||||
userGrant.UserID = existingUserGrant.UserID
|
||||
userGrant.ProjectID = existingUserGrant.ProjectID
|
||||
userGrant.ProjectGrantID = existingUserGrant.ProjectGrantID
|
||||
userGrant.ResourceOwner = existingUserGrant.ResourceOwner
|
||||
|
||||
err = c.checkUserGrantPreCondition(ctx, userGrant, check)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
changedUserGrant := NewUserGrantWriteModel(userGrant.AggregateID, userGrant.ResourceOwner)
|
||||
userGrantAgg := UserGrantAggregateFromWriteModel(&changedUserGrant.WriteModel)
|
||||
|
||||
var event eventstore.Command = usergrant.NewUserGrantChangedEvent(ctx, userGrantAgg, existingUserGrant.UserID, userGrant.RoleKeys)
|
||||
if cascade {
|
||||
event = usergrant.NewUserGrantCascadeChangedEvent(ctx, userGrantAgg, userGrant.RoleKeys)
|
||||
}
|
||||
pushedEvents, err := c.eventstore.Push(ctx, event)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = AppendAndReduce(changedUserGrant, pushedEvents...)
|
||||
err = AppendAndReduce(existingUserGrant, pushedEvents...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return userGrantWriteModelToUserGrant(changedUserGrant), nil
|
||||
}
|
||||
|
||||
func (c *Commands) changeUserGrant(ctx context.Context, userGrant *domain.UserGrant, resourceOwner string, cascade bool) (_ eventstore.Command, _ *UserGrantWriteModel, err error) {
|
||||
if userGrant.AggregateID == "" {
|
||||
return nil, nil, zerrors.ThrowInvalidArgument(nil, "COMMAND-3M0sd", "Errors.UserGrant.Invalid")
|
||||
}
|
||||
existingUserGrant, err := c.userGrantWriteModelByID(ctx, userGrant.AggregateID, userGrant.ResourceOwner)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
err = checkExplicitProjectPermission(ctx, existingUserGrant.ProjectGrantID, existingUserGrant.ProjectID)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
if existingUserGrant.State == domain.UserGrantStateUnspecified || existingUserGrant.State == domain.UserGrantStateRemoved {
|
||||
return nil, nil, zerrors.ThrowNotFound(nil, "COMMAND-3M9sd", "Errors.UserGrant.NotFound")
|
||||
}
|
||||
if reflect.DeepEqual(existingUserGrant.RoleKeys, userGrant.RoleKeys) {
|
||||
return nil, nil, zerrors.ThrowPreconditionFailed(nil, "COMMAND-Rs8fy", "Errors.UserGrant.NotChanged")
|
||||
}
|
||||
userGrant.ProjectID = existingUserGrant.ProjectID
|
||||
userGrant.ProjectGrantID = existingUserGrant.ProjectGrantID
|
||||
err = c.checkUserGrantPreCondition(ctx, userGrant, resourceOwner)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
changedUserGrant := NewUserGrantWriteModel(userGrant.AggregateID, resourceOwner)
|
||||
userGrantAgg := UserGrantAggregateFromWriteModel(&changedUserGrant.WriteModel)
|
||||
|
||||
if cascade {
|
||||
return usergrant.NewUserGrantCascadeChangedEvent(ctx, userGrantAgg, userGrant.RoleKeys), existingUserGrant, nil
|
||||
}
|
||||
return usergrant.NewUserGrantChangedEvent(ctx, userGrantAgg, existingUserGrant.UserID, userGrant.RoleKeys), existingUserGrant, nil
|
||||
return userGrantWriteModelToUserGrant(existingUserGrant), nil
|
||||
}
|
||||
|
||||
func (c *Commands) removeRoleFromUserGrant(ctx context.Context, userGrantID string, roleKeys []string, cascade bool) (_ eventstore.Command, err error) {
|
||||
@@ -144,8 +144,8 @@ func (c *Commands) removeRoleFromUserGrant(ctx context.Context, userGrantID stri
|
||||
return usergrant.NewUserGrantChangedEvent(ctx, userGrantAgg, existingUserGrant.UserID, existingUserGrant.RoleKeys), nil
|
||||
}
|
||||
|
||||
func (c *Commands) DeactivateUserGrant(ctx context.Context, grantID, resourceOwner string) (objectDetails *domain.ObjectDetails, err error) {
|
||||
if grantID == "" || resourceOwner == "" {
|
||||
func (c *Commands) DeactivateUserGrant(ctx context.Context, grantID string, resourceOwner string, check UserGrantPermissionCheck) (objectDetails *domain.ObjectDetails, err error) {
|
||||
if grantID == "" {
|
||||
return nil, zerrors.ThrowInvalidArgument(nil, "COMMAND-N2OhG", "Errors.UserGrant.IDMissing")
|
||||
}
|
||||
|
||||
@@ -157,14 +157,17 @@ func (c *Commands) DeactivateUserGrant(ctx context.Context, grantID, resourceOwn
|
||||
return nil, zerrors.ThrowNotFound(nil, "COMMAND-3M9sd", "Errors.UserGrant.NotFound")
|
||||
}
|
||||
if existingUserGrant.State != domain.UserGrantStateActive {
|
||||
return nil, zerrors.ThrowPreconditionFailed(nil, "COMMAND-1S9gx", "Errors.UserGrant.NotActive")
|
||||
return writeModelToObjectDetails(&existingUserGrant.WriteModel), nil
|
||||
}
|
||||
if check != nil {
|
||||
err = check(existingUserGrant.ProjectID, existingUserGrant.ProjectGrantID)(existingUserGrant.ResourceOwner, "")
|
||||
} else {
|
||||
err = checkExplicitProjectPermission(ctx, existingUserGrant.ProjectGrantID, existingUserGrant.ProjectID)
|
||||
}
|
||||
err = checkExplicitProjectPermission(ctx, existingUserGrant.ProjectGrantID, existingUserGrant.ProjectID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
deactivateUserGrant := NewUserGrantWriteModel(grantID, resourceOwner)
|
||||
deactivateUserGrant := NewUserGrantWriteModel(grantID, existingUserGrant.ResourceOwner)
|
||||
userGrantAgg := UserGrantAggregateFromWriteModel(&deactivateUserGrant.WriteModel)
|
||||
pushedEvents, err := c.eventstore.Push(ctx, usergrant.NewUserGrantDeactivatedEvent(ctx, userGrantAgg))
|
||||
if err != nil {
|
||||
@@ -177,8 +180,8 @@ func (c *Commands) DeactivateUserGrant(ctx context.Context, grantID, resourceOwn
|
||||
return writeModelToObjectDetails(&existingUserGrant.WriteModel), nil
|
||||
}
|
||||
|
||||
func (c *Commands) ReactivateUserGrant(ctx context.Context, grantID, resourceOwner string) (objectDetails *domain.ObjectDetails, err error) {
|
||||
if grantID == "" || resourceOwner == "" {
|
||||
func (c *Commands) ReactivateUserGrant(ctx context.Context, grantID string, resourceOwner string, check UserGrantPermissionCheck) (objectDetails *domain.ObjectDetails, err error) {
|
||||
if grantID == "" {
|
||||
return nil, zerrors.ThrowInvalidArgument(nil, "COMMAND-Qxy8v", "Errors.UserGrant.IDMissing")
|
||||
}
|
||||
|
||||
@@ -190,13 +193,17 @@ func (c *Commands) ReactivateUserGrant(ctx context.Context, grantID, resourceOwn
|
||||
return nil, zerrors.ThrowNotFound(nil, "COMMAND-Lp0gs", "Errors.UserGrant.NotFound")
|
||||
}
|
||||
if existingUserGrant.State != domain.UserGrantStateInactive {
|
||||
return nil, zerrors.ThrowPreconditionFailed(nil, "COMMAND-1ML0v", "Errors.UserGrant.NotInactive")
|
||||
return writeModelToObjectDetails(&existingUserGrant.WriteModel), nil
|
||||
}
|
||||
if check != nil {
|
||||
err = check(existingUserGrant.ProjectID, existingUserGrant.ProjectGrantID)(existingUserGrant.ResourceOwner, "")
|
||||
} else {
|
||||
err = checkExplicitProjectPermission(ctx, existingUserGrant.ProjectGrantID, existingUserGrant.ProjectID)
|
||||
}
|
||||
err = checkExplicitProjectPermission(ctx, existingUserGrant.ProjectGrantID, existingUserGrant.ProjectID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
deactivateUserGrant := NewUserGrantWriteModel(grantID, resourceOwner)
|
||||
deactivateUserGrant := NewUserGrantWriteModel(grantID, existingUserGrant.ResourceOwner)
|
||||
userGrantAgg := UserGrantAggregateFromWriteModel(&deactivateUserGrant.WriteModel)
|
||||
pushedEvents, err := c.eventstore.Push(ctx, usergrant.NewUserGrantReactivatedEvent(ctx, userGrantAgg))
|
||||
if err != nil {
|
||||
@@ -209,12 +216,14 @@ func (c *Commands) ReactivateUserGrant(ctx context.Context, grantID, resourceOwn
|
||||
return writeModelToObjectDetails(&existingUserGrant.WriteModel), nil
|
||||
}
|
||||
|
||||
func (c *Commands) RemoveUserGrant(ctx context.Context, grantID, resourceOwner string) (objectDetails *domain.ObjectDetails, err error) {
|
||||
event, existingUserGrant, err := c.removeUserGrant(ctx, grantID, resourceOwner, false)
|
||||
func (c *Commands) RemoveUserGrant(ctx context.Context, grantID string, resourceOwner string, ignoreNotFound bool, check UserGrantPermissionCheck) (objectDetails *domain.ObjectDetails, err error) {
|
||||
event, existingUserGrant, err := c.removeUserGrant(ctx, grantID, resourceOwner, false, ignoreNotFound, check)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if event == nil {
|
||||
return writeModelToObjectDetails(&existingUserGrant.WriteModel), nil
|
||||
}
|
||||
pushedEvents, err := c.eventstore.Push(ctx, event)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -232,7 +241,7 @@ func (c *Commands) BulkRemoveUserGrant(ctx context.Context, grantIDs []string, r
|
||||
}
|
||||
events := make([]eventstore.Command, len(grantIDs))
|
||||
for i, grantID := range grantIDs {
|
||||
event, _, err := c.removeUserGrant(ctx, grantID, resourceOwner, false)
|
||||
event, _, err := c.removeUserGrant(ctx, grantID, resourceOwner, false, false, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -242,7 +251,7 @@ func (c *Commands) BulkRemoveUserGrant(ctx context.Context, grantIDs []string, r
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *Commands) removeUserGrant(ctx context.Context, grantID, resourceOwner string, cascade bool) (_ eventstore.Command, writeModel *UserGrantWriteModel, err error) {
|
||||
func (c *Commands) removeUserGrant(ctx context.Context, grantID string, resourceOwner string, cascade, ignoreNotFound bool, check UserGrantPermissionCheck) (_ eventstore.Command, writeModel *UserGrantWriteModel, err error) {
|
||||
if grantID == "" {
|
||||
return nil, nil, zerrors.ThrowInvalidArgument(nil, "COMMAND-J9sc5", "Errors.UserGrant.IDMissing")
|
||||
}
|
||||
@@ -252,15 +261,22 @@ func (c *Commands) removeUserGrant(ctx context.Context, grantID, resourceOwner s
|
||||
return nil, nil, err
|
||||
}
|
||||
if existingUserGrant.State == domain.UserGrantStateUnspecified || existingUserGrant.State == domain.UserGrantStateRemoved {
|
||||
if ignoreNotFound {
|
||||
return nil, existingUserGrant, nil
|
||||
}
|
||||
return nil, nil, zerrors.ThrowNotFound(nil, "COMMAND-1My0t", "Errors.UserGrant.NotFound")
|
||||
}
|
||||
if !cascade {
|
||||
if !cascade && check == nil {
|
||||
err = checkExplicitProjectPermission(ctx, existingUserGrant.ProjectGrantID, existingUserGrant.ProjectID)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
}
|
||||
|
||||
if check != nil {
|
||||
if err = check(existingUserGrant.ProjectID, existingUserGrant.ProjectGrantID)(existingUserGrant.ResourceOwner, ""); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
}
|
||||
removeUserGrant := NewUserGrantWriteModel(grantID, existingUserGrant.ResourceOwner)
|
||||
userGrantAgg := UserGrantAggregateFromWriteModel(&removeUserGrant.WriteModel)
|
||||
if cascade {
|
||||
@@ -279,7 +295,7 @@ func (c *Commands) removeUserGrant(ctx context.Context, grantID, resourceOwner s
|
||||
existingUserGrant.ProjectGrantID), existingUserGrant, nil
|
||||
}
|
||||
|
||||
func (c *Commands) userGrantWriteModelByID(ctx context.Context, userGrantID, resourceOwner string) (writeModel *UserGrantWriteModel, err error) {
|
||||
func (c *Commands) userGrantWriteModelByID(ctx context.Context, userGrantID string, resourceOwner string) (writeModel *UserGrantWriteModel, err error) {
|
||||
ctx, span := tracing.NewSpan(ctx)
|
||||
defer func() { span.EndWithError(err) }()
|
||||
|
||||
@@ -291,31 +307,46 @@ func (c *Commands) userGrantWriteModelByID(ctx context.Context, userGrantID, res
|
||||
return writeModel, nil
|
||||
}
|
||||
|
||||
func (c *Commands) checkUserGrantPreCondition(ctx context.Context, usergrant *domain.UserGrant, resourceOwner string) (err error) {
|
||||
func (c *Commands) checkUserGrantPreCondition(ctx context.Context, usergrant *domain.UserGrant, check UserGrantPermissionCheck) (err error) {
|
||||
if !authz.GetFeatures(ctx).ShouldUseImprovedPerformance(feature.ImprovedPerformanceTypeUserGrant) {
|
||||
return c.checkUserGrantPreConditionOld(ctx, usergrant, resourceOwner)
|
||||
return c.checkUserGrantPreConditionOld(ctx, usergrant, check)
|
||||
}
|
||||
|
||||
ctx, span := tracing.NewSpan(ctx)
|
||||
defer func() { span.EndWithError(err) }()
|
||||
|
||||
if err := c.checkUserExists(ctx, usergrant.UserID, ""); err != nil {
|
||||
if _, err := c.checkUserExists(ctx, usergrant.UserID, ""); err != nil {
|
||||
return err
|
||||
}
|
||||
existingRoleKeys, err := c.searchUserGrantPreConditionState(ctx, usergrant, resourceOwner)
|
||||
if usergrant.ProjectGrantID != "" || usergrant.ResourceOwner == "" {
|
||||
projectOwner, grantID, err := c.searchProjectOwnerAndGrantID(ctx, usergrant.ProjectID, "")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if usergrant.ResourceOwner == "" {
|
||||
usergrant.ResourceOwner = projectOwner
|
||||
}
|
||||
if usergrant.ProjectGrantID == "" {
|
||||
usergrant.ProjectGrantID = grantID
|
||||
}
|
||||
}
|
||||
existingRoleKeys, err := c.searchUserGrantPreConditionState(ctx, usergrant)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if usergrant.HasInvalidRoles(existingRoleKeys) {
|
||||
return zerrors.ThrowPreconditionFailed(err, "COMMAND-mm9F4", "Errors.Project.Role.NotFound")
|
||||
}
|
||||
return nil
|
||||
if check != nil {
|
||||
return check(usergrant.ProjectID, usergrant.ProjectGrantID)(usergrant.ResourceOwner, "")
|
||||
}
|
||||
return checkExplicitProjectPermission(ctx, usergrant.ProjectGrantID, usergrant.ProjectID)
|
||||
}
|
||||
|
||||
// this code needs to be rewritten anyways as soon as we improved the fields handling
|
||||
//
|
||||
//nolint:gocognit
|
||||
func (c *Commands) searchUserGrantPreConditionState(ctx context.Context, userGrant *domain.UserGrant, resourceOwner string) (existingRoleKeys []string, err error) {
|
||||
func (c *Commands) searchUserGrantPreConditionState(ctx context.Context, userGrant *domain.UserGrant) (existingRoleKeys []string, err error) {
|
||||
criteria := []map[eventstore.FieldType]any{
|
||||
// project state query
|
||||
{
|
||||
@@ -327,7 +358,7 @@ func (c *Commands) searchUserGrantPreConditionState(ctx context.Context, userGra
|
||||
// granted org query
|
||||
{
|
||||
eventstore.FieldTypeAggregateType: org.AggregateType,
|
||||
eventstore.FieldTypeAggregateID: resourceOwner,
|
||||
eventstore.FieldTypeAggregateID: userGrant.ResourceOwner,
|
||||
eventstore.FieldTypeFieldName: org.OrgStateSearchField,
|
||||
eventstore.FieldTypeObjectType: org.OrgSearchType,
|
||||
},
|
||||
@@ -386,7 +417,7 @@ func (c *Commands) searchUserGrantPreConditionState(ctx context.Context, userGra
|
||||
case project.ProjectGrantGrantedOrgIDSearchField:
|
||||
var orgID string
|
||||
err := result.Value.Unmarshal(&orgID)
|
||||
if err != nil || orgID != resourceOwner {
|
||||
if err != nil || orgID != userGrant.ResourceOwner {
|
||||
return nil, zerrors.ThrowPreconditionFailed(err, "COMMAND-3m9gg", "Errors.Org.NotFound")
|
||||
}
|
||||
case project.ProjectGrantStateSearchField:
|
||||
@@ -425,26 +456,63 @@ func (c *Commands) searchUserGrantPreConditionState(ctx context.Context, userGra
|
||||
return existingRoleKeys, nil
|
||||
}
|
||||
|
||||
func (c *Commands) checkUserGrantPreConditionOld(ctx context.Context, usergrant *domain.UserGrant, resourceOwner string) (err error) {
|
||||
func (c *Commands) checkUserGrantPreConditionOld(ctx context.Context, usergrant *domain.UserGrant, check UserGrantPermissionCheck) (err error) {
|
||||
ctx, span := tracing.NewSpan(ctx)
|
||||
defer func() { span.EndWithError(err) }()
|
||||
|
||||
preConditions := NewUserGrantPreConditionReadModel(usergrant.UserID, usergrant.ProjectID, usergrant.ProjectGrantID, resourceOwner)
|
||||
preConditions := NewUserGrantPreConditionReadModel(usergrant.UserID, usergrant.ProjectID, usergrant.ProjectGrantID, usergrant.ResourceOwner)
|
||||
err = c.eventstore.FilterToQueryReducer(ctx, preConditions)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if usergrant.ResourceOwner == "" {
|
||||
usergrant.ResourceOwner = preConditions.ProjectResourceOwner
|
||||
}
|
||||
if usergrant.ProjectGrantID == "" {
|
||||
usergrant.ProjectGrantID = preConditions.ProjectGrantID
|
||||
}
|
||||
if !preConditions.UserExists {
|
||||
return zerrors.ThrowPreconditionFailed(err, "COMMAND-4f8sg", "Errors.User.NotFound")
|
||||
}
|
||||
if usergrant.ProjectGrantID == "" && !preConditions.ProjectExists {
|
||||
projectIsOwned := usergrant.ResourceOwner == "" || usergrant.ResourceOwner == preConditions.ProjectResourceOwner
|
||||
if projectIsOwned && !preConditions.ProjectExists {
|
||||
return zerrors.ThrowPreconditionFailed(err, "COMMAND-3n77S", "Errors.Project.NotFound")
|
||||
}
|
||||
if usergrant.ProjectGrantID != "" && !preConditions.ProjectGrantExists {
|
||||
if !projectIsOwned && !preConditions.ProjectGrantExists {
|
||||
return zerrors.ThrowPreconditionFailed(err, "COMMAND-4m9ff", "Errors.Project.Grant.NotFound")
|
||||
}
|
||||
if usergrant.HasInvalidRoles(preConditions.ExistingRoleKeys) {
|
||||
return zerrors.ThrowPreconditionFailed(err, "COMMAND-mm9F4", "Errors.Project.Role.NotFound")
|
||||
}
|
||||
return nil
|
||||
if check != nil {
|
||||
return check(usergrant.ProjectID, usergrant.ProjectGrantID)(usergrant.ResourceOwner, "")
|
||||
}
|
||||
return checkExplicitProjectPermission(ctx, usergrant.ProjectGrantID, usergrant.ProjectID)
|
||||
}
|
||||
|
||||
func (c *Commands) searchProjectOwnerAndGrantID(ctx context.Context, projectID string, grantedOrgID string) (projectOwner string, grantID string, err error) {
|
||||
grantIDQuery := map[eventstore.FieldType]any{
|
||||
eventstore.FieldTypeAggregateType: project.AggregateType,
|
||||
eventstore.FieldTypeAggregateID: projectID,
|
||||
eventstore.FieldTypeFieldName: project.ProjectGrantGrantedOrgIDSearchField,
|
||||
}
|
||||
if grantedOrgID != "" {
|
||||
grantIDQuery[eventstore.FieldTypeValue] = grantedOrgID
|
||||
grantIDQuery[eventstore.FieldTypeObjectType] = project.ProjectGrantSearchType
|
||||
|
||||
}
|
||||
results, err := c.eventstore.Search(ctx, grantIDQuery)
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
for _, result := range results {
|
||||
projectOwner = result.Aggregate.ResourceOwner
|
||||
if grantedOrgID != "" && grantedOrgID == projectOwner {
|
||||
return projectOwner, "", nil
|
||||
}
|
||||
if result.Object.Type == project.ProjectGrantSearchType {
|
||||
return projectOwner, result.Object.ID, nil
|
||||
}
|
||||
}
|
||||
return projectOwner, grantID, err
|
||||
}
|
||||
|
||||
@@ -36,6 +36,7 @@ func (wm *UserGrantWriteModel) Reduce() error {
|
||||
wm.ProjectGrantID = e.ProjectGrantID
|
||||
wm.RoleKeys = e.RoleKeys
|
||||
wm.State = domain.UserGrantStateActive
|
||||
wm.ResourceOwner = e.Aggregate().ResourceOwner
|
||||
case *usergrant.UserGrantChangedEvent:
|
||||
wm.RoleKeys = e.RoleKeys
|
||||
case *usergrant.UserGrantCascadeChangedEvent:
|
||||
@@ -86,17 +87,18 @@ func UserGrantAggregateFromWriteModel(wm *eventstore.WriteModel) *eventstore.Agg
|
||||
type UserGrantPreConditionReadModel struct {
|
||||
eventstore.WriteModel
|
||||
|
||||
UserID string
|
||||
ProjectID string
|
||||
ProjectGrantID string
|
||||
ResourceOwner string
|
||||
UserExists bool
|
||||
ProjectExists bool
|
||||
ProjectGrantExists bool
|
||||
ExistingRoleKeys []string
|
||||
UserID string
|
||||
ProjectID string
|
||||
ProjectResourceOwner string
|
||||
ProjectGrantID string
|
||||
ResourceOwner string
|
||||
UserExists bool
|
||||
ProjectExists bool
|
||||
ProjectGrantExists bool
|
||||
ExistingRoleKeys []string
|
||||
}
|
||||
|
||||
func NewUserGrantPreConditionReadModel(userID, projectID, projectGrantID, resourceOwner string) *UserGrantPreConditionReadModel {
|
||||
func NewUserGrantPreConditionReadModel(userID, projectID, projectGrantID string, resourceOwner string) *UserGrantPreConditionReadModel {
|
||||
return &UserGrantPreConditionReadModel{
|
||||
UserID: userID,
|
||||
ProjectID: projectID,
|
||||
@@ -117,15 +119,19 @@ func (wm *UserGrantPreConditionReadModel) Reduce() error {
|
||||
case *user.UserRemovedEvent:
|
||||
wm.UserExists = false
|
||||
case *project.ProjectAddedEvent:
|
||||
if wm.ProjectGrantID == "" && wm.ResourceOwner == e.Aggregate().ResourceOwner {
|
||||
if wm.ResourceOwner == "" || wm.ResourceOwner == e.Aggregate().ResourceOwner {
|
||||
wm.ProjectExists = true
|
||||
}
|
||||
wm.ProjectResourceOwner = e.Aggregate().ResourceOwner
|
||||
case *project.ProjectRemovedEvent:
|
||||
wm.ProjectExists = false
|
||||
case *project.GrantAddedEvent:
|
||||
if wm.ProjectGrantID == e.GrantID && wm.ResourceOwner == e.GrantedOrgID {
|
||||
if (wm.ProjectGrantID == e.GrantID || wm.ProjectGrantID == "") && wm.ResourceOwner != "" && wm.ResourceOwner == e.GrantedOrgID {
|
||||
wm.ProjectGrantExists = true
|
||||
wm.ExistingRoleKeys = e.RoleKeys
|
||||
if wm.ProjectGrantID == "" {
|
||||
wm.ProjectGrantID = e.GrantID
|
||||
}
|
||||
}
|
||||
case *project.GrantChangedEvent:
|
||||
if wm.ProjectGrantID == e.GrantID {
|
||||
|
||||
+1105
-191
File diff suppressed because it is too large
Load Diff
@@ -26,7 +26,7 @@ func (c *Commands) ImportHumanTOTP(ctx context.Context, userID, userAgentID, res
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err = c.checkUserExists(ctx, userID, resourceOwner); err != nil {
|
||||
if _, err = c.checkUserExists(ctx, userID, resourceOwner); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
|
||||
@@ -56,7 +56,7 @@ func (c *Commands) BulkAddedUserIDPLinks(ctx context.Context, userID, resourceOw
|
||||
return zerrors.ThrowInvalidArgument(nil, "COMMAND-Ek9s", "Errors.User.ExternalIDP.MinimumExternalIDPNeeded")
|
||||
}
|
||||
|
||||
if err := c.checkUserExists(ctx, userID, resourceOwner); err != nil {
|
||||
if _, err := c.checkUserExists(ctx, userID, resourceOwner); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package command
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/domain"
|
||||
@@ -14,12 +15,25 @@ func (c *Commands) SetUserMetadata(ctx context.Context, metadata *domain.Metadat
|
||||
ctx, span := tracing.NewSpan(ctx)
|
||||
defer func() { span.EndWithError(err) }()
|
||||
|
||||
err = c.checkUserExists(ctx, userID, resourceOwner)
|
||||
userResourceOwner, err := c.checkUserExists(ctx, userID, resourceOwner)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := c.checkPermissionUpdateUser(ctx, userResourceOwner, userID); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
setMetadata, err := c.getUserMetadataModelByID(ctx, userID, userResourceOwner, metadata.Key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
setMetadata := NewUserMetadataWriteModel(userID, resourceOwner, metadata.Key)
|
||||
userAgg := UserAggregateFromWriteModel(&setMetadata.WriteModel)
|
||||
// return if no change in the metadata
|
||||
if bytes.Equal(setMetadata.Value, metadata.Value) {
|
||||
return writeModelToUserMetadata(setMetadata), nil
|
||||
}
|
||||
|
||||
event, err := c.setUserMetadata(ctx, userAgg, metadata)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -40,20 +54,35 @@ func (c *Commands) BulkSetUserMetadata(ctx context.Context, userID, resourceOwne
|
||||
if len(metadatas) == 0 {
|
||||
return nil, zerrors.ThrowPreconditionFailed(nil, "META-9mm2d", "Errors.Metadata.NoData")
|
||||
}
|
||||
err = c.checkUserExists(ctx, userID, resourceOwner)
|
||||
userResourceOwner, err := c.checkUserExists(ctx, userID, resourceOwner)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
events := make([]eventstore.Command, len(metadatas))
|
||||
setMetadata := NewUserMetadataListWriteModel(userID, resourceOwner)
|
||||
if err := c.checkPermissionUpdateUser(ctx, userResourceOwner, userID); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
events := make([]eventstore.Command, 0)
|
||||
setMetadata, err := c.getUserMetadataListModelByID(ctx, userID, userResourceOwner)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
userAgg := UserAggregateFromWriteModel(&setMetadata.WriteModel)
|
||||
for i, data := range metadatas {
|
||||
for _, data := range metadatas {
|
||||
// if no change to metadata no event has to be pushed
|
||||
if existingValue, ok := setMetadata.metadataList[data.Key]; ok && bytes.Equal(existingValue, data.Value) {
|
||||
continue
|
||||
}
|
||||
event, err := c.setUserMetadata(ctx, userAgg, data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
events[i] = event
|
||||
events = append(events, event)
|
||||
}
|
||||
// no changes for the metadata
|
||||
if len(events) == 0 {
|
||||
return writeModelToObjectDetails(&setMetadata.WriteModel), nil
|
||||
}
|
||||
|
||||
pushedEvents, err := c.eventstore.Push(ctx, events...)
|
||||
@@ -84,11 +113,16 @@ func (c *Commands) RemoveUserMetadata(ctx context.Context, metadataKey, userID,
|
||||
if metadataKey == "" {
|
||||
return nil, zerrors.ThrowInvalidArgument(nil, "META-2n0fs", "Errors.Metadata.Invalid")
|
||||
}
|
||||
err = c.checkUserExists(ctx, userID, resourceOwner)
|
||||
userResourceOwner, err := c.checkUserExists(ctx, userID, resourceOwner)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
removeMetadata, err := c.getUserMetadataModelByID(ctx, userID, resourceOwner, metadataKey)
|
||||
|
||||
if err := c.checkPermissionUpdateUser(ctx, userResourceOwner, userID); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
removeMetadata, err := c.getUserMetadataModelByID(ctx, userID, userResourceOwner, metadataKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -116,13 +150,17 @@ func (c *Commands) BulkRemoveUserMetadata(ctx context.Context, userID, resourceO
|
||||
if len(metadataKeys) == 0 {
|
||||
return nil, zerrors.ThrowPreconditionFailed(nil, "META-9mm2d", "Errors.Metadata.NoData")
|
||||
}
|
||||
err = c.checkUserExists(ctx, userID, resourceOwner)
|
||||
userResourceOwner, err := c.checkUserExists(ctx, userID, resourceOwner)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := c.checkPermissionUpdateUser(ctx, userResourceOwner, userID); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
events := make([]eventstore.Command, len(metadataKeys))
|
||||
removeMetadata, err := c.getUserMetadataListModelByID(ctx, userID, resourceOwner)
|
||||
removeMetadata, err := c.getUserMetadataListModelByID(ctx, userID, userResourceOwner)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -153,24 +191,6 @@ func (c *Commands) BulkRemoveUserMetadata(ctx context.Context, userID, resourceO
|
||||
return writeModelToObjectDetails(&removeMetadata.WriteModel), nil
|
||||
}
|
||||
|
||||
func (c *Commands) removeUserMetadataFromOrg(ctx context.Context, resourceOwner string) ([]eventstore.Command, error) {
|
||||
existingUserMetadata, err := c.getUserMetadataByOrgListModelByID(ctx, resourceOwner)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(existingUserMetadata.UserMetadata) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
events := make([]eventstore.Command, 0)
|
||||
for key, value := range existingUserMetadata.UserMetadata {
|
||||
if len(value) == 0 {
|
||||
continue
|
||||
}
|
||||
events = append(events, user.NewMetadataRemovedAllEvent(ctx, &user.NewAggregate(key, resourceOwner).Aggregate))
|
||||
}
|
||||
return events, nil
|
||||
}
|
||||
|
||||
func (c *Commands) removeUserMetadata(ctx context.Context, userAgg *eventstore.Aggregate, metadataKey string) (command eventstore.Command, err error) {
|
||||
command = user.NewMetadataRemovedEvent(
|
||||
ctx,
|
||||
@@ -197,12 +217,3 @@ func (c *Commands) getUserMetadataListModelByID(ctx context.Context, userID, res
|
||||
}
|
||||
return userMetadataWriteModel, nil
|
||||
}
|
||||
|
||||
func (c *Commands) getUserMetadataByOrgListModelByID(ctx context.Context, resourceOwner string) (*UserMetadataByOrgListWriteModel, error) {
|
||||
userMetadataWriteModel := NewUserMetadataByOrgListWriteModel(resourceOwner)
|
||||
err := c.eventstore.FilterToQueryReducer(ctx, userMetadataWriteModel)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return userMetadataWriteModel, nil
|
||||
}
|
||||
|
||||
@@ -16,7 +16,8 @@ import (
|
||||
|
||||
func TestCommandSide_SetUserMetadata(t *testing.T) {
|
||||
type fields struct {
|
||||
eventstore *eventstore.Eventstore
|
||||
eventstore func(t *testing.T) *eventstore.Eventstore
|
||||
checkPermission domain.PermissionCheck
|
||||
}
|
||||
type (
|
||||
args struct {
|
||||
@@ -39,10 +40,10 @@ func TestCommandSide_SetUserMetadata(t *testing.T) {
|
||||
{
|
||||
name: "user not existing, pre condition error",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(),
|
||||
),
|
||||
checkPermission: newMockPermissionCheckAllowed(),
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
@@ -60,8 +61,7 @@ func TestCommandSide_SetUserMetadata(t *testing.T) {
|
||||
{
|
||||
name: "invalid metadata, pre condition error",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
user.NewHumanAddedEvent(context.Background(),
|
||||
@@ -78,7 +78,17 @@ func TestCommandSide_SetUserMetadata(t *testing.T) {
|
||||
),
|
||||
),
|
||||
),
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
user.NewMetadataSetEvent(context.Background(),
|
||||
&user.NewAggregate("user1", "org1").Aggregate,
|
||||
"key",
|
||||
[]byte("value"),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
checkPermission: newMockPermissionCheckAllowed(),
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
@@ -93,10 +103,9 @@ func TestCommandSide_SetUserMetadata(t *testing.T) {
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "add metadata, ok",
|
||||
name: "add metadata, no permission",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
user.NewHumanAddedEvent(context.Background(),
|
||||
@@ -113,6 +122,43 @@ func TestCommandSide_SetUserMetadata(t *testing.T) {
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
checkPermission: newMockPermissionCheckNotAllowed(),
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
orgID: "org1",
|
||||
userID: "user1",
|
||||
metadata: &domain.Metadata{
|
||||
Key: "key",
|
||||
Value: []byte("value"),
|
||||
},
|
||||
},
|
||||
res: res{
|
||||
err: zerrors.IsPermissionDenied,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "add metadata, ok",
|
||||
fields: fields{
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
user.NewHumanAddedEvent(context.Background(),
|
||||
&user.NewAggregate("user1", "org1").Aggregate,
|
||||
"username",
|
||||
"firstname",
|
||||
"lastname",
|
||||
"",
|
||||
"firstname lastname",
|
||||
language.Und,
|
||||
domain.GenderUnspecified,
|
||||
"[email protected]",
|
||||
true,
|
||||
),
|
||||
),
|
||||
),
|
||||
expectFilter(),
|
||||
expectPush(
|
||||
user.NewMetadataSetEvent(context.Background(),
|
||||
&user.NewAggregate("user1", "org1").Aggregate,
|
||||
@@ -121,6 +167,7 @@ func TestCommandSide_SetUserMetadata(t *testing.T) {
|
||||
),
|
||||
),
|
||||
),
|
||||
checkPermission: newMockPermissionCheckAllowed(),
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
@@ -143,11 +190,116 @@ func TestCommandSide_SetUserMetadata(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "add metadata, reset, invalid",
|
||||
fields: fields{
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
user.NewHumanAddedEvent(context.Background(),
|
||||
&user.NewAggregate("user1", "org1").Aggregate,
|
||||
"username",
|
||||
"firstname",
|
||||
"lastname",
|
||||
"",
|
||||
"firstname lastname",
|
||||
language.Und,
|
||||
domain.GenderUnspecified,
|
||||
"[email protected]",
|
||||
true,
|
||||
),
|
||||
),
|
||||
),
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
user.NewMetadataSetEvent(context.Background(),
|
||||
&user.NewAggregate("user1", "org1").Aggregate,
|
||||
"key",
|
||||
[]byte("value"),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
checkPermission: newMockPermissionCheckAllowed(),
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
orgID: "org1",
|
||||
userID: "user1",
|
||||
metadata: &domain.Metadata{
|
||||
Key: "key",
|
||||
},
|
||||
},
|
||||
res: res{
|
||||
err: zerrors.IsErrorInvalidArgument,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "add metadata, reset, ok",
|
||||
fields: fields{
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
user.NewHumanAddedEvent(context.Background(),
|
||||
&user.NewAggregate("user1", "org1").Aggregate,
|
||||
"username",
|
||||
"firstname",
|
||||
"lastname",
|
||||
"",
|
||||
"firstname lastname",
|
||||
language.Und,
|
||||
domain.GenderUnspecified,
|
||||
"[email protected]",
|
||||
true,
|
||||
),
|
||||
),
|
||||
),
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
user.NewMetadataSetEvent(context.Background(),
|
||||
&user.NewAggregate("user1", "org1").Aggregate,
|
||||
"key",
|
||||
[]byte("value"),
|
||||
),
|
||||
),
|
||||
),
|
||||
expectPush(
|
||||
user.NewMetadataSetEvent(context.Background(),
|
||||
&user.NewAggregate("user1", "org1").Aggregate,
|
||||
"key",
|
||||
[]byte("value2"),
|
||||
),
|
||||
),
|
||||
),
|
||||
checkPermission: newMockPermissionCheckAllowed(),
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
orgID: "org1",
|
||||
userID: "user1",
|
||||
metadata: &domain.Metadata{
|
||||
Key: "key",
|
||||
Value: []byte("value2"),
|
||||
},
|
||||
},
|
||||
res: res{
|
||||
want: &domain.Metadata{
|
||||
ObjectRoot: models.ObjectRoot{
|
||||
AggregateID: "user1",
|
||||
ResourceOwner: "org1",
|
||||
},
|
||||
Key: "key",
|
||||
Value: []byte("value2"),
|
||||
State: domain.MetadataStateActive,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
r := &Commands{
|
||||
eventstore: tt.fields.eventstore,
|
||||
eventstore: tt.fields.eventstore(t),
|
||||
checkPermission: tt.fields.checkPermission,
|
||||
}
|
||||
got, err := r.SetUserMetadata(tt.args.ctx, tt.args.metadata, tt.args.userID, tt.args.orgID)
|
||||
if tt.res.err == nil {
|
||||
@@ -165,7 +317,8 @@ func TestCommandSide_SetUserMetadata(t *testing.T) {
|
||||
|
||||
func TestCommandSide_BulkSetUserMetadata(t *testing.T) {
|
||||
type fields struct {
|
||||
eventstore *eventstore.Eventstore
|
||||
eventstore func(t *testing.T) *eventstore.Eventstore
|
||||
checkPermission domain.PermissionCheck
|
||||
}
|
||||
type (
|
||||
args struct {
|
||||
@@ -188,9 +341,7 @@ func TestCommandSide_BulkSetUserMetadata(t *testing.T) {
|
||||
{
|
||||
name: "empty meta data list, pre condition error",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
),
|
||||
eventstore: expectEventstore(),
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
@@ -204,8 +355,7 @@ func TestCommandSide_BulkSetUserMetadata(t *testing.T) {
|
||||
{
|
||||
name: "user not existing, pre condition error",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(),
|
||||
),
|
||||
},
|
||||
@@ -225,8 +375,7 @@ func TestCommandSide_BulkSetUserMetadata(t *testing.T) {
|
||||
{
|
||||
name: "invalid metadata, pre condition error",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
user.NewHumanAddedEvent(context.Background(),
|
||||
@@ -243,7 +392,9 @@ func TestCommandSide_BulkSetUserMetadata(t *testing.T) {
|
||||
),
|
||||
),
|
||||
),
|
||||
expectFilter(),
|
||||
),
|
||||
checkPermission: newMockPermissionCheckAllowed(),
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
@@ -259,10 +410,9 @@ func TestCommandSide_BulkSetUserMetadata(t *testing.T) {
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "add metadata, ok",
|
||||
name: "add metadata, no permission",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
user.NewHumanAddedEvent(context.Background(),
|
||||
@@ -279,6 +429,43 @@ func TestCommandSide_BulkSetUserMetadata(t *testing.T) {
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
checkPermission: newMockPermissionCheckNotAllowed(),
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
orgID: "org1",
|
||||
userID: "user1",
|
||||
metadataList: []*domain.Metadata{
|
||||
{Key: "key", Value: []byte("value")},
|
||||
{Key: "key1", Value: []byte("value1")},
|
||||
},
|
||||
},
|
||||
res: res{
|
||||
err: zerrors.IsPermissionDenied,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "add metadata, ok",
|
||||
fields: fields{
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
user.NewHumanAddedEvent(context.Background(),
|
||||
&user.NewAggregate("user1", "org1").Aggregate,
|
||||
"username",
|
||||
"firstname",
|
||||
"lastname",
|
||||
"",
|
||||
"firstname lastname",
|
||||
language.Und,
|
||||
domain.GenderUnspecified,
|
||||
"[email protected]",
|
||||
true,
|
||||
),
|
||||
),
|
||||
),
|
||||
expectFilter(),
|
||||
expectPush(
|
||||
user.NewMetadataSetEvent(context.Background(),
|
||||
&user.NewAggregate("user1", "org1").Aggregate,
|
||||
@@ -292,6 +479,7 @@ func TestCommandSide_BulkSetUserMetadata(t *testing.T) {
|
||||
),
|
||||
),
|
||||
),
|
||||
checkPermission: newMockPermissionCheckAllowed(),
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
@@ -312,7 +500,8 @@ func TestCommandSide_BulkSetUserMetadata(t *testing.T) {
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
r := &Commands{
|
||||
eventstore: tt.fields.eventstore,
|
||||
eventstore: tt.fields.eventstore(t),
|
||||
checkPermission: tt.fields.checkPermission,
|
||||
}
|
||||
got, err := r.BulkSetUserMetadata(tt.args.ctx, tt.args.userID, tt.args.orgID, tt.args.metadataList...)
|
||||
if tt.res.err == nil {
|
||||
@@ -330,7 +519,8 @@ func TestCommandSide_BulkSetUserMetadata(t *testing.T) {
|
||||
|
||||
func TestCommandSide_UserRemoveMetadata(t *testing.T) {
|
||||
type fields struct {
|
||||
eventstore *eventstore.Eventstore
|
||||
eventstore func(t *testing.T) *eventstore.Eventstore
|
||||
checkPermission domain.PermissionCheck
|
||||
}
|
||||
type (
|
||||
args struct {
|
||||
@@ -353,8 +543,7 @@ func TestCommandSide_UserRemoveMetadata(t *testing.T) {
|
||||
{
|
||||
name: "user not existing, pre condition error",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(),
|
||||
),
|
||||
},
|
||||
@@ -371,9 +560,7 @@ func TestCommandSide_UserRemoveMetadata(t *testing.T) {
|
||||
{
|
||||
name: "invalid metadata, pre condition error",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
),
|
||||
eventstore: expectEventstore(),
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
@@ -388,8 +575,7 @@ func TestCommandSide_UserRemoveMetadata(t *testing.T) {
|
||||
{
|
||||
name: "meta data not existing, not found error",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
user.NewHumanAddedEvent(context.Background(),
|
||||
@@ -408,6 +594,7 @@ func TestCommandSide_UserRemoveMetadata(t *testing.T) {
|
||||
),
|
||||
expectFilter(),
|
||||
),
|
||||
checkPermission: newMockPermissionCheckAllowed(),
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
@@ -419,11 +606,43 @@ func TestCommandSide_UserRemoveMetadata(t *testing.T) {
|
||||
err: zerrors.IsNotFound,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "remove metadata, no permission",
|
||||
fields: fields{
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
user.NewHumanAddedEvent(context.Background(),
|
||||
&user.NewAggregate("user1", "org1").Aggregate,
|
||||
"username",
|
||||
"firstname",
|
||||
"lastname",
|
||||
"",
|
||||
"firstname lastname",
|
||||
language.Und,
|
||||
domain.GenderUnspecified,
|
||||
"[email protected]",
|
||||
true,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
checkPermission: newMockPermissionCheckNotAllowed(),
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
orgID: "org1",
|
||||
userID: "user1",
|
||||
metadataKey: "key",
|
||||
},
|
||||
res: res{
|
||||
err: zerrors.IsPermissionDenied,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "remove metadata, ok",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
user.NewHumanAddedEvent(context.Background(),
|
||||
@@ -456,6 +675,7 @@ func TestCommandSide_UserRemoveMetadata(t *testing.T) {
|
||||
),
|
||||
),
|
||||
),
|
||||
checkPermission: newMockPermissionCheckAllowed(),
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
@@ -473,7 +693,8 @@ func TestCommandSide_UserRemoveMetadata(t *testing.T) {
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
r := &Commands{
|
||||
eventstore: tt.fields.eventstore,
|
||||
eventstore: tt.fields.eventstore(t),
|
||||
checkPermission: tt.fields.checkPermission,
|
||||
}
|
||||
got, err := r.RemoveUserMetadata(tt.args.ctx, tt.args.metadataKey, tt.args.userID, tt.args.orgID)
|
||||
if tt.res.err == nil {
|
||||
@@ -491,7 +712,8 @@ func TestCommandSide_UserRemoveMetadata(t *testing.T) {
|
||||
|
||||
func TestCommandSide_BulkRemoveUserMetadata(t *testing.T) {
|
||||
type fields struct {
|
||||
eventstore *eventstore.Eventstore
|
||||
eventstore func(t *testing.T) *eventstore.Eventstore
|
||||
checkPermission domain.PermissionCheck
|
||||
}
|
||||
type (
|
||||
args struct {
|
||||
@@ -514,9 +736,7 @@ func TestCommandSide_BulkRemoveUserMetadata(t *testing.T) {
|
||||
{
|
||||
name: "empty meta data list, pre condition error",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
),
|
||||
eventstore: expectEventstore(),
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
@@ -530,8 +750,7 @@ func TestCommandSide_BulkRemoveUserMetadata(t *testing.T) {
|
||||
{
|
||||
name: "user not existing, pre condition error",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(),
|
||||
),
|
||||
},
|
||||
@@ -548,8 +767,7 @@ func TestCommandSide_BulkRemoveUserMetadata(t *testing.T) {
|
||||
{
|
||||
name: "remove metadata keys not existing, precondition error",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
user.NewHumanAddedEvent(context.Background(),
|
||||
@@ -576,6 +794,7 @@ func TestCommandSide_BulkRemoveUserMetadata(t *testing.T) {
|
||||
),
|
||||
),
|
||||
),
|
||||
checkPermission: newMockPermissionCheckAllowed(),
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
@@ -590,8 +809,7 @@ func TestCommandSide_BulkRemoveUserMetadata(t *testing.T) {
|
||||
{
|
||||
name: "invalid metadata, pre condition error",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
user.NewHumanAddedEvent(context.Background(),
|
||||
@@ -625,6 +843,7 @@ func TestCommandSide_BulkRemoveUserMetadata(t *testing.T) {
|
||||
),
|
||||
),
|
||||
),
|
||||
checkPermission: newMockPermissionCheckAllowed(),
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
@@ -636,11 +855,43 @@ func TestCommandSide_BulkRemoveUserMetadata(t *testing.T) {
|
||||
err: zerrors.IsErrorInvalidArgument,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "remove metadata, no permission",
|
||||
fields: fields{
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
user.NewHumanAddedEvent(context.Background(),
|
||||
&user.NewAggregate("user1", "org1").Aggregate,
|
||||
"username",
|
||||
"firstname",
|
||||
"lastname",
|
||||
"",
|
||||
"firstname lastname",
|
||||
language.Und,
|
||||
domain.GenderUnspecified,
|
||||
"[email protected]",
|
||||
true,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
checkPermission: newMockPermissionCheckNotAllowed(),
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
orgID: "org1",
|
||||
userID: "user1",
|
||||
metadataList: []string{"key", "key1"},
|
||||
},
|
||||
res: res{
|
||||
err: zerrors.IsPermissionDenied,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "remove metadata, ok",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
user.NewHumanAddedEvent(context.Background(),
|
||||
@@ -684,6 +935,7 @@ func TestCommandSide_BulkRemoveUserMetadata(t *testing.T) {
|
||||
),
|
||||
),
|
||||
),
|
||||
checkPermission: newMockPermissionCheckAllowed(),
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
@@ -701,7 +953,8 @@ func TestCommandSide_BulkRemoveUserMetadata(t *testing.T) {
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
r := &Commands{
|
||||
eventstore: tt.fields.eventstore,
|
||||
eventstore: tt.fields.eventstore(t),
|
||||
checkPermission: tt.fields.checkPermission,
|
||||
}
|
||||
got, err := r.BulkRemoveUserMetadata(tt.args.ctx, tt.args.userID, tt.args.orgID, tt.args.metadataList...)
|
||||
if tt.res.err == nil {
|
||||
|
||||
@@ -2,7 +2,6 @@ package command
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/zitadel/logging"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/domain"
|
||||
@@ -150,7 +149,7 @@ func (c *Commands) RemoveUserV2(ctx context.Context, userID, resourceOwner strin
|
||||
events = append(events, user.NewUserRemovedEvent(ctx, &existingUser.Aggregate().Aggregate, existingUser.UserName, existingUser.IDPLinks, domainPolicy.UserLoginMustBeDomain))
|
||||
|
||||
for _, grantID := range cascadingGrantIDs {
|
||||
removeEvent, _, err := c.removeUserGrant(ctx, grantID, "", true)
|
||||
removeEvent, _, err := c.removeUserGrant(ctx, grantID, "", true, true, nil)
|
||||
if err != nil {
|
||||
logging.WithFields("usergrantid", grantID).WithError(err).Warn("could not cascade remove role on user grant")
|
||||
continue
|
||||
|
||||
@@ -25,10 +25,6 @@ func (i *Member) IsValid() bool {
|
||||
return i.AggregateID != "" && i.UserID != "" && len(i.Roles) != 0
|
||||
}
|
||||
|
||||
func (i *Member) IsIAMValid() bool {
|
||||
return i.UserID != "" && len(i.Roles) != 0
|
||||
}
|
||||
|
||||
type MemberState int32
|
||||
|
||||
const (
|
||||
@@ -42,3 +38,7 @@ const (
|
||||
func (f MemberState) Valid() bool {
|
||||
return f >= 0 && f < memberStateCount
|
||||
}
|
||||
|
||||
func (f MemberState) Exists() bool {
|
||||
return f != MemberStateRemoved && f != MemberStateUnspecified
|
||||
}
|
||||
|
||||
@@ -27,29 +27,44 @@ func (p *Permissions) appendPermission(ctxID, permission string) {
|
||||
type PermissionCheck func(ctx context.Context, permission, resourceOwnerID, aggregateID string) (err error)
|
||||
|
||||
const (
|
||||
PermissionUserWrite = "user.write"
|
||||
PermissionUserRead = "user.read"
|
||||
PermissionUserDelete = "user.delete"
|
||||
PermissionUserCredentialWrite = "user.credential.write"
|
||||
PermissionSessionWrite = "session.write"
|
||||
PermissionSessionRead = "session.read"
|
||||
PermissionSessionLink = "session.link"
|
||||
PermissionSessionDelete = "session.delete"
|
||||
PermissionOrgRead = "org.read"
|
||||
PermissionIDPRead = "iam.idp.read"
|
||||
PermissionOrgIDPRead = "org.idp.read"
|
||||
PermissionProjectWrite = "project.write"
|
||||
PermissionProjectRead = "project.read"
|
||||
PermissionProjectDelete = "project.delete"
|
||||
PermissionProjectGrantWrite = "project.grant.write"
|
||||
PermissionProjectGrantRead = "project.grant.read"
|
||||
PermissionProjectGrantDelete = "project.grant.delete"
|
||||
PermissionProjectRoleWrite = "project.role.write"
|
||||
PermissionProjectRoleRead = "project.role.read"
|
||||
PermissionProjectRoleDelete = "project.role.delete"
|
||||
PermissionProjectAppWrite = "project.app.write"
|
||||
PermissionProjectAppDelete = "project.app.delete"
|
||||
PermissionProjectAppRead = "project.app.read"
|
||||
PermissionUserWrite = "user.write"
|
||||
PermissionUserRead = "user.read"
|
||||
PermissionUserDelete = "user.delete"
|
||||
PermissionUserCredentialWrite = "user.credential.write"
|
||||
PermissionSessionWrite = "session.write"
|
||||
PermissionSessionRead = "session.read"
|
||||
PermissionSessionLink = "session.link"
|
||||
PermissionSessionDelete = "session.delete"
|
||||
PermissionOrgRead = "org.read"
|
||||
PermissionIDPRead = "iam.idp.read"
|
||||
PermissionOrgIDPRead = "org.idp.read"
|
||||
PermissionProjectWrite = "project.write"
|
||||
PermissionProjectRead = "project.read"
|
||||
PermissionProjectDelete = "project.delete"
|
||||
PermissionProjectGrantWrite = "project.grant.write"
|
||||
PermissionProjectGrantRead = "project.grant.read"
|
||||
PermissionProjectGrantDelete = "project.grant.delete"
|
||||
PermissionProjectRoleWrite = "project.role.write"
|
||||
PermissionProjectRoleRead = "project.role.read"
|
||||
PermissionProjectRoleDelete = "project.role.delete"
|
||||
PermissionProjectAppWrite = "project.app.write"
|
||||
PermissionProjectAppDelete = "project.app.delete"
|
||||
PermissionProjectAppRead = "project.app.read"
|
||||
PermissionInstanceMemberWrite = "iam.member.write"
|
||||
PermissionInstanceMemberDelete = "iam.member.delete"
|
||||
PermissionInstanceMemberRead = "iam.member.read"
|
||||
PermissionOrgMemberWrite = "org.member.write"
|
||||
PermissionOrgMemberDelete = "org.member.delete"
|
||||
PermissionOrgMemberRead = "org.member.read"
|
||||
PermissionProjectMemberWrite = "project.member.write"
|
||||
PermissionProjectMemberDelete = "project.member.delete"
|
||||
PermissionProjectMemberRead = "project.member.read"
|
||||
PermissionProjectGrantMemberWrite = "project.grant.member.write"
|
||||
PermissionProjectGrantMemberDelete = "project.grant.member.delete"
|
||||
PermissionProjectGrantMemberRead = "project.grant.member.read"
|
||||
PermissionUserGrantWrite = "user.grant.write"
|
||||
PermissionUserGrantRead = "user.grant.read"
|
||||
PermissionUserGrantDelete = "user.grant.delete"
|
||||
)
|
||||
|
||||
// ProjectPermissionCheck is used as a check for preconditions dependent on application, project, user resourceowner and usergrants.
|
||||
|
||||
+162
-82
@@ -2,6 +2,7 @@ package integration
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"sync"
|
||||
"testing"
|
||||
@@ -24,11 +25,13 @@ import (
|
||||
"github.com/zitadel/zitadel/pkg/grpc/admin"
|
||||
app "github.com/zitadel/zitadel/pkg/grpc/app/v2beta"
|
||||
"github.com/zitadel/zitadel/pkg/grpc/auth"
|
||||
authorization "github.com/zitadel/zitadel/pkg/grpc/authorization/v2beta"
|
||||
"github.com/zitadel/zitadel/pkg/grpc/feature/v2"
|
||||
feature_v2beta "github.com/zitadel/zitadel/pkg/grpc/feature/v2beta"
|
||||
"github.com/zitadel/zitadel/pkg/grpc/idp"
|
||||
idp_pb "github.com/zitadel/zitadel/pkg/grpc/idp/v2"
|
||||
instance "github.com/zitadel/zitadel/pkg/grpc/instance/v2beta"
|
||||
internal_permission_v2beta "github.com/zitadel/zitadel/pkg/grpc/internal_permission/v2beta"
|
||||
mgmt "github.com/zitadel/zitadel/pkg/grpc/management"
|
||||
"github.com/zitadel/zitadel/pkg/grpc/object/v2"
|
||||
object_v3alpha "github.com/zitadel/zitadel/pkg/grpc/object/v3alpha"
|
||||
@@ -52,33 +55,35 @@ import (
|
||||
)
|
||||
|
||||
type Client struct {
|
||||
CC *grpc.ClientConn
|
||||
Admin admin.AdminServiceClient
|
||||
Mgmt mgmt.ManagementServiceClient
|
||||
Auth auth.AuthServiceClient
|
||||
UserV2beta user_v2beta.UserServiceClient
|
||||
UserV2 user_v2.UserServiceClient
|
||||
SessionV2beta session_v2beta.SessionServiceClient
|
||||
SessionV2 session.SessionServiceClient
|
||||
SettingsV2beta settings_v2beta.SettingsServiceClient
|
||||
SettingsV2 settings.SettingsServiceClient
|
||||
OIDCv2beta oidc_pb_v2beta.OIDCServiceClient
|
||||
OIDCv2 oidc_pb.OIDCServiceClient
|
||||
OrgV2beta org_v2beta.OrganizationServiceClient
|
||||
OrgV2 org.OrganizationServiceClient
|
||||
ActionV2beta action.ActionServiceClient
|
||||
FeatureV2beta feature_v2beta.FeatureServiceClient
|
||||
FeatureV2 feature.FeatureServiceClient
|
||||
UserSchemaV3 userschema_v3alpha.ZITADELUserSchemasClient
|
||||
WebKeyV2Beta webkey_v2beta.WebKeyServiceClient
|
||||
WebKeyV2 webkey_v2.WebKeyServiceClient
|
||||
IDPv2 idp_pb.IdentityProviderServiceClient
|
||||
UserV3Alpha user_v3alpha.ZITADELUsersClient
|
||||
SAMLv2 saml_pb.SAMLServiceClient
|
||||
SCIM *scim.Client
|
||||
Projectv2Beta project_v2beta.ProjectServiceClient
|
||||
InstanceV2Beta instance.InstanceServiceClient
|
||||
AppV2Beta app.AppServiceClient
|
||||
CC *grpc.ClientConn
|
||||
Admin admin.AdminServiceClient
|
||||
Mgmt mgmt.ManagementServiceClient
|
||||
Auth auth.AuthServiceClient
|
||||
UserV2beta user_v2beta.UserServiceClient
|
||||
UserV2 user_v2.UserServiceClient
|
||||
SessionV2beta session_v2beta.SessionServiceClient
|
||||
SessionV2 session.SessionServiceClient
|
||||
SettingsV2beta settings_v2beta.SettingsServiceClient
|
||||
SettingsV2 settings.SettingsServiceClient
|
||||
OIDCv2beta oidc_pb_v2beta.OIDCServiceClient
|
||||
OIDCv2 oidc_pb.OIDCServiceClient
|
||||
OrgV2beta org_v2beta.OrganizationServiceClient
|
||||
OrgV2 org.OrganizationServiceClient
|
||||
ActionV2beta action.ActionServiceClient
|
||||
FeatureV2beta feature_v2beta.FeatureServiceClient
|
||||
FeatureV2 feature.FeatureServiceClient
|
||||
UserSchemaV3 userschema_v3alpha.ZITADELUserSchemasClient
|
||||
WebKeyV2 webkey_v2.WebKeyServiceClient
|
||||
WebKeyV2Beta webkey_v2beta.WebKeyServiceClient
|
||||
IDPv2 idp_pb.IdentityProviderServiceClient
|
||||
UserV3Alpha user_v3alpha.ZITADELUsersClient
|
||||
SAMLv2 saml_pb.SAMLServiceClient
|
||||
SCIM *scim.Client
|
||||
Projectv2Beta project_v2beta.ProjectServiceClient
|
||||
InstanceV2Beta instance.InstanceServiceClient
|
||||
AppV2Beta app.AppServiceClient
|
||||
InternalPermissionv2Beta internal_permission_v2beta.InternalPermissionServiceClient
|
||||
AuthorizationV2Beta authorization.AuthorizationServiceClient
|
||||
}
|
||||
|
||||
func NewDefaultClient(ctx context.Context) (*Client, error) {
|
||||
@@ -93,33 +98,35 @@ func newClient(ctx context.Context, target string) (*Client, error) {
|
||||
return nil, err
|
||||
}
|
||||
client := &Client{
|
||||
CC: cc,
|
||||
Admin: admin.NewAdminServiceClient(cc),
|
||||
Mgmt: mgmt.NewManagementServiceClient(cc),
|
||||
Auth: auth.NewAuthServiceClient(cc),
|
||||
UserV2beta: user_v2beta.NewUserServiceClient(cc),
|
||||
UserV2: user_v2.NewUserServiceClient(cc),
|
||||
SessionV2beta: session_v2beta.NewSessionServiceClient(cc),
|
||||
SessionV2: session.NewSessionServiceClient(cc),
|
||||
SettingsV2beta: settings_v2beta.NewSettingsServiceClient(cc),
|
||||
SettingsV2: settings.NewSettingsServiceClient(cc),
|
||||
OIDCv2beta: oidc_pb_v2beta.NewOIDCServiceClient(cc),
|
||||
OIDCv2: oidc_pb.NewOIDCServiceClient(cc),
|
||||
OrgV2beta: org_v2beta.NewOrganizationServiceClient(cc),
|
||||
OrgV2: org.NewOrganizationServiceClient(cc),
|
||||
ActionV2beta: action.NewActionServiceClient(cc),
|
||||
FeatureV2beta: feature_v2beta.NewFeatureServiceClient(cc),
|
||||
FeatureV2: feature.NewFeatureServiceClient(cc),
|
||||
UserSchemaV3: userschema_v3alpha.NewZITADELUserSchemasClient(cc),
|
||||
WebKeyV2Beta: webkey_v2beta.NewWebKeyServiceClient(cc),
|
||||
WebKeyV2: webkey_v2.NewWebKeyServiceClient(cc),
|
||||
IDPv2: idp_pb.NewIdentityProviderServiceClient(cc),
|
||||
UserV3Alpha: user_v3alpha.NewZITADELUsersClient(cc),
|
||||
SAMLv2: saml_pb.NewSAMLServiceClient(cc),
|
||||
SCIM: scim.NewScimClient(target),
|
||||
Projectv2Beta: project_v2beta.NewProjectServiceClient(cc),
|
||||
InstanceV2Beta: instance.NewInstanceServiceClient(cc),
|
||||
AppV2Beta: app.NewAppServiceClient(cc),
|
||||
CC: cc,
|
||||
Admin: admin.NewAdminServiceClient(cc),
|
||||
Mgmt: mgmt.NewManagementServiceClient(cc),
|
||||
Auth: auth.NewAuthServiceClient(cc),
|
||||
UserV2beta: user_v2beta.NewUserServiceClient(cc),
|
||||
UserV2: user_v2.NewUserServiceClient(cc),
|
||||
SessionV2beta: session_v2beta.NewSessionServiceClient(cc),
|
||||
SessionV2: session.NewSessionServiceClient(cc),
|
||||
SettingsV2beta: settings_v2beta.NewSettingsServiceClient(cc),
|
||||
SettingsV2: settings.NewSettingsServiceClient(cc),
|
||||
OIDCv2beta: oidc_pb_v2beta.NewOIDCServiceClient(cc),
|
||||
OIDCv2: oidc_pb.NewOIDCServiceClient(cc),
|
||||
OrgV2beta: org_v2beta.NewOrganizationServiceClient(cc),
|
||||
OrgV2: org.NewOrganizationServiceClient(cc),
|
||||
ActionV2beta: action.NewActionServiceClient(cc),
|
||||
FeatureV2beta: feature_v2beta.NewFeatureServiceClient(cc),
|
||||
FeatureV2: feature.NewFeatureServiceClient(cc),
|
||||
UserSchemaV3: userschema_v3alpha.NewZITADELUserSchemasClient(cc),
|
||||
WebKeyV2: webkey_v2.NewWebKeyServiceClient(cc),
|
||||
WebKeyV2Beta: webkey_v2beta.NewWebKeyServiceClient(cc),
|
||||
IDPv2: idp_pb.NewIdentityProviderServiceClient(cc),
|
||||
UserV3Alpha: user_v3alpha.NewZITADELUsersClient(cc),
|
||||
SAMLv2: saml_pb.NewSAMLServiceClient(cc),
|
||||
SCIM: scim.NewScimClient(target),
|
||||
Projectv2Beta: project_v2beta.NewProjectServiceClient(cc),
|
||||
InstanceV2Beta: instance.NewInstanceServiceClient(cc),
|
||||
AppV2Beta: app.NewAppServiceClient(cc),
|
||||
InternalPermissionv2Beta: internal_permission_v2beta.NewInternalPermissionServiceClient(cc),
|
||||
AuthorizationV2Beta: authorization.NewAuthorizationServiceClient(cc),
|
||||
}
|
||||
return client, client.pollHealth(ctx)
|
||||
}
|
||||
@@ -239,7 +246,29 @@ func (i *Instance) CreateHumanUserWithTOTP(ctx context.Context, secret string) *
|
||||
return resp
|
||||
}
|
||||
|
||||
func (i *Instance) CreateUserTypeHuman(ctx context.Context) *user_v2.CreateUserResponse {
|
||||
func (i *Instance) SetUserMetadata(ctx context.Context, id, key, value string) *user_v2.SetUserMetadataResponse {
|
||||
resp, err := i.Client.UserV2.SetUserMetadata(ctx, &user_v2.SetUserMetadataRequest{
|
||||
UserId: id,
|
||||
Metadata: []*user_v2.Metadata{{
|
||||
Key: key,
|
||||
Value: []byte(base64.StdEncoding.EncodeToString([]byte(value))),
|
||||
},
|
||||
},
|
||||
})
|
||||
logging.OnError(err).Panic("set user metadata")
|
||||
return resp
|
||||
}
|
||||
|
||||
func (i *Instance) DeleteUserMetadata(ctx context.Context, id, key string) *user_v2.DeleteUserMetadataResponse {
|
||||
resp, err := i.Client.UserV2.DeleteUserMetadata(ctx, &user_v2.DeleteUserMetadataRequest{
|
||||
UserId: id,
|
||||
Keys: []string{key},
|
||||
})
|
||||
logging.OnError(err).Panic("delete user metadata")
|
||||
return resp
|
||||
}
|
||||
|
||||
func (i *Instance) CreateUserTypeHuman(ctx context.Context, email string) *user_v2.CreateUserResponse {
|
||||
resp, err := i.Client.UserV2.CreateUser(ctx, &user_v2.CreateUserRequest{
|
||||
OrganizationId: i.DefaultOrg.GetId(),
|
||||
UserType: &user_v2.CreateUserRequest_Human_{
|
||||
@@ -249,7 +278,7 @@ func (i *Instance) CreateUserTypeHuman(ctx context.Context) *user_v2.CreateUserR
|
||||
FamilyName: "Mouse",
|
||||
},
|
||||
Email: &user_v2.SetHumanEmail{
|
||||
Email: fmt.Sprintf("%[email protected]", time.Now().UnixNano()),
|
||||
Email: email,
|
||||
Verification: &user_v2.SetHumanEmail_ReturnCode{
|
||||
ReturnCode: &user_v2.ReturnEmailVerificationCode{},
|
||||
},
|
||||
@@ -262,7 +291,7 @@ func (i *Instance) CreateUserTypeHuman(ctx context.Context) *user_v2.CreateUserR
|
||||
return resp
|
||||
}
|
||||
|
||||
func (i *Instance) CreateUserTypeMachine(ctx context.Context) *user_v2.CreateUserResponse {
|
||||
func (i *Instance) CreateUserTypeMachine(ctx context.Context, orgId string) *user_v2.CreateUserResponse {
|
||||
resp, err := i.Client.UserV2.CreateUser(ctx, &user_v2.CreateUserRequest{
|
||||
OrganizationId: i.DefaultOrg.GetId(),
|
||||
UserType: &user_v2.CreateUserRequest_Machine_{
|
||||
@@ -629,14 +658,6 @@ func (i *Instance) AddOrgGenericOAuthProvider(ctx context.Context, name string)
|
||||
},
|
||||
})
|
||||
logging.OnError(err).Panic("create generic OAuth idp")
|
||||
/*
|
||||
mustAwait(func() error {
|
||||
_, err := i.Client.Mgmt.GetProviderByID(ctx, &mgmt.GetProviderByIDRequest{
|
||||
Id: resp.GetId(),
|
||||
})
|
||||
return err
|
||||
})
|
||||
*/
|
||||
return resp
|
||||
}
|
||||
|
||||
@@ -883,48 +904,107 @@ func (i *Instance) ActivateProjectGrant(ctx context.Context, t *testing.T, proje
|
||||
return resp
|
||||
}
|
||||
|
||||
func (i *Instance) CreateProjectUserGrant(t *testing.T, ctx context.Context, projectID, userID string) string {
|
||||
func (i *Instance) CreateProjectUserGrant(t *testing.T, ctx context.Context, projectID, userID string) *mgmt.AddUserGrantResponse {
|
||||
resp, err := i.Client.Mgmt.AddUserGrant(ctx, &mgmt.AddUserGrantRequest{
|
||||
UserId: userID,
|
||||
ProjectId: projectID,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
return resp.GetUserGrantId()
|
||||
return resp
|
||||
}
|
||||
|
||||
func (i *Instance) CreateProjectGrantUserGrant(ctx context.Context, orgID, projectID, projectGrantID, userID string) string {
|
||||
func (i *Instance) CreateProjectGrantUserGrant(ctx context.Context, orgID, projectID, projectGrantID, userID string) *mgmt.AddUserGrantResponse {
|
||||
resp, err := i.Client.Mgmt.AddUserGrant(SetOrgID(ctx, orgID), &mgmt.AddUserGrantRequest{
|
||||
UserId: userID,
|
||||
ProjectId: projectID,
|
||||
ProjectGrantId: projectGrantID,
|
||||
})
|
||||
logging.OnError(err).Panic("create project grant user grant")
|
||||
return resp.GetUserGrantId()
|
||||
return resp
|
||||
}
|
||||
|
||||
func (i *Instance) CreateOrgMembership(t *testing.T, ctx context.Context, userID string) {
|
||||
_, err := i.Client.Mgmt.AddOrgMember(ctx, &mgmt.AddOrgMemberRequest{
|
||||
func (i *Instance) CreateInstanceMembership(t *testing.T, ctx context.Context, userID string) *internal_permission_v2beta.CreateAdministratorResponse {
|
||||
resp, err := i.Client.InternalPermissionv2Beta.CreateAdministrator(ctx, &internal_permission_v2beta.CreateAdministratorRequest{
|
||||
Resource: &internal_permission_v2beta.ResourceType{
|
||||
Resource: &internal_permission_v2beta.ResourceType_Instance{Instance: true},
|
||||
},
|
||||
UserId: userID,
|
||||
Roles: []string{domain.RoleIAMOwner},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
return resp
|
||||
}
|
||||
|
||||
func (i *Instance) DeleteInstanceMembership(t *testing.T, ctx context.Context, userID string) {
|
||||
_, err := i.Client.Admin.RemoveIAMMember(ctx, &admin.RemoveIAMMemberRequest{
|
||||
UserId: userID,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
func (i *Instance) CreateOrgMembership(t *testing.T, ctx context.Context, orgID, userID string) *internal_permission_v2beta.CreateAdministratorResponse {
|
||||
resp, err := i.Client.InternalPermissionv2Beta.CreateAdministrator(ctx, &internal_permission_v2beta.CreateAdministratorRequest{
|
||||
Resource: &internal_permission_v2beta.ResourceType{
|
||||
Resource: &internal_permission_v2beta.ResourceType_OrganizationId{OrganizationId: orgID},
|
||||
},
|
||||
UserId: userID,
|
||||
Roles: []string{domain.RoleOrgOwner},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
return resp
|
||||
}
|
||||
|
||||
func (i *Instance) CreateProjectMembership(t *testing.T, ctx context.Context, projectID, userID string) {
|
||||
_, err := i.Client.Mgmt.AddProjectMember(ctx, &mgmt.AddProjectMemberRequest{
|
||||
ProjectId: projectID,
|
||||
UserId: userID,
|
||||
Roles: []string{domain.RoleProjectOwner},
|
||||
func (i *Instance) DeleteOrgMembership(t *testing.T, ctx context.Context, userID string) {
|
||||
_, err := i.Client.Mgmt.RemoveOrgMember(ctx, &mgmt.RemoveOrgMemberRequest{
|
||||
UserId: userID,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
func (i *Instance) CreateProjectGrantMembership(t *testing.T, ctx context.Context, projectID, grantID, userID string) {
|
||||
_, err := i.Client.Mgmt.AddProjectGrantMember(ctx, &mgmt.AddProjectGrantMemberRequest{
|
||||
ProjectId: projectID,
|
||||
GrantId: grantID,
|
||||
UserId: userID,
|
||||
Roles: []string{domain.RoleProjectGrantOwner},
|
||||
func (i *Instance) CreateProjectMembership(t *testing.T, ctx context.Context, projectID, userID string) *internal_permission_v2beta.CreateAdministratorResponse {
|
||||
resp, err := i.Client.InternalPermissionv2Beta.CreateAdministrator(ctx, &internal_permission_v2beta.CreateAdministratorRequest{
|
||||
Resource: &internal_permission_v2beta.ResourceType{
|
||||
Resource: &internal_permission_v2beta.ResourceType_ProjectId{ProjectId: projectID},
|
||||
},
|
||||
UserId: userID,
|
||||
Roles: []string{domain.RoleProjectOwner},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
return resp
|
||||
}
|
||||
|
||||
func (i *Instance) DeleteProjectMembership(t *testing.T, ctx context.Context, projectID, userID string) {
|
||||
_, err := i.Client.InternalPermissionv2Beta.DeleteAdministrator(ctx, &internal_permission_v2beta.DeleteAdministratorRequest{
|
||||
Resource: &internal_permission_v2beta.ResourceType{Resource: &internal_permission_v2beta.ResourceType_ProjectId{ProjectId: projectID}},
|
||||
UserId: userID,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
func (i *Instance) CreateProjectGrantMembership(t *testing.T, ctx context.Context, projectID, grantID, userID string) *internal_permission_v2beta.CreateAdministratorResponse {
|
||||
resp, err := i.Client.InternalPermissionv2Beta.CreateAdministrator(ctx, &internal_permission_v2beta.CreateAdministratorRequest{
|
||||
Resource: &internal_permission_v2beta.ResourceType{
|
||||
Resource: &internal_permission_v2beta.ResourceType_ProjectGrant_{ProjectGrant: &internal_permission_v2beta.ResourceType_ProjectGrant{
|
||||
ProjectId: projectID,
|
||||
ProjectGrantId: grantID,
|
||||
}},
|
||||
},
|
||||
UserId: userID,
|
||||
Roles: []string{domain.RoleProjectGrantOwner},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
return resp
|
||||
}
|
||||
|
||||
func (i *Instance) DeleteProjectGrantMembership(t *testing.T, ctx context.Context, projectID, grantID, userID string) {
|
||||
_, err := i.Client.InternalPermissionv2Beta.DeleteAdministrator(ctx, &internal_permission_v2beta.DeleteAdministratorRequest{
|
||||
Resource: &internal_permission_v2beta.ResourceType{
|
||||
Resource: &internal_permission_v2beta.ResourceType_ProjectGrant_{ProjectGrant: &internal_permission_v2beta.ResourceType_ProjectGrant{
|
||||
ProjectId: projectID,
|
||||
ProjectGrantId: grantID,
|
||||
}},
|
||||
},
|
||||
UserId: userID,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
package integration
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/zitadel/zitadel/pkg/grpc/feature/v2"
|
||||
)
|
||||
|
||||
func EnsureInstanceFeature(t *testing.T, ctx context.Context, instance *Instance, features *feature.SetInstanceFeaturesRequest, assertFeatures func(t *assert.CollectT, got *feature.GetInstanceFeaturesResponse)) {
|
||||
ctx = instance.WithAuthorizationToken(ctx, UserTypeIAMOwner)
|
||||
_, err := instance.Client.FeatureV2.SetInstanceFeatures(ctx, features)
|
||||
require.NoError(t, err)
|
||||
retryDuration, tick := WaitForAndTickWithMaxDuration(ctx, 5*time.Minute)
|
||||
require.EventuallyWithT(t,
|
||||
func(tt *assert.CollectT) {
|
||||
got, err := instance.Client.FeatureV2.GetInstanceFeatures(ctx, &feature.GetInstanceFeaturesRequest{
|
||||
Inheritance: true,
|
||||
})
|
||||
require.NoError(tt, err)
|
||||
assertFeatures(tt, got)
|
||||
},
|
||||
retryDuration,
|
||||
tick,
|
||||
"timed out waiting for ensuring instance feature")
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user