From bb6b1895137826e3f2faefc69412a62638deda6b Mon Sep 17 00:00:00 2001 From: Jeff Mitchell Date: Thu, 13 Aug 2020 17:39:48 -0400 Subject: [PATCH] Create a default role on new scope creation (#265) --- api/roles/role_test.go | 4 +- internal/auth/auth.go | 4 +- internal/auth/option.go | 8 + internal/auth/testing.go | 1 + internal/cmd/base/servers.go | 2 +- internal/iam/repository_scope.go | 167 ++++++++++++++++-- internal/iam/repository_scope_test.go | 16 +- internal/iam/testing.go | 8 +- .../handlers/scopes/scope_service.go | 7 +- .../handlers/scopes/scope_service_test.go | 117 ++++++++---- 10 files changed, 264 insertions(+), 70 deletions(-) diff --git a/api/roles/role_test.go b/api/roles/role_test.go index 5a541333d..87cdf02f4 100644 --- a/api/roles/role_test.go +++ b/api/roles/role_test.go @@ -180,10 +180,10 @@ func TestRole_List(t *testing.T) { assert.Nil(apiErr) var defaultRoleIds []string if tt.name == "org" { - require.True(2 == len(p1)) + require.True(len(p1) == 2) defaultRoleIds = []string{p1[0].Id, p1[1].Id} } else { - require.Empty(p1) + require.Len(p1, 0) } var expected []*roles.Role diff --git a/internal/auth/auth.go b/internal/auth/auth.go index 393029185..180534a83 100644 --- a/internal/auth/auth.go +++ b/internal/auth/auth.go @@ -45,6 +45,7 @@ type RequestInfo struct { // This is used for operations on the scopes collection scopeIdOverride string + userIdOverride string // The following are useful for tests DisableAuthzFailures bool @@ -96,6 +97,7 @@ func Verify(ctx context.Context, opt ...Option) (ret VerifyResults) { // context we won't catch in tests panic("no verifier information found in context") } + opts := getOpts(opt...) ret.Scope = new(scopes.ScopeInfo) if v.requestInfo.DisableAuthEntirely { ret.Scope.Id = v.requestInfo.scopeIdOverride @@ -107,11 +109,11 @@ func Verify(ctx context.Context, opt ...Option) (ret VerifyResults) { case strings.HasPrefix(ret.Scope.Id, scope.Project.Prefix()): ret.Scope.Type = scope.Project.String() } + ret.UserId = v.requestInfo.userIdOverride ret.Valid = true return } v.ctx = ctx - opts := getOpts(opt...) v.requestInfo.scopeIdOverride = opts.withScopeId if err := v.parseAuthParams(); err != nil { v.logger.Trace("error reading auth parameters from URL", "url", v.requestInfo.Path, "method", v.requestInfo.Method, "error", err) diff --git a/internal/auth/option.go b/internal/auth/option.go index 7ba2bb509..1f571b597 100644 --- a/internal/auth/option.go +++ b/internal/auth/option.go @@ -14,11 +14,13 @@ type Option func(*options) // options = how options are represented type options struct { withScopeId string + withUserId string } func getDefaultOptions() options { return options{ withScopeId: "", + withUserId: "", } } @@ -27,3 +29,9 @@ func WithScopeId(id string) Option { o.withScopeId = id } } + +func WithUserId(id string) Option { + return func(o *options) { + o.withUserId = id + } +} diff --git a/internal/auth/testing.go b/internal/auth/testing.go index ed31702f4..a7c1f9c4b 100644 --- a/internal/auth/testing.go +++ b/internal/auth/testing.go @@ -7,5 +7,6 @@ func DisabledAuthTestContext(opt ...Option) context.Context { reqInfo := RequestInfo{DisableAuthEntirely: true} opts := getOpts(opt...) reqInfo.scopeIdOverride = opts.withScopeId + reqInfo.userIdOverride = opts.withUserId return NewVerifierContext(context.Background(), nil, nil, nil, reqInfo) } diff --git a/internal/cmd/base/servers.go b/internal/cmd/base/servers.go index ae84fba98..b49324e45 100644 --- a/internal/cmd/base/servers.go +++ b/internal/cmd/base/servers.go @@ -457,7 +457,7 @@ func (b *Server) CreateDevDatabase(dialect string) error { if err != nil { return fmt.Errorf("error creating new org scope: %w", err) } - orgScope, err = repo.CreateScope(ctx, orgScope, iam.WithPublicId(b.DefaultOrgId)) + orgScope, err = repo.CreateScope(ctx, orgScope, "", iam.WithPublicId(b.DefaultOrgId)) if err != nil { return fmt.Errorf("error persisting new org scope: %w", err) } diff --git a/internal/iam/repository_scope.go b/internal/iam/repository_scope.go index a88ec81cf..0c9d78947 100644 --- a/internal/iam/repository_scope.go +++ b/internal/iam/repository_scope.go @@ -8,12 +8,14 @@ import ( "github.com/hashicorp/boundary/internal/db" dbcommon "github.com/hashicorp/boundary/internal/db/common" + "github.com/hashicorp/boundary/internal/oplog" + "github.com/hashicorp/boundary/internal/types/resource" "github.com/hashicorp/boundary/internal/types/scope" ) // CreateScope will create a scope in the repository and return the written // scope. Supported options include: WithPublicId. -func (r *Repository) CreateScope(ctx context.Context, s *Scope, opt ...Option) (*Scope, error) { +func (r *Repository) CreateScope(ctx context.Context, s *Scope, userId string, opt ...Option) (*Scope, error) { if s == nil { return nil, fmt.Errorf("create scope: missing scope %w", db.ErrNilParameter) } @@ -29,31 +31,162 @@ func (r *Repository) CreateScope(ctx context.Context, s *Scope, opt ...Option) ( case scope.Global.String(): return nil, fmt.Errorf("create scope: invalid type: %w", db.ErrInvalidParameter) } + opts := getOpts(opt...) - var publicId string - t := scope.Map[s.Type] - if opts.withPublicId != "" { - if !strings.HasPrefix(opts.withPublicId, t.Prefix()+"_") { - return nil, fmt.Errorf("create scope: passed-in public ID %q has wrong prefix for type %q which uses prefix %q", opts.withPublicId, t.String(), t.Prefix()) + + var err error + var scopePublicId string + var scopeMetadata oplog.Metadata + var scopeRaw interface{} + { + scopeType := scope.Map[s.Type] + if opts.withPublicId != "" { + if !strings.HasPrefix(opts.withPublicId, scopeType.Prefix()+"_") { + return nil, fmt.Errorf("create scope: passed-in public ID %q has wrong prefix for type %q which uses prefix %q", opts.withPublicId, scopeType.String(), scopeType.Prefix()) + } + scopePublicId = opts.withPublicId + } else { + scopePublicId, err = newScopeId(scopeType) + if err != nil { + return nil, fmt.Errorf("create scope: error generating public id for new scope: %w", err) + } } - publicId = opts.withPublicId - } else { - var err error - publicId, err = newScopeId(t) + sc := s.Clone().(*Scope) + sc.PublicId = scopePublicId + scopeRaw = sc + scopeMetadata, err = r.stdMetadata(ctx, sc) if err != nil { - return nil, fmt.Errorf("create scope: error generating public id %w for new scope", err) + return nil, fmt.Errorf("create scope: error getting metadata for scope create: %w", err) + } + scopeMetadata["op-type"] = []string{oplog.OpType_OP_TYPE_CREATE.String()} + } + + var rolePublicId string + var roleMetadata oplog.Metadata + var role *Role + var roleRaw interface{} + switch userId { + case "", "u_anon", "u_auth": + // TODO: Cause a log entry. The repo doesn't have a logger right now, + // and ideally we will be using context to pass around log info scoped + // to this request for grouped display in the server log. The only + // reason this should ever happen anyways is via the administrative + // recovery workflow so it's already a special case. + + // Also, stop linter from complaining + _ = role + + default: + role, err = NewRole(scopePublicId) + if err != nil { + return nil, fmt.Errorf("create scope: error instantiating new role: %w", err) + } + rolePublicId, err = newRoleId() + if err != nil { + return nil, fmt.Errorf("create scope: error generating public id for new role: %w", err) + } + role.PublicId = rolePublicId + role.Name = "on-scope-creation" + role.Description = fmt.Sprintf("Role created for administration of scope %s at its creation time", scopePublicId) + roleRaw = role + roleMetadata = oplog.Metadata{ + "resource-public-id": []string{rolePublicId}, + "scope-id": []string{scopePublicId}, + "scope-type": []string{s.Type}, + "resource-type": []string{resource.Role.String()}, + "op-type": []string{oplog.OpType_OP_TYPE_CREATE.String()}, } } - sc := s.Clone().(*Scope) - sc.PublicId = publicId - resource, err := r.create(ctx, sc) + + _, err = r.writer.DoTx( + ctx, + db.StdRetryCnt, + db.ExpBackoff{}, + func(_ db.Reader, w db.Writer) error { + if err := w.Create( + ctx, + scopeRaw, + db.WithOplog(r.wrapper, scopeMetadata), + ); err != nil { + return fmt.Errorf("error creating scope: %w", err) + } + + // We create a new role, then set grants and principals on it. This + // turns into a bunch of stuff sadly because the role is the + // aggregate. + if roleRaw != nil { + if err := w.Create( + ctx, + roleRaw, + db.WithOplog(r.wrapper, roleMetadata), + ); err != nil { + return fmt.Errorf("error creating role: %w", err) + } + + role = roleRaw.(*Role) + + msgs := make([]*oplog.Message, 0, 3) + roleTicket, err := w.GetTicket(role) + if err != nil { + return fmt.Errorf("unable to get ticket: %w", err) + } + + // We need to update the role version as that's the aggregate + var roleOplogMsg oplog.Message + rowsUpdated, err := w.Update(ctx, role, []string{"Version"}, nil, db.NewOplogMsg(&roleOplogMsg), db.WithVersion(&role.Version)) + if err != nil { + return fmt.Errorf("unable to update role version for adding grant: %w", err) + } + if rowsUpdated != 1 { + return fmt.Errorf("updated role but %d rows updated", rowsUpdated) + } + + msgs = append(msgs, &roleOplogMsg) + + roleGrant, err := NewRoleGrant(rolePublicId, "id=*;actions=*") + if err != nil { + return fmt.Errorf("unable to create in memory role grant: %w", err) + } + roleGrantOplogMsgs := make([]*oplog.Message, 0, 1) + if err := w.CreateItems(ctx, []interface{}{roleGrant}, db.NewOplogMsgs(&roleGrantOplogMsgs)); err != nil { + return fmt.Errorf("unable to add grants: %w", err) + } + msgs = append(msgs, roleGrantOplogMsgs...) + + rolePrincipal, err := NewUserRole(rolePublicId, userId) + if err != nil { + return fmt.Errorf("unable to create in memory role user: %w", err) + } + roleUserOplogMsgs := make([]*oplog.Message, 0, 1) + if err := w.CreateItems(ctx, []interface{}{rolePrincipal}, db.NewOplogMsgs(&roleUserOplogMsgs)); err != nil { + return fmt.Errorf("unable to add grants: %w", err) + } + msgs = append(msgs, roleUserOplogMsgs...) + + s := scopeRaw.(*Scope) + + metadata := oplog.Metadata{ + "op-type": []string{oplog.OpType_OP_TYPE_CREATE.String()}, + "scope-id": []string{s.PublicId}, + "scope-type": []string{s.Type}, + "resource-public-id": []string{role.PublicId}, + } + if err := w.WriteOplogEntryWith(ctx, r.wrapper, roleTicket, metadata, msgs); err != nil { + return fmt.Errorf("unable to write oplog: %w", err) + } + } + + return nil + }, + ) + if err != nil { if db.IsUniqueError(err) { - return nil, fmt.Errorf("create scope: scope %s/%s already exists: %w", sc.PublicId, sc.Name, db.ErrNotUnique) + return nil, fmt.Errorf("create scope: scope %s/%s already exists: %w", scopePublicId, s.Name, db.ErrNotUnique) } - return nil, fmt.Errorf("create scope: %w for %s", err, sc.PublicId) + return nil, fmt.Errorf("create scope: id %s got error: %w", scopePublicId, err) } - return resource.(*Scope), nil + return scopeRaw.(*Scope), nil } // UpdateScope will update a scope in the repository and return the written diff --git a/internal/iam/repository_scope_test.go b/internal/iam/repository_scope_test.go index 4f85be860..469a640fe 100644 --- a/internal/iam/repository_scope_test.go +++ b/internal/iam/repository_scope_test.go @@ -30,7 +30,7 @@ func Test_Repository_Scope_Create(t *testing.T) { s, err := NewOrg(WithName(id)) require.NoError(err) - s, err = repo.CreateScope(context.Background(), s) + s, err = repo.CreateScope(context.Background(), s, "") require.NoError(err) require.NotNil(s) assert.NotEmpty(s.GetPublicId()) @@ -53,7 +53,7 @@ func Test_Repository_Scope_Create(t *testing.T) { require.NoError(err) s, err := NewOrg() require.NoError(err) - s, err = repo.CreateScope(context.Background(), s, WithPublicId(publicId)) + s, err = repo.CreateScope(context.Background(), s, "", WithPublicId(publicId)) require.NoError(err) require.NotNil(s) assert.Equal(publicId, s.GetPublicId()) @@ -74,7 +74,7 @@ func Test_Repository_Scope_Create(t *testing.T) { s, err := NewOrg(WithName(id)) require.NoError(err) - s, err = repo.CreateScope(context.Background(), s) + s, err = repo.CreateScope(context.Background(), s, "") require.NoError(err) require.NotNil(s) assert.NotEmpty(s.GetPublicId()) @@ -82,7 +82,7 @@ func Test_Repository_Scope_Create(t *testing.T) { s2, err := NewOrg(WithName(id)) require.NoError(err) - s2, err = repo.CreateScope(context.Background(), s2) + s2, err = repo.CreateScope(context.Background(), s2, "") require.Error(err) assert.Nil(s2) }) @@ -96,7 +96,7 @@ func Test_Repository_Scope_Create(t *testing.T) { s, err := NewOrg(WithName(id)) require.NoError(err) - s, err = repo.CreateScope(context.Background(), s) + s, err = repo.CreateScope(context.Background(), s, "") require.NoError(err) require.NotNil(s) assert.NotEmpty(s.GetPublicId()) @@ -104,13 +104,13 @@ func Test_Repository_Scope_Create(t *testing.T) { p, err := NewProject(s.PublicId, WithName(id)) require.NoError(err) - p, err = repo.CreateScope(context.Background(), p) + p, err = repo.CreateScope(context.Background(), p, "") require.NoError(err) require.NotEmpty(p.PublicId) p2, err := NewProject(s.PublicId, WithName(id)) require.NoError(err) - p2, err = repo.CreateScope(context.Background(), p2) + p2, err = repo.CreateScope(context.Background(), p2, "") assert.Error(err) assert.Nil(p2) }) @@ -178,7 +178,7 @@ func Test_Repository_Scope_Update(t *testing.T) { project, err := NewProject(s.PublicId) require.NoError(err) - project, err = repo.CreateScope(context.Background(), project) + project, err = repo.CreateScope(context.Background(), project, "") require.NoError(err) require.NotNil(project) diff --git a/internal/iam/testing.go b/internal/iam/testing.go index dd92f5dd5..a176601ef 100644 --- a/internal/iam/testing.go +++ b/internal/iam/testing.go @@ -24,14 +24,14 @@ func TestScopes(t *testing.T, conn *gorm.DB) (org *Scope, prj *Scope) { org, err = NewOrg() require.NoError(err) - org, err = repo.CreateScope(context.Background(), org) + org, err = repo.CreateScope(context.Background(), org, "") require.NoError(err) require.NotNil(org) require.NotEmpty(org.GetPublicId()) prj, err = NewProject(org.GetPublicId()) require.NoError(err) - prj, err = repo.CreateScope(context.Background(), prj) + prj, err = repo.CreateScope(context.Background(), prj, "") require.NoError(err) require.NotNil(prj) require.NotEmpty(prj.GetPublicId()) @@ -49,7 +49,7 @@ func testOrg(t *testing.T, conn *gorm.DB, name, description string) (org *Scope) o, err := NewOrg(WithDescription(description), WithName(name)) require.NoError(err) - o, err = repo.CreateScope(context.Background(), o) + o, err = repo.CreateScope(context.Background(), o, "") require.NoError(err) require.NotNil(o) require.NotEmpty(o.GetPublicId()) @@ -66,7 +66,7 @@ func testProject(t *testing.T, conn *gorm.DB, orgId string, opt ...Option) *Scop p, err := NewProject(orgId, opt...) require.NoError(err) - p, err = repo.CreateScope(context.Background(), p) + p, err = repo.CreateScope(context.Background(), p, "") require.NoError(err) require.NotNil(p) require.NotEmpty(p.GetPublicId()) diff --git a/internal/servers/controller/handlers/scopes/scope_service.go b/internal/servers/controller/handlers/scopes/scope_service.go index ce5751f7b..e3667cd65 100644 --- a/internal/servers/controller/handlers/scopes/scope_service.go +++ b/internal/servers/controller/handlers/scopes/scope_service.go @@ -106,7 +106,7 @@ func (s Service) CreateScope(ctx context.Context, req *pbs.CreateScopeRequest) ( if err := validateCreateRequest(req); err != nil { return nil, err } - p, err := s.createInRepo(ctx, authResults.Scope, req.GetItem()) + p, err := s.createInRepo(ctx, authResults, req.GetItem()) if err != nil { return nil, err } @@ -162,7 +162,7 @@ func (s Service) getFromRepo(ctx context.Context, id string) (*pb.Scope, error) return ToProto(p), nil } -func (s Service) createInRepo(ctx context.Context, parentScope *scopes.ScopeInfo, item *pb.Scope) (*pb.Scope, error) { +func (s Service) createInRepo(ctx context.Context, authResults auth.VerifyResults, item *pb.Scope) (*pb.Scope, error) { var opts []iam.Option if item.GetName() != nil { opts = append(opts, iam.WithName(item.GetName().GetValue())) @@ -171,6 +171,7 @@ func (s Service) createInRepo(ctx context.Context, parentScope *scopes.ScopeInfo opts = append(opts, iam.WithDescription(item.GetDescription().GetValue())) } + parentScope := authResults.Scope var iamScope *iam.Scope var err error switch parentScope.GetType() { @@ -186,7 +187,7 @@ func (s Service) createInRepo(ctx context.Context, parentScope *scopes.ScopeInfo if err != nil { return nil, err } - out, err := repo.CreateScope(ctx, iamScope) + out, err := repo.CreateScope(ctx, iamScope, authResults.UserId) if err != nil { return nil, status.Errorf(codes.Internal, "Unable to create scope: %v.", err) } diff --git a/internal/servers/controller/handlers/scopes/scope_service_test.go b/internal/servers/controller/handlers/scopes/scope_service_test.go index ac741359b..cbf324125 100644 --- a/internal/servers/controller/handlers/scopes/scope_service_test.go +++ b/internal/servers/controller/handlers/scopes/scope_service_test.go @@ -2,6 +2,7 @@ package scopes_test import ( "context" + "fmt" "strings" "testing" @@ -211,7 +212,7 @@ func TestList(t *testing.T) { for i := 0; i < 10; i++ { newO, err := iam.NewOrg() require.NoError(t, err) - o, err := repo.CreateScope(context.Background(), newO) + o, err := repo.CreateScope(context.Background(), newO, "") require.NoError(t, err) wantOrgs = append(wantOrgs, &pb.Scope{ Id: o.GetPublicId(), @@ -228,7 +229,7 @@ func TestList(t *testing.T) { for i := 0; i < 10; i++ { newP, err := iam.NewProject(oWithProjects.GetPublicId()) require.NoError(t, err) - p, err := repo.CreateScope(context.Background(), newP) + p, err := repo.CreateScope(context.Background(), newP, "") require.NoError(t, err) wantProjects = append(wantProjects, &pb.Scope{ Id: p.GetPublicId(), @@ -393,12 +394,24 @@ func TestDelete_twice(t *testing.T) { } func TestCreate(t *testing.T) { + ctx := context.Background() require := require.New(t) - defaultOrg, defaultProj, repo := createDefaultScopesAndRepo(t) + defaultOrg, defaultProj, repoFn := createDefaultScopesAndRepo(t) defaultProjCreated, err := ptypes.Timestamp(defaultProj.GetCreateTime().GetTimestamp()) require.NoError(err, "Error converting proto to timestamp.") toMerge := &pbs.CreateScopeRequest{} + repo, err := repoFn() + require.NoError(err) + globalUser, err := iam.NewUser(scope.Global.String()) + require.NoError(err) + globalUser, err = repo.CreateUser(ctx, globalUser) + require.NoError(err) + orgUser, err := iam.NewUser(defaultOrg.GetPublicId()) + require.NoError(err) + orgUser, err = repo.CreateUser(ctx, orgUser) + require.NoError(err) + cases := []struct { name string scopeId string @@ -477,39 +490,75 @@ func TestCreate(t *testing.T) { }, } for _, tc := range cases { - t.Run(tc.name, func(t *testing.T) { - assert := assert.New(t) - req := proto.Clone(toMerge).(*pbs.CreateScopeRequest) - proto.Merge(req, tc.req) - - s, err := scopes.NewService(repo) - require.NoError(err, "Error when getting new project service.") - - got, gErr := s.CreateScope(auth.DisabledAuthTestContext(auth.WithScopeId(tc.scopeId)), req) - assert.Equal(tc.errCode, status.Code(gErr), "CreateScope(%+v) got error %v, wanted %v", req, gErr, tc.errCode) - if got != nil { - assert.Contains(got.GetUri(), tc.res.Uri) - switch tc.scopeId { - case "global": - assert.True(strings.HasPrefix(got.GetItem().GetId(), "o_")) - default: - assert.True(strings.HasPrefix(got.GetItem().GetId(), "p_")) + for _, withUserId := range []bool{false, true} { + t.Run(fmt.Sprintf("%s-userid-%t", tc.name, withUserId), func(t *testing.T) { + var name string + if tc.req != nil && tc.req.GetItem() != nil && tc.req.GetItem().GetName() != nil { + name = tc.req.GetItem().GetName().GetValue() + localName := name + defer func() { + tc.res.GetItem().GetName().Value = localName + }() } - gotCreateTime, err := ptypes.Timestamp(got.GetItem().GetCreatedTime()) - require.NoError(err, "Error converting proto to timestamp.") - gotUpdateTime, err := ptypes.Timestamp(got.GetItem().GetUpdatedTime()) - require.NoError(err, "Error converting proto to timestamp.") - // Verify it is a project created after the test setup's default project - assert.True(gotCreateTime.After(defaultProjCreated), "New scope should have been created after default project. Was created %v, which is after %v", gotCreateTime, defaultProjCreated) - assert.True(gotUpdateTime.After(defaultProjCreated), "New scope should have been updated after default project. Was updated %v, which is after %v", gotUpdateTime, defaultProjCreated) + assert := assert.New(t) + req := proto.Clone(toMerge).(*pbs.CreateScopeRequest) + proto.Merge(req, tc.req) - // Clear all values which are hard to compare against. - got.Uri, tc.res.Uri = "", "" - got.Item.Id, tc.res.Item.Id = "", "" - got.Item.CreatedTime, got.Item.UpdatedTime, tc.res.Item.CreatedTime, tc.res.Item.UpdatedTime = nil, nil, nil, nil - } - assert.Empty(cmp.Diff(got, tc.res, protocmp.Transform()), "CreateScope(%q) got response %q, wanted %q", req, got, tc.res) - }) + s, err := scopes.NewService(repoFn) + require.NoError(err, "Error when getting new project service.") + + if name != "" { + name = fmt.Sprintf("%s-%t", name, withUserId) + req.GetItem().GetName().Value = name + } + var userId string + if withUserId { + if tc.scopeId == scope.Global.String() { + userId = globalUser.GetPublicId() + } else { + userId = orgUser.GetPublicId() + } + assert.NotEmpty(userId) + } + got, gErr := s.CreateScope(auth.DisabledAuthTestContext(auth.WithScopeId(tc.scopeId), auth.WithUserId(userId)), req) + assert.Equal(tc.errCode, status.Code(gErr), "CreateScope(%+v) got error %v, wanted %v", req, gErr, tc.errCode) + if got != nil { + assert.Contains(got.GetUri(), tc.res.Uri) + switch tc.scopeId { + case "global": + assert.True(strings.HasPrefix(got.GetItem().GetId(), "o_")) + default: + assert.True(strings.HasPrefix(got.GetItem().GetId(), "p_")) + } + gotCreateTime, err := ptypes.Timestamp(got.GetItem().GetCreatedTime()) + require.NoError(err, "Error converting proto to timestamp.") + gotUpdateTime, err := ptypes.Timestamp(got.GetItem().GetUpdatedTime()) + require.NoError(err, "Error converting proto to timestamp.") + // Verify it is a project created after the test setup's default project + assert.True(gotCreateTime.After(defaultProjCreated), "New scope should have been created after default project. Was created %v, which is after %v", gotCreateTime, defaultProjCreated) + assert.True(gotUpdateTime.After(defaultProjCreated), "New scope should have been updated after default project. Was updated %v, which is after %v", gotUpdateTime, defaultProjCreated) + + if withUserId { + repo, err := repoFn() + require.NoError(err) + roles, err := repo.ListRoles(ctx, got.GetItem().GetId()) + require.NoError(err) + require.Len(roles, 1) + role := roles[0] + assert.Equal("on-scope-creation", role.GetName()) + assert.Equal(fmt.Sprintf("Role created for administration of scope %s at its creation time", got.GetItem().GetId()), role.GetDescription()) + } + + // Clear all values which are hard to compare against. + assert.Equal(name, got.GetItem().GetName().GetValue()) + got.Item.Name = tc.res.GetItem().GetName() + got.Uri, tc.res.Uri = "", "" + got.Item.Id, tc.res.Item.Id = "", "" + got.Item.CreatedTime, got.Item.UpdatedTime, tc.res.Item.CreatedTime, tc.res.Item.UpdatedTime = nil, nil, nil, nil + } + assert.Empty(cmp.Diff(tc.res, got, protocmp.Transform()), "CreateScope(%q) got response %q, wanted %q", req, got, tc.res) + }) + } } }