Create a default role on new scope creation (#265)

This commit is contained in:
Jeff Mitchell authored and GitHub committed 2020-08-13 17:39:48 -04:00
1 parent e89e9d1349
commit bb6b189513
10 files changed
+264 -70

No files matched your search

+2 -2
View File
@@ -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
+3 -1
View File
@@ -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)
+8
View File
@@ -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
}
}
+1
View File
@@ -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)
}
+1 -1
View File
@@ -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)
}
+150 -17
View File
@@ -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
+8 -8
View File
@@ -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)
+4 -4
View File
@@ -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())
@@ -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)
}
@@ -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)
})
}
}
}