mirror of
https://github.com/andwati/zitadel.git
synced 2026-11-06 22:32:16 +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
@@ -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
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user