diff --git a/.golangci.yml b/.golangci.yml index f5d56b5a01..8612016074 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -15,3 +15,9 @@ issues: - linters: - staticcheck text: "SA1019: pbs.AuthorizedWorkerList is deprecated:" + - linters: + - staticcheck + text: "SA1019: item.GetGrantScopeId is deprecated:" + - linters: + - staticcheck + text: "SA1019: out.GrantScopeId is deprecated:" diff --git a/CHANGELOG.md b/CHANGELOG.md index 49a57478f2..da5f778a3e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,9 +15,18 @@ Canonical reference for changes, improvements, and bugfixes for Boundary. affects existing addresses (not just creation/updating via the API) so any existing addresses containing a port will not be able to be used as part of a target's session authorization call. +* The `grant_scope_id` field on roles is now deprecated in favor of the multiple + grant scope support. ### New and Improved +* Multiple grant scopes in roles: Roles now support multiple grant scopes, along + with the special values `this`, `children` (global/org only) to apply to all + direct children of a scope, and `descendants` (global only) to apply to all + descendants of a scope. These use the new actions `add-grant-scopes`, + `set-grant-scopes`, and `remove-grant-scopes` on roles. For now the + `grant_scope_id` field on roles will continue to be able to be set, which will + set a single grant scope, but this capability is now deprecated. * Policies (Enterprise and HCP Boundary only): This release introduces Policies, a Boundary resource that represents a Governance Policy to enforce. The first implementation targets Storage Policies, which enables administrators to automate diff --git a/Makefile b/Makefile index 0a8f9e83b0..33cd93b67d 100644 --- a/Makefile +++ b/Makefile @@ -157,6 +157,7 @@ protobuild: @protoc-go-inject-tag -input=./internal/iam/store/role.pb.go @protoc-go-inject-tag -input=./internal/iam/store/principal_role.pb.go @protoc-go-inject-tag -input=./internal/iam/store/role_grant.pb.go + @protoc-go-inject-tag -input=./internal/iam/store/role_grant_scope.pb.go @protoc-go-inject-tag -input=./internal/iam/store/user.pb.go @protoc-go-inject-tag -input=./internal/iam/store/scope.pb.go @protoc-go-inject-tag -input=./internal/iam/store/group.pb.go diff --git a/api/roles/role.gen.go b/api/roles/role.gen.go index 283ba80d37..f5d7753065 100644 --- a/api/roles/role.gen.go +++ b/api/roles/role.gen.go @@ -27,6 +27,7 @@ type Role struct { UpdatedTime time.Time `json:"updated_time,omitempty"` Version uint32 `json:"version,omitempty"` GrantScopeId string `json:"grant_scope_id,omitempty"` + GrantScopeIds []string `json:"grant_scope_ids,omitempty"` PrincipalIds []string `json:"principal_ids,omitempty"` Principals []*Principal `json:"principals,omitempty"` GrantStrings []string `json:"grant_strings,omitempty"` @@ -448,6 +449,76 @@ func (c *Client) List(ctx context.Context, scopeId string, opt ...Option) (*Role return target, nil } +func (c *Client) AddGrantScopes(ctx context.Context, id string, version uint32, grantScopeIds []string, opt ...Option) (*RoleUpdateResult, error) { + if id == "" { + return nil, fmt.Errorf("empty id value passed into AddGrantScopes request") + } + + if len(grantScopeIds) == 0 { + return nil, errors.New("empty grantScopeIds passed into AddGrantScopes request") + } + + if c.client == nil { + return nil, errors.New("nil client") + } + + opts, apiOpts := getOpts(opt...) + + if version == 0 { + if !opts.withAutomaticVersioning { + return nil, errors.New("zero version number passed into AddGrantScopes request") + } + existingTarget, existingErr := c.Read(ctx, id, append([]Option{WithSkipCurlOutput(true)}, opt...)...) + if existingErr != nil { + if api.AsServerError(existingErr) != nil { + return nil, fmt.Errorf("error from controller when performing initial check-and-set read: %w", existingErr) + } + return nil, fmt.Errorf("error performing initial check-and-set read: %w", existingErr) + } + if existingTarget == nil { + return nil, errors.New("nil resource response found when performing initial check-and-set read") + } + if existingTarget.Item == nil { + return nil, errors.New("nil resource found when performing initial check-and-set read") + } + version = existingTarget.Item.Version + } + + opts.postMap["version"] = version + + opts.postMap["grant_scope_ids"] = grantScopeIds + + req, err := c.client.NewRequest(ctx, "POST", fmt.Sprintf("roles/%s:add-grant-scopes", url.PathEscape(id)), opts.postMap, apiOpts...) + if err != nil { + return nil, fmt.Errorf("error creating AddGrantScopes request: %w", err) + } + + if len(opts.queryMap) > 0 { + q := url.Values{} + for k, v := range opts.queryMap { + q.Add(k, v) + } + req.URL.RawQuery = q.Encode() + } + + resp, err := c.client.Do(req) + if err != nil { + return nil, fmt.Errorf("error performing client request during AddGrantScopes call: %w", err) + } + + target := new(RoleUpdateResult) + target.Item = new(Role) + apiErr, err := resp.Decode(target.Item) + if err != nil { + return nil, fmt.Errorf("error decoding AddGrantScopes response: %w", err) + } + if apiErr != nil { + return nil, apiErr + } + target.response = resp + return target, nil +} + func (c *Client) AddGrants(ctx context.Context, id string, version uint32, grantStrings []string, opt ...Option) (*RoleUpdateResult, error) { if id == "" { return nil, fmt.Errorf("empty id value passed into AddGrants request") @@ -588,6 +659,72 @@ func (c *Client) AddPrincipals(ctx context.Context, id string, version uint32, p return target, nil } +func (c *Client) SetGrantScopes(ctx context.Context, id string, version uint32, grantScopeIds []string, opt ...Option) (*RoleUpdateResult, error) { + if id == "" { + return nil, fmt.Errorf("empty id value passed into SetGrantScopes request") + } + + if c.client == nil { + return nil, errors.New("nil client") + } + + opts, apiOpts := getOpts(opt...) + + if version == 0 { + if !opts.withAutomaticVersioning { + return nil, errors.New("zero version number passed into SetGrantScopes request") + } + existingTarget, existingErr := c.Read(ctx, id, append([]Option{WithSkipCurlOutput(true)}, opt...)...) + if existingErr != nil { + if api.AsServerError(existingErr) != nil { + return nil, fmt.Errorf("error from controller when performing initial check-and-set read: %w", existingErr) + } + return nil, fmt.Errorf("error performing initial check-and-set read: %w", existingErr) + } + if existingTarget == nil { + return nil, errors.New("nil resource response found when performing initial check-and-set read") + } + if existingTarget.Item == nil { + return nil, errors.New("nil resource found when performing initial check-and-set read") + } + version = existingTarget.Item.Version + } + + opts.postMap["version"] = version + + opts.postMap["grant_scope_ids"] = grantScopeIds + + req, err := c.client.NewRequest(ctx, "POST", fmt.Sprintf("roles/%s:set-grant-scopes", url.PathEscape(id)), opts.postMap, apiOpts...) + if err != nil { + return nil, fmt.Errorf("error creating SetGrantScopes request: %w", err) + } + + if len(opts.queryMap) > 0 { + q := url.Values{} + for k, v := range opts.queryMap { + q.Add(k, v) + } + req.URL.RawQuery = q.Encode() + } + + resp, err := c.client.Do(req) + if err != nil { + return nil, fmt.Errorf("error performing client request during SetGrantScopes call: %w", err) + } + + target := new(RoleUpdateResult) + target.Item = new(Role) + apiErr, err := resp.Decode(target.Item) + if err != nil { + return nil, fmt.Errorf("error decoding SetGrantScopes response: %w", err) + } + if apiErr != nil { + return nil, apiErr + } + target.response = resp + return target, nil +} + func (c *Client) SetGrants(ctx context.Context, id string, version uint32, grantStrings []string, opt ...Option) (*RoleUpdateResult, error) { if id == "" { return nil, fmt.Errorf("empty id value passed into SetGrants request") @@ -720,6 +857,76 @@ func (c *Client) SetPrincipals(ctx context.Context, id string, version uint32, p return target, nil } +func (c *Client) RemoveGrantScopes(ctx context.Context, id string, version uint32, grantScopeIds []string, opt ...Option) (*RoleUpdateResult, error) { + if id == "" { + return nil, fmt.Errorf("empty id value passed into RemoveGrantScopes request") + } + + if len(grantScopeIds) == 0 { + return nil, errors.New("empty grantScopeIds passed into RemoveGrantScopes request") + } + + if c.client == nil { + return nil, errors.New("nil client") + } + + opts, apiOpts := getOpts(opt...) + + if version == 0 { + if !opts.withAutomaticVersioning { + return nil, errors.New("zero version number passed into RemoveGrantScopes request") + } + existingTarget, existingErr := c.Read(ctx, id, append([]Option{WithSkipCurlOutput(true)}, opt...)...) + if existingErr != nil { + if api.AsServerError(existingErr) != nil { + return nil, fmt.Errorf("error from controller when performing initial check-and-set read: %w", existingErr) + } + return nil, fmt.Errorf("error performing initial check-and-set read: %w", existingErr) + } + if existingTarget == nil { + return nil, errors.New("nil resource response found when performing initial check-and-set read") + } + if existingTarget.Item == nil { + return nil, errors.New("nil resource found when performing initial check-and-set read") + } + version = existingTarget.Item.Version + } + + opts.postMap["version"] = version + + opts.postMap["grant_scope_ids"] = grantScopeIds + + req, err := c.client.NewRequest(ctx, "POST", fmt.Sprintf("roles/%s:remove-grant-scopes", url.PathEscape(id)), opts.postMap, apiOpts...) + if err != nil { + return nil, fmt.Errorf("error creating RemoveGrantScopes request: %w", err) + } + + if len(opts.queryMap) > 0 { + q := url.Values{} + for k, v := range opts.queryMap { + q.Add(k, v) + } + req.URL.RawQuery = q.Encode() + } + + resp, err := c.client.Do(req) + if err != nil { + return nil, fmt.Errorf("error performing client request during RemoveGrantScopes call: %w", err) + } + + target := new(RoleUpdateResult) + target.Item = new(Role) + apiErr, err := resp.Decode(target.Item) + if err != nil { + return nil, fmt.Errorf("error decoding RemoveGrantScopes response: %w", err) + } + if apiErr != nil { + return nil, apiErr + } + target.response = resp + return target, nil +} + func (c *Client) RemoveGrants(ctx context.Context, id string, version uint32, grantStrings []string, opt ...Option) (*RoleUpdateResult, error) { if id == "" { return nil, fmt.Errorf("empty id value passed into RemoveGrants request") diff --git a/globals/fields.go b/globals/fields.go index c94489197d..cac3a68e2b 100644 --- a/globals/fields.go +++ b/globals/fields.go @@ -32,6 +32,7 @@ const ( PrincipalIdsField = "principal_ids" PrincipalsField = "principals" GrantScopeIdField = "grant_scope_id" + GrantScopeIdsField = "grant_scope_ids" GrantsField = "grants" GrantStringsField = "grant_strings" PrimaryAuthMethodIdField = "primary_auth_method_id" diff --git a/globals/globals.go b/globals/globals.go index 1c712ca309..a98e6da8dd 100644 --- a/globals/globals.go +++ b/globals/globals.go @@ -14,6 +14,10 @@ const ( RecoveryUserId = "u_recovery" MinimumSupportedPostgresVersion = "12" + + GrantScopeThis = "this" + GrantScopeChildren = "children" + GrantScopeDescendants = "descendants" ) type ( diff --git a/internal/api/genapi/input.go b/internal/api/genapi/input.go index e683c362b1..4ad71b87e3 100644 --- a/internal/api/genapi/input.go +++ b/internal/api/genapi/input.go @@ -316,6 +316,10 @@ var inputStructs = []*structInfo{ SliceType: "[]string", VarName: "grantStrings", }, + "GrantScopes": { + SliceType: "[]string", + VarName: "grantScopeIds", + }, }, pluralResourceName: "roles", versionEnabled: true, diff --git a/internal/auth/additional_verification_test.go b/internal/auth/additional_verification_test.go index 77bdb16aa2..ad8e7c6e17 100644 --- a/internal/auth/additional_verification_test.go +++ b/internal/auth/additional_verification_test.go @@ -12,6 +12,7 @@ import ( "github.com/hashicorp/boundary/api/authmethods" "github.com/hashicorp/boundary/api/authtokens" + "github.com/hashicorp/boundary/api/roles" "github.com/hashicorp/boundary/globals" "github.com/hashicorp/boundary/internal/auth/password" "github.com/hashicorp/boundary/internal/authtoken" @@ -48,6 +49,26 @@ func TestFetchActionSetForId(t *testing.T) { return tc.AuthTokenRepo(), nil } + // Delete the global default role so it doesn't interfere with the + // permissions we're testing here + rolesClient := roles.NewClient(tc.Client()) + rolesResp, err := rolesClient.List(tc.Context(), scope.Global.String()) + require.NoError(t, err) + require.NotNil(t, rolesResp) + assert.Len(t, rolesResp.GetItems(), 3) + var adminRoleId string + for _, item := range rolesResp.GetItems() { + if strings.Contains(item.Name, "Authenticated User") || + strings.Contains(item.Name, "Login") { + _, err := rolesClient.Delete(tc.Context(), item.Id) + require.NoError(t, err) + } else { + adminRoleId = item.Id + } + } + _, err = rolesClient.Delete(tc.Context(), adminRoleId) + require.NoError(t, err) + orgRole := iam.TestRole(t, conn, org.GetPublicId()) iam.TestUserRole(t, conn, orgRole.PublicId, token.UserId) iam.TestRoleGrant(t, conn, orgRole.PublicId, "ids=ttcp_foo;actions=read,update") @@ -132,6 +153,21 @@ func TestRecursiveListingDifferentOutputFields(t *testing.T) { token := tc.Token() client.SetToken(token.Token) + // Delete the global default role so it doesn't interfere with the + // permissions we're testing here + rolesClient := roles.NewClient(tc.Client()) + rolesResp, err := rolesClient.List(tc.Context(), scope.Global.String()) + require.NoError(err) + require.NotNil(rolesResp) + assert.Len(rolesResp.GetItems(), 3) + for _, item := range rolesResp.GetItems() { + if strings.Contains(item.Name, "Authenticated User") || + strings.Contains(item.Name, "Login") { + _, err := rolesClient.Delete(tc.Context(), item.Id) + require.NoError(err) + } + } + // Set some global permissions so we can read the auth method there. Here we // will expect the defaults. globalRole := iam.TestRole(t, conn, scope.Global.String()) @@ -146,7 +182,7 @@ func TestRecursiveListingDifferentOutputFields(t *testing.T) { orgRole := iam.TestRole(t, conn, org.GetPublicId()) iam.TestUserRole(t, conn, orgRole.PublicId, token.UserId) // The first and second will actually not take effect for output - // grantsbecause it's a list and output fields are scoped by action. So we + // grants because it's a list and output fields are scoped by action. So we // expect only name and scope_id for the auth methods in the scope, using // two patterns below. However, since you need an action on the resource for // list to return anything, those grants allow us to list the items, while diff --git a/internal/cmd/base/dev.go b/internal/cmd/base/dev.go index 91674a18b7..5125737883 100644 --- a/internal/cmd/base/dev.go +++ b/internal/cmd/base/dev.go @@ -114,11 +114,19 @@ func (b *Server) CreateDevDatabase(ctx context.Context, opt ...Option) error { return err } - if _, err := b.CreateInitialLoginRole(ctx); err != nil { - if c != nil { - err = errors.Join(err, c()) + if !opts.withSkipDefaultRoleCreation { + if _, err := b.CreateInitialLoginRole(ctx); err != nil { + if c != nil { + err = errors.Join(err, c()) + } + return err + } + if _, err := b.CreateInitialAuthenticatedUserRole(ctx, WithAuthUserTargetAuthorizeSessionGrant(true)); err != nil { + if c != nil { + err = errors.Join(err, c()) + } + return err } - return err } if opts.withSkipAuthMethodCreation { @@ -151,7 +159,10 @@ func (b *Server) CreateDevDatabase(ctx context.Context, opt ...Option) error { return nil } - if _, _, err := b.CreateInitialScopes(ctx); err != nil { + if _, _, err := b.CreateInitialScopes(ctx, WithIamOptions( + iam.WithSkipAdminRoleCreation(true), + iam.WithSkipDefaultRoleCreation(true), + )); err != nil { return err } diff --git a/internal/cmd/base/initial_resources.go b/internal/cmd/base/initial_resources.go index a60e8ab36d..e32e9d3fdb 100644 --- a/internal/cmd/base/initial_resources.go +++ b/internal/cmd/base/initial_resources.go @@ -45,26 +45,84 @@ func (b *Server) CreateInitialLoginRole(ctx context.Context) (*iam.Role, error) pr, err := iam.NewRole(ctx, scope.Global.String(), - iam.WithName("Login and Default Grants"), - iam.WithDescription(`Role created for login capability, account self-management, and other default grants for users of the global scope at its creation time`), + iam.WithName("Login Grants"), + iam.WithDescription(`Role created for login capability for unauthenticated users`), ) if err != nil { return nil, fmt.Errorf("error creating in memory role for generated grants: %w", err) } - role, err := iamRepo.CreateRole(ctx, pr) + role, _, _, _, err := iamRepo.CreateRole(ctx, pr) if err != nil { return nil, fmt.Errorf("error creating role for default generated grants: %w", err) } if _, err := iamRepo.AddRoleGrants(ctx, role.PublicId, role.Version, []string{ "ids=*;type=scope;actions=list,no-op", - "ids=*;type=auth-method;actions=authenticate,list", - "ids={{.Account.Id}};actions=read,change-password", - "ids=*;type=auth-token;actions=list,read:self,delete:self", + "ids=*;type=auth-method;actions=list,authenticate", + "ids=*;type=auth-token;actions=read:self,delete:self", }); err != nil { - return nil, fmt.Errorf("error creating grant for default generated grants: %w", err) + return nil, fmt.Errorf("error creating grant for initial login grants: %w", err) + } + if _, err := iamRepo.AddPrincipalRoles(ctx, role.PublicId, role.Version+1, []string{globals.AnonymousUserId}); err != nil { + return nil, fmt.Errorf("error adding principal to role for initial login grants: %w", err) } - if _, err := iamRepo.AddPrincipalRoles(ctx, role.PublicId, role.Version+1, []string{globals.AnonymousUserId}, nil); err != nil { - return nil, fmt.Errorf("error adding principal to role for default generated grants: %w", err) + if _, _, err := iamRepo.SetRoleGrantScopes(ctx, role.PublicId, role.Version+2, []string{"this", "descendants"}); err != nil { + return nil, fmt.Errorf("error adding scope grants to role for initial login grants: %w", err) + } + + return role, nil +} + +func (b *Server) CreateInitialAuthenticatedUserRole(ctx context.Context, opt ...Option) (*iam.Role, error) { + rw := db.New(b.Database) + + kmsCache, err := kms.New(ctx, rw, rw) + if err != nil { + return nil, fmt.Errorf("error creating kms cache: %w", err) + } + if err := kmsCache.AddExternalWrappers( + b.Context, + kms.WithRootWrapper(b.RootKms), + ); err != nil { + return nil, fmt.Errorf("error adding config keys to kms: %w", err) + } + + iamRepo, err := iam.NewRepository(ctx, rw, rw, kmsCache, iam.WithRandomReader(b.SecureRandomReader)) + if err != nil { + return nil, fmt.Errorf("unable to create repo for initial login role: %w", err) + } + + pr, err := iam.NewRole(ctx, + scope.Global.String(), + iam.WithName("Authenticated User Grants"), + iam.WithDescription(`Role created for account self-management, and other initial authenticated user grants`), + ) + if err != nil { + return nil, fmt.Errorf("error creating in memory role for generated grants: %w", err) + } + role, _, _, _, err := iamRepo.CreateRole(ctx, pr) + if err != nil { + return nil, fmt.Errorf("error creating role for default generated grants: %w", err) + } + grants := []string{ + "ids=*;type=scope;actions=read", + "ids=*;type=auth-token;actions=list", + "ids={{.Account.Id}};actions=read,change-password", + "ids=*;type=session;actions=list,read:self,cancel:self", + } + opts := GetOpts(opt...) + if opts.withAuthUserTargetAuthorizeSessionGrant { + grants = append(grants, "ids=*;type=target;actions=list,read,authorize-session") + } else { + grants = append(grants, "ids=*;type=target;actions=list,read") + } + if _, err := iamRepo.AddRoleGrants(ctx, role.PublicId, role.Version, grants); err != nil { + return nil, fmt.Errorf("error creating grant for initial authenticated user grants: %w", err) + } + if _, err := iamRepo.AddPrincipalRoles(ctx, role.PublicId, role.Version+1, []string{globals.AnyAuthenticatedUserId}); err != nil { + return nil, fmt.Errorf("error adding principal to role for initial authenticated user grants: %w", err) + } + if _, _, err := iamRepo.SetRoleGrantScopes(ctx, role.PublicId, role.Version+2, []string{"this", "descendants"}); err != nil { + return nil, fmt.Errorf("error adding scope grants to role for initial authenticated user grants: %w", err) } return role, nil @@ -201,21 +259,24 @@ func (b *Server) CreateInitialPasswordAuthMethod(ctx context.Context) (*password pr, err := iam.NewRole(ctx, scope.Global.String(), iam.WithName("Administration"), - iam.WithDescription(fmt.Sprintf(`Provides admin grants within the "%s" scope to the initial user`, scope.Global.String())), + iam.WithDescription("Provides admin grants within all scopes to the initial admin user"), ) if err != nil { return nil, fmt.Errorf("error creating in memory role for generated grants: %w", err) } - defPermsRole, err := iamRepo.CreateRole(ctx, pr) + adminRole, _, _, _, err := iamRepo.CreateRole(ctx, pr) if err != nil { return nil, fmt.Errorf("error creating role for default generated grants: %w", err) } - if _, err := iamRepo.AddRoleGrants(ctx, defPermsRole.PublicId, defPermsRole.Version, []string{"ids=*;type=*;actions=*"}); err != nil { + if _, err := iamRepo.AddRoleGrants(ctx, adminRole.PublicId, adminRole.Version, []string{"ids=*;type=*;actions=*"}); err != nil { return nil, fmt.Errorf("error creating grant for default generated grants: %w", err) } - if _, err := iamRepo.AddPrincipalRoles(ctx, defPermsRole.PublicId, defPermsRole.Version+1, []string{u.GetPublicId()}, nil); err != nil { + if _, err := iamRepo.AddPrincipalRoles(ctx, adminRole.PublicId, adminRole.Version+1, []string{u.GetPublicId()}, nil); err != nil { return nil, fmt.Errorf("error adding principal to role for default generated grants: %w", err) } + if _, _, err := iamRepo.SetRoleGrantScopes(ctx, adminRole.PublicId, adminRole.Version+2, []string{"this", "descendants"}); err != nil { + return nil, fmt.Errorf("error adding scope grants to role for default generated grants: %w", err) + } return u, nil } @@ -256,7 +317,7 @@ func (b *Server) CreateInitialPasswordAuthMethod(ctx context.Context) (*password return am, u, nil } -func (b *Server) CreateInitialScopes(ctx context.Context) (*iam.Scope, *iam.Scope, error) { +func (b *Server) CreateInitialScopes(ctx context.Context, opt ...Option) (*iam.Scope, *iam.Scope, error) { rw := db.New(b.Database) kmsCache, err := kms.New(ctx, rw, rw) @@ -270,6 +331,8 @@ func (b *Server) CreateInitialScopes(ctx context.Context) (*iam.Scope, *iam.Scop return nil, nil, fmt.Errorf("error adding config keys to kms: %w", err) } + opts := GetOpts(opt...) + iamRepo, err := iam.NewRepository(ctx, rw, rw, kmsCache) if err != nil { return nil, nil, fmt.Errorf("error creating scopes repository: %w", err) @@ -282,17 +345,20 @@ func (b *Server) CreateInitialScopes(ctx context.Context) (*iam.Scope, *iam.Scop return nil, nil, fmt.Errorf("error generating initial org id: %w", err) } } - opts := []iam.Option{ + iamOpts := []iam.Option{ iam.WithName("Generated org scope"), iam.WithDescription("Provides an initial org scope in Boundary"), iam.WithRandomReader(b.SecureRandomReader), iam.WithPublicId(b.DevOrgId), } - orgScope, err := iam.NewOrg(ctx, opts...) + if len(opts.withIamOptions) > 0 { + iamOpts = append(iamOpts, opts.withIamOptions...) + } + orgScope, err := iam.NewOrg(ctx, iamOpts...) if err != nil { return nil, nil, fmt.Errorf("error creating new in memory org scope: %w", err) } - orgScope, err = iamRepo.CreateScope(ctx, orgScope, b.DevUserId, opts...) + orgScope, err = iamRepo.CreateScope(ctx, orgScope, b.DevUserId, iamOpts...) if err != nil { return nil, nil, fmt.Errorf("error saving org scope to the db: %w", err) } @@ -305,17 +371,20 @@ func (b *Server) CreateInitialScopes(ctx context.Context) (*iam.Scope, *iam.Scop return nil, nil, fmt.Errorf("error generating initial project id: %w", err) } } - opts = []iam.Option{ + iamOpts = []iam.Option{ iam.WithName("Generated project scope"), iam.WithDescription("Provides an initial project scope in Boundary"), iam.WithRandomReader(b.SecureRandomReader), iam.WithPublicId(b.DevProjectId), } - projScope, err := iam.NewProject(ctx, b.DevOrgId, opts...) + if len(opts.withIamOptions) > 0 { + iamOpts = append(iamOpts, opts.withIamOptions...) + } + projScope, err := iam.NewProject(ctx, b.DevOrgId, iamOpts...) if err != nil { return nil, nil, fmt.Errorf("error creating new in memory project scope: %w", err) } - projScope, err = iamRepo.CreateScope(ctx, projScope, b.DevUserId, opts...) + projScope, err = iamRepo.CreateScope(ctx, projScope, b.DevUserId, iamOpts...) if err != nil { return nil, nil, fmt.Errorf("error saving project scope to the db: %w", err) } @@ -472,17 +541,6 @@ func (b *Server) CreateInitialTargetWithAddress(ctx context.Context) (target.Tar b.InfoKeys = append(b.InfoKeys, "generated target with address id") b.Info["generated target with address id"] = b.DevTargetId - if b.DevUnprivilegedUserId != "" { - iamRepo, err := iam.NewRepository(ctx, rw, rw, kmsCache, iam.WithRandomReader(b.SecureRandomReader)) - if err != nil { - return nil, fmt.Errorf("failed to create iam repository: %w", err) - } - err = unprivilegedDevUserRoleSetup(ctx, iamRepo, b.DevUnprivilegedUserId, b.DevProjectId, b.DevTargetId) - if err != nil { - return nil, fmt.Errorf("failed to set up unprivileged dev user: %w", err) - } - } - return tt, nil } @@ -540,17 +598,6 @@ func (b *Server) CreateInitialTargetWithHostSources(ctx context.Context) (target b.InfoKeys = append(b.InfoKeys, "generated target with host source id") b.Info["generated target with host source id"] = b.DevSecondaryTargetId - if b.DevUnprivilegedUserId != "" { - iamRepo, err := iam.NewRepository(ctx, rw, rw, kmsCache, iam.WithRandomReader(b.SecureRandomReader)) - if err != nil { - return nil, fmt.Errorf("failed to create iam repository: %w", err) - } - err = unprivilegedDevUserRoleSetup(ctx, iamRepo, b.DevUnprivilegedUserId, b.DevProjectId, b.DevSecondaryTargetId) - if err != nil { - return nil, fmt.Errorf("failed to set up unprivileged dev user: %w", err) - } - } - return tt, nil } @@ -626,83 +673,3 @@ func (b *Server) RegisterPlugin(ctx context.Context, name string, hostClient plg return plg, nil } - -// unprivilegedDevUserRoleSetup adds dev user to the role that grants -// list/read:self/cancel:self on sessions and read:self/delete:self/list on -// tokens. It also creates a role with an `authorize-session` grant for the -// provided targetId. -func unprivilegedDevUserRoleSetup(ctx context.Context, repo *iam.Repository, userId, projectId, targetId string) error { - noopFilter := func(ctx context.Context, item *iam.Role) (bool, error) { - return true, nil - } - roles, err := iam.ListRoles(ctx, []byte("dummy grant"), globals.DefaultMaxPageSize, noopFilter, repo, []string{projectId}) - if err != nil { - return fmt.Errorf("failed to list existing roles for project id %q: %w", projectId, err) - } - - // Look for default grants role to set unprivileged user as a principal. - defaultRoleIdx := -1 - for i, r := range roles.Items { - // Hacky, I know, but saves a DB trip to look up other - // characteristics like "if any principals are currently attached". - // No matter what we pick here it's a bit heuristical. - if r.Name == "Default Grants" { - defaultRoleIdx = i - break - } - } - if defaultRoleIdx == -1 { - return fmt.Errorf("couldn't find default grants role for project id %q", projectId) - } - defaultRole := roles.Items[defaultRoleIdx] - - // This function may be called more than once for the same boundary - // deployment (eg: if we're creating more than one target), so we need to - // check if the unprivileged user is not already a principal for the default - // role in this project, as attempting to add an existing principal is an - // error. - principals, err := repo.ListPrincipalRoles(ctx, defaultRole.GetPublicId()) - if err != nil { - return fmt.Errorf("failed to list principals for default project role: %w", err) - } - found := false - for _, p := range principals { - if p.PrincipalId == userId { - found = true - } - } - if !found { - _, err = repo.AddPrincipalRoles(ctx, defaultRole.GetPublicId(), defaultRole.GetVersion(), []string{userId}) - if err != nil { - return fmt.Errorf("failed to add %q as principal for role id %q", userId, defaultRole.GetPublicId()) - } - defaultRole.Version++ // The above call increments the role version in the database, so we must also track that with our state. - } - - // Create a new role for the "authorize-session" grant and add the - // unprivileged user as a principal. - asRole, err := iam.NewRole(ctx, - projectId, - iam.WithName(fmt.Sprintf("Session authorization for %s", targetId)), - iam.WithDescription(fmt.Sprintf("Provides grants within the dev project scope to allow the initial unprivileged user to authorize sessions against %s", targetId)), - ) - if err != nil { - return fmt.Errorf("failed to create role object: %w", err) - } - - asRole, err = repo.CreateRole(ctx, asRole) - if err != nil { - return fmt.Errorf("failed to create role for unprivileged user: %w", err) - } - if _, err := repo.AddPrincipalRoles(ctx, asRole.GetPublicId(), asRole.GetVersion(), []string{userId}, nil); err != nil { - return fmt.Errorf("failed to add unprivileged user as principal to new role: %w", err) - } - asRole.Version++ - - _, err = repo.AddRoleGrants(ctx, asRole.GetPublicId(), asRole.GetVersion(), []string{fmt.Sprintf("ids=%s;actions=authorize-session", targetId)}) - if err != nil { - return fmt.Errorf("failed to add authorize-session grant for unprivileged user: %w", err) - } - - return nil -} diff --git a/internal/cmd/base/option.go b/internal/cmd/base/option.go index 2fdce8ac38..7b452bbae6 100644 --- a/internal/cmd/base/option.go +++ b/internal/cmd/base/option.go @@ -5,6 +5,7 @@ package base import ( "github.com/hashicorp/boundary/internal/event" + "github.com/hashicorp/boundary/internal/iam" "github.com/hashicorp/boundary/sdk/pbs/plugin" wrapping "github.com/hashicorp/go-kms-wrapping/v2" ) @@ -25,28 +26,31 @@ type Option func(*Options) // Options - how Options are represented. type Options struct { - withNoTokenScope bool - withNoTokenValue bool - withSkipDatabaseDestruction bool - withSkipAuthMethodCreation bool - withSkipOidcAuthMethodCreation bool - withSkipLdapAuthMethodCreation bool - withSkipScopesCreation bool - withSkipHostResourcesCreation bool - withSkipTargetCreation bool - withContainerImage string - withDialect string - withDatabaseTemplate string - withEventerConfig *event.EventerConfig - withEventFlags *EventFlags - withEventWrapper wrapping.Wrapper - withAttributeFieldPrefix string - withStatusCode int - withHostPlugin func() (string, plugin.HostPluginServiceClient) - withEventGating bool - withImplicitId string - WithSkipScopeIdFlag bool - WithInterceptedToken *string + withNoTokenScope bool + withNoTokenValue bool + withSkipDefaultRoleCreation bool + withSkipDatabaseDestruction bool + withSkipAuthMethodCreation bool + withSkipOidcAuthMethodCreation bool + withSkipLdapAuthMethodCreation bool + withSkipScopesCreation bool + withSkipHostResourcesCreation bool + withSkipTargetCreation bool + withContainerImage string + withDialect string + withDatabaseTemplate string + withEventerConfig *event.EventerConfig + withEventFlags *EventFlags + withEventWrapper wrapping.Wrapper + withAttributeFieldPrefix string + withStatusCode int + withHostPlugin func() (string, plugin.HostPluginServiceClient) + withEventGating bool + withImplicitId string + WithSkipScopeIdFlag bool + WithInterceptedToken *string + withAuthUserTargetAuthorizeSessionGrant bool + withIamOptions []iam.Option } func getDefaultOptions() Options { @@ -81,6 +85,14 @@ func WithNoTokenValue() Option { } } +// WithSkipDefaultRoleCreation tells the command not to instantiate the default +// global role +func WithSkipDefaultRoleCreation() Option { + return func(o *Options) { + o.withSkipDefaultRoleCreation = true + } +} + // WithSkipAuthMethodCreation tells the command not to instantiate any auth // method on first run. func WithSkipAuthMethodCreation() Option { @@ -227,3 +239,20 @@ func WithInterceptedToken(s *string) Option { o.WithInterceptedToken = s } } + +// WithAuthUserTargetAuthorizeSessionGrant indicates that we should add an +// authorize-session grant to the global authenticated user role. This is the +// default for dev mode. +func WithAuthUserTargetAuthorizeSessionGrant(with bool) Option { + return func(o *Options) { + o.withAuthUserTargetAuthorizeSessionGrant = with + } +} + +// WithIamOptions provides options to pass through to the IAM package when +// creating initial resources +func WithIamOptions(with ...iam.Option) Option { + return func(o *Options) { + o.withIamOptions = with + } +} diff --git a/internal/cmd/base/option_test.go b/internal/cmd/base/option_test.go index 93265d3441..c8d44a23c6 100644 --- a/internal/cmd/base/option_test.go +++ b/internal/cmd/base/option_test.go @@ -7,6 +7,7 @@ import ( "testing" "github.com/hashicorp/boundary/internal/event" + "github.com/hashicorp/boundary/internal/iam" "github.com/stretchr/testify/assert" ) @@ -71,6 +72,13 @@ func Test_GetOpts(t *testing.T) { testOpts.withNoTokenValue = true assert.Equal(opts, testOpts) }) + t.Run("WithSkipDefaultRoleCreation", func(t *testing.T) { + assert := assert.New(t) + opts := GetOpts(WithSkipDefaultRoleCreation()) + testOpts := getDefaultOptions() + testOpts.withSkipDefaultRoleCreation = true + assert.Equal(opts, testOpts) + }) t.Run("WithSkipAuthMethodCreation", func(t *testing.T) { assert := assert.New(t) opts := GetOpts(WithSkipAuthMethodCreation()) @@ -151,4 +159,20 @@ func Test_GetOpts(t *testing.T) { testOpts.WithInterceptedToken = &s assert.Equal(opts, testOpts) }) + + t.Run("WithAuthUserTargetAuthorizeSessionGrant", func(t *testing.T) { + assert := assert.New(t) + opts := getDefaultOptions() + assert.False(opts.withAuthUserTargetAuthorizeSessionGrant) + opts = GetOpts(WithAuthUserTargetAuthorizeSessionGrant(true)) + assert.True(opts.withAuthUserTargetAuthorizeSessionGrant) + }) + + t.Run("WithIamOptions", func(t *testing.T) { + assert := assert.New(t) + testOpts := getDefaultOptions() + assert.Len(testOpts.withIamOptions, 0) + opts := GetOpts(WithIamOptions(iam.WithSkipAdminRoleCreation(true))) + assert.Len(opts.withIamOptions, 1) + }) } diff --git a/internal/cmd/commands.go b/internal/cmd/commands.go index 815e964d7c..734b3a24b1 100644 --- a/internal/cmd/commands.go +++ b/internal/cmd/commands.go @@ -895,6 +895,21 @@ func initCommands(ui, serverCmdUi cli.Ui, runOpts *RunOptions) { Command: base.NewCommand(ui, opts...), Func: "remove-grants", }), + "roles add-grant-scopes": clientCacheWrapper( + &rolescmd.Command{ + Command: base.NewCommand(ui, opts...), + Func: "add-grant-scopes", + }), + "roles set-grant-scopes": clientCacheWrapper( + &rolescmd.Command{ + Command: base.NewCommand(ui, opts...), + Func: "set-grant-scopes", + }), + "roles remove-grant-scopes": clientCacheWrapper( + &rolescmd.Command{ + Command: base.NewCommand(ui, opts...), + Func: "remove-grant-scopes", + }), "scopes": func() (cli.Command, error) { return &scopescmd.Command{ diff --git a/internal/cmd/commands/authenticate/authenticate.go b/internal/cmd/commands/authenticate/authenticate.go index cde6ab9a34..10921aa704 100644 --- a/internal/cmd/commands/authenticate/authenticate.go +++ b/internal/cmd/commands/authenticate/authenticate.go @@ -71,6 +71,13 @@ func (c *Command) Flags() *base.FlagSets { } func (c *Command) Run(args []string) int { + f := c.Flags() + + if err := f.Parse(args); err != nil { + c.PrintCliError(err) + return base.CommandUserError + } + client, err := c.Client(base.WithNoTokenScope(), base.WithNoTokenValue()) if c.WrapperCleanupFunc != nil { defer func() { @@ -84,13 +91,6 @@ func (c *Command) Run(args []string) int { return base.CommandCliError } - f := c.Flags() - - if err := f.Parse(args); err != nil { - c.PrintCliError(err) - return base.CommandUserError - } - // Lookup the primary auth method ID in the global scope aClient := authmethods.NewClient(client) pri, err := getPrimaryAuthMethodId(c.Context, aClient, scope.Global.String(), "") diff --git a/internal/cmd/commands/database/funcs.go b/internal/cmd/commands/database/funcs.go index 044350fe75..2e667982fb 100644 --- a/internal/cmd/commands/database/funcs.go +++ b/internal/cmd/commands/database/funcs.go @@ -107,7 +107,7 @@ type RoleInfo struct { Name string `json:"name"` } -func generateInitialRoleTableOutput(in *RoleInfo) string { +func generateInitialLoginRoleTableOutput(in *RoleInfo) string { nonAttributeMap := map[string]any{ "Role ID": in.RoleId, "Name": in.Name, @@ -129,6 +129,28 @@ func generateInitialRoleTableOutput(in *RoleInfo) string { return base.WrapForHelpText(ret) } +func generateInitialAuthenticatedUserRoleOutput(in *RoleInfo) string { + nonAttributeMap := map[string]any{ + "Role ID": in.RoleId, + "Name": in.Name, + } + + maxLength := 0 + for k := range nonAttributeMap { + if len(k) > maxLength { + maxLength = len(k) + } + } + + ret := []string{ + "", + "Initial authenticated user role information:", + base.WrapMap(2, maxLength+2, nonAttributeMap), + } + + return base.WrapForHelpText(ret) +} + type AuthInfo struct { AuthMethodId string `json:"auth_method_id"` AuthMethodName string `json:"auth_method_name"` diff --git a/internal/cmd/commands/database/init.go b/internal/cmd/commands/database/init.go index 574d88a927..da970d5fc8 100644 --- a/internal/cmd/commands/database/init.go +++ b/internal/cmd/commands/database/init.go @@ -37,16 +37,17 @@ type InitCommand struct { // deferred function on the Run method. configWrapperCleanupFunc func() error - flagConfig []string - flagConfigKms string - flagLogLevel string - flagLogFormat string - flagMigrationUrl string - flagSkipInitialLoginRoleCreation bool - flagSkipAuthMethodCreation bool - flagSkipScopesCreation bool - flagSkipHostResourcesCreation bool - flagSkipTargetCreation bool + flagConfig []string + flagConfigKms string + flagLogLevel string + flagLogFormat string + flagMigrationUrl string + flagSkipInitialLoginRoleCreation bool + flagSkipInitialAuthenticatedUserRoleCreation bool + flagSkipAuthMethodCreation bool + flagSkipScopesCreation bool + flagSkipHostResourcesCreation bool + flagSkipTargetCreation bool } func (c *InitCommand) Synopsis() string { @@ -124,31 +125,37 @@ func (c *InitCommand) Flags() *base.FlagSets { f.BoolVar(&base.BoolVar{ Name: "skip-initial-login-role-creation", Target: &c.flagSkipInitialLoginRoleCreation, - Usage: "If not set, a default role allowing necessary grants for logging in will not be created as part of initialization. If set, the recovery KMS will be needed to perform any actions.", + Usage: "If set, a role providing necessary grants for logging in will not be created as part of initialization. If set, the recovery KMS will be needed to perform any actions.", + }) + + f.BoolVar(&base.BoolVar{ + Name: "skip-initial-authenticated-user-role-creation", + Target: &c.flagSkipInitialAuthenticatedUserRoleCreation, + Usage: "If set, a role providing initial grants for any authenticated user will not be created as part of initialization.", }) f.BoolVar(&base.BoolVar{ Name: "skip-auth-method-creation", Target: &c.flagSkipAuthMethodCreation, - Usage: "If not set, an auth method will not be created as part of initialization. If set, the recovery KMS will be needed to perform any actions.", + Usage: "If set, an auth method will not be created as part of initialization. If set, the recovery KMS will be needed to perform any actions.", }) f.BoolVar(&base.BoolVar{ Name: "skip-scopes-creation", Target: &c.flagSkipScopesCreation, - Usage: "If not set, scopes will not be created as part of initialization.", + Usage: "If set, scopes will not be created as part of initialization.", }) f.BoolVar(&base.BoolVar{ Name: "skip-host-resources-creation", Target: &c.flagSkipHostResourcesCreation, - Usage: "If not set, host resources (host catalog, host set, host) will not be created as part of initialization.", + Usage: "If set, host resources (host catalog, host set, host) will not be created as part of initialization.", }) f.BoolVar(&base.BoolVar{ Name: "skip-target-creation", Target: &c.flagSkipTargetCreation, - Usage: "If not set, a target will not be created as part of initialization.", + Usage: "If set, a target will not be created as part of initialization.", }) f.StringVar(&base.StringVar{ @@ -330,11 +337,32 @@ func (c *InitCommand) Run(args []string) (retCode int) { } switch base.Format(c.UI) { case "table": - c.UI.Output(generateInitialRoleTableOutput(roleInfo)) + c.UI.Output(generateInitialLoginRoleTableOutput(roleInfo)) case "json": jsonMap["login_role"] = roleInfo } + if c.flagSkipInitialAuthenticatedUserRoleCreation { + return base.CommandSuccess + } + + role, err = c.CreateInitialAuthenticatedUserRole(c.Context) + if err != nil { + c.UI.Error(fmt.Errorf("Error creating initial global-scoped authenticated user role: %w", err).Error()) + return base.CommandCliError + } + + roleInfo = &RoleInfo{ + RoleId: role.PublicId, + Name: role.Name, + } + switch base.Format(c.UI) { + case "table": + c.UI.Output(generateInitialAuthenticatedUserRoleOutput(roleInfo)) + case "json": + jsonMap["authenticated_user_role"] = roleInfo + } + if c.flagSkipAuthMethodCreation { return base.CommandSuccess } diff --git a/internal/cmd/commands/dev/dev.go b/internal/cmd/commands/dev/dev.go index 7fe6150687..552b85d3a9 100644 --- a/internal/cmd/commands/dev/dev.go +++ b/internal/cmd/commands/dev/dev.go @@ -79,7 +79,7 @@ type Command struct { flagTargetDefaultPort int flagTargetSessionMaxSeconds int flagTargetSessionConnectionLimit int - flagControllerAPIListenAddr string + flagControllerApiListenAddr string flagControllerClusterListenAddr string flagControllerPublicClusterAddr string flagControllerOnly bool @@ -102,6 +102,8 @@ type Command struct { flagCreateLoopbackPlugin bool flagPluginExecutionDir string flagSkipPlugins bool + flagSkipOidcAuthMethodCreation bool + flagSkipLdapAuthMethodCreation bool flagWorkerDnsServer string flagWorkerAuthMethod string flagWorkerAuthStorageDir string @@ -202,7 +204,7 @@ func (c *Command) Flags() *base.FlagSets { f.StringVar(&base.StringVar{ Name: "api-listen-address", - Target: &c.flagControllerAPIListenAddr, + Target: &c.flagControllerApiListenAddr, EnvVar: "BOUNDARY_DEV_CONTROLLER_API_LISTEN_ADDRESS", Usage: "Address to bind to for controller \"api\" purpose. If this begins with a forward slash, it will be assumed to be a Unix domain socket path.", }) @@ -381,6 +383,16 @@ func (c *Command) Flags() *base.FlagSets { Usage: "Skip loading compiled-in plugins. This does not prevent loopback plugins from being loaded if enabled.", Hidden: true, }) + f.BoolVar(&base.BoolVar{ + Name: "skip-oidc-auth-method-creation", + Target: &c.flagSkipOidcAuthMethodCreation, + Usage: "Skip creating a test OIDC auth method. This is useful if e.g. running a Unix API listener.", + }) + f.BoolVar(&base.BoolVar{ + Name: "skip-ldap-auth-method-creation", + Target: &c.flagSkipLdapAuthMethodCreation, + Usage: "Skip creating a test LDAP auth method. This is useful if e.g. running a Unix API listener.", + }) f.StringVar(&base.StringVar{ Name: "worker-dns-server", Target: &c.flagWorkerDnsServer, @@ -594,8 +606,8 @@ func (c *Command) Run(args []string) int { } switch l.Purpose[0] { case "api": - if c.flagControllerAPIListenAddr != "" { - l.Address = c.flagControllerAPIListenAddr + if c.flagControllerApiListenAddr != "" { + l.Address = c.flagControllerApiListenAddr } if strings.HasPrefix(l.Address, "/") { l.Type = "unix" @@ -750,6 +762,12 @@ func (c *Command) Run(args []string) int { } var opts []base.Option + if c.flagSkipOidcAuthMethodCreation { + opts = append(opts, base.WithSkipOidcAuthMethodCreation()) + } + if c.flagSkipLdapAuthMethodCreation { + opts = append(opts, base.WithSkipLdapAuthMethodCreation()) + } if c.flagCreateLoopbackPlugin { c.DevLoopbackPluginId = "pl_1234567890" c.EnabledPlugins = append(c.EnabledPlugins, base.EnabledPluginLoopback) diff --git a/internal/cmd/commands/rolescmd/funcs.go b/internal/cmd/commands/rolescmd/funcs.go index 73c553b74b..ee38913a9d 100644 --- a/internal/cmd/commands/rolescmd/funcs.go +++ b/internal/cmd/commands/rolescmd/funcs.go @@ -26,41 +26,42 @@ func init() { } type extraCmdVars struct { - flagGrantScopeId string - flagPrincipals []string - flagGrants []string + flagGrantScopeIds []string + flagPrincipals []string + flagGrants []string } func extraActionsFlagsMapFuncImpl() map[string][]string { return map[string][]string{ - "create": {"grant-scope-id"}, - "update": {"grant-scope-id"}, - "add-principals": {"id", "principal", "version"}, - "set-principals": {"id", "principal", "version"}, - "remove-principals": {"id", "principal", "version"}, - "add-grants": {"id", "grant", "version"}, - "set-grants": {"id", "grant", "version"}, - "remove-grants": {"id", "grant", "version"}, + "create": {"grant-scope-id"}, + "update": {"grant-scope-id"}, + "add-principals": {"id", "principal", "version"}, + "set-principals": {"id", "principal", "version"}, + "remove-principals": {"id", "principal", "version"}, + "add-grants": {"id", "grant", "version"}, + "set-grants": {"id", "grant", "version"}, + "remove-grants": {"id", "grant", "version"}, + "add-grant-scopes": {"id", "grant-scope-id", "version"}, + "set-grant-scopes": {"id", "grant-scope-id", "version"}, + "remove-grant-scopes": {"id", "grant-scope-id", "version"}, } } func extraSynopsisFuncImpl(c *Command) string { switch c.Func { case "add-principals", "set-principals", "remove-principals": - return c.principalsGrantsSynopsisFunc(c.Func, true) + return c.principalsGrantsSynopsisFunc(c.Func, "principals (users, groups)") case "add-grants", "set-grants", "remove-grants": - return c.principalsGrantsSynopsisFunc(c.Func, false) + return c.principalsGrantsSynopsisFunc(c.Func, "grants") + case "add-grant-scopes": + return c.principalsGrantsSynopsisFunc(c.Func, "grant scopes") } return "" } -func (c *Command) principalsGrantsSynopsisFunc(inFunc string, principals bool) string { +func (c *Command) principalsGrantsSynopsisFunc(inFunc, switchStr string) string { var in string - switchStr := "principals (users, groups)" - if !principals { - switchStr = "grants" - } switch { case strings.HasPrefix(inFunc, "add"): in = fmt.Sprintf("Add %s to", switchStr) @@ -141,6 +142,39 @@ func (c *Command) extraHelpFunc(helpMap map[string]func() string) string { "", }) + case "add-grant-scopes": + helpStr = base.WrapForHelpText([]string{ + "Usage: boundary roles add-grant-scopes [options] [args]", + "", + ` Adds grant scopes to a role given its ID. The "grant-scope-id" flag can be specified multiple times. Example:`, + "", + ` $ boundary roles add-grant-scopes -id o_1234567890 -grant-scope-id "this" -grant-scope-id "children"`, + "", + "", + }) + + case "set-grant-scopes": + helpStr = base.WrapForHelpText([]string{ + "Usage: boundary roles set-grant-scopes [options] [args]", + "", + ` Sets the complete set of grant scopes on a role given its ID. The "grant-scope-id" flag can be specified multiple times. Example:`, + "", + ` $ boundary roles set-grant-scopes -id o_1234567890 -grant-scope-id "this" -grant-scope-id "children"`, + "", + "", + }) + + case "remove-grant-scopes": + helpStr = base.WrapForHelpText([]string{ + "Usage: boundary roles remove-grant-scopes [options] [args]", + "", + ` Removes grant scopes from a role given its ID. The "grant-scope-id" flags can be specified multiple times. Example:`, + "", + ` $ boundary roles remove-grant-scopes -id o_1234567890 -grant-scope-id "this" -grant-scope-id "children"`, + "", + "", + }) + default: helpStr = helpMap["base"]() } @@ -151,10 +185,10 @@ func extraFlagsFuncImpl(c *Command, _ *base.FlagSets, f *base.FlagSet) { for _, name := range flagsMap[c.Func] { switch name { case "grant-scope-id": - f.StringVar(&base.StringVar{ + f.StringSliceVar(&base.StringSliceVar{ Name: "grant-scope-id", - Target: &c.flagGrantScopeId, - Usage: "The scope ID for grants set on the role", + Target: &c.flagGrantScopeIds, + Usage: "The scope IDs to inherit grants set on the role", }) case "principal": f.StringSliceVar(&base.StringSliceVar{ @@ -173,15 +207,21 @@ func extraFlagsFuncImpl(c *Command, _ *base.FlagSets, f *base.FlagSet) { } func extraFlagsHandlingFuncImpl(c *Command, _ *base.FlagSets, opts *[]roles.Option) bool { - switch c.flagGrantScopeId { - case "": - case "null": - *opts = append(*opts, roles.DefaultGrantScopeId()) - default: - *opts = append(*opts, roles.WithGrantScopeId(c.flagGrantScopeId)) - } - switch c.Func { + case "create", "update": + switch len(c.flagGrantScopeIds) { + case 0: + case 1: + if c.flagGrantScopeIds[0] == "null" { + *opts = append(*opts, roles.DefaultGrantScopeId()) + } else { + *opts = append(*opts, roles.WithGrantScopeId(c.flagGrantScopeIds[0])) + } + default: + c.UI.Error("-grant-scope-id cannot be specified multiple times when using the deprecated create/update grant_scope_id mechanism") + return false + } + case "add-principals", "remove-principals": if len(c.flagPrincipals) == 0 { c.UI.Error("No principals supplied via -principal") @@ -194,6 +234,12 @@ func extraFlagsHandlingFuncImpl(c *Command, _ *base.FlagSets, opts *[]roles.Opti return false } + case "add-grant-scopes", "remove-grant-scopes": + if len(c.flagGrantScopeIds) == 0 { + c.UI.Error("No grant scope IDs supplied via -grant-scope-id") + return false + } + case "set-principals": switch len(c.flagPrincipals) { case 0: @@ -215,6 +261,17 @@ func extraFlagsHandlingFuncImpl(c *Command, _ *base.FlagSets, opts *[]roles.Opti c.flagGrants = nil } } + + case "set-grant-scopes": + switch len(c.flagGrantScopeIds) { + case 0: + c.UI.Error("No grant scope IDs supplied via -grant-scope-id") + return false + case 1: + if c.flagGrantScopeIds[0] == "null" { + c.flagGrantScopeIds = nil + } + } } if len(c.flagGrants) > 0 { @@ -277,6 +334,24 @@ func executeExtraActionsImpl(c *Command, origResp *api.Response, origItem *roles return nil, nil, nil, err } return result.GetResponse(), result.GetItem(), nil, err + case "add-grant-scopes": + result, err := roleClient.AddGrantScopes(c.Context, c.FlagId, version, c.flagGrantScopeIds, opts...) + if err != nil { + return nil, nil, nil, err + } + return result.GetResponse(), result.GetItem(), nil, err + case "set-grant-scopes": + result, err := roleClient.SetGrantScopes(c.Context, c.FlagId, version, c.flagGrantScopeIds, opts...) + if err != nil { + return nil, nil, nil, err + } + return result.GetResponse(), result.GetItem(), nil, err + case "remove-grant-scopes": + result, err := roleClient.RemoveGrantScopes(c.Context, c.FlagId, version, c.flagGrantScopeIds, opts...) + if err != nil { + return nil, nil, nil, err + } + return result.GetResponse(), result.GetItem(), nil, err } return origResp, origItem, origItems, origError } @@ -407,6 +482,17 @@ func printItemTable(item *roles.Role, resp *api.Response) string { fmt.Sprintf(" %s", grant.Canonical), ) } + if len(item.GrantScopeIds) > 0 { + ret = append(ret, + "", + fmt.Sprintf(" Grant Scope IDs: %s", ""), + ) + } + for _, grantScope := range item.GrantScopeIds { + ret = append(ret, + fmt.Sprintf(" ID: %s", grantScope), + ) + } return base.WrapForHelpText(ret) } diff --git a/internal/cmd/commands/rolescmd/roles.gen.go b/internal/cmd/commands/rolescmd/roles.gen.go index 334f4a9bb3..9fab1d3405 100644 --- a/internal/cmd/commands/rolescmd/roles.gen.go +++ b/internal/cmd/commands/rolescmd/roles.gen.go @@ -269,6 +269,30 @@ func (c *Command) Run(args []string) int { version = uint32(c.FlagVersion) } + case "add-grant-scopes": + switch c.FlagVersion { + case 0: + opts = append(opts, roles.WithAutomaticVersioning(true)) + default: + version = uint32(c.FlagVersion) + } + + case "remove-grant-scopes": + switch c.FlagVersion { + case 0: + opts = append(opts, roles.WithAutomaticVersioning(true)) + default: + version = uint32(c.FlagVersion) + } + + case "set-grant-scopes": + switch c.FlagVersion { + case 0: + opts = append(opts, roles.WithAutomaticVersioning(true)) + default: + version = uint32(c.FlagVersion) + } + } if ok := extraFlagsHandlingFunc(c, f, &opts); !ok { diff --git a/internal/cmd/gencli/input.go b/internal/cmd/gencli/input.go index b8e797171e..925aea3ca0 100644 --- a/internal/cmd/gencli/input.go +++ b/internal/cmd/gencli/input.go @@ -567,7 +567,7 @@ var inputStructs = map[string][]*cmdInfo{ Container: "Scope", HasName: true, HasDescription: true, - VersionedActions: []string{"update", "add-grants", "remove-grants", "set-grants", "add-principals", "remove-principals", "set-principals"}, + VersionedActions: []string{"update", "add-grants", "remove-grants", "set-grants", "add-principals", "remove-principals", "set-principals", "add-grant-scopes", "remove-grant-scopes", "set-grant-scopes"}, }, }, "scopes": { diff --git a/internal/daemon/controller/handlers/roles/role_service.go b/internal/daemon/controller/handlers/roles/role_service.go index 8a4a038755..aa6c295658 100644 --- a/internal/daemon/controller/handlers/roles/role_service.go +++ b/internal/daemon/controller/handlers/roles/role_service.go @@ -6,6 +6,8 @@ package roles import ( "context" "fmt" + "sort" + "strings" "github.com/hashicorp/boundary/globals" "github.com/hashicorp/boundary/internal/daemon/controller/auth" @@ -47,6 +49,9 @@ var ( action.AddGrants, action.SetGrants, action.RemoveGrants, + action.AddGrantScopes, + action.SetGrantScopes, + action.RemoveGrantScopes, ) // CollectionActions contains the set of actions that can be performed on @@ -144,7 +149,7 @@ func (s Service) ListRoles(ctx context.Context, req *pbs.ListRolesRequest) (*pbs if !ok { return false, nil } - pbItem, err := toProto(ctx, item, nil, nil, outputOpts...) + pbItem, err := toProto(ctx, item, nil, nil, nil, outputOpts...) if err != nil { return false, err } @@ -209,7 +214,7 @@ func (s Service) ListRoles(ctx context.Context, req *pbs.ListRolesRequest) (*pbs if !ok { continue } - item, err := toProto(ctx, item, nil, nil, outputOpts...) + item, err := toProto(ctx, item, nil, nil, nil, outputOpts...) if err != nil { return nil, errors.Wrap(ctx, err, op) } @@ -248,7 +253,7 @@ func (s Service) GetRole(ctx context.Context, req *pbs.GetRoleRequest) (*pbs.Get if authResults.Error != nil { return nil, authResults.Error } - r, prs, rgs, err := s.getFromRepo(ctx, req.GetId()) + r, prs, rgs, grantScopes, err := s.getFromRepo(ctx, req.GetId()) if err != nil { return nil, err } @@ -267,7 +272,7 @@ func (s Service) GetRole(ctx context.Context, req *pbs.GetRoleRequest) (*pbs.Get outputOpts = append(outputOpts, handlers.WithAuthorizedActions(authResults.FetchActionSetForId(ctx, r.GetPublicId(), IdActions).Strings())) } - item, err := toProto(ctx, r, prs, rgs, outputOpts...) + item, err := toProto(ctx, r, prs, rgs, grantScopes, outputOpts...) if err != nil { return nil, err } @@ -286,7 +291,7 @@ func (s Service) CreateRole(ctx context.Context, req *pbs.CreateRoleRequest) (*p if authResults.Error != nil { return nil, authResults.Error } - r, err := s.createInRepo(ctx, authResults.Scope.GetId(), req.GetItem()) + r, prs, rgs, grantScopes, err := s.createInRepo(ctx, authResults.Scope.GetId(), req.GetItem()) if err != nil { return nil, err } @@ -305,7 +310,7 @@ func (s Service) CreateRole(ctx context.Context, req *pbs.CreateRoleRequest) (*p outputOpts = append(outputOpts, handlers.WithAuthorizedActions(authResults.FetchActionSetForId(ctx, r.GetPublicId(), IdActions).Strings())) } - item, err := toProto(ctx, r, nil, nil, outputOpts...) + item, err := toProto(ctx, r, prs, rgs, grantScopes, outputOpts...) if err != nil { return nil, err } @@ -324,7 +329,7 @@ func (s Service) UpdateRole(ctx context.Context, req *pbs.UpdateRoleRequest) (*p if authResults.Error != nil { return nil, authResults.Error } - r, prs, rgs, err := s.updateInRepo(ctx, authResults.Scope.GetId(), req.GetId(), req.GetUpdateMask().GetPaths(), req.GetItem()) + r, prs, rgs, grantScopes, err := s.updateInRepo(ctx, authResults.Scope.GetId(), req.GetId(), req.GetUpdateMask().GetPaths(), req.GetItem()) if err != nil { return nil, err } @@ -343,7 +348,7 @@ func (s Service) UpdateRole(ctx context.Context, req *pbs.UpdateRoleRequest) (*p outputOpts = append(outputOpts, handlers.WithAuthorizedActions(authResults.FetchActionSetForId(ctx, r.GetPublicId(), IdActions).Strings())) } - item, err := toProto(ctx, r, prs, rgs, outputOpts...) + item, err := toProto(ctx, r, prs, rgs, grantScopes, outputOpts...) if err != nil { return nil, err } @@ -378,7 +383,7 @@ func (s Service) AddRolePrincipals(ctx context.Context, req *pbs.AddRolePrincipa if authResults.Error != nil { return nil, authResults.Error } - r, prs, rgs, err := s.addPrinciplesInRepo(ctx, req.GetId(), req.GetPrincipalIds(), req.GetVersion()) + r, prs, rgs, grantScopes, err := s.addPrincipalsInRepo(ctx, req.GetId(), req.GetPrincipalIds(), req.GetVersion()) if err != nil { return nil, err } @@ -397,7 +402,7 @@ func (s Service) AddRolePrincipals(ctx context.Context, req *pbs.AddRolePrincipa outputOpts = append(outputOpts, handlers.WithAuthorizedActions(authResults.FetchActionSetForId(ctx, r.GetPublicId(), IdActions).Strings())) } - item, err := toProto(ctx, r, prs, rgs, outputOpts...) + item, err := toProto(ctx, r, prs, rgs, grantScopes, outputOpts...) if err != nil { return nil, err } @@ -416,7 +421,7 @@ func (s Service) SetRolePrincipals(ctx context.Context, req *pbs.SetRolePrincipa if authResults.Error != nil { return nil, authResults.Error } - r, prs, rgs, err := s.setPrinciplesInRepo(ctx, req.GetId(), req.GetPrincipalIds(), req.GetVersion()) + r, prs, rgs, grantScopes, err := s.setPrincipalsInRepo(ctx, req.GetId(), req.GetPrincipalIds(), req.GetVersion()) if err != nil { return nil, err } @@ -435,7 +440,7 @@ func (s Service) SetRolePrincipals(ctx context.Context, req *pbs.SetRolePrincipa outputOpts = append(outputOpts, handlers.WithAuthorizedActions(authResults.FetchActionSetForId(ctx, r.GetPublicId(), IdActions).Strings())) } - item, err := toProto(ctx, r, prs, rgs, outputOpts...) + item, err := toProto(ctx, r, prs, rgs, grantScopes, outputOpts...) if err != nil { return nil, err } @@ -454,7 +459,7 @@ func (s Service) RemoveRolePrincipals(ctx context.Context, req *pbs.RemoveRolePr if authResults.Error != nil { return nil, authResults.Error } - r, prs, rgs, err := s.removePrinciplesInRepo(ctx, req.GetId(), req.GetPrincipalIds(), req.GetVersion()) + r, prs, rgs, grantScopes, err := s.removePrincipalsInRepo(ctx, req.GetId(), req.GetPrincipalIds(), req.GetVersion()) if err != nil { return nil, err } @@ -473,7 +478,7 @@ func (s Service) RemoveRolePrincipals(ctx context.Context, req *pbs.RemoveRolePr outputOpts = append(outputOpts, handlers.WithAuthorizedActions(authResults.FetchActionSetForId(ctx, r.GetPublicId(), IdActions).Strings())) } - item, err := toProto(ctx, r, prs, rgs, outputOpts...) + item, err := toProto(ctx, r, prs, rgs, grantScopes, outputOpts...) if err != nil { return nil, err } @@ -492,7 +497,7 @@ func (s Service) AddRoleGrants(ctx context.Context, req *pbs.AddRoleGrantsReques if authResults.Error != nil { return nil, authResults.Error } - r, prs, rgs, err := s.addGrantsInRepo(ctx, req.GetId(), req.GetGrantStrings(), req.GetVersion()) + r, prs, rgs, grantScopes, err := s.addGrantsInRepo(ctx, req.GetId(), req.GetGrantStrings(), req.GetVersion()) if err != nil { return nil, err } @@ -511,7 +516,7 @@ func (s Service) AddRoleGrants(ctx context.Context, req *pbs.AddRoleGrantsReques outputOpts = append(outputOpts, handlers.WithAuthorizedActions(authResults.FetchActionSetForId(ctx, r.GetPublicId(), IdActions).Strings())) } - item, err := toProto(ctx, r, prs, rgs, outputOpts...) + item, err := toProto(ctx, r, prs, rgs, grantScopes, outputOpts...) if err != nil { return nil, err } @@ -530,7 +535,7 @@ func (s Service) SetRoleGrants(ctx context.Context, req *pbs.SetRoleGrantsReques if authResults.Error != nil { return nil, authResults.Error } - r, prs, rgs, err := s.setGrantsInRepo(ctx, req.GetId(), req.GetGrantStrings(), req.GetVersion()) + r, prs, rgs, grantScopes, err := s.setGrantsInRepo(ctx, req.GetId(), req.GetGrantStrings(), req.GetVersion()) if err != nil { return nil, err } @@ -549,7 +554,7 @@ func (s Service) SetRoleGrants(ctx context.Context, req *pbs.SetRoleGrantsReques outputOpts = append(outputOpts, handlers.WithAuthorizedActions(authResults.FetchActionSetForId(ctx, r.GetPublicId(), IdActions).Strings())) } - item, err := toProto(ctx, r, prs, rgs, outputOpts...) + item, err := toProto(ctx, r, prs, rgs, grantScopes, outputOpts...) if err != nil { return nil, err } @@ -568,7 +573,7 @@ func (s Service) RemoveRoleGrants(ctx context.Context, req *pbs.RemoveRoleGrants if authResults.Error != nil { return nil, authResults.Error } - r, prs, rgs, err := s.removeGrantsInRepo(ctx, req.GetId(), req.GetGrantStrings(), req.GetVersion()) + r, prs, rgs, grantScopes, err := s.removeGrantsInRepo(ctx, req.GetId(), req.GetGrantStrings(), req.GetVersion()) if err != nil { return nil, err } @@ -587,7 +592,7 @@ func (s Service) RemoveRoleGrants(ctx context.Context, req *pbs.RemoveRoleGrants outputOpts = append(outputOpts, handlers.WithAuthorizedActions(authResults.FetchActionSetForId(ctx, r.GetPublicId(), IdActions).Strings())) } - item, err := toProto(ctx, r, prs, rgs, outputOpts...) + item, err := toProto(ctx, r, prs, rgs, grantScopes, outputOpts...) if err != nil { return nil, err } @@ -595,25 +600,142 @@ func (s Service) RemoveRoleGrants(ctx context.Context, req *pbs.RemoveRoleGrants return &pbs.RemoveRoleGrantsResponse{Item: item}, nil } -func (s Service) getFromRepo(ctx context.Context, id string) (*iam.Role, []*iam.PrincipalRole, []*iam.RoleGrant, error) { +// AddRoleGrantScopes implements the interface pbs.RoleServiceServer. +func (s Service) AddRoleGrantScopes(ctx context.Context, req *pbs.AddRoleGrantScopesRequest) (*pbs.AddRoleGrantScopesResponse, error) { + const op = "roles.(Service).AddRoleGrantScopes" + + if err := validateRoleGrantScopesRequest(ctx, req); err != nil { + return nil, err + } + authResults := s.authResult(ctx, req.GetId(), action.AddGrantScopes) + if authResults.Error != nil { + return nil, authResults.Error + } + + r, prs, rgs, grantScopes, err := s.addGrantScopesInRepo(ctx, req) + if err != nil { + return nil, err + } + + outputFields, ok := requests.OutputFields(ctx) + if !ok { + return nil, errors.New(ctx, errors.Internal, op, "no request context found") + } + + outputOpts := make([]handlers.Option, 0, 3) + outputOpts = append(outputOpts, handlers.WithOutputFields(outputFields)) + if outputFields.Has(globals.ScopeField) { + outputOpts = append(outputOpts, handlers.WithScope(authResults.Scope)) + } + if outputFields.Has(globals.AuthorizedActionsField) { + outputOpts = append(outputOpts, handlers.WithAuthorizedActions(authResults.FetchActionSetForId(ctx, r.GetPublicId(), IdActions).Strings())) + } + + item, err := toProto(ctx, r, prs, rgs, grantScopes, outputOpts...) + if err != nil { + return nil, err + } + + return &pbs.AddRoleGrantScopesResponse{Item: item}, nil +} + +// SetRoleGrantScopes implements the interface pbs.RoleServiceServer. +func (s Service) SetRoleGrantScopes(ctx context.Context, req *pbs.SetRoleGrantScopesRequest) (*pbs.SetRoleGrantScopesResponse, error) { + const op = "roles.(Service).SetRoleGrantScopes" + + if err := validateRoleGrantScopesRequest(ctx, req); err != nil { + return nil, err + } + authResults := s.authResult(ctx, req.GetId(), action.SetGrantScopes) + if authResults.Error != nil { + return nil, authResults.Error + } + + r, prs, rgs, grantScopes, err := s.setGrantScopesInRepo(ctx, req) + if err != nil { + return nil, err + } + + outputFields, ok := requests.OutputFields(ctx) + if !ok { + return nil, errors.New(ctx, errors.Internal, op, "no request context found") + } + + outputOpts := make([]handlers.Option, 0, 3) + outputOpts = append(outputOpts, handlers.WithOutputFields(outputFields)) + if outputFields.Has(globals.ScopeField) { + outputOpts = append(outputOpts, handlers.WithScope(authResults.Scope)) + } + if outputFields.Has(globals.AuthorizedActionsField) { + outputOpts = append(outputOpts, handlers.WithAuthorizedActions(authResults.FetchActionSetForId(ctx, r.GetPublicId(), IdActions).Strings())) + } + + item, err := toProto(ctx, r, prs, rgs, grantScopes, outputOpts...) + if err != nil { + return nil, err + } + + return &pbs.SetRoleGrantScopesResponse{Item: item}, nil +} + +// RemoveRoleGrantScopes implements the interface pbs.RoleServiceServer. +func (s Service) RemoveRoleGrantScopes(ctx context.Context, req *pbs.RemoveRoleGrantScopesRequest) (*pbs.RemoveRoleGrantScopesResponse, error) { + const op = "roles.(Service).RemoveRoleGrantScopes" + + if err := validateRoleGrantScopesRequest(ctx, req); err != nil { + return nil, err + } + authResults := s.authResult(ctx, req.GetId(), action.RemoveGrants) + if authResults.Error != nil { + return nil, authResults.Error + } + + r, prs, rgs, grantScopes, err := s.removeGrantScopesInRepo(ctx, req) + if err != nil { + return nil, err + } + + outputFields, ok := requests.OutputFields(ctx) + if !ok { + return nil, errors.New(ctx, errors.Internal, op, "no request context found") + } + + outputOpts := make([]handlers.Option, 0, 3) + outputOpts = append(outputOpts, handlers.WithOutputFields(outputFields)) + if outputFields.Has(globals.ScopeField) { + outputOpts = append(outputOpts, handlers.WithScope(authResults.Scope)) + } + if outputFields.Has(globals.AuthorizedActionsField) { + outputOpts = append(outputOpts, handlers.WithAuthorizedActions(authResults.FetchActionSetForId(ctx, r.GetPublicId(), IdActions).Strings())) + } + + item, err := toProto(ctx, r, prs, rgs, grantScopes, outputOpts...) + if err != nil { + return nil, err + } + + return &pbs.RemoveRoleGrantScopesResponse{Item: item}, nil +} + +func (s Service) getFromRepo(ctx context.Context, id string) (*iam.Role, []*iam.PrincipalRole, []*iam.RoleGrant, []*iam.RoleGrantScope, error) { repo, err := s.repoFn() if err != nil { - return nil, nil, nil, err + return nil, nil, nil, nil, err } - out, pr, roleGrants, err := repo.LookupRole(ctx, id) + out, pr, roleGrants, roleGrantScopes, err := repo.LookupRole(ctx, id) if err != nil { if errors.IsNotFoundError(err) { - return nil, nil, nil, handlers.NotFoundErrorf("Role %q doesn't exist.", id) + return nil, nil, nil, nil, handlers.NotFoundErrorf("Role %q doesn't exist.", id) } - return nil, nil, nil, err + return nil, nil, nil, nil, err } if out == nil { - return nil, nil, nil, handlers.NotFoundErrorf("Role %q doesn't exist.", id) + return nil, nil, nil, nil, handlers.NotFoundErrorf("Role %q doesn't exist.", id) } - return out, pr, roleGrants, nil + return out, pr, roleGrants, roleGrantScopes, nil } -func (s Service) createInRepo(ctx context.Context, scopeId string, item *pb.Role) (*iam.Role, error) { +func (s Service) createInRepo(ctx context.Context, scopeId string, item *pb.Role) (*iam.Role, []*iam.PrincipalRole, []*iam.RoleGrant, []*iam.RoleGrantScope, error) { const op = "roles.(Service).createInRepo" var opts []iam.Option if item.GetName() != nil { @@ -627,23 +749,23 @@ func (s Service) createInRepo(ctx context.Context, scopeId string, item *pb.Role } u, err := iam.NewRole(ctx, scopeId, opts...) if err != nil { - return nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "Unable to build role for creation: %v.", err) + return nil, nil, nil, nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "Unable to build role for creation: %v.", err) } repo, err := s.repoFn() if err != nil { - return nil, err + return nil, nil, nil, nil, err } - out, err := repo.CreateRole(ctx, u) + out, pr, roleGrants, roleGrantScopes, err := repo.CreateRole(ctx, u, opts...) if err != nil { - return nil, errors.Wrap(ctx, err, op) + return nil, nil, nil, nil, errors.Wrap(ctx, err, op) } if out == nil { - return nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "Unable to create role but no error returned from repository.") + return nil, nil, nil, nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "Unable to create role but no error returned from repository.") } - return out, nil + return out, pr, roleGrants, roleGrantScopes, nil } -func (s Service) updateInRepo(ctx context.Context, scopeId, id string, mask []string, item *pb.Role) (*iam.Role, []*iam.PrincipalRole, []*iam.RoleGrant, error) { +func (s Service) updateInRepo(ctx context.Context, scopeId, id string, mask []string, item *pb.Role) (*iam.Role, []*iam.PrincipalRole, []*iam.RoleGrant, []*iam.RoleGrantScope, error) { const op = "roles.(Service).updateInRepo" var opts []iam.Option if desc := item.GetDescription(); desc != nil { @@ -652,32 +774,54 @@ func (s Service) updateInRepo(ctx context.Context, scopeId, id string, mask []st if name := item.GetName(); name != nil { opts = append(opts, iam.WithName(name.GetValue())) } - if grantScopeId := item.GetGrantScopeId(); grantScopeId != nil { - opts = append(opts, iam.WithGrantScopeId(grantScopeId.GetValue())) - } version := item.GetVersion() u, err := iam.NewRole(ctx, scopeId, opts...) if err != nil { - return nil, nil, nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "Unable to build role for update: %v.", err) + return nil, nil, nil, nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "Unable to build role for update: %v.", err) } u.PublicId = id - dbMask := maskManager.Translate(mask) - if len(dbMask) == 0 { - return nil, nil, nil, handlers.InvalidArgumentErrorf("No valid fields included in the update mask.", map[string]string{"update_mask": "No valid fields provided in the update mask."}) - } + repo, err := s.repoFn() if err != nil { - return nil, nil, nil, err + return nil, nil, nil, nil, err } - out, pr, gr, rowsUpdated, err := repo.UpdateRole(ctx, u, version, dbMask) + + // This has to be separate from Translate since the field only exists on the + // API side now. + var hasGrantScopeIdMask bool + if grantScopeId := item.GetGrantScopeId(); grantScopeId != nil { + // Have to manually check the masks, which could be concatenated strings or slices + for _, v := range mask { + if strutil.StrListContains(strings.Split(v, ","), "grant_scope_id") { + opts = append(opts, iam.WithGrantScopeId(grantScopeId.GetValue())) + hasGrantScopeIdMask = true + break + } + } + if hasGrantScopeIdMask { + if err := validateRoleGrantScopesHierarchy(ctx, repo, id, []string{item.GetGrantScopeId().GetValue()}); err != nil { + return nil, nil, nil, nil, err + } + } + } + + dbMask := maskManager.Translate(mask) + if len(dbMask) == 0 && !hasGrantScopeIdMask { + return nil, nil, nil, nil, handlers.InvalidArgumentErrorf("No valid fields provided in the update mask.", map[string]string{"update_mask": "No valid fields provided in the update mask."}) + } + + out, pr, gr, grantScopes, rowsUpdated, err := repo.UpdateRole(ctx, u, version, dbMask, opts...) if err != nil { - return nil, nil, nil, errors.Wrap(ctx, err, op) + return nil, nil, nil, nil, errors.Wrap(ctx, err, op) } - if rowsUpdated == 0 { - return nil, nil, nil, handlers.NotFoundErrorf("Role %q doesn't exist or incorrect version provided.", id) + // This is slightly problematic but it's a very unlikely error case and when + // we remove the ability to update grant scope ID via here in 0.17 it will + // go away. + if rowsUpdated == 0 && !hasGrantScopeIdMask { + return nil, nil, nil, nil, handlers.NotFoundErrorf("Role %q doesn't exist or incorrect version provided.", id) } - return out, pr, gr, nil + return out, pr, gr, grantScopes, nil } func (s Service) deleteFromRepo(ctx context.Context, id string) (bool, error) { @@ -696,95 +840,95 @@ func (s Service) deleteFromRepo(ctx context.Context, id string) (bool, error) { return rows > 0, nil } -func (s Service) addPrinciplesInRepo(ctx context.Context, roleId string, principalIds []string, version uint32) (*iam.Role, []*iam.PrincipalRole, []*iam.RoleGrant, error) { +func (s Service) addPrincipalsInRepo(ctx context.Context, roleId string, principalIds []string, version uint32) (*iam.Role, []*iam.PrincipalRole, []*iam.RoleGrant, []*iam.RoleGrantScope, error) { const op = "roles.(Service).addPrincpleInRepo" repo, err := s.repoFn() if err != nil { - return nil, nil, nil, err + return nil, nil, nil, nil, err } _, err = repo.AddPrincipalRoles(ctx, roleId, version, strutil.RemoveDuplicates(principalIds, false)) if err != nil { // TODO: Figure out a way to surface more helpful error info beyond the Internal error. - return nil, nil, nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "Unable to add principals to role: %v.", err) + return nil, nil, nil, nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "Unable to add principals to role: %v.", err) } - out, pr, roleGrants, err := repo.LookupRole(ctx, roleId) + out, pr, roleGrants, grantScopes, err := repo.LookupRole(ctx, roleId) if err != nil { - return nil, nil, nil, errors.Wrap(ctx, err, op, errors.WithMsg("unable to look up role after adding principals")) + return nil, nil, nil, nil, errors.Wrap(ctx, err, op, errors.WithMsg("unable to look up role after adding principals")) } if out == nil { - return nil, nil, nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "Unable to lookup role after adding principals to it.") + return nil, nil, nil, nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "Unable to lookup role after adding principals to it.") } - return out, pr, roleGrants, nil + return out, pr, roleGrants, grantScopes, nil } -func (s Service) setPrinciplesInRepo(ctx context.Context, roleId string, principalIds []string, version uint32) (*iam.Role, []*iam.PrincipalRole, []*iam.RoleGrant, error) { - const op = "roles.(Service).setPrinciplesInRepo" +func (s Service) setPrincipalsInRepo(ctx context.Context, roleId string, principalIds []string, version uint32) (*iam.Role, []*iam.PrincipalRole, []*iam.RoleGrant, []*iam.RoleGrantScope, error) { + const op = "roles.(Service).setPrincipalsInRepo" repo, err := s.repoFn() if err != nil { - return nil, nil, nil, err + return nil, nil, nil, nil, err } _, _, err = repo.SetPrincipalRoles(ctx, roleId, version, strutil.RemoveDuplicates(principalIds, false)) if err != nil { // TODO: Figure out a way to surface more helpful error info beyond the Internal error. - return nil, nil, nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "Unable to set principals on role: %v.", err) + return nil, nil, nil, nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "Unable to set principals on role: %v.", err) } - out, pr, roleGrants, err := repo.LookupRole(ctx, roleId) + out, pr, roleGrants, grantScopes, err := repo.LookupRole(ctx, roleId) if err != nil { - return nil, nil, nil, errors.Wrap(ctx, err, op, errors.WithMsg("unable to look up role after setting principals")) + return nil, nil, nil, nil, errors.Wrap(ctx, err, op, errors.WithMsg("unable to look up role after setting principals")) } if out == nil { - return nil, nil, nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "Unable to lookup role after setting principals for it.") + return nil, nil, nil, nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "Unable to lookup role after setting principals for it.") } - return out, pr, roleGrants, nil + return out, pr, roleGrants, grantScopes, nil } -func (s Service) removePrinciplesInRepo(ctx context.Context, roleId string, principalIds []string, version uint32) (*iam.Role, []*iam.PrincipalRole, []*iam.RoleGrant, error) { - const op = "roles.(Service).removePrinciplesInRepo" +func (s Service) removePrincipalsInRepo(ctx context.Context, roleId string, principalIds []string, version uint32) (*iam.Role, []*iam.PrincipalRole, []*iam.RoleGrant, []*iam.RoleGrantScope, error) { + const op = "roles.(Service).removePrincipalsInRepo" repo, err := s.repoFn() if err != nil { - return nil, nil, nil, err + return nil, nil, nil, nil, err } _, err = repo.DeletePrincipalRoles(ctx, roleId, version, strutil.RemoveDuplicates(principalIds, false)) if err != nil { // TODO: Figure out a way to surface more helpful error info beyond the Internal error. - return nil, nil, nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "Unable to remove principals from role: %v.", err) + return nil, nil, nil, nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "Unable to remove principals from role: %v.", err) } - out, pr, roleGrants, err := repo.LookupRole(ctx, roleId) + out, pr, roleGrants, grantScopes, err := repo.LookupRole(ctx, roleId) if err != nil { - return nil, nil, nil, errors.Wrap(ctx, err, op, errors.WithMsg("unable to look up role after removing principals")) + return nil, nil, nil, nil, errors.Wrap(ctx, err, op, errors.WithMsg("unable to look up role after removing principals")) } if out == nil { - return nil, nil, nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "Unable to lookup role after removing principals from it.") + return nil, nil, nil, nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "Unable to lookup role after removing principals from it.") } - return out, pr, roleGrants, nil + return out, pr, roleGrants, grantScopes, nil } -func (s Service) addGrantsInRepo(ctx context.Context, roleId string, grants []string, version uint32) (*iam.Role, []*iam.PrincipalRole, []*iam.RoleGrant, error) { +func (s Service) addGrantsInRepo(ctx context.Context, roleId string, grants []string, version uint32) (*iam.Role, []*iam.PrincipalRole, []*iam.RoleGrant, []*iam.RoleGrantScope, error) { const op = "service.(Service).addGrantsInRepo" repo, err := s.repoFn() if err != nil { - return nil, nil, nil, err + return nil, nil, nil, nil, err } _, err = repo.AddRoleGrants(ctx, roleId, version, strutil.RemoveDuplicates(grants, false)) if err != nil { // TODO: Figure out a way to surface more helpful error info beyond the Internal error. - return nil, nil, nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "Unable to add grants to role: %v.", err) + return nil, nil, nil, nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "Unable to add grants to role: %v.", err) } - out, pr, roleGrants, err := repo.LookupRole(ctx, roleId) + out, pr, roleGrants, grantScopes, err := repo.LookupRole(ctx, roleId) if err != nil { - return nil, nil, nil, errors.Wrap(ctx, err, op, errors.WithMsg("unable to look up role after adding grants")) + return nil, nil, nil, nil, errors.Wrap(ctx, err, op, errors.WithMsg("unable to look up role after adding grants")) } if out == nil { - return nil, nil, nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "Unable to lookup role after adding grants to it.") + return nil, nil, nil, nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "Unable to lookup role after adding grants to it.") } - return out, pr, roleGrants, nil + return out, pr, roleGrants, grantScopes, nil } -func (s Service) setGrantsInRepo(ctx context.Context, roleId string, grants []string, version uint32) (*iam.Role, []*iam.PrincipalRole, []*iam.RoleGrant, error) { +func (s Service) setGrantsInRepo(ctx context.Context, roleId string, grants []string, version uint32) (*iam.Role, []*iam.PrincipalRole, []*iam.RoleGrant, []*iam.RoleGrantScope, error) { const op = "roles.(Service).setGrantsInRepo" repo, err := s.repoFn() if err != nil { - return nil, nil, nil, err + return nil, nil, nil, nil, err } // If no grant was provided, we clear the grants. if grants == nil { @@ -793,37 +937,115 @@ func (s Service) setGrantsInRepo(ctx context.Context, roleId string, grants []st _, _, err = repo.SetRoleGrants(ctx, roleId, version, strutil.RemoveDuplicates(grants, false)) if err != nil { // TODO: Figure out a way to surface more helpful error info beyond the Internal error. - return nil, nil, nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "Unable to set grants on role: %v.", err) + return nil, nil, nil, nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "Unable to set grants on role: %v.", err) } - out, pr, roleGrants, err := repo.LookupRole(ctx, roleId) + out, pr, roleGrants, grantScopes, err := repo.LookupRole(ctx, roleId) if err != nil { - return nil, nil, nil, errors.Wrap(ctx, err, op, errors.WithMsg("unable to look up role after setting grants")) + return nil, nil, nil, nil, errors.Wrap(ctx, err, op, errors.WithMsg("unable to look up role after setting grants")) } if out == nil { - return nil, nil, nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "Unable to lookup role after setting grants on it.") + return nil, nil, nil, nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "Unable to lookup role after setting grants on it.") } - return out, pr, roleGrants, nil + return out, pr, roleGrants, grantScopes, nil } -func (s Service) removeGrantsInRepo(ctx context.Context, roleId string, grants []string, version uint32) (*iam.Role, []*iam.PrincipalRole, []*iam.RoleGrant, error) { +func (s Service) removeGrantsInRepo(ctx context.Context, roleId string, grants []string, version uint32) (*iam.Role, []*iam.PrincipalRole, []*iam.RoleGrant, []*iam.RoleGrantScope, error) { const op = "roles.(Service).removeGrantsInRepo" repo, err := s.repoFn() if err != nil { - return nil, nil, nil, err + return nil, nil, nil, nil, err } _, err = repo.DeleteRoleGrants(ctx, roleId, version, strutil.RemoveDuplicates(grants, false)) if err != nil { // TODO: Figure out a way to surface more helpful error info beyond the Internal error. - return nil, nil, nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "Unable to remove grants from role: %v", err) + return nil, nil, nil, nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "Unable to remove grants from role: %v", err) + } + out, pr, roleGrants, grantScopes, err := repo.LookupRole(ctx, roleId) + if err != nil { + return nil, nil, nil, nil, errors.Wrap(ctx, err, op, errors.WithMsg("uable to look up role after removing grant")) + } + if out == nil { + return nil, nil, nil, nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "Unable to lookup role after removing grants from it.") + } + return out, pr, roleGrants, grantScopes, nil +} + +func (s Service) addGrantScopesInRepo(ctx context.Context, req grantScopeRequest) (*iam.Role, []*iam.PrincipalRole, []*iam.RoleGrant, []*iam.RoleGrantScope, error) { + const op = "service.(Service).addGrantScopesInRepo" + repo, err := s.repoFn() + if err != nil { + return nil, nil, nil, nil, err + } + + deduped := strutil.RemoveDuplicates(req.GetGrantScopeIds(), false) + + if err := validateRoleGrantScopesHierarchy(ctx, repo, req.GetId(), deduped); err != nil { + return nil, nil, nil, nil, err + } + + _, err = repo.AddRoleGrantScopes(ctx, req.GetId(), req.GetVersion(), deduped) + if err != nil { + // TODO: Figure out a way to surface more helpful error info beyond the Internal error. + return nil, nil, nil, nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "Unable to add grant scopes to role: %v.", err) } - out, pr, roleGrants, err := repo.LookupRole(ctx, roleId) + out, pr, roleGrants, grantScopes, err := repo.LookupRole(ctx, req.GetId()) if err != nil { - return nil, nil, nil, errors.Wrap(ctx, err, op, errors.WithMsg("uable to look up role after removing grant")) + return nil, nil, nil, nil, errors.Wrap(ctx, err, op, errors.WithMsg("unable to look up role after adding grant scopes")) } if out == nil { - return nil, nil, nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "Unable to lookup role after removing grants from it.") + return nil, nil, nil, nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "Unable to lookup role after adding grant scopes to it.") } - return out, pr, roleGrants, nil + return out, pr, roleGrants, grantScopes, nil +} + +func (s Service) setGrantScopesInRepo(ctx context.Context, req grantScopeRequest) (*iam.Role, []*iam.PrincipalRole, []*iam.RoleGrant, []*iam.RoleGrantScope, error) { + const op = "service.(Service).setGrantScopesInRepo" + repo, err := s.repoFn() + if err != nil { + return nil, nil, nil, nil, err + } + + deduped := strutil.RemoveDuplicates(req.GetGrantScopeIds(), false) + + if err := validateRoleGrantScopesHierarchy(ctx, repo, req.GetId(), deduped); err != nil { + return nil, nil, nil, nil, err + } + + _, _, err = repo.SetRoleGrantScopes(ctx, req.GetId(), req.GetVersion(), deduped) + if err != nil { + // TODO: Figure out a way to surface more helpful error info beyond the Internal error. + return nil, nil, nil, nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "Unable to set grant scopes on role: %v.", err) + } + out, pr, roleGrants, grantScopes, err := repo.LookupRole(ctx, req.GetId()) + if err != nil { + return nil, nil, nil, nil, errors.Wrap(ctx, err, op, errors.WithMsg("unable to look up role after setting grant scopes")) + } + if out == nil { + return nil, nil, nil, nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "Unable to lookup role after setting grant scopes to it.") + } + return out, pr, roleGrants, grantScopes, nil +} + +func (s Service) removeGrantScopesInRepo(ctx context.Context, req grantScopeRequest) (*iam.Role, []*iam.PrincipalRole, []*iam.RoleGrant, []*iam.RoleGrantScope, error) { + const op = "service.(Service).setGrantScopesInRepo" + repo, err := s.repoFn() + if err != nil { + return nil, nil, nil, nil, err + } + + _, err = repo.DeleteRoleGrantScopes(ctx, req.GetId(), req.GetVersion(), strutil.RemoveDuplicates(req.GetGrantScopeIds(), false)) + if err != nil { + // TODO: Figure out a way to surface more helpful error info beyond the Internal error. + return nil, nil, nil, nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "Unable to remove grant scopes from role: %v.", err) + } + out, pr, roleGrants, grantScopes, err := repo.LookupRole(ctx, req.GetId()) + if err != nil { + return nil, nil, nil, nil, errors.Wrap(ctx, err, op, errors.WithMsg("unable to look up role after removing grant scopes")) + } + if out == nil { + return nil, nil, nil, nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "Unable to lookup role after removing grant scopes to it.") + } + return out, pr, roleGrants, grantScopes, nil } func (s Service) authResult(ctx context.Context, id string, a action.Type) auth.VerifyResults { @@ -849,7 +1071,7 @@ func (s Service) authResult(ctx context.Context, id string, a action.Type) auth. return res } default: - r, _, _, err := repo.LookupRole(ctx, id) + r, _, _, _, err := repo.LookupRole(ctx, id) if err != nil { res.Error = err return res @@ -865,7 +1087,7 @@ func (s Service) authResult(ctx context.Context, id string, a action.Type) auth. return auth.Verify(ctx, opts...) } -func toProto(ctx context.Context, in *iam.Role, principals []*iam.PrincipalRole, grants []*iam.RoleGrant, opt ...handlers.Option) (*pb.Role, error) { +func toProto(ctx context.Context, in *iam.Role, principals []*iam.PrincipalRole, grants []*iam.RoleGrant, grantScopes []*iam.RoleGrantScope, opt ...handlers.Option) (*pb.Role, error) { opts := handlers.GetOpts(opt...) if opts.WithOutputFields == nil { return nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "output fields not found when building role proto") @@ -900,14 +1122,20 @@ func toProto(ctx context.Context, in *iam.Role, principals []*iam.PrincipalRole, if outputFields.Has(globals.AuthorizedActionsField) { out.AuthorizedActions = opts.WithAuthorizedActions } - if outputFields.Has(globals.GrantScopeIdField) && in.GetGrantScopeId() != "" { - out.GrantScopeId = &wrapperspb.StringValue{Value: in.GetGrantScopeId()} - } if outputFields.Has(globals.PrincipalIdsField) { for _, p := range principals { out.PrincipalIds = append(out.PrincipalIds, p.GetPrincipalId()) } } + if outputFields.Has(globals.GrantScopeIdsField) { + for _, gs := range grantScopes { + out.GrantScopeIds = append(out.GrantScopeIds, gs.GetScopeIdOrSpecial()) + } + sort.Strings(out.GrantScopeIds) + } + if outputFields.Has(globals.GrantScopeIdField) && len(grantScopes) == 1 { + out.GrantScopeId = &wrapperspb.StringValue{Value: grantScopes[0].ScopeIdOrSpecial} + } if outputFields.Has(globals.PrincipalsField) { for _, p := range principals { principal := &pb.Principal{ @@ -925,7 +1153,7 @@ func toProto(ctx context.Context, in *iam.Role, principals []*iam.PrincipalRole, } if outputFields.Has(globals.GrantsField) { for _, g := range grants { - parsed, err := perms.Parse(ctx, in.GetGrantScopeId(), g.GetRawGrant()) + parsed, err := perms.Parse(ctx, in.GetScopeId(), g.GetRawGrant()) if err != nil { // This should never happen as we validate on the way in, but let's // return what we can since we are still returning the raw grant @@ -971,9 +1199,15 @@ func validateCreateRequest(req *pbs.CreateRoleRequest) error { scope.Global.String() != item.GetScopeId() { badFields["scope_id"] = "This field is missing or improperly formatted." } - if item.GetGrantScopeId() != nil && handlers.ValidId(handlers.Id(item.GetScopeId()), scope.Project.Prefix()) { - if item.GetGrantScopeId().GetValue() != item.GetScopeId() { - badFields["grant_scope_id"] = "When the role is in a project scope this value must be that project's scope ID." + if item.GetGrantScopeId() != nil { + switch { + case handlers.ValidId(handlers.Id(item.GetScopeId()), scope.Project.Prefix()): + switch item.GetGrantScopeId().GetValue() { + case item.GetScopeId(), + globals.GrantScopeThis: + default: + badFields["grant_scope_id"] = "When the role is in a project scope this value must be that project's scope ID or \"this\"." + } } } if item.GetPrincipals() != nil { @@ -1001,9 +1235,15 @@ func validateUpdateRequest(req *pbs.UpdateRoleRequest) error { if req.GetItem().GetGrantStrings() != nil { badFields["grant_strings"] = "This is a read only field and cannot be specified in an update request." } - if req.GetItem().GetGrantScopeId() != nil && handlers.ValidId(handlers.Id(req.GetItem().GetScopeId()), scope.Project.Prefix()) { - if req.GetItem().GetGrantScopeId().GetValue() != req.GetItem().GetScopeId() { - badFields["grant_scope_id"] = "When the role is in a project scope this value must be that project's scope ID" + if item := req.GetItem(); item != nil { + switch { + case handlers.ValidId(handlers.Id(item.GetScopeId()), scope.Project.Prefix()): + switch item.GetGrantScopeId().GetValue() { + case item.GetScopeId(), + globals.GrantScopeThis: + default: + badFields["grant_scope_id"] = "When the role is in a project scope this value must be that project's scope ID or \"this\"." + } } } return badFields @@ -1224,6 +1464,113 @@ func validateRemoveRoleGrantsRequest(ctx context.Context, req *pbs.RemoveRoleGra return nil } +// grantScopeRequest allows us to reuse a few request types in a common way for +// grant scope add/set/remove operations +type grantScopeRequest interface { + GetId() string + GetVersion() uint32 + GetGrantScopeIds() []string +} + +func validateRoleGrantScopesRequest(ctx context.Context, req grantScopeRequest) error { + badFields := map[string]string{} + if !handlers.ValidId(handlers.Id(req.GetId()), globals.RolePrefix) { + badFields["id"] = "Incorrectly formatted identifier." + } + if req.GetVersion() == 0 { + badFields["version"] = "Required field." + } + if len(req.GetGrantScopeIds()) == 0 { + // This is actually okay for Set because they could be setting to null, + // e.g. removing all grant scope ids + if _, ok := req.(*pbs.SetRoleGrantScopesRequest); !ok { + badFields["grant_scope_ids"] = "Must be non-empty." + } + } + for _, v := range req.GetGrantScopeIds() { + if len(v) == 0 { + badFields["grant_scope_ids"] = "Grant scope IDs must not be empty." + break + } + switch { + case v == scope.Global.String(), + v == globals.GrantScopeThis, + v == globals.GrantScopeChildren, + v == globals.GrantScopeDescendants: + case globals.ResourceInfoFromPrefix(v).Type == resource.Scope: + if !handlers.ValidId(handlers.Id(v), globals.ProjectPrefix) && + !handlers.ValidId(handlers.Id(v), globals.OrgPrefix) { + badFields["grant_scope_ids"] = fmt.Sprintf("Incorrectly formatted identifier %q.", v) + break + } + default: + badFields["grant_scope_ids"] = fmt.Sprintf("Unknown value %q.", v) + break + } + } + if len(badFields) > 0 { + return handlers.InvalidArgumentErrorf("Errors in provided fields.", badFields) + } + return nil +} + +// validateRoleGrantScopesHierarchy is the companion to the domain-side logic to +// validate scopes. It doesn't do all of the same checking but will allow for +// better error messages when possible. We perform this check after +// authentication to limit the possibility of an anonymous user causing DB load +// due to this lookup, which is not a cheap one. +func validateRoleGrantScopesHierarchy(ctx context.Context, repo *iam.Repository, roleId string, grantScopes []string) error { + const op = "service.(Service).validateRoleGrantScopesHierarchy" + // We want to ensure that the values being passed in make sense to whatever + // extent we can right now, so we can provide nice errors back instead of DB + // errors. + role, _, _, _, err := repo.LookupRole(ctx, roleId) + if err != nil { + return errors.Wrap(ctx, err, op) + } + switch { + case role.ScopeId == scope.Global.String(): + // Nothing, any grant scope is allowed for global + case strings.HasPrefix(role.ScopeId, scope.Project.Prefix()): + // In this case only "this" or the same project scope is allowed + for _, grantScope := range grantScopes { + switch grantScope { + case globals.GrantScopeThis, role.ScopeId: + default: + return handlers.InvalidArgumentErrorf( + "Invalid grant scope.", + map[string]string{ + "grant_scope_id": `Project scopes can only have their own scope ID or "this" as a grant scope ID.`, + }) + } + } + case strings.HasPrefix(role.ScopeId, scope.Org.Prefix()): + // Orgs can have "this", its own scope, a project scope, or "children" + for _, grantScope := range grantScopes { + switch { + case grantScope == role.ScopeId, + grantScope == globals.GrantScopeThis, + grantScope == globals.GrantScopeChildren, + strings.HasPrefix(grantScope, scope.Project.Prefix()): + default: + return handlers.InvalidArgumentErrorf( + "Invalid grant scope.", + map[string]string{ + "grant_scope_id": fmt.Sprintf("Grant scope ID %q is not valid to set on an organization role.", grantScope), + }) + } + } + default: + // Should never happen + return handlers.InvalidArgumentErrorf( + "Improperly formatted identifier.", + map[string]string{ + "grant_scope_id": `Unknown scope prefix type.`, + }) + } + return nil +} + func newOutputOpts(ctx context.Context, item *iam.Role, scopeInfoMap map[string]*scopes.ScopeInfo, authResults auth.VerifyResults) ([]handlers.Option, bool) { res := perms.Resource{ Type: resource.Role, diff --git a/internal/daemon/controller/handlers/roles/role_service_test.go b/internal/daemon/controller/handlers/roles/role_service_test.go index 7720969399..9f9a95861f 100644 --- a/internal/daemon/controller/handlers/roles/role_service_test.go +++ b/internal/daemon/controller/handlers/roles/role_service_test.go @@ -46,7 +46,7 @@ import ( "github.com/stretchr/testify/require" ) -var testAuthorizedActions = []string{"no-op", "read", "update", "delete", "add-principals", "set-principals", "remove-principals", "add-grants", "set-grants", "remove-grants"} +var testAuthorizedActions = []string{"no-op", "read", "update", "delete", "add-principals", "set-principals", "remove-principals", "add-grants", "set-grants", "remove-grants", "add-grant-scopes", "set-grant-scopes", "remove-grant-scopes"} func createDefaultRolesAndRepo(t *testing.T) (*iam.Role, *iam.Role, func() (*iam.Repository, error)) { t.Helper() @@ -100,7 +100,8 @@ func TestGet(t *testing.T) { Scope: &scopes.ScopeInfo{Id: or.GetScopeId(), Type: scope.Org.String(), ParentScopeId: scope.Global.String()}, Name: &wrapperspb.StringValue{Value: or.GetName()}, Description: &wrapperspb.StringValue{Value: or.GetDescription()}, - GrantScopeId: &wrapperspb.StringValue{Value: pr.GetGrantScopeId()}, + GrantScopeId: &wrapperspb.StringValue{Value: pr.GetScopeId()}, + GrantScopeIds: []string{pr.GetScopeId()}, CreatedTime: or.CreateTime.GetTimestamp(), UpdatedTime: or.UpdateTime.GetTimestamp(), Version: or.GetVersion(), @@ -113,7 +114,8 @@ func TestGet(t *testing.T) { Scope: &scopes.ScopeInfo{Id: pr.GetScopeId(), Type: scope.Project.String(), ParentScopeId: or.GetScopeId()}, Name: &wrapperspb.StringValue{Value: pr.GetName()}, Description: &wrapperspb.StringValue{Value: pr.GetDescription()}, - GrantScopeId: &wrapperspb.StringValue{Value: pr.GetGrantScopeId()}, + GrantScopeId: &wrapperspb.StringValue{Value: globals.GrantScopeThis}, + GrantScopeIds: []string{globals.GrantScopeThis}, CreatedTime: pr.CreateTime.GetTimestamp(), UpdatedTime: pr.UpdateTime.GetTimestamp(), Version: pr.GetVersion(), @@ -225,7 +227,6 @@ func TestList(t *testing.T) { Scope: &scopes.ScopeInfo{Id: or.GetScopeId(), Type: scope.Org.String(), ParentScopeId: scope.Global.String()}, CreatedTime: or.GetCreateTime().GetTimestamp(), UpdatedTime: or.GetUpdateTime().GetTimestamp(), - GrantScopeId: &wrapperspb.StringValue{Value: or.GetGrantScopeId()}, Version: or.GetVersion(), AuthorizedActions: testAuthorizedActions, }) @@ -237,7 +238,6 @@ func TestList(t *testing.T) { Scope: &scopes.ScopeInfo{Id: pr.GetScopeId(), Type: scope.Project.String(), ParentScopeId: oNoRoles.GetPublicId()}, CreatedTime: pr.GetCreateTime().GetTimestamp(), UpdatedTime: pr.GetUpdateTime().GetTimestamp(), - GrantScopeId: &wrapperspb.StringValue{Value: pr.GetGrantScopeId()}, Version: pr.GetVersion(), AuthorizedActions: testAuthorizedActions, }) @@ -429,7 +429,6 @@ func roleToProto(r *iam.Role, scope *scopes.ScopeInfo, authorizedActions []strin Scope: scope, CreatedTime: r.GetCreateTime().GetTimestamp(), UpdatedTime: r.GetUpdateTime().GetTimestamp(), - GrantScopeId: &wrapperspb.StringValue{Value: r.GetGrantScopeId()}, Version: r.GetVersion(), AuthorizedActions: testAuthorizedActions, } @@ -882,7 +881,8 @@ func TestCreate(t *testing.T) { Name: &wrapperspb.StringValue{Value: "name"}, Description: &wrapperspb.StringValue{Value: "desc"}, GrantScopeId: &wrapperspb.StringValue{Value: defaultProjRole.ScopeId}, - Version: 1, + GrantScopeIds: []string{defaultProjRole.ScopeId}, + Version: 2, AuthorizedActions: testAuthorizedActions, }, }, @@ -903,7 +903,8 @@ func TestCreate(t *testing.T) { Name: &wrapperspb.StringValue{Value: "name"}, Description: &wrapperspb.StringValue{Value: "desc"}, GrantScopeId: &wrapperspb.StringValue{Value: defaultProjRole.ScopeId}, - Version: 1, + GrantScopeIds: []string{defaultProjRole.ScopeId}, + Version: 2, AuthorizedActions: testAuthorizedActions, }, }, @@ -924,8 +925,9 @@ func TestCreate(t *testing.T) { Scope: &scopes.ScopeInfo{Id: defaultProjRole.GetScopeId(), Type: scope.Project.String(), ParentScopeId: defaultOrgRole.GetScopeId()}, Name: &wrapperspb.StringValue{Value: "name"}, Description: &wrapperspb.StringValue{Value: "desc"}, - GrantScopeId: &wrapperspb.StringValue{Value: defaultProjRole.ScopeId}, - Version: 1, + GrantScopeId: &wrapperspb.StringValue{Value: globals.GrantScopeThis}, + GrantScopeIds: []string{globals.GrantScopeThis}, + Version: 2, AuthorizedActions: testAuthorizedActions, }, }, @@ -1037,7 +1039,7 @@ func TestUpdate(t *testing.T) { o, p := iam.TestScopes(t, iamRepo) u := iam.TestUser(t, iamRepo, o.GetPublicId()) - or := iam.TestRole(t, conn, o.GetPublicId(), iam.WithDescription("default"), iam.WithName("default"), iam.WithGrantScopeId(p.GetPublicId())) + or := iam.TestRole(t, conn, o.GetPublicId(), iam.WithDescription("default"), iam.WithName("default"), iam.WithGrantScopeIds([]string{p.GetPublicId()})) _ = iam.TestRoleGrant(t, conn, or.GetPublicId(), grantString) _ = iam.TestUserRole(t, conn, or.GetPublicId(), u.GetPublicId()) @@ -1051,8 +1053,8 @@ func TestUpdate(t *testing.T) { ScopeId: u.GetScopeId(), } - var orVersion uint32 = 1 - var prVersion uint32 = 1 + orVersion := or.Version + prVersion := pr.Version tested, err := roles.NewService(ctx, repoFn, 0) require.NoError(t, err, "Error when getting new role service.") @@ -1061,15 +1063,13 @@ func TestUpdate(t *testing.T) { repo, err := repoFn() require.NoError(t, err, "Couldn't get a new repo") if proj { - prVersion++ - pr, _, _, _, err = repo.UpdateRole(context.Background(), pr, prVersion, []string{"Name", "Description"}) + pr, _, _, _, _, err = repo.UpdateRole(context.Background(), pr, prVersion, []string{"Name", "Description"}) require.NoError(t, err, "Failed to reset the role") - prVersion++ + prVersion = pr.Version } else { - orVersion++ - or, _, _, _, err = repo.UpdateRole(context.Background(), or, orVersion, []string{"Name", "Description"}) + or, _, _, _, _, err = repo.UpdateRole(context.Background(), or, orVersion, []string{"Name", "Description"}) require.NoError(t, err, "Failed to reset the role") - orVersion++ + orVersion = or.Version } } @@ -1090,12 +1090,11 @@ func TestUpdate(t *testing.T) { scopeId: or.GetScopeId(), req: &pbs.UpdateRoleRequest{ UpdateMask: &field_mask.FieldMask{ - Paths: []string{"name", "description", "grant_scope_id"}, + Paths: []string{"name", "description"}, }, Item: &pb.Role{ - Name: &wrapperspb.StringValue{Value: "new"}, - Description: &wrapperspb.StringValue{Value: "desc"}, - GrantScopeId: &wrapperspb.StringValue{Value: or.GetScopeId()}, + Name: &wrapperspb.StringValue{Value: "new"}, + Description: &wrapperspb.StringValue{Value: "desc"}, }, }, res: &pbs.UpdateRoleResponse{ @@ -1106,7 +1105,8 @@ func TestUpdate(t *testing.T) { Name: &wrapperspb.StringValue{Value: "new"}, Description: &wrapperspb.StringValue{Value: "desc"}, CreatedTime: or.GetCreateTime().GetTimestamp(), - GrantScopeId: &wrapperspb.StringValue{Value: or.GetScopeId()}, + GrantScopeId: &wrapperspb.StringValue{Value: p.GetPublicId()}, + GrantScopeIds: []string{p.GetPublicId()}, GrantStrings: []string{grant.GetRaw()}, Grants: []*pb.Grant{grant}, PrincipalIds: []string{u.GetPublicId()}, @@ -1120,11 +1120,12 @@ func TestUpdate(t *testing.T) { scopeId: or.GetScopeId(), req: &pbs.UpdateRoleRequest{ UpdateMask: &field_mask.FieldMask{ - Paths: []string{"name,description"}, + Paths: []string{"name,description,grant_scope_id"}, }, Item: &pb.Role{ - Name: &wrapperspb.StringValue{Value: "new"}, - Description: &wrapperspb.StringValue{Value: "desc"}, + Name: &wrapperspb.StringValue{Value: "new"}, + Description: &wrapperspb.StringValue{Value: "desc"}, + GrantScopeId: &wrapperspb.StringValue{Value: globals.GrantScopeThis}, }, }, res: &pbs.UpdateRoleResponse{ @@ -1135,7 +1136,8 @@ func TestUpdate(t *testing.T) { Name: &wrapperspb.StringValue{Value: "new"}, Description: &wrapperspb.StringValue{Value: "desc"}, CreatedTime: or.GetCreateTime().GetTimestamp(), - GrantScopeId: &wrapperspb.StringValue{Value: or.GetScopeId()}, + GrantScopeId: &wrapperspb.StringValue{Value: globals.GrantScopeThis}, + GrantScopeIds: []string{globals.GrantScopeThis}, GrantStrings: []string{grant.GetRaw()}, Grants: []*pb.Grant{grant}, PrincipalIds: []string{u.GetPublicId()}, @@ -1165,7 +1167,8 @@ func TestUpdate(t *testing.T) { Name: &wrapperspb.StringValue{Value: "new"}, Description: &wrapperspb.StringValue{Value: "desc"}, CreatedTime: pr.GetCreateTime().GetTimestamp(), - GrantScopeId: &wrapperspb.StringValue{Value: pr.GetScopeId()}, + GrantScopeId: &wrapperspb.StringValue{Value: globals.GrantScopeThis}, + GrantScopeIds: []string{globals.GrantScopeThis}, GrantStrings: []string{grant.GetRaw()}, Grants: []*pb.Grant{grant}, PrincipalIds: []string{u.GetPublicId()}, @@ -1195,7 +1198,8 @@ func TestUpdate(t *testing.T) { Name: &wrapperspb.StringValue{Value: "new"}, Description: &wrapperspb.StringValue{Value: "desc"}, CreatedTime: pr.GetCreateTime().GetTimestamp(), - GrantScopeId: &wrapperspb.StringValue{Value: pr.GetScopeId()}, + GrantScopeId: &wrapperspb.StringValue{Value: globals.GrantScopeThis}, + GrantScopeIds: []string{globals.GrantScopeThis}, GrantStrings: []string{grant.GetRaw()}, Grants: []*pb.Grant{grant}, PrincipalIds: []string{u.GetPublicId()}, @@ -1256,7 +1260,8 @@ func TestUpdate(t *testing.T) { Scope: &scopes.ScopeInfo{Id: or.GetScopeId(), Type: scope.Org.String(), ParentScopeId: scope.Global.String()}, Description: &wrapperspb.StringValue{Value: "default"}, CreatedTime: or.GetCreateTime().GetTimestamp(), - GrantScopeId: &wrapperspb.StringValue{Value: or.GetScopeId()}, + GrantScopeId: &wrapperspb.StringValue{Value: globals.GrantScopeThis}, + GrantScopeIds: []string{globals.GrantScopeThis}, GrantStrings: []string{grant.GetRaw()}, Grants: []*pb.Grant{grant}, PrincipalIds: []string{u.GetPublicId()}, @@ -1285,7 +1290,8 @@ func TestUpdate(t *testing.T) { Name: &wrapperspb.StringValue{Value: "updated"}, Description: &wrapperspb.StringValue{Value: "default"}, CreatedTime: or.GetCreateTime().GetTimestamp(), - GrantScopeId: &wrapperspb.StringValue{Value: or.GetScopeId()}, + GrantScopeId: &wrapperspb.StringValue{Value: globals.GrantScopeThis}, + GrantScopeIds: []string{globals.GrantScopeThis}, GrantStrings: []string{grant.GetRaw()}, Grants: []*pb.Grant{grant}, PrincipalIds: []string{u.GetPublicId()}, @@ -1314,7 +1320,8 @@ func TestUpdate(t *testing.T) { Name: &wrapperspb.StringValue{Value: "default"}, Description: &wrapperspb.StringValue{Value: "notignored"}, CreatedTime: or.GetCreateTime().GetTimestamp(), - GrantScopeId: &wrapperspb.StringValue{Value: or.GetScopeId()}, + GrantScopeId: &wrapperspb.StringValue{Value: globals.GrantScopeThis}, + GrantScopeIds: []string{globals.GrantScopeThis}, GrantStrings: []string{grant.GetRaw()}, Grants: []*pb.Grant{grant}, PrincipalIds: []string{u.GetPublicId()}, @@ -1417,15 +1424,6 @@ func TestUpdate(t *testing.T) { req := proto.Clone(toMerge).(*pbs.UpdateRoleRequest) proto.Merge(req, tc.req) - // Test with bad version (too high, too low) - req.Item.Version = ver + 2 - _, gErr := tested.UpdateRole(auth.DisabledAuthTestContext(repoFn, tc.scopeId), req) - require.Error(gErr) - req.Item.Version = ver - 1 - _, gErr = tested.UpdateRole(auth.DisabledAuthTestContext(repoFn, tc.scopeId), req) - require.Error(gErr) - req.Item.Version = ver - got, gErr := tested.UpdateRole(auth.DisabledAuthTestContext(repoFn, tc.scopeId), req) if tc.err != nil { require.Error(gErr) @@ -1443,11 +1441,15 @@ func TestUpdate(t *testing.T) { // Verify it is a role updated after it was created assert.True(gotUpdateTime.After(created), "Updated role should have been updated after it's creation. Was updated %v, which is after %v", gotUpdateTime, created) - // Clear all values which are hard to compare against. + // Clear or set all values which are hard to compare against. got.Item.UpdatedTime, tc.res.Item.UpdatedTime = nil, nil + tc.res.Item.Version = got.Item.Version - assert.Equal(ver+1, got.GetItem().GetVersion()) - tc.res.Item.Version = ver + 1 + if req.Id == or.PublicId { + orVersion = got.Item.Version + } else { + prVersion = got.Item.Version + } } assert.Empty(cmp.Diff( got, @@ -2560,3 +2562,833 @@ func TestRemoveGrants(t *testing.T) { }) } } + +func TestAddGrantScopes(t *testing.T) { + conn, _ := db.TestSetup(t, "postgres") + wrap := db.TestWrapper(t) + iamRepo := iam.TestRepo(t, conn, wrap) + repoFn := func() (*iam.Repository, error) { + return iamRepo, nil + } + s, err := roles.NewService(context.Background(), repoFn, 1000) + require.NoError(t, err, "Error when getting new role service.") + + o, p := iam.TestScopes(t, iamRepo) + o2, p2 := iam.TestScopes(t, iamRepo) + + addCases := []struct { + name string + scopeId string + existing []string + add []string + result []string + wantErr bool + wantErrContains string + }{ + { + name: "Add grant scopes on empty role - global", + scopeId: scope.Global.String(), + add: []string{"this", "descendants"}, + result: []string{"this", "descendants"}, + }, + { + name: "Add grant scopes on role with grant scopes - global", + scopeId: scope.Global.String(), + existing: []string{"global", o.PublicId}, + add: []string{"children", p.PublicId}, + result: []string{"global", o.PublicId, p.PublicId, "children"}, + }, + { + name: "Add duplicate grant on role with grant scopes - global", + scopeId: scope.Global.String(), + existing: []string{"this"}, + add: []string{"children", "children"}, + result: []string{"this", "children"}, + }, + { + name: "Add other org/proj scopes - global", + scopeId: scope.Global.String(), + existing: []string{"this"}, + add: []string{o2.PublicId, p2.PublicId}, + result: []string{"this", o2.PublicId, p2.PublicId}, + }, + { + name: "Add grant scope matching existing grant scopes - global", + scopeId: scope.Global.String(), + existing: []string{"this"}, + add: []string{"this", "children"}, + wantErr: true, + }, + { + name: "Add invalid grant scope - global", + scopeId: scope.Global.String(), + existing: []string{"this"}, + add: []string{"p_foobar1234", "children"}, + wantErr: true, + }, + { + name: "Add this to scope grant scope - global", + scopeId: scope.Global.String(), + existing: []string{scope.Global.String()}, + add: []string{"this"}, + wantErr: true, + }, + { + name: "Add scope to this grant scope - global", + scopeId: scope.Global.String(), + existing: []string{"this"}, + add: []string{scope.Global.String()}, + wantErr: true, + }, + { + name: "Add children when descendants exists - global", + scopeId: scope.Global.String(), + existing: []string{"descendants"}, + add: []string{"children"}, + wantErr: true, + }, + { + name: "Add descendants when children exists - global", + scopeId: scope.Global.String(), + existing: []string{"children"}, + add: []string{"descendants"}, + wantErr: true, + }, + { + name: "Add grant scopes on empty role - org", + scopeId: o.PublicId, + add: []string{"this", "children"}, + result: []string{"this", "children"}, + }, + { + name: "Add grant scopes on role with grant scopes - org", + scopeId: o.PublicId, + existing: []string{o.PublicId, p.PublicId}, + add: []string{"children"}, + result: []string{o.PublicId, p.PublicId, "children"}, + }, + { + name: "Add duplicate grant on role with grant scopes - org", + scopeId: o.PublicId, + existing: []string{"this"}, + add: []string{"children", "children"}, + result: []string{"this", "children"}, + }, + { + name: "Add grant scope matching existing grant scopes - org", + scopeId: o.PublicId, + existing: []string{"this"}, + add: []string{"this", "children"}, + wantErr: true, + }, + { + name: "Add invalid grant scope - org", + scopeId: o.PublicId, + existing: []string{"this"}, + add: []string{"p_foobar1234", "children"}, + wantErr: true, + }, + { + name: "Add invalid grant scope - org - descendants", + scopeId: o.PublicId, + existing: []string{"this"}, + add: []string{"descendants", "children"}, + wantErr: true, + }, + { + name: "Add this to scope grant scope - org", + scopeId: o.PublicId, + existing: []string{o.PublicId}, + add: []string{"this"}, + wantErr: true, + }, + { + name: "Add scope to this grant scope - org", + scopeId: o.PublicId, + existing: []string{"this"}, + add: []string{o.PublicId}, + wantErr: true, + }, + { + name: "Add other org/proj scopes - org", + scopeId: o.PublicId, + existing: []string{"this"}, + add: []string{p2.PublicId}, + wantErr: true, + }, + { + name: "Add grant scopes on empty role - proj with id", + scopeId: p.PublicId, + add: []string{p.PublicId}, + result: []string{p.PublicId}, + }, + { + name: "Add grant scopes on empty role - proj with this", + scopeId: p.PublicId, + add: []string{"this"}, + result: []string{"this"}, + }, + { + name: "Add duplicate scopes on empty role - proj", + scopeId: p.PublicId, + add: []string{"this", "this"}, + result: []string{"this"}, + }, + { + name: "Add invalid grant scope - proj", + scopeId: p.PublicId, + existing: []string{"this"}, + add: []string{"p_foobar1234"}, + wantErr: true, + }, + { + name: "Add invalid grant scope - proj - descendants", + scopeId: p.PublicId, + existing: []string{"this"}, + add: []string{"descendants"}, + wantErr: true, + }, + { + name: "Add invalid grant scope - proj - children", + scopeId: p.PublicId, + existing: []string{"this"}, + add: []string{"children"}, + wantErr: true, + }, + { + name: "Add this to scope grant scope - proj", + scopeId: p.PublicId, + existing: []string{p.PublicId}, + add: []string{"this"}, + wantErr: true, + }, + { + name: "Add scope to this grant scope - proj", + scopeId: p.PublicId, + existing: []string{"this"}, + add: []string{p.PublicId}, + wantErr: true, + }, + { + name: "Add other org/proj scopes - proj", + scopeId: p.PublicId, + existing: []string{}, + add: []string{p2.PublicId}, + wantErr: true, + }, + } + + for _, tc := range addCases { + t.Run(tc.name, func(t *testing.T) { + assert, require := assert.New(t), require.New(t) + role := iam.TestRole(t, conn, tc.scopeId, iam.WithGrantScopeIds([]string{"testing-none"})) + for _, e := range tc.existing { + _ = iam.TestRoleGrantScope(t, conn, role.GetPublicId(), e) + } + req := &pbs.AddRoleGrantScopesRequest{ + Id: role.GetPublicId(), + Version: role.GetVersion(), + } + req.GrantScopeIds = tc.add + got, err := s.AddRoleGrantScopes(auth.DisabledAuthTestContext(repoFn, tc.scopeId), req) + if tc.wantErr { + assert.Error(err) + if tc.wantErrContains != "" { + assert.Contains(err.Error(), tc.wantErrContains) + } + return + } + require.NoError(err) + require.ElementsMatch(tc.result, got.GetItem().GetGrantScopeIds()) + }) + } + + _, p = iam.TestScopes(t, iamRepo) + role := iam.TestRole(t, conn, p.GetPublicId(), iam.WithGrantScopeIds([]string{"testing-none"})) + failCases := []struct { + name string + req *pbs.AddRoleGrantScopesRequest + err error + }{ + { + name: "Bad Version", + req: &pbs.AddRoleGrantScopesRequest{ + Id: role.GetPublicId(), + GrantScopeIds: []string{"this"}, + Version: role.GetVersion() + 2, + }, + err: handlers.ApiErrorWithCode(codes.Internal), + }, + { + name: "Bad Role Id", + req: &pbs.AddRoleGrantScopesRequest{ + Id: "bad id", + GrantScopeIds: []string{"this"}, + Version: role.GetVersion(), + }, + err: handlers.ApiErrorWithCode(codes.InvalidArgument), + }, + { + name: "Invalid scope ID", + req: &pbs.AddRoleGrantScopesRequest{ + Id: role.GetPublicId(), + GrantScopeIds: []string{"p_foobar1234"}, + Version: role.GetVersion(), + }, + err: handlers.ApiErrorWithCode(codes.InvalidArgument), + }, + { + name: "Empty Grant Scope ID", + req: &pbs.AddRoleGrantScopesRequest{ + Id: role.GetPublicId(), + GrantScopeIds: []string{"this", ""}, + Version: role.GetVersion(), + }, + err: handlers.ApiErrorWithCode(codes.InvalidArgument), + }, + } + for _, tc := range failCases { + t.Run(tc.name, func(t *testing.T) { + assert, require := assert.New(t), require.New(t) + _, gErr := s.AddRoleGrantScopes(auth.DisabledAuthTestContext(repoFn, p.GetPublicId()), tc.req) + if tc.err != nil { + require.Error(gErr) + assert.True(errors.Is(gErr, tc.err), "AddRoleGrantScopes(%+v) got error %#v, wanted %#v", tc.req, gErr, tc.err) + } + }) + } +} + +func TestSetGrantScopes(t *testing.T) { + conn, _ := db.TestSetup(t, "postgres") + wrap := db.TestWrapper(t) + iamRepo := iam.TestRepo(t, conn, wrap) + repoFn := func() (*iam.Repository, error) { + return iamRepo, nil + } + s, err := roles.NewService(context.Background(), repoFn, 1000) + require.NoError(t, err, "Error when getting new role service.") + + o, p := iam.TestScopes(t, iamRepo) + o2, p2 := iam.TestScopes(t, iamRepo) + + setCases := []struct { + name string + scopeId string + existing []string + set []string + result []string + wantErr bool + wantErrContains string + }{ + { + name: "Set grant scopes on empty role - global", + scopeId: scope.Global.String(), + set: []string{"this", "descendants"}, + result: []string{"this", "descendants"}, + }, + { + name: "Set grant scopes on role with grant scopes - global", + scopeId: scope.Global.String(), + existing: []string{"global", o.PublicId}, + set: []string{"children", p.PublicId}, + result: []string{"children", p.PublicId}, + }, + { + name: "Set duplicate grant on role with grant scopes - global", + scopeId: scope.Global.String(), + existing: []string{"this"}, + set: []string{"children", "children"}, + result: []string{"children"}, + }, + { + name: "Set grant scope matching existing grant scopes - global", + scopeId: scope.Global.String(), + existing: []string{"this"}, + set: []string{"this", "children"}, + result: []string{"this", "children"}, + }, + { + name: "Set this to scope grant scope - global", + scopeId: scope.Global.String(), + existing: []string{scope.Global.String()}, + set: []string{"this", "children"}, + result: []string{"this", "children"}, + }, + { + name: "Set scope to this grant scope - global", + scopeId: scope.Global.String(), + existing: []string{"this"}, + set: []string{scope.Global.String(), "children"}, + result: []string{scope.Global.String(), "children"}, + }, + { + name: "Set other org/proj scopes - global", + scopeId: scope.Global.String(), + existing: []string{"this"}, + set: []string{"this", o2.PublicId, p2.PublicId}, + result: []string{"this", o2.PublicId, p2.PublicId}, + }, + { + name: "Set invalid grant scope - global", + scopeId: scope.Global.String(), + existing: []string{"this"}, + set: []string{"p_foobar1234", "children"}, + wantErr: true, + }, + { + name: "Set both on grant scope - global", + scopeId: scope.Global.String(), + existing: []string{}, + set: []string{scope.Global.String(), "children", "this"}, + wantErr: true, + }, + { + name: "Set both grant scope - global", + scopeId: scope.Global.String(), + existing: []string{}, + set: []string{"this", scope.Global.String(), "children"}, + wantErr: true, + }, + { + name: "Set children and descendants - global", + scopeId: scope.Global.String(), + existing: []string{}, + set: []string{"children", "descendants"}, + wantErr: true, + }, + { + name: "Set descendants and children- global", + scopeId: scope.Global.String(), + existing: []string{}, + set: []string{"descendants", "children"}, + wantErr: true, + }, + { + name: "Set grant scopes on empty role - org", + scopeId: o.PublicId, + set: []string{"this", "children"}, + result: []string{"this", "children"}, + }, + { + name: "Set grant scopes on role with grant scopes - org", + scopeId: o.PublicId, + existing: []string{o.PublicId, p.PublicId}, + set: []string{"children"}, + result: []string{"children"}, + }, + { + name: "Set duplicate grant on role with grant scopes - org", + scopeId: o.PublicId, + existing: []string{"this"}, + set: []string{p.PublicId, "children", "children"}, + result: []string{p.PublicId, "children"}, + }, + { + name: "Set grant scope matching existing grant scopes - org", + scopeId: o.PublicId, + existing: []string{"this"}, + set: []string{"this", "children"}, + result: []string{"this", "children"}, + }, + { + name: "Set this to scope grant scope - org", + scopeId: o.PublicId, + existing: []string{o.PublicId}, + set: []string{"this", "children"}, + result: []string{"this", "children"}, + }, + { + name: "Set scope to this grant scope - org", + scopeId: o.PublicId, + existing: []string{"this"}, + set: []string{o.PublicId, "children"}, + result: []string{o.PublicId, "children"}, + }, + { + name: "Set invalid grant scope - org", + scopeId: o.PublicId, + existing: []string{"this"}, + set: []string{"p_foobar1234", "children"}, + wantErr: true, + }, + { + name: "Set invalid grant scope - org - descendants", + scopeId: o.PublicId, + existing: []string{"this"}, + set: []string{"descendants", "children"}, + wantErr: true, + }, + { + name: "Set both on grant scope - org", + scopeId: o.PublicId, + existing: []string{}, + set: []string{o.PublicId, "children", "this"}, + wantErr: true, + }, + { + name: "Set both grant scope - org", + scopeId: o.PublicId, + existing: []string{}, + set: []string{"this", o.PublicId, "children"}, + wantErr: true, + }, + { + name: "Set grant scopes on empty role - proj with id", + scopeId: p.PublicId, + set: []string{p.PublicId}, + result: []string{p.PublicId}, + }, + { + name: "Set grant scopes on empty role - proj with this", + scopeId: p.PublicId, + set: []string{"this"}, + result: []string{"this"}, + }, + { + name: "Set duplicate scopes on empty role - proj", + scopeId: p.PublicId, + set: []string{"this", "this"}, + result: []string{"this"}, + }, + { + name: "Set this to scope grant scope - proj", + scopeId: p.PublicId, + existing: []string{p.PublicId}, + set: []string{"this"}, + result: []string{"this"}, + }, + { + name: "Set scope to this grant scope - proj", + scopeId: p.PublicId, + existing: []string{"this"}, + set: []string{p.PublicId}, + result: []string{p.PublicId}, + }, + { + name: "Set other org/proj scopes - org", + scopeId: o.PublicId, + existing: []string{"this"}, + set: []string{"this", p2.PublicId}, + wantErr: true, + }, + { + name: "Set invalid grant scope - proj", + scopeId: p.PublicId, + existing: []string{"this"}, + set: []string{"p_foobar1234"}, + wantErr: true, + }, + { + name: "Set invalid grant scope - proj - descendants", + scopeId: p.PublicId, + existing: []string{"this"}, + set: []string{"descendants"}, + wantErr: true, + }, + { + name: "Set invalid grant scope - proj - children", + scopeId: p.PublicId, + existing: []string{"this"}, + set: []string{"children"}, + wantErr: true, + }, + { + name: "Set both on grant scope - proj", + scopeId: p.PublicId, + existing: []string{}, + set: []string{p.PublicId, "this"}, + wantErr: true, + }, + { + name: "Set both grant scope - proj", + scopeId: p.PublicId, + existing: []string{}, + set: []string{"this", p.PublicId}, + wantErr: true, + }, + { + name: "Set other org/proj scopes - proj", + scopeId: p.PublicId, + existing: []string{}, + set: []string{p2.PublicId}, + wantErr: true, + }, + } + + for _, tc := range setCases { + t.Run(tc.name, func(t *testing.T) { + assert, require := assert.New(t), require.New(t) + role := iam.TestRole(t, conn, tc.scopeId, iam.WithGrantScopeIds([]string{"testing-none"})) + for _, e := range tc.existing { + _ = iam.TestRoleGrantScope(t, conn, role.GetPublicId(), e) + } + req := &pbs.SetRoleGrantScopesRequest{ + Id: role.GetPublicId(), + Version: role.GetVersion(), + } + req.GrantScopeIds = tc.set + got, err := s.SetRoleGrantScopes(auth.DisabledAuthTestContext(repoFn, tc.scopeId), req) + if tc.wantErr { + assert.Error(err) + if tc.wantErrContains != "" { + assert.Contains(err.Error(), tc.wantErrContains) + } + return + } + require.NoError(err) + require.ElementsMatch(tc.result, got.GetItem().GetGrantScopeIds()) + }) + } + + role := iam.TestRole(t, conn, p.GetPublicId(), iam.WithGrantScopeIds([]string{"testing-none"})) + failCases := []struct { + name string + req *pbs.SetRoleGrantScopesRequest + err error + }{ + { + name: "Bad Version", + req: &pbs.SetRoleGrantScopesRequest{ + Id: role.GetPublicId(), + GrantScopeIds: []string{"this"}, + Version: role.GetVersion() + 2, + }, + err: handlers.ApiErrorWithCode(codes.Internal), + }, + { + name: "Bad Role Id", + req: &pbs.SetRoleGrantScopesRequest{ + Id: "bad id", + GrantScopeIds: []string{"this"}, + Version: role.GetVersion(), + }, + err: handlers.ApiErrorWithCode(codes.InvalidArgument), + }, + { + name: "Invalid scope ID", + req: &pbs.SetRoleGrantScopesRequest{ + Id: role.GetPublicId(), + GrantScopeIds: []string{"p_foobar1234"}, + Version: role.GetVersion(), + }, + err: handlers.ApiErrorWithCode(codes.InvalidArgument), + }, + { + name: "Empty Grant Scope ID", + req: &pbs.SetRoleGrantScopesRequest{ + Id: role.GetPublicId(), + GrantScopeIds: []string{"this", ""}, + Version: role.GetVersion(), + }, + err: handlers.ApiErrorWithCode(codes.InvalidArgument), + }, + } + for _, tc := range failCases { + t.Run(tc.name, func(t *testing.T) { + assert, require := assert.New(t), require.New(t) + _, gErr := s.SetRoleGrantScopes(auth.DisabledAuthTestContext(repoFn, p.GetPublicId()), tc.req) + if tc.err != nil { + require.Error(gErr) + assert.True(errors.Is(gErr, tc.err), "SetRoleGrantScopes(%+v) got error %#v, wanted %#v", tc.req, gErr, tc.err) + } + }) + } +} + +func TestRemoveGrantScopes(t *testing.T) { + conn, _ := db.TestSetup(t, "postgres") + wrap := db.TestWrapper(t) + iamRepo := iam.TestRepo(t, conn, wrap) + repoFn := func() (*iam.Repository, error) { + return iamRepo, nil + } + s, err := roles.NewService(context.Background(), repoFn, 1000) + require.NoError(t, err, "Error when getting new role service.") + + o, p := iam.TestScopes(t, iamRepo) + + removeCases := []struct { + name string + scopeId string + existing []string + remove []string + result []string + wantErr bool + }{ + { + name: "Remove all - global", + scopeId: scope.Global.String(), + existing: []string{"this", "descendants"}, + remove: []string{"this", "descendants"}, + }, + { + name: "Remove partial - global", + scopeId: scope.Global.String(), + existing: []string{"this", o.PublicId, "descendants", p.PublicId}, + remove: []string{o.PublicId, p.PublicId}, + result: []string{"this", "descendants"}, + }, + { + name: "Remove duplicate - global", + scopeId: scope.Global.String(), + existing: []string{"this", "children", o.PublicId}, + remove: []string{"children", o.PublicId, "children"}, + result: []string{"this"}, + }, + { + name: "Remove non existant - global", + scopeId: scope.Global.String(), + existing: []string{"this", "descendants"}, + remove: []string{"p_foobar1234"}, + result: []string{"this", "descendants"}, + }, + { + name: "Remove from empty role - global", + scopeId: scope.Global.String(), + existing: []string{}, + remove: []string{"this"}, + result: nil, + }, + { + name: "Remove all - org", + scopeId: o.PublicId, + existing: []string{"this", "children"}, + remove: []string{"this", "children"}, + }, + { + name: "Remove partial - org", + scopeId: o.PublicId, + existing: []string{"children", o.PublicId, p.PublicId}, + remove: []string{"children"}, + result: []string{o.PublicId, p.PublicId}, + }, + { + name: "Remove duplicate - org", + scopeId: o.PublicId, + existing: []string{"this", p.PublicId, "children"}, + remove: []string{"children", p.PublicId, "children"}, + result: []string{"this"}, + }, + { + name: "Remove non existant - org", + scopeId: o.PublicId, + existing: []string{"this", "children"}, + remove: []string{"p_foobar1234"}, + result: []string{"this", "children"}, + }, + { + name: "Remove from empty role - org", + scopeId: o.PublicId, + existing: []string{}, + remove: []string{"this"}, + result: nil, + }, + { + name: "Remove all - proj", + scopeId: p.PublicId, + existing: []string{p.PublicId}, + remove: []string{p.PublicId}, + }, + { + name: "Remove duplicate - proj", + scopeId: p.PublicId, + existing: []string{"this"}, + remove: []string{"this", "this"}, + result: []string{}, + }, + { + name: "Remove non existant - proj", + scopeId: p.PublicId, + existing: []string{"this"}, + remove: []string{"p_foobar1234"}, + result: []string{"this"}, + }, + { + name: "Remove from empty role - proj", + scopeId: p.PublicId, + existing: []string{}, + remove: []string{"this"}, + result: nil, + }, + } + + for _, tc := range removeCases { + t.Run(tc.name+"_", func(t *testing.T) { + assert, require := assert.New(t), require.New(t) + role := iam.TestRole(t, conn, tc.scopeId, iam.WithGrantScopeIds([]string{"testing-none"})) + for _, e := range tc.existing { + _ = iam.TestRoleGrantScope(t, conn, role.GetPublicId(), e) + } + req := &pbs.RemoveRoleGrantScopesRequest{ + Id: role.GetPublicId(), + Version: role.GetVersion(), + } + req.GrantScopeIds = tc.remove + got, err := s.RemoveRoleGrantScopes(auth.DisabledAuthTestContext(repoFn, tc.scopeId), req) + if tc.wantErr { + assert.Error(err) + return + } + s, ok := status.FromError(err) + assert.True(ok) + require.NoError(err, "Got error %v", s) + require.ElementsMatch(tc.result, got.GetItem().GetGrantScopeIds()) + }) + } + + role := iam.TestRole(t, conn, p.GetPublicId(), iam.WithGrantScopeIds([]string{"testing-none"})) + failCases := []struct { + name string + req *pbs.RemoveRoleGrantScopesRequest + + err error + }{ + { + name: "Bad Version", + req: &pbs.RemoveRoleGrantScopesRequest{ + Id: role.GetPublicId(), + GrantScopeIds: []string{"this"}, + Version: role.GetVersion() + 2, + }, + err: handlers.ApiErrorWithCode(codes.Internal), + }, + { + name: "Bad Role Id", + req: &pbs.RemoveRoleGrantScopesRequest{ + Id: "bad id", + GrantScopeIds: []string{"this"}, + Version: role.GetVersion(), + }, + err: handlers.ApiErrorWithCode(codes.InvalidArgument), + }, + { + name: "Empty Grant Scope ID", + req: &pbs.RemoveRoleGrantScopesRequest{ + Id: role.GetPublicId(), + GrantScopeIds: []string{"this", ""}, + Version: role.GetVersion(), + }, + err: handlers.ApiErrorWithCode(codes.InvalidArgument), + }, + { + name: "Invalid Grant Scope ID", + req: &pbs.RemoveRoleGrantScopesRequest{ + Id: role.GetPublicId(), + GrantScopeIds: []string{"r_foobar1234"}, + Version: role.GetVersion(), + }, + err: handlers.ApiErrorWithCode(codes.InvalidArgument), + }, + } + for _, tc := range failCases { + t.Run(tc.name, func(t *testing.T) { + assert, require := assert.New(t), require.New(t) + _, gErr := s.RemoveRoleGrantScopes(auth.DisabledAuthTestContext(repoFn, p.GetPublicId()), tc.req) + if tc.err != nil { + require.Error(gErr) + assert.True(errors.Is(gErr, tc.err), "RemoveRoleGrantScopes(%+v) got error %v, wanted %v", tc.req, gErr, tc.err) + } + }) + } +} diff --git a/internal/daemon/controller/rate_limiter_test.go b/internal/daemon/controller/rate_limiter_test.go index 179b2d516e..24bfac23ef 100644 --- a/internal/daemon/controller/rate_limiter_test.go +++ b/internal/daemon/controller/rate_limiter_test.go @@ -63,7 +63,7 @@ func Test_newRateLimiterConfig(t *testing.T) { ratelimit.DefaultLimiterMaxQuotas(), false, &rateLimiterConfig{ - maxSize: 316158, + maxSize: 322161, configs: nil, disabled: false, limits: defaultLimits, diff --git a/internal/daemon/controller/testdata/Test_rateLimiterConfig_writeSysEvent/defaults.json b/internal/daemon/controller/testdata/Test_rateLimiterConfig_writeSysEvent/defaults.json index 1803b342d1..b1fa65f07c 100644 --- a/internal/daemon/controller/testdata/Test_rateLimiterConfig_writeSysEvent/defaults.json +++ b/internal/daemon/controller/testdata/Test_rateLimiterConfig_writeSysEvent/defaults.json @@ -2031,6 +2031,32 @@ "unlimited": false } ], + "add-grant-scopes": [ + { + "action": "add-grant-scopes", + "limit": 30000, + "per": "ip-address", + "period": "30s", + "resource": "role", + "unlimited": false + }, + { + "action": "add-grant-scopes", + "limit": 3000, + "per": "auth-token", + "period": "30s", + "resource": "role", + "unlimited": false + }, + { + "action": "add-grant-scopes", + "limit": 30000, + "per": "total", + "period": "30s", + "resource": "role", + "unlimited": false + } + ], "add-principals": [ { "action": "add-principals", @@ -2213,6 +2239,32 @@ "unlimited": false } ], + "remove-grant-scopes": [ + { + "action": "remove-grant-scopes", + "limit": 30000, + "per": "total", + "period": "30s", + "resource": "role", + "unlimited": false + }, + { + "action": "remove-grant-scopes", + "limit": 30000, + "per": "ip-address", + "period": "30s", + "resource": "role", + "unlimited": false + }, + { + "action": "remove-grant-scopes", + "limit": 3000, + "per": "auth-token", + "period": "30s", + "resource": "role", + "unlimited": false + } + ], "remove-principals": [ { "action": "remove-principals", @@ -2265,6 +2317,32 @@ "unlimited": false } ], + "set-grant-scopes": [ + { + "action": "set-grant-scopes", + "limit": 30000, + "per": "ip-address", + "period": "30s", + "resource": "role", + "unlimited": false + }, + { + "action": "set-grant-scopes", + "limit": 3000, + "per": "auth-token", + "period": "30s", + "resource": "role", + "unlimited": false + }, + { + "action": "set-grant-scopes", + "limit": 30000, + "per": "total", + "period": "30s", + "resource": "role", + "unlimited": false + } + ], "set-principals": [ { "action": "set-principals", @@ -3997,7 +4075,7 @@ ] } }, - "max_size": 316158, + "max_size": 322161, "msg": "controller api rate limiter" }, "op": "controller.(rateLimiterConfig).writeSysEvent", diff --git a/internal/daemon/controller/testdata/Test_rateLimiterConfig_writeSysEvent/max_size.json b/internal/daemon/controller/testdata/Test_rateLimiterConfig_writeSysEvent/max_size.json index 5c02a87d3e..32686920de 100644 --- a/internal/daemon/controller/testdata/Test_rateLimiterConfig_writeSysEvent/max_size.json +++ b/internal/daemon/controller/testdata/Test_rateLimiterConfig_writeSysEvent/max_size.json @@ -2031,6 +2031,32 @@ "unlimited": false } ], + "add-grant-scopes": [ + { + "action": "add-grant-scopes", + "limit": 30000, + "per": "ip-address", + "period": "30s", + "resource": "role", + "unlimited": false + }, + { + "action": "add-grant-scopes", + "limit": 3000, + "per": "auth-token", + "period": "30s", + "resource": "role", + "unlimited": false + }, + { + "action": "add-grant-scopes", + "limit": 30000, + "per": "total", + "period": "30s", + "resource": "role", + "unlimited": false + } + ], "add-principals": [ { "action": "add-principals", @@ -2213,6 +2239,32 @@ "unlimited": false } ], + "remove-grant-scopes": [ + { + "action": "remove-grant-scopes", + "limit": 30000, + "per": "total", + "period": "30s", + "resource": "role", + "unlimited": false + }, + { + "action": "remove-grant-scopes", + "limit": 30000, + "per": "ip-address", + "period": "30s", + "resource": "role", + "unlimited": false + }, + { + "action": "remove-grant-scopes", + "limit": 3000, + "per": "auth-token", + "period": "30s", + "resource": "role", + "unlimited": false + } + ], "remove-principals": [ { "action": "remove-principals", @@ -2265,6 +2317,32 @@ "unlimited": false } ], + "set-grant-scopes": [ + { + "action": "set-grant-scopes", + "limit": 30000, + "per": "ip-address", + "period": "30s", + "resource": "role", + "unlimited": false + }, + { + "action": "set-grant-scopes", + "limit": 3000, + "per": "auth-token", + "period": "30s", + "resource": "role", + "unlimited": false + }, + { + "action": "set-grant-scopes", + "limit": 30000, + "per": "total", + "period": "30s", + "resource": "role", + "unlimited": false + } + ], "set-principals": [ { "action": "set-principals", diff --git a/internal/daemon/controller/testdata/Test_rateLimiterConfig_writeSysEvent/override.json b/internal/daemon/controller/testdata/Test_rateLimiterConfig_writeSysEvent/override.json index cbe66203e1..4c928c0513 100644 --- a/internal/daemon/controller/testdata/Test_rateLimiterConfig_writeSysEvent/override.json +++ b/internal/daemon/controller/testdata/Test_rateLimiterConfig_writeSysEvent/override.json @@ -2031,6 +2031,32 @@ "unlimited": false } ], + "add-grant-scopes": [ + { + "action": "add-grant-scopes", + "limit": 100, + "per": "ip-address", + "period": "1m0s", + "resource": "role", + "unlimited": false + }, + { + "action": "add-grant-scopes", + "limit": 100, + "per": "auth-token", + "period": "1m0s", + "resource": "role", + "unlimited": false + }, + { + "action": "add-grant-scopes", + "limit": 100, + "per": "total", + "period": "1m0s", + "resource": "role", + "unlimited": false + } + ], "add-principals": [ { "action": "add-principals", @@ -2213,6 +2239,32 @@ "unlimited": false } ], + "remove-grant-scopes": [ + { + "action": "remove-grant-scopes", + "limit": 100, + "per": "total", + "period": "1m0s", + "resource": "role", + "unlimited": false + }, + { + "action": "remove-grant-scopes", + "limit": 100, + "per": "ip-address", + "period": "1m0s", + "resource": "role", + "unlimited": false + }, + { + "action": "remove-grant-scopes", + "limit": 100, + "per": "auth-token", + "period": "1m0s", + "resource": "role", + "unlimited": false + } + ], "remove-principals": [ { "action": "remove-principals", @@ -2265,6 +2317,32 @@ "unlimited": false } ], + "set-grant-scopes": [ + { + "action": "set-grant-scopes", + "limit": 100, + "per": "ip-address", + "period": "1m0s", + "resource": "role", + "unlimited": false + }, + { + "action": "set-grant-scopes", + "limit": 100, + "per": "auth-token", + "period": "1m0s", + "resource": "role", + "unlimited": false + }, + { + "action": "set-grant-scopes", + "limit": 100, + "per": "total", + "period": "1m0s", + "resource": "role", + "unlimited": false + } + ], "set-principals": [ { "action": "set-principals", @@ -3997,7 +4075,7 @@ ] } }, - "max_size": 316158, + "max_size": 322161, "msg": "controller api rate limiter" }, "op": "controller.(rateLimiterConfig).writeSysEvent", diff --git a/internal/daemon/controller/testing.go b/internal/daemon/controller/testing.go index 04431068e9..0e333e4f68 100644 --- a/internal/daemon/controller/testing.go +++ b/internal/daemon/controller/testing.go @@ -370,8 +370,8 @@ type TestControllerOpts struct { // DefaultPassword is the password used when creating the default accounts. DefaultPassword string - // DisableInitialLoginRoleCreation can be set true to disable creating the - // global scope login role automatically. + // DisableInitialLoginRoleCreation can be set true to disable creating the global + // scope default role automatically. DisableInitialLoginRoleCreation bool // DisableAuthMethodCreation can be set true to disable creating an auth @@ -424,6 +424,11 @@ type TestControllerOpts struct { // eventer. EnableEventing bool + // EventerConfig allows specifying a custom event config. You must not run + // the test in parallel (no calls to t.Parallel) since the this option + // relies on modifying the system wide default eventer. + EventerConfig *event.EventerConfig + // DisableAuthorizationFailures will still cause authz checks to be // performed but they won't cause 403 Forbidden. Useful for API-level // testing to avoid a lot of faff. @@ -661,12 +666,15 @@ func TestControllerConfig(t testing.TB, ctx context.Context, tc *TestController, opts.Config.Controller.Scheduler.JobRunIntervalDuration = opts.SchedulerRunJobInterval opts.Config.Controller.ApiRateLimiterMaxQuotas = ratelimit.DefaultLimiterMaxQuotas() - if opts.EnableEventing { - opts.Config.Eventing = &event.EventerConfig{ - AuditEnabled: true, - ObservationsEnabled: true, - SysEventsEnabled: true, - ErrorEventsDisabled: true, + if opts.EnableEventing || opts.EventerConfig != nil { + opts.Config.Eventing = opts.EventerConfig + if opts.Config.Eventing == nil { + opts.Config.Eventing = &event.EventerConfig{ + AuditEnabled: true, + ObservationsEnabled: true, + SysEventsEnabled: true, + ErrorEventsDisabled: true, + } } } serverName, err := os.Hostname() @@ -757,6 +765,9 @@ func TestControllerConfig(t testing.TB, ctx context.Context, tc *TestController, if _, err := tc.b.CreateInitialLoginRole(ctx); err != nil { t.Fatal(err) } + if _, err := tc.b.CreateInitialAuthenticatedUserRole(ctx); err != nil { + t.Fatal(err) + } if !opts.DisableAuthMethodCreation { if _, _, err := tc.b.CreateInitialPasswordAuthMethod(ctx); err != nil { t.Fatal(err) @@ -791,6 +802,9 @@ func TestControllerConfig(t testing.TB, ctx context.Context, tc *TestController, } } else if !opts.DisableDatabaseCreation { var createOpts []base.Option + if opts.DisableInitialLoginRoleCreation { + createOpts = append(createOpts, base.WithSkipDefaultRoleCreation()) + } if opts.DisableAuthMethodCreation { createOpts = append(createOpts, base.WithSkipAuthMethodCreation()) } diff --git a/internal/db/schema/migrations/oss/postgres/0/06_iam.up.sql b/internal/db/schema/migrations/oss/postgres/0/06_iam.up.sql index ac2e721c7e..4f5816fd2e 100644 --- a/internal/db/schema/migrations/oss/postgres/0/06_iam.up.sql +++ b/internal/db/schema/migrations/oss/postgres/0/06_iam.up.sql @@ -223,6 +223,8 @@ begin end; $$ language plpgsql; +-- Dropped in 83/01_iam_role_grant_scope since we moved to multiple scopes per +-- role create or replace function grant_scope_id_valid() returns trigger as $$ declare parent_scope_id text; diff --git a/internal/db/schema/migrations/oss/postgres/83/01_iam_role_grant_scope.up.sql b/internal/db/schema/migrations/oss/postgres/83/01_iam_role_grant_scope.up.sql new file mode 100644 index 0000000000..1bfcece905 --- /dev/null +++ b/internal/db/schema/migrations/oss/postgres/83/01_iam_role_grant_scope.up.sql @@ -0,0 +1,210 @@ +-- Copyright (c) HashiCorp, Inc. +-- SPDX-License-Identifier: MPL-2.0 + +begin; + + -- This table is a join table to map roles to the various scopes the role + -- grants apply to. role_id is a foreign key, but scope_id_or_special is not a + -- foreign key to scopes because we support specific special values which are + -- checked for in the constraint. We never query by scope_id_or_special so the + -- ordering of the primary key should be correct. + create table iam_role_grant_scope ( + create_time wt_timestamp, + role_id wt_role_id not null -- pk + references iam_role(public_id) + on delete cascade + on update cascade, + scope_id_or_special text not null -- pk + constraint scope_id_or_special_is_valid + check ( + length(trim(scope_id_or_special)) = 12 + or + scope_id_or_special in ('global', 'this', 'children', 'descendants') + ), + primary key(role_id, scope_id_or_special) + ); + comment on table iam_role_grant_scope is + 'table to map roles to the scopes they grant access to'; + + insert into oplog_ticket (name, version) + values + ('iam_role_grant_scope', 1); + + create trigger default_create_time_column before insert on iam_role_grant_scope + for each row execute procedure default_create_time(); + + -- iam_immutable_role_grant_scope() ensures that grant scopes assigned to + -- roles are immutable. + create function iam_immutable_role_grant_scope() returns trigger + as $$ + begin + raise exception 'role grant scopes are immutable'; + end; + $$ language plpgsql; + + create trigger immutable_role_grant_scope before update on iam_role_grant_scope + for each row execute procedure iam_immutable_role_grant_scope(); + + -- cascade_role_grant_scope_deletion() ensures that grant scopes entries are + -- deleted when scopes are deleted + create function cascade_role_grant_scope_deletion() returns trigger + as $$ + begin + delete from iam_role_grant_scope where scope_id_or_special = old.public_id; + return old; + end; + $$ language plpgsql; + + -- Create a trigger to ensure scope deletion cascades, since we're not using wt_scope_id + create trigger cascade_deletion_iam_scope_to_iam_role_grant_scope after delete on iam_scope + for each row execute procedure cascade_role_grant_scope_deletion(); + + -- role_grant_scope_id_or_special_valid ensures that a given grant scope ID is for a + -- scope that exists or one of our known values + create function role_grant_scope_id_or_special_valid() returns trigger + as $$ + declare new_scope_type text; + declare role_scope_id text; + declare parent_scope_id text; + declare role_scope_type text; + declare validated_scope_id text; + declare existing_scope_id_or_special text; + begin + -- We want to make a few checks based on the role's actual scope so select it + select ir.scope_id from iam_role ir where ir.public_id = new.role_id into role_scope_id; + -- It's always allowed to have a scope_id_or_special of "this" but don't + -- allow it as well as the role's explicit scope ID + if new.scope_id_or_special = 'this' then + select rgs.scope_id_or_special + from iam_role_grant_scope rgs + where rgs.role_id = new.role_id and rgs.scope_id_or_special = role_scope_id + into existing_scope_id_or_special; + if existing_scope_id_or_special is not null then + raise exception 'invalid to specify both "this" and a role''s actual scope id as a grant scope'; + end if; + return new; + end if; + -- Fetch the scope id for the role + select ir.scope_id from iam_role ir where ir.public_id = new.role_id into role_scope_id; + -- It's always allowed to have the scope_id_or_special be the same as the role's + if new.scope_id_or_special = role_scope_id then + select rgs.scope_id_or_special + from iam_role_grant_scope rgs + where rgs.role_id = new.role_id and rgs.scope_id_or_special = 'this' + into existing_scope_id_or_special; + if existing_scope_id_or_special is not null then + raise exception 'invalid to specify both "this" and a role''s actual scope id as a grant scope'; + end if; + return new; + end if; + + -- A note about the above: this technically allows us to have grant scope + -- IDs defined on projects. The original grant_scope_id logic allowed this + -- in the domain layer so long as it referred only to its own scope. + -- Accordingly we keep this behavior, even though we could choose to + -- disallow this at the API layer. + + -- At this point we've covered the same-as-role-scope and "this" cases + -- above. Now, fetch the type of the role's scope, then check two situations + -- that are either always or never allowed. + select isc.type from iam_scope isc where isc.public_id = role_scope_id into role_scope_type; + + -- Always allowed, because any scope is a child of global, and we've handled + -- same-scope-id case above; however we have to check the scope is actually + -- valid/known. Distinction check is used in the final case because if it's + -- not known it's null. + if role_scope_type = 'global' then + case + when new.scope_id_or_special = 'children' then + select rgs.scope_id_or_special + from iam_role_grant_scope rgs + where rgs.role_id = new.role_id and rgs.scope_id_or_special = 'descendants' + into existing_scope_id_or_special; + if existing_scope_id_or_special is not null then + raise exception 'invalid to specify both "children" and "descendants"as a grant scope'; + end if; + when new.scope_id_or_special = 'descendants' then + select rgs.scope_id_or_special + from iam_role_grant_scope rgs + where rgs.role_id = new.role_id and rgs.scope_id_or_special = 'children' + into existing_scope_id_or_special; + if existing_scope_id_or_special is not null then + raise exception 'invalid to specify both "children" and "descendants"as a grant scope'; + end if; + else + select isc.public_id from iam_scope isc where isc.public_id = new.scope_id_or_special into validated_scope_id; + if validated_scope_id is distinct from new.scope_id_or_special then + raise exception 'invalid grant scope id'; + end if; + end case; + return new; + end if; + + -- Never allowed, because projects don't have child scopes (and we've + -- already allowed same-scope-id above) + if role_scope_type = 'project' then + raise exception 'invalid to set a grant scope ID to non-same scope_id_or_special when role scope type is project'; + end if; + + -- Ensure that what remains really is org + if role_scope_type != 'org' then + raise exception 'unknown scope type'; + end if; + -- If it's "children" then allow it + if new.scope_id_or_special = 'children' then + return new; + end if; + -- Make "descendants" an error for orgs + if new.scope_id_or_special = 'descendants' then + raise exception 'invalid to specify "descendants" as a grant scope when the role''s scope ID is not "global"'; + end if; + + -- We are now dealing with a bare scope ID and need to ensure that it's a + -- child project of the role's org scope. Look up the parent scope ID for + -- the grant scope ID given in the row. Allow iff the grant scope ID's + -- parent matches the role's scope ID. We know that the role is in an org + -- scope, so the only acceptable possibility here is that the new scope ID + -- is a project and its parent scope is this org's. + + -- Ensure it exists + select isc.public_id from iam_scope isc where isc.public_id = new.scope_id_or_special into validated_scope_id; + if validated_scope_id is distinct from new.scope_id_or_special then + raise exception 'invalid grant scope id'; + end if; + + -- Ensure it's a project + select isc.type from iam_scope isc where isc.public_id = new.scope_id_or_special into new_scope_type; + if new_scope_type != 'project' then + raise exception 'expected grant scope id scope type to be project'; + end if; + + -- Ensure that the parent of the project is the role's org scope + select isc.parent_id from iam_scope isc where isc.public_id = new.scope_id_or_special into parent_scope_id; + if parent_scope_id != role_scope_id then + raise exception 'grant scope id is not a child project of the role''s org scope'; + end if; + + return new; + end; + $$ language plpgsql; + comment on function role_grant_scope_id_or_special_valid() is + 'function used to ensure grant scope ids are valid'; + + create trigger ensure_role_grant_scope_id_or_special_valid before insert or update on iam_role_grant_scope + for each row execute procedure role_grant_scope_id_or_special_valid(); + + -- Now perform migrations: + + -- First, copy current grant scope ID values from existing roles to the new + -- table and set the grant scope ID value on each role to the scope ID + insert into iam_role_grant_scope(role_id, scope_id_or_special) + select public_id as role_id, grant_scope_id as scope_id_or_special from iam_role; + + -- Drop the now-unnecessary trigger and function from 0/06_iam + drop trigger ensure_grant_scope_id_valid on iam_role; + drop function grant_scope_id_valid; + + -- Remove the column from iam_role + alter table iam_role drop column grant_scope_id; + +commit; diff --git a/internal/db/sqltest/initdb.d/01_colors_persona.sql b/internal/db/sqltest/initdb.d/01_colors_persona.sql index da7c98377e..1c5484aee5 100644 --- a/internal/db/sqltest/initdb.d/01_colors_persona.sql +++ b/internal/db/sqltest/initdb.d/01_colors_persona.sql @@ -106,19 +106,33 @@ begin; ('g___cg-group', 'u_______cora'); insert into iam_role - (scope_id, grant_scope_id, public_id, name) - values - ('p____bcolors', 'p____bcolors', 'r_pp_bc__mix', 'Color Mixer'), - ('p____rcolors', 'p____rcolors', 'r_pp_rc__mix', 'Color Mixer'), - ('p____gcolors', 'p____gcolors', 'r_pp_gc__mix', 'Color Mixer'), - ('o_____colors', 'p____bcolors', 'r_op_bc__art', 'Blue Color Artist'), - ('o_____colors', 'p____rcolors', 'r_op_rc__art', 'Red Color Artist'), - ('o_____colors', 'p____gcolors', 'r_op_gc__art', 'Green Color Artist'), - ('o_____colors', 'o_____colors', 'r_oo_____art', 'Color Artist'), - ('global', 'o_____colors', 'r_go____name', 'Color Namer'), - ('global', 'p____bcolors', 'r_gp____spec', 'Blue Color Inspector'), - ('global', 'global', 'r_gg_____buy', 'Purchaser'), - ('global', 'global', 'r_gg____shop', 'Shopper'); + (scope_id, public_id, name) + values + ('p____bcolors', 'r_pp_bc__mix', 'Color Mixer'), + ('p____rcolors', 'r_pp_rc__mix', 'Color Mixer'), + ('p____gcolors', 'r_pp_gc__mix', 'Color Mixer'), + ('o_____colors', 'r_op_bc__art', 'Blue Color Artist'), + ('o_____colors', 'r_op_rc__art', 'Red Color Artist'), + ('o_____colors', 'r_op_gc__art', 'Green Color Artist'), + ('o_____colors', 'r_oo_____art', 'Color Artist'), + ('global', 'r_go____name', 'Color Namer'), + ('global', 'r_gp____spec', 'Blue Color Inspector'), + ('global', 'r_gg_____buy', 'Purchaser'), + ('global', 'r_gg____shop', 'Shopper'); + + insert into iam_role_grant_scope + (role_id, scope_id_or_special) + values + ('r_pp_bc__mix', 'this'), + ('r_pp_rc__mix', 'p____rcolors'), + ('r_pp_gc__mix', 'this'), + ('r_op_bc__art', 'p____bcolors'), + ('r_op_rc__art', 'p____rcolors'), + ('r_op_gc__art', 'p____gcolors'), + ('r_go____name', 'o_____colors'), + ('r_gp____spec', 'p____bcolors'), + ('r_gg_____buy', 'global'), + ('r_gg____shop', 'global'); insert into iam_role_grant (role_id, canonical_grant, raw_grant) diff --git a/internal/db/sqltest/initdb.d/03_widgets_persona.sql b/internal/db/sqltest/initdb.d/03_widgets_persona.sql index be3341db7d..f151a962b7 100644 --- a/internal/db/sqltest/initdb.d/03_widgets_persona.sql +++ b/internal/db/sqltest/initdb.d/03_widgets_persona.sql @@ -55,14 +55,22 @@ begin; ('g___ws-group', 'u_____waylon'); insert into iam_role - (scope_id, grant_scope_id, public_id, name) - values - -- ('global', 'global', 'r_gg_____buy', 'Purchaser'), - -- ('global', 'global', 'r_gg____shop', 'Shopper'), - ('p____bwidget', 'p____bwidget', 'r_pp_bw__bld', 'Widget Builder'), - ('p____swidget', 'p____swidget', 'r_pp_sw__bld', 'Widget Builder'), - ('o_____widget', 'p____swidget', 'r_op_sw__eng', 'Small Widget Engineer'), - ('o_____widget', 'o_____widget', 'r_oo_____eng', 'Widget Engineer'); + (scope_id, public_id, name) + values + -- ('global', 'r_gg_____buy', 'Purchaser'), + -- ('global', 'r_gg____shop', 'Shopper'), + ('p____bwidget', 'r_pp_bw__bld', 'Widget Builder'), + ('p____swidget', 'r_pp_sw__bld', 'Widget Builder'), + ('o_____widget', 'r_op_sw__eng', 'Small Widget Engineer'), + ('o_____widget', 'r_oo_____eng', 'Widget Engineer'); + + insert into iam_role_grant_scope + (role_id, scope_id_or_special) + values + ('r_pp_bw__bld', 'p____bwidget'), + ('r_pp_sw__bld', 'this'), + ('r_op_sw__eng', 'p____swidget'), + ('r_oo_____eng', 'o_____widget'); insert into iam_role_grant (role_id, canonical_grant, raw_grant) diff --git a/internal/db/sqltest/initdb.d/04_foodtruck_persona.sql b/internal/db/sqltest/initdb.d/04_foodtruck_persona.sql index 8725d77095..580a7356e0 100644 --- a/internal/db/sqltest/initdb.d/04_foodtruck_persona.sql +++ b/internal/db/sqltest/initdb.d/04_foodtruck_persona.sql @@ -107,9 +107,14 @@ begin; -- Roles insert into iam_role - (scope_id, grant_scope_id, public_id, name) + (scope_id, public_id, name) values - ('p______tacos', 'p______tacos', 'r_pp___tacos', 'Tacos'); + ('p______tacos', 'r_pp___tacos', 'Tacos'); + + insert into iam_role_grant_scope + (role_id, scope_id_or_special ) + values + ('r_pp___tacos', 'p______tacos'); insert into iam_role_grant (role_id, canonical_grant, raw_grant) diff --git a/internal/gen/controller.swagger.json b/internal/gen/controller.swagger.json index 55be1f0c02..cefdcf8699 100644 --- a/internal/gen/controller.swagger.json +++ b/internal/gen/controller.swagger.json @@ -2570,6 +2570,54 @@ ] } }, + "/v1/roles/{id}:add-grant-scopes": { + "post": { + "summary": "Adds grant scopes to a Role", + "operationId": "RoleService_AddRoleGrantScopes", + "responses": { + "200": { + "description": "", + "schema": { + "$ref": "#/definitions/controller.api.resources.roles.v1.Role" + } + } + }, + "parameters": [ + { + "name": "id", + "description": "", + "in": "path", + "required": true, + "type": "string" + }, + { + "name": "body", + "in": "body", + "required": true, + "schema": { + "type": "object", + "properties": { + "version": { + "type": "integer", + "format": "int64", + "description": "Version is used to ensure this resource has not changed.\nThe mutation will fail if the version does not match the latest known good version." + }, + "grant_scope_ids": { + "type": "array", + "items": { + "type": "string" + }, + "title": "" + } + } + } + } + ], + "tags": [ + "controller.api.services.v1.RoleService" + ] + } + }, "/v1/roles/{id}:add-grants": { "post": { "summary": "Adds grants to a Role", @@ -2666,6 +2714,54 @@ ] } }, + "/v1/roles/{id}:remove-grant-scopes": { + "post": { + "summary": "Removes grant scopes from a Role.", + "operationId": "RoleService_RemoveRoleGrantScopes", + "responses": { + "200": { + "description": "", + "schema": { + "$ref": "#/definitions/controller.api.resources.roles.v1.Role" + } + } + }, + "parameters": [ + { + "name": "id", + "description": "", + "in": "path", + "required": true, + "type": "string" + }, + { + "name": "body", + "in": "body", + "required": true, + "schema": { + "type": "object", + "properties": { + "version": { + "type": "integer", + "format": "int64", + "description": "Version is used to ensure this resource has not changed.\nThe mutation will fail if the version does not match the latest known good version." + }, + "grant_scope_ids": { + "type": "array", + "items": { + "type": "string" + }, + "title": "" + } + } + } + } + ], + "tags": [ + "controller.api.services.v1.RoleService" + ] + } + }, "/v1/roles/{id}:remove-grants": { "post": { "summary": "Removes grants from a Role.", @@ -2762,6 +2858,54 @@ ] } }, + "/v1/roles/{id}:set-grant-scopes": { + "post": { + "summary": "Set grant scopes for a Role, removing any grant scopes that are not specified in the request.", + "operationId": "RoleService_SetRoleGrantScopes", + "responses": { + "200": { + "description": "", + "schema": { + "$ref": "#/definitions/controller.api.resources.roles.v1.Role" + } + } + }, + "parameters": [ + { + "name": "id", + "description": "", + "in": "path", + "required": true, + "type": "string" + }, + { + "name": "body", + "in": "body", + "required": true, + "schema": { + "type": "object", + "properties": { + "version": { + "type": "integer", + "format": "int64", + "description": "Version is used to ensure this resource has not changed.\nThe mutation will fail if the version does not match the latest known good version." + }, + "grant_scope_ids": { + "type": "array", + "items": { + "type": "string" + }, + "title": "" + } + } + } + } + ], + "tags": [ + "controller.api.services.v1.RoleService" + ] + } + }, "/v1/roles/{id}:set-grants": { "post": { "summary": "Set grants for a Role, removing any grants that are not specified in the request.", @@ -5944,7 +6088,15 @@ }, "grant_scope_id": { "type": "string", - "description": "The Scope the grants will apply to. If the Role is at the global scope, this can be an org or project. If the Role is at an org scope, this can be a project within the org. It is invalid for this to be anything other than the Role's scope when the Role's scope is a project." + "description": "The Scope the grants will apply to. If the Role is at the global scope,\nthis can be an org or project. If the Role is at an org scope, this can be\na project within the org. It is invalid for this to be anything other than\nthe Role's scope when the Role's scope is a project.\n\nDeprecated: Use \"grant_scope_ids\" instead." + }, + "grant_scope_ids": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Output only. The IDs of Scopes the grants will apply to. This can include\nthe role's own scope ID, or \"this\" for the same behavior; specific IDs of\nscopes that are children of the role's scope; the value \"children\" to match\nall direct child scopes of the role's scope; or the value \"descendants\" to\nmatch all descendant scopes (e.g. child scopes, children of child scopes;\nonly valid at \"global\" scope since it is the only one with children of\nchildren).", + "readOnly": true }, "principal_ids": { "type": "array", @@ -7583,6 +7735,14 @@ } } }, + "controller.api.services.v1.AddRoleGrantScopesResponse": { + "type": "object", + "properties": { + "item": { + "$ref": "#/definitions/controller.api.resources.roles.v1.Role" + } + } + }, "controller.api.services.v1.AddRoleGrantsResponse": { "type": "object", "properties": { @@ -8995,6 +9155,14 @@ } } }, + "controller.api.services.v1.RemoveRoleGrantScopesResponse": { + "type": "object", + "properties": { + "item": { + "$ref": "#/definitions/controller.api.resources.roles.v1.Role" + } + } + }, "controller.api.services.v1.RemoveRoleGrantsResponse": { "type": "object", "properties": { @@ -9083,6 +9251,14 @@ } } }, + "controller.api.services.v1.SetRoleGrantScopesResponse": { + "type": "object", + "properties": { + "item": { + "$ref": "#/definitions/controller.api.resources.roles.v1.Role" + } + } + }, "controller.api.services.v1.SetRoleGrantsResponse": { "type": "object", "properties": { diff --git a/internal/gen/controller/api/services/role_service.pb.go b/internal/gen/controller/api/services/role_service.pb.go index ef31f73ece..867bf6acf4 100644 --- a/internal/gen/controller/api/services/role_service.pb.go +++ b/internal/gen/controller/api/services/role_service.pb.go @@ -1285,6 +1285,342 @@ func (x *RemoveRoleGrantsResponse) GetItem() *roles.Role { return nil } +type AddRoleGrantScopesRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty" class:"public"` // @gotags: `class:"public"` + // Version is used to ensure this resource has not changed. + // The mutation will fail if the version does not match the latest known good version. + Version uint32 `protobuf:"varint,2,opt,name=version,proto3" json:"version,omitempty" class:"public"` // @gotags: `class:"public"` + GrantScopeIds []string `protobuf:"bytes,3,rep,name=grant_scope_ids,proto3" json:"grant_scope_ids,omitempty" class:"public"` // @gotags: `class:"public"` +} + +func (x *AddRoleGrantScopesRequest) Reset() { + *x = AddRoleGrantScopesRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_controller_api_services_v1_role_service_proto_msgTypes[22] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *AddRoleGrantScopesRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*AddRoleGrantScopesRequest) ProtoMessage() {} + +func (x *AddRoleGrantScopesRequest) ProtoReflect() protoreflect.Message { + mi := &file_controller_api_services_v1_role_service_proto_msgTypes[22] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use AddRoleGrantScopesRequest.ProtoReflect.Descriptor instead. +func (*AddRoleGrantScopesRequest) Descriptor() ([]byte, []int) { + return file_controller_api_services_v1_role_service_proto_rawDescGZIP(), []int{22} +} + +func (x *AddRoleGrantScopesRequest) GetId() string { + if x != nil { + return x.Id + } + return "" +} + +func (x *AddRoleGrantScopesRequest) GetVersion() uint32 { + if x != nil { + return x.Version + } + return 0 +} + +func (x *AddRoleGrantScopesRequest) GetGrantScopeIds() []string { + if x != nil { + return x.GrantScopeIds + } + return nil +} + +type AddRoleGrantScopesResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Item *roles.Role `protobuf:"bytes,1,opt,name=item,proto3" json:"item,omitempty"` +} + +func (x *AddRoleGrantScopesResponse) Reset() { + *x = AddRoleGrantScopesResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_controller_api_services_v1_role_service_proto_msgTypes[23] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *AddRoleGrantScopesResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*AddRoleGrantScopesResponse) ProtoMessage() {} + +func (x *AddRoleGrantScopesResponse) ProtoReflect() protoreflect.Message { + mi := &file_controller_api_services_v1_role_service_proto_msgTypes[23] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use AddRoleGrantScopesResponse.ProtoReflect.Descriptor instead. +func (*AddRoleGrantScopesResponse) Descriptor() ([]byte, []int) { + return file_controller_api_services_v1_role_service_proto_rawDescGZIP(), []int{23} +} + +func (x *AddRoleGrantScopesResponse) GetItem() *roles.Role { + if x != nil { + return x.Item + } + return nil +} + +type SetRoleGrantScopesRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty" class:"public"` // @gotags: `class:"public"` + // Version is used to ensure this resource has not changed. + // The mutation will fail if the version does not match the latest known good version. + Version uint32 `protobuf:"varint,2,opt,name=version,proto3" json:"version,omitempty" class:"public"` // @gotags: `class:"public"` + GrantScopeIds []string `protobuf:"bytes,3,rep,name=grant_scope_ids,proto3" json:"grant_scope_ids,omitempty" class:"public"` // @gotags: `class:"public"` +} + +func (x *SetRoleGrantScopesRequest) Reset() { + *x = SetRoleGrantScopesRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_controller_api_services_v1_role_service_proto_msgTypes[24] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *SetRoleGrantScopesRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SetRoleGrantScopesRequest) ProtoMessage() {} + +func (x *SetRoleGrantScopesRequest) ProtoReflect() protoreflect.Message { + mi := &file_controller_api_services_v1_role_service_proto_msgTypes[24] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SetRoleGrantScopesRequest.ProtoReflect.Descriptor instead. +func (*SetRoleGrantScopesRequest) Descriptor() ([]byte, []int) { + return file_controller_api_services_v1_role_service_proto_rawDescGZIP(), []int{24} +} + +func (x *SetRoleGrantScopesRequest) GetId() string { + if x != nil { + return x.Id + } + return "" +} + +func (x *SetRoleGrantScopesRequest) GetVersion() uint32 { + if x != nil { + return x.Version + } + return 0 +} + +func (x *SetRoleGrantScopesRequest) GetGrantScopeIds() []string { + if x != nil { + return x.GrantScopeIds + } + return nil +} + +type SetRoleGrantScopesResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Item *roles.Role `protobuf:"bytes,1,opt,name=item,proto3" json:"item,omitempty"` +} + +func (x *SetRoleGrantScopesResponse) Reset() { + *x = SetRoleGrantScopesResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_controller_api_services_v1_role_service_proto_msgTypes[25] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *SetRoleGrantScopesResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SetRoleGrantScopesResponse) ProtoMessage() {} + +func (x *SetRoleGrantScopesResponse) ProtoReflect() protoreflect.Message { + mi := &file_controller_api_services_v1_role_service_proto_msgTypes[25] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SetRoleGrantScopesResponse.ProtoReflect.Descriptor instead. +func (*SetRoleGrantScopesResponse) Descriptor() ([]byte, []int) { + return file_controller_api_services_v1_role_service_proto_rawDescGZIP(), []int{25} +} + +func (x *SetRoleGrantScopesResponse) GetItem() *roles.Role { + if x != nil { + return x.Item + } + return nil +} + +type RemoveRoleGrantScopesRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty" class:"public"` // @gotags: `class:"public"` + // Version is used to ensure this resource has not changed. + // The mutation will fail if the version does not match the latest known good version. + Version uint32 `protobuf:"varint,2,opt,name=version,proto3" json:"version,omitempty" class:"public"` // @gotags: `class:"public"` + GrantScopeIds []string `protobuf:"bytes,3,rep,name=grant_scope_ids,proto3" json:"grant_scope_ids,omitempty" class:"public"` // @gotags: `class:"public"` +} + +func (x *RemoveRoleGrantScopesRequest) Reset() { + *x = RemoveRoleGrantScopesRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_controller_api_services_v1_role_service_proto_msgTypes[26] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *RemoveRoleGrantScopesRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*RemoveRoleGrantScopesRequest) ProtoMessage() {} + +func (x *RemoveRoleGrantScopesRequest) ProtoReflect() protoreflect.Message { + mi := &file_controller_api_services_v1_role_service_proto_msgTypes[26] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use RemoveRoleGrantScopesRequest.ProtoReflect.Descriptor instead. +func (*RemoveRoleGrantScopesRequest) Descriptor() ([]byte, []int) { + return file_controller_api_services_v1_role_service_proto_rawDescGZIP(), []int{26} +} + +func (x *RemoveRoleGrantScopesRequest) GetId() string { + if x != nil { + return x.Id + } + return "" +} + +func (x *RemoveRoleGrantScopesRequest) GetVersion() uint32 { + if x != nil { + return x.Version + } + return 0 +} + +func (x *RemoveRoleGrantScopesRequest) GetGrantScopeIds() []string { + if x != nil { + return x.GrantScopeIds + } + return nil +} + +type RemoveRoleGrantScopesResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Item *roles.Role `protobuf:"bytes,1,opt,name=item,proto3" json:"item,omitempty"` +} + +func (x *RemoveRoleGrantScopesResponse) Reset() { + *x = RemoveRoleGrantScopesResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_controller_api_services_v1_role_service_proto_msgTypes[27] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *RemoveRoleGrantScopesResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*RemoveRoleGrantScopesResponse) ProtoMessage() {} + +func (x *RemoveRoleGrantScopesResponse) ProtoReflect() protoreflect.Message { + mi := &file_controller_api_services_v1_role_service_proto_msgTypes[27] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use RemoveRoleGrantScopesResponse.ProtoReflect.Descriptor instead. +func (*RemoveRoleGrantScopesResponse) Descriptor() ([]byte, []int) { + return file_controller_api_services_v1_role_service_proto_rawDescGZIP(), []int{27} +} + +func (x *RemoveRoleGrantScopesResponse) GetItem() *roles.Role { + if x != nil { + return x.Item + } + return nil +} + var File_controller_api_services_v1_role_service_proto protoreflect.FileDescriptor var file_controller_api_services_v1_role_service_proto_rawDesc = []byte{ @@ -1441,150 +1777,235 @@ var file_controller_api_services_v1_role_service_proto_rawDesc = []byte{ 0x69, 0x74, 0x65, 0x6d, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x27, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x2e, 0x72, 0x6f, 0x6c, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x52, - 0x6f, 0x6c, 0x65, 0x52, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x32, 0x98, 0x11, 0x0a, 0x0b, 0x52, 0x6f, - 0x6c, 0x65, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x98, 0x01, 0x0a, 0x07, 0x47, 0x65, - 0x74, 0x52, 0x6f, 0x6c, 0x65, 0x12, 0x2a, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, - 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, - 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x52, 0x6f, 0x6c, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x1a, 0x2b, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x61, - 0x70, 0x69, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x47, - 0x65, 0x74, 0x52, 0x6f, 0x6c, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x34, - 0x92, 0x41, 0x15, 0x12, 0x13, 0x47, 0x65, 0x74, 0x73, 0x20, 0x61, 0x20, 0x73, 0x69, 0x6e, 0x67, - 0x6c, 0x65, 0x20, 0x52, 0x6f, 0x6c, 0x65, 0x2e, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x16, 0x62, 0x04, - 0x69, 0x74, 0x65, 0x6d, 0x12, 0x0e, 0x2f, 0x76, 0x31, 0x2f, 0x72, 0x6f, 0x6c, 0x65, 0x73, 0x2f, - 0x7b, 0x69, 0x64, 0x7d, 0x12, 0x90, 0x01, 0x0a, 0x09, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x6f, 0x6c, - 0x65, 0x73, 0x12, 0x2c, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, - 0x61, 0x70, 0x69, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, - 0x4c, 0x69, 0x73, 0x74, 0x52, 0x6f, 0x6c, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x1a, 0x2d, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x61, 0x70, - 0x69, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, - 0x73, 0x74, 0x52, 0x6f, 0x6c, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, - 0x26, 0x92, 0x41, 0x12, 0x12, 0x10, 0x4c, 0x69, 0x73, 0x74, 0x73, 0x20, 0x61, 0x6c, 0x6c, 0x20, - 0x52, 0x6f, 0x6c, 0x65, 0x73, 0x2e, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x0b, 0x12, 0x09, 0x2f, 0x76, - 0x31, 0x2f, 0x72, 0x6f, 0x6c, 0x65, 0x73, 0x12, 0xa5, 0x01, 0x0a, 0x0a, 0x43, 0x72, 0x65, 0x61, - 0x74, 0x65, 0x52, 0x6f, 0x6c, 0x65, 0x12, 0x2d, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, - 0x6c, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, - 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x52, 0x6f, 0x6c, 0x65, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2e, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, - 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, - 0x76, 0x31, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x52, 0x6f, 0x6c, 0x65, 0x52, 0x65, 0x73, - 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x38, 0x92, 0x41, 0x18, 0x12, 0x16, 0x43, 0x72, 0x65, 0x61, - 0x74, 0x65, 0x73, 0x20, 0x61, 0x20, 0x73, 0x69, 0x6e, 0x67, 0x6c, 0x65, 0x20, 0x52, 0x6f, 0x6c, - 0x65, 0x2e, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x17, 0x3a, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x62, 0x04, - 0x69, 0x74, 0x65, 0x6d, 0x22, 0x09, 0x2f, 0x76, 0x31, 0x2f, 0x72, 0x6f, 0x6c, 0x65, 0x73, 0x12, - 0xa3, 0x01, 0x0a, 0x0a, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x52, 0x6f, 0x6c, 0x65, 0x12, 0x2d, + 0x6f, 0x6c, 0x65, 0x52, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x22, 0x6f, 0x0a, 0x19, 0x41, 0x64, 0x64, + 0x52, 0x6f, 0x6c, 0x65, 0x47, 0x72, 0x61, 0x6e, 0x74, 0x53, 0x63, 0x6f, 0x70, 0x65, 0x73, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x18, 0x0a, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, + 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, + 0x12, 0x28, 0x0a, 0x0f, 0x67, 0x72, 0x61, 0x6e, 0x74, 0x5f, 0x73, 0x63, 0x6f, 0x70, 0x65, 0x5f, + 0x69, 0x64, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0f, 0x67, 0x72, 0x61, 0x6e, 0x74, + 0x5f, 0x73, 0x63, 0x6f, 0x70, 0x65, 0x5f, 0x69, 0x64, 0x73, 0x22, 0x59, 0x0a, 0x1a, 0x41, 0x64, + 0x64, 0x52, 0x6f, 0x6c, 0x65, 0x47, 0x72, 0x61, 0x6e, 0x74, 0x53, 0x63, 0x6f, 0x70, 0x65, 0x73, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3b, 0x0a, 0x04, 0x69, 0x74, 0x65, 0x6d, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x27, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, + 0x6c, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, + 0x73, 0x2e, 0x72, 0x6f, 0x6c, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x6f, 0x6c, 0x65, 0x52, + 0x04, 0x69, 0x74, 0x65, 0x6d, 0x22, 0x6f, 0x0a, 0x19, 0x53, 0x65, 0x74, 0x52, 0x6f, 0x6c, 0x65, + 0x47, 0x72, 0x61, 0x6e, 0x74, 0x53, 0x63, 0x6f, 0x70, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, + 0x69, 0x64, 0x12, 0x18, 0x0a, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x0d, 0x52, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x28, 0x0a, 0x0f, + 0x67, 0x72, 0x61, 0x6e, 0x74, 0x5f, 0x73, 0x63, 0x6f, 0x70, 0x65, 0x5f, 0x69, 0x64, 0x73, 0x18, + 0x03, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0f, 0x67, 0x72, 0x61, 0x6e, 0x74, 0x5f, 0x73, 0x63, 0x6f, + 0x70, 0x65, 0x5f, 0x69, 0x64, 0x73, 0x22, 0x59, 0x0a, 0x1a, 0x53, 0x65, 0x74, 0x52, 0x6f, 0x6c, + 0x65, 0x47, 0x72, 0x61, 0x6e, 0x74, 0x53, 0x63, 0x6f, 0x70, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3b, 0x0a, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x27, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, + 0x61, 0x70, 0x69, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x2e, 0x72, 0x6f, + 0x6c, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x6f, 0x6c, 0x65, 0x52, 0x04, 0x69, 0x74, 0x65, + 0x6d, 0x22, 0x72, 0x0a, 0x1c, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x52, 0x6f, 0x6c, 0x65, 0x47, + 0x72, 0x61, 0x6e, 0x74, 0x53, 0x63, 0x6f, 0x70, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, + 0x64, 0x12, 0x18, 0x0a, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x0d, 0x52, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x28, 0x0a, 0x0f, 0x67, + 0x72, 0x61, 0x6e, 0x74, 0x5f, 0x73, 0x63, 0x6f, 0x70, 0x65, 0x5f, 0x69, 0x64, 0x73, 0x18, 0x03, + 0x20, 0x03, 0x28, 0x09, 0x52, 0x0f, 0x67, 0x72, 0x61, 0x6e, 0x74, 0x5f, 0x73, 0x63, 0x6f, 0x70, + 0x65, 0x5f, 0x69, 0x64, 0x73, 0x22, 0x5c, 0x0a, 0x1d, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x52, + 0x6f, 0x6c, 0x65, 0x47, 0x72, 0x61, 0x6e, 0x74, 0x53, 0x63, 0x6f, 0x70, 0x65, 0x73, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3b, 0x0a, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x0b, 0x32, 0x27, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, + 0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x2e, + 0x72, 0x6f, 0x6c, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x6f, 0x6c, 0x65, 0x52, 0x04, 0x69, + 0x74, 0x65, 0x6d, 0x32, 0xf5, 0x16, 0x0a, 0x0b, 0x52, 0x6f, 0x6c, 0x65, 0x53, 0x65, 0x72, 0x76, + 0x69, 0x63, 0x65, 0x12, 0x98, 0x01, 0x0a, 0x07, 0x47, 0x65, 0x74, 0x52, 0x6f, 0x6c, 0x65, 0x12, + 0x2a, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, + 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, + 0x52, 0x6f, 0x6c, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2b, 0x2e, 0x63, 0x6f, + 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x73, 0x65, 0x72, + 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x52, 0x6f, 0x6c, 0x65, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x34, 0x92, 0x41, 0x15, 0x12, 0x13, 0x47, + 0x65, 0x74, 0x73, 0x20, 0x61, 0x20, 0x73, 0x69, 0x6e, 0x67, 0x6c, 0x65, 0x20, 0x52, 0x6f, 0x6c, + 0x65, 0x2e, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x16, 0x62, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x12, 0x0e, + 0x2f, 0x76, 0x31, 0x2f, 0x72, 0x6f, 0x6c, 0x65, 0x73, 0x2f, 0x7b, 0x69, 0x64, 0x7d, 0x12, 0x90, + 0x01, 0x0a, 0x09, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x6f, 0x6c, 0x65, 0x73, 0x12, 0x2c, 0x2e, 0x63, + 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x73, 0x65, + 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x6f, + 0x6c, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2d, 0x2e, 0x63, 0x6f, 0x6e, + 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x73, 0x65, 0x72, 0x76, + 0x69, 0x63, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x6f, 0x6c, 0x65, + 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x26, 0x92, 0x41, 0x12, 0x12, 0x10, + 0x4c, 0x69, 0x73, 0x74, 0x73, 0x20, 0x61, 0x6c, 0x6c, 0x20, 0x52, 0x6f, 0x6c, 0x65, 0x73, 0x2e, + 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x0b, 0x12, 0x09, 0x2f, 0x76, 0x31, 0x2f, 0x72, 0x6f, 0x6c, 0x65, + 0x73, 0x12, 0xa5, 0x01, 0x0a, 0x0a, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x52, 0x6f, 0x6c, 0x65, + 0x12, 0x2d, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x61, 0x70, + 0x69, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x72, + 0x65, 0x61, 0x74, 0x65, 0x52, 0x6f, 0x6c, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, + 0x2e, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, + 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x72, 0x65, + 0x61, 0x74, 0x65, 0x52, 0x6f, 0x6c, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, + 0x38, 0x92, 0x41, 0x18, 0x12, 0x16, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x73, 0x20, 0x61, 0x20, + 0x73, 0x69, 0x6e, 0x67, 0x6c, 0x65, 0x20, 0x52, 0x6f, 0x6c, 0x65, 0x2e, 0x82, 0xd3, 0xe4, 0x93, + 0x02, 0x17, 0x3a, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x62, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x22, 0x09, + 0x2f, 0x76, 0x31, 0x2f, 0x72, 0x6f, 0x6c, 0x65, 0x73, 0x12, 0xa3, 0x01, 0x0a, 0x0a, 0x55, 0x70, + 0x64, 0x61, 0x74, 0x65, 0x52, 0x6f, 0x6c, 0x65, 0x12, 0x2d, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, + 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, + 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x52, 0x6f, 0x6c, 0x65, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2e, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, + 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, + 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x52, 0x6f, 0x6c, 0x65, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x36, 0x92, 0x41, 0x11, 0x12, 0x0f, 0x55, 0x70, + 0x64, 0x61, 0x74, 0x65, 0x73, 0x20, 0x61, 0x20, 0x52, 0x6f, 0x6c, 0x65, 0x2e, 0x82, 0xd3, 0xe4, + 0x93, 0x02, 0x1c, 0x3a, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x62, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x32, + 0x0e, 0x2f, 0x76, 0x31, 0x2f, 0x72, 0x6f, 0x6c, 0x65, 0x73, 0x2f, 0x7b, 0x69, 0x64, 0x7d, 0x12, + 0x97, 0x01, 0x0a, 0x0a, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x52, 0x6f, 0x6c, 0x65, 0x12, 0x2d, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e, - 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x70, 0x64, 0x61, + 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x52, 0x6f, 0x6c, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2e, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x73, - 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, - 0x65, 0x52, 0x6f, 0x6c, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x36, 0x92, - 0x41, 0x11, 0x12, 0x0f, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x73, 0x20, 0x61, 0x20, 0x52, 0x6f, - 0x6c, 0x65, 0x2e, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x1c, 0x3a, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x62, - 0x04, 0x69, 0x74, 0x65, 0x6d, 0x32, 0x0e, 0x2f, 0x76, 0x31, 0x2f, 0x72, 0x6f, 0x6c, 0x65, 0x73, - 0x2f, 0x7b, 0x69, 0x64, 0x7d, 0x12, 0x97, 0x01, 0x0a, 0x0a, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, - 0x52, 0x6f, 0x6c, 0x65, 0x12, 0x2d, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, - 0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x76, - 0x31, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x52, 0x6f, 0x6c, 0x65, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x1a, 0x2e, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, - 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x76, 0x31, - 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x52, 0x6f, 0x6c, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, - 0x6e, 0x73, 0x65, 0x22, 0x2a, 0x92, 0x41, 0x11, 0x12, 0x0f, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, - 0x73, 0x20, 0x61, 0x20, 0x52, 0x6f, 0x6c, 0x65, 0x2e, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x10, 0x2a, - 0x0e, 0x2f, 0x76, 0x31, 0x2f, 0x72, 0x6f, 0x6c, 0x65, 0x73, 0x2f, 0x7b, 0x69, 0x64, 0x7d, 0x12, - 0xd8, 0x01, 0x0a, 0x11, 0x41, 0x64, 0x64, 0x52, 0x6f, 0x6c, 0x65, 0x50, 0x72, 0x69, 0x6e, 0x63, - 0x69, 0x70, 0x61, 0x6c, 0x73, 0x12, 0x34, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, + 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, + 0x65, 0x52, 0x6f, 0x6c, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x2a, 0x92, + 0x41, 0x11, 0x12, 0x0f, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x73, 0x20, 0x61, 0x20, 0x52, 0x6f, + 0x6c, 0x65, 0x2e, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x10, 0x2a, 0x0e, 0x2f, 0x76, 0x31, 0x2f, 0x72, + 0x6f, 0x6c, 0x65, 0x73, 0x2f, 0x7b, 0x69, 0x64, 0x7d, 0x12, 0xd8, 0x01, 0x0a, 0x11, 0x41, 0x64, + 0x64, 0x52, 0x6f, 0x6c, 0x65, 0x50, 0x72, 0x69, 0x6e, 0x63, 0x69, 0x70, 0x61, 0x6c, 0x73, 0x12, + 0x34, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, + 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x64, 0x64, + 0x52, 0x6f, 0x6c, 0x65, 0x50, 0x72, 0x69, 0x6e, 0x63, 0x69, 0x70, 0x61, 0x6c, 0x73, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x35, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x64, 0x64, 0x52, 0x6f, 0x6c, 0x65, 0x50, 0x72, 0x69, 0x6e, 0x63, 0x69, - 0x70, 0x61, 0x6c, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x35, 0x2e, 0x63, 0x6f, - 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x73, 0x65, 0x72, - 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x64, 0x64, 0x52, 0x6f, 0x6c, 0x65, - 0x50, 0x72, 0x69, 0x6e, 0x63, 0x69, 0x70, 0x61, 0x6c, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, - 0x73, 0x65, 0x22, 0x56, 0x92, 0x41, 0x25, 0x12, 0x23, 0x41, 0x64, 0x64, 0x73, 0x20, 0x55, 0x73, - 0x65, 0x72, 0x73, 0x20, 0x61, 0x6e, 0x64, 0x2f, 0x6f, 0x72, 0x20, 0x47, 0x72, 0x6f, 0x75, 0x70, - 0x73, 0x20, 0x74, 0x6f, 0x20, 0x61, 0x20, 0x52, 0x6f, 0x6c, 0x65, 0x2e, 0x82, 0xd3, 0xe4, 0x93, - 0x02, 0x28, 0x3a, 0x01, 0x2a, 0x62, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x22, 0x1d, 0x2f, 0x76, 0x31, - 0x2f, 0x72, 0x6f, 0x6c, 0x65, 0x73, 0x2f, 0x7b, 0x69, 0x64, 0x7d, 0x3a, 0x61, 0x64, 0x64, 0x2d, - 0x70, 0x72, 0x69, 0x6e, 0x63, 0x69, 0x70, 0x61, 0x6c, 0x73, 0x12, 0x97, 0x02, 0x0a, 0x11, 0x53, - 0x65, 0x74, 0x52, 0x6f, 0x6c, 0x65, 0x50, 0x72, 0x69, 0x6e, 0x63, 0x69, 0x70, 0x61, 0x6c, 0x73, - 0x12, 0x34, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x61, 0x70, + 0x70, 0x61, 0x6c, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x56, 0x92, 0x41, + 0x25, 0x12, 0x23, 0x41, 0x64, 0x64, 0x73, 0x20, 0x55, 0x73, 0x65, 0x72, 0x73, 0x20, 0x61, 0x6e, + 0x64, 0x2f, 0x6f, 0x72, 0x20, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x73, 0x20, 0x74, 0x6f, 0x20, 0x61, + 0x20, 0x52, 0x6f, 0x6c, 0x65, 0x2e, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x28, 0x3a, 0x01, 0x2a, 0x62, + 0x04, 0x69, 0x74, 0x65, 0x6d, 0x22, 0x1d, 0x2f, 0x76, 0x31, 0x2f, 0x72, 0x6f, 0x6c, 0x65, 0x73, + 0x2f, 0x7b, 0x69, 0x64, 0x7d, 0x3a, 0x61, 0x64, 0x64, 0x2d, 0x70, 0x72, 0x69, 0x6e, 0x63, 0x69, + 0x70, 0x61, 0x6c, 0x73, 0x12, 0x97, 0x02, 0x0a, 0x11, 0x53, 0x65, 0x74, 0x52, 0x6f, 0x6c, 0x65, + 0x50, 0x72, 0x69, 0x6e, 0x63, 0x69, 0x70, 0x61, 0x6c, 0x73, 0x12, 0x34, 0x2e, 0x63, 0x6f, 0x6e, + 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x73, 0x65, 0x72, 0x76, + 0x69, 0x63, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x65, 0x74, 0x52, 0x6f, 0x6c, 0x65, 0x50, + 0x72, 0x69, 0x6e, 0x63, 0x69, 0x70, 0x61, 0x6c, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x1a, 0x35, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x65, 0x74, 0x52, 0x6f, 0x6c, 0x65, 0x50, 0x72, 0x69, 0x6e, 0x63, 0x69, 0x70, 0x61, 0x6c, 0x73, 0x52, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x35, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, - 0x6c, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, - 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x65, 0x74, 0x52, 0x6f, 0x6c, 0x65, 0x50, 0x72, 0x69, 0x6e, 0x63, - 0x69, 0x70, 0x61, 0x6c, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x94, 0x01, - 0x92, 0x41, 0x63, 0x12, 0x61, 0x53, 0x65, 0x74, 0x20, 0x55, 0x73, 0x65, 0x72, 0x73, 0x20, 0x61, - 0x6e, 0x64, 0x2f, 0x6f, 0x72, 0x20, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x73, 0x20, 0x74, 0x6f, 0x20, - 0x61, 0x20, 0x52, 0x6f, 0x6c, 0x65, 0x2c, 0x20, 0x72, 0x65, 0x6d, 0x6f, 0x76, 0x69, 0x6e, 0x67, - 0x20, 0x61, 0x6e, 0x79, 0x20, 0x70, 0x72, 0x69, 0x6e, 0x63, 0x69, 0x70, 0x61, 0x6c, 0x73, 0x20, - 0x74, 0x68, 0x61, 0x74, 0x20, 0x61, 0x72, 0x65, 0x20, 0x6e, 0x6f, 0x74, 0x20, 0x73, 0x70, 0x65, - 0x63, 0x69, 0x66, 0x69, 0x65, 0x64, 0x20, 0x69, 0x6e, 0x20, 0x74, 0x68, 0x65, 0x20, 0x72, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x28, 0x3a, 0x01, 0x2a, 0x62, - 0x04, 0x69, 0x74, 0x65, 0x6d, 0x22, 0x1d, 0x2f, 0x76, 0x31, 0x2f, 0x72, 0x6f, 0x6c, 0x65, 0x73, - 0x2f, 0x7b, 0x69, 0x64, 0x7d, 0x3a, 0x73, 0x65, 0x74, 0x2d, 0x70, 0x72, 0x69, 0x6e, 0x63, 0x69, - 0x70, 0x61, 0x6c, 0x73, 0x12, 0xf7, 0x01, 0x0a, 0x14, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x52, - 0x6f, 0x6c, 0x65, 0x50, 0x72, 0x69, 0x6e, 0x63, 0x69, 0x70, 0x61, 0x6c, 0x73, 0x12, 0x37, 0x2e, - 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x73, - 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x6d, 0x6f, 0x76, - 0x65, 0x52, 0x6f, 0x6c, 0x65, 0x50, 0x72, 0x69, 0x6e, 0x63, 0x69, 0x70, 0x61, 0x6c, 0x73, 0x52, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x38, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, - 0x6c, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, - 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x52, 0x6f, 0x6c, 0x65, 0x50, 0x72, - 0x69, 0x6e, 0x63, 0x69, 0x70, 0x61, 0x6c, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, - 0x22, 0x6c, 0x92, 0x41, 0x38, 0x12, 0x36, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x73, 0x20, 0x74, - 0x68, 0x65, 0x20, 0x73, 0x70, 0x65, 0x63, 0x69, 0x66, 0x69, 0x65, 0x64, 0x20, 0x55, 0x73, 0x65, - 0x72, 0x73, 0x20, 0x61, 0x6e, 0x64, 0x2f, 0x6f, 0x72, 0x20, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x73, - 0x20, 0x66, 0x72, 0x6f, 0x6d, 0x20, 0x61, 0x20, 0x52, 0x6f, 0x6c, 0x65, 0x2e, 0x82, 0xd3, 0xe4, - 0x93, 0x02, 0x2b, 0x3a, 0x01, 0x2a, 0x62, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x22, 0x20, 0x2f, 0x76, - 0x31, 0x2f, 0x72, 0x6f, 0x6c, 0x65, 0x73, 0x2f, 0x7b, 0x69, 0x64, 0x7d, 0x3a, 0x72, 0x65, 0x6d, - 0x6f, 0x76, 0x65, 0x2d, 0x70, 0x72, 0x69, 0x6e, 0x63, 0x69, 0x70, 0x61, 0x6c, 0x73, 0x12, 0xba, - 0x01, 0x0a, 0x0d, 0x41, 0x64, 0x64, 0x52, 0x6f, 0x6c, 0x65, 0x47, 0x72, 0x61, 0x6e, 0x74, 0x73, - 0x12, 0x30, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x61, 0x70, - 0x69, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x64, - 0x64, 0x52, 0x6f, 0x6c, 0x65, 0x47, 0x72, 0x61, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x1a, 0x31, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, - 0x61, 0x70, 0x69, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, - 0x41, 0x64, 0x64, 0x52, 0x6f, 0x6c, 0x65, 0x47, 0x72, 0x61, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x73, - 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x44, 0x92, 0x41, 0x17, 0x12, 0x15, 0x41, 0x64, 0x64, 0x73, - 0x20, 0x67, 0x72, 0x61, 0x6e, 0x74, 0x73, 0x20, 0x74, 0x6f, 0x20, 0x61, 0x20, 0x52, 0x6f, 0x6c, - 0x65, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x24, 0x3a, 0x01, 0x2a, 0x62, 0x04, 0x69, 0x74, 0x65, 0x6d, - 0x22, 0x19, 0x2f, 0x76, 0x31, 0x2f, 0x72, 0x6f, 0x6c, 0x65, 0x73, 0x2f, 0x7b, 0x69, 0x64, 0x7d, - 0x3a, 0x61, 0x64, 0x64, 0x2d, 0x67, 0x72, 0x61, 0x6e, 0x74, 0x73, 0x12, 0xf7, 0x01, 0x0a, 0x0d, - 0x53, 0x65, 0x74, 0x52, 0x6f, 0x6c, 0x65, 0x47, 0x72, 0x61, 0x6e, 0x74, 0x73, 0x12, 0x30, 0x2e, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x94, 0x01, 0x92, 0x41, 0x63, 0x12, 0x61, 0x53, + 0x65, 0x74, 0x20, 0x55, 0x73, 0x65, 0x72, 0x73, 0x20, 0x61, 0x6e, 0x64, 0x2f, 0x6f, 0x72, 0x20, + 0x47, 0x72, 0x6f, 0x75, 0x70, 0x73, 0x20, 0x74, 0x6f, 0x20, 0x61, 0x20, 0x52, 0x6f, 0x6c, 0x65, + 0x2c, 0x20, 0x72, 0x65, 0x6d, 0x6f, 0x76, 0x69, 0x6e, 0x67, 0x20, 0x61, 0x6e, 0x79, 0x20, 0x70, + 0x72, 0x69, 0x6e, 0x63, 0x69, 0x70, 0x61, 0x6c, 0x73, 0x20, 0x74, 0x68, 0x61, 0x74, 0x20, 0x61, + 0x72, 0x65, 0x20, 0x6e, 0x6f, 0x74, 0x20, 0x73, 0x70, 0x65, 0x63, 0x69, 0x66, 0x69, 0x65, 0x64, + 0x20, 0x69, 0x6e, 0x20, 0x74, 0x68, 0x65, 0x20, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, + 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x28, 0x3a, 0x01, 0x2a, 0x62, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x22, + 0x1d, 0x2f, 0x76, 0x31, 0x2f, 0x72, 0x6f, 0x6c, 0x65, 0x73, 0x2f, 0x7b, 0x69, 0x64, 0x7d, 0x3a, + 0x73, 0x65, 0x74, 0x2d, 0x70, 0x72, 0x69, 0x6e, 0x63, 0x69, 0x70, 0x61, 0x6c, 0x73, 0x12, 0xf7, + 0x01, 0x0a, 0x14, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x52, 0x6f, 0x6c, 0x65, 0x50, 0x72, 0x69, + 0x6e, 0x63, 0x69, 0x70, 0x61, 0x6c, 0x73, 0x12, 0x37, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, + 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, + 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x52, 0x6f, 0x6c, 0x65, 0x50, + 0x72, 0x69, 0x6e, 0x63, 0x69, 0x70, 0x61, 0x6c, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x1a, 0x38, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x61, 0x70, + 0x69, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, + 0x6d, 0x6f, 0x76, 0x65, 0x52, 0x6f, 0x6c, 0x65, 0x50, 0x72, 0x69, 0x6e, 0x63, 0x69, 0x70, 0x61, + 0x6c, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x6c, 0x92, 0x41, 0x38, 0x12, + 0x36, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x73, 0x20, 0x74, 0x68, 0x65, 0x20, 0x73, 0x70, 0x65, + 0x63, 0x69, 0x66, 0x69, 0x65, 0x64, 0x20, 0x55, 0x73, 0x65, 0x72, 0x73, 0x20, 0x61, 0x6e, 0x64, + 0x2f, 0x6f, 0x72, 0x20, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x73, 0x20, 0x66, 0x72, 0x6f, 0x6d, 0x20, + 0x61, 0x20, 0x52, 0x6f, 0x6c, 0x65, 0x2e, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x2b, 0x3a, 0x01, 0x2a, + 0x62, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x22, 0x20, 0x2f, 0x76, 0x31, 0x2f, 0x72, 0x6f, 0x6c, 0x65, + 0x73, 0x2f, 0x7b, 0x69, 0x64, 0x7d, 0x3a, 0x72, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x2d, 0x70, 0x72, + 0x69, 0x6e, 0x63, 0x69, 0x70, 0x61, 0x6c, 0x73, 0x12, 0xba, 0x01, 0x0a, 0x0d, 0x41, 0x64, 0x64, + 0x52, 0x6f, 0x6c, 0x65, 0x47, 0x72, 0x61, 0x6e, 0x74, 0x73, 0x12, 0x30, 0x2e, 0x63, 0x6f, 0x6e, + 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x73, 0x65, 0x72, 0x76, + 0x69, 0x63, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x64, 0x64, 0x52, 0x6f, 0x6c, 0x65, 0x47, + 0x72, 0x61, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x31, 0x2e, 0x63, + 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x73, 0x65, + 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x64, 0x64, 0x52, 0x6f, 0x6c, + 0x65, 0x47, 0x72, 0x61, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, + 0x44, 0x92, 0x41, 0x17, 0x12, 0x15, 0x41, 0x64, 0x64, 0x73, 0x20, 0x67, 0x72, 0x61, 0x6e, 0x74, + 0x73, 0x20, 0x74, 0x6f, 0x20, 0x61, 0x20, 0x52, 0x6f, 0x6c, 0x65, 0x82, 0xd3, 0xe4, 0x93, 0x02, + 0x24, 0x3a, 0x01, 0x2a, 0x62, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x22, 0x19, 0x2f, 0x76, 0x31, 0x2f, + 0x72, 0x6f, 0x6c, 0x65, 0x73, 0x2f, 0x7b, 0x69, 0x64, 0x7d, 0x3a, 0x61, 0x64, 0x64, 0x2d, 0x67, + 0x72, 0x61, 0x6e, 0x74, 0x73, 0x12, 0xf7, 0x01, 0x0a, 0x0d, 0x53, 0x65, 0x74, 0x52, 0x6f, 0x6c, + 0x65, 0x47, 0x72, 0x61, 0x6e, 0x74, 0x73, 0x12, 0x30, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, + 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, + 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x65, 0x74, 0x52, 0x6f, 0x6c, 0x65, 0x47, 0x72, 0x61, 0x6e, + 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x31, 0x2e, 0x63, 0x6f, 0x6e, 0x74, + 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, + 0x63, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x65, 0x74, 0x52, 0x6f, 0x6c, 0x65, 0x47, 0x72, + 0x61, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x80, 0x01, 0x92, + 0x41, 0x53, 0x12, 0x51, 0x53, 0x65, 0x74, 0x20, 0x67, 0x72, 0x61, 0x6e, 0x74, 0x73, 0x20, 0x66, + 0x6f, 0x72, 0x20, 0x61, 0x20, 0x52, 0x6f, 0x6c, 0x65, 0x2c, 0x20, 0x72, 0x65, 0x6d, 0x6f, 0x76, + 0x69, 0x6e, 0x67, 0x20, 0x61, 0x6e, 0x79, 0x20, 0x67, 0x72, 0x61, 0x6e, 0x74, 0x73, 0x20, 0x74, + 0x68, 0x61, 0x74, 0x20, 0x61, 0x72, 0x65, 0x20, 0x6e, 0x6f, 0x74, 0x20, 0x73, 0x70, 0x65, 0x63, + 0x69, 0x66, 0x69, 0x65, 0x64, 0x20, 0x69, 0x6e, 0x20, 0x74, 0x68, 0x65, 0x20, 0x72, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x2e, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x24, 0x3a, 0x01, 0x2a, 0x62, 0x04, + 0x69, 0x74, 0x65, 0x6d, 0x22, 0x19, 0x2f, 0x76, 0x31, 0x2f, 0x72, 0x6f, 0x6c, 0x65, 0x73, 0x2f, + 0x7b, 0x69, 0x64, 0x7d, 0x3a, 0x73, 0x65, 0x74, 0x2d, 0x67, 0x72, 0x61, 0x6e, 0x74, 0x73, 0x12, + 0xcc, 0x01, 0x0a, 0x10, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x52, 0x6f, 0x6c, 0x65, 0x47, 0x72, + 0x61, 0x6e, 0x74, 0x73, 0x12, 0x33, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, + 0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x76, + 0x31, 0x2e, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x52, 0x6f, 0x6c, 0x65, 0x47, 0x72, 0x61, 0x6e, + 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x34, 0x2e, 0x63, 0x6f, 0x6e, 0x74, + 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, + 0x63, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x52, 0x6f, 0x6c, + 0x65, 0x47, 0x72, 0x61, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, + 0x4d, 0x92, 0x41, 0x1d, 0x12, 0x1b, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x73, 0x20, 0x67, 0x72, + 0x61, 0x6e, 0x74, 0x73, 0x20, 0x66, 0x72, 0x6f, 0x6d, 0x20, 0x61, 0x20, 0x52, 0x6f, 0x6c, 0x65, + 0x2e, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x27, 0x3a, 0x01, 0x2a, 0x62, 0x04, 0x69, 0x74, 0x65, 0x6d, + 0x22, 0x1c, 0x2f, 0x76, 0x31, 0x2f, 0x72, 0x6f, 0x6c, 0x65, 0x73, 0x2f, 0x7b, 0x69, 0x64, 0x7d, + 0x3a, 0x72, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x2d, 0x67, 0x72, 0x61, 0x6e, 0x74, 0x73, 0x12, 0xd5, + 0x01, 0x0a, 0x12, 0x41, 0x64, 0x64, 0x52, 0x6f, 0x6c, 0x65, 0x47, 0x72, 0x61, 0x6e, 0x74, 0x53, + 0x63, 0x6f, 0x70, 0x65, 0x73, 0x12, 0x35, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, + 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, + 0x76, 0x31, 0x2e, 0x41, 0x64, 0x64, 0x52, 0x6f, 0x6c, 0x65, 0x47, 0x72, 0x61, 0x6e, 0x74, 0x53, + 0x63, 0x6f, 0x70, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x36, 0x2e, 0x63, + 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x73, 0x65, + 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x64, 0x64, 0x52, 0x6f, 0x6c, + 0x65, 0x47, 0x72, 0x61, 0x6e, 0x74, 0x53, 0x63, 0x6f, 0x70, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x50, 0x92, 0x41, 0x1d, 0x12, 0x1b, 0x41, 0x64, 0x64, 0x73, 0x20, + 0x67, 0x72, 0x61, 0x6e, 0x74, 0x20, 0x73, 0x63, 0x6f, 0x70, 0x65, 0x73, 0x20, 0x74, 0x6f, 0x20, + 0x61, 0x20, 0x52, 0x6f, 0x6c, 0x65, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x2a, 0x3a, 0x01, 0x2a, 0x62, + 0x04, 0x69, 0x74, 0x65, 0x6d, 0x22, 0x1f, 0x2f, 0x76, 0x31, 0x2f, 0x72, 0x6f, 0x6c, 0x65, 0x73, + 0x2f, 0x7b, 0x69, 0x64, 0x7d, 0x3a, 0x61, 0x64, 0x64, 0x2d, 0x67, 0x72, 0x61, 0x6e, 0x74, 0x2d, + 0x73, 0x63, 0x6f, 0x70, 0x65, 0x73, 0x12, 0x98, 0x02, 0x0a, 0x12, 0x53, 0x65, 0x74, 0x52, 0x6f, + 0x6c, 0x65, 0x47, 0x72, 0x61, 0x6e, 0x74, 0x53, 0x63, 0x6f, 0x70, 0x65, 0x73, 0x12, 0x35, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x65, 0x74, 0x52, 0x6f, - 0x6c, 0x65, 0x47, 0x72, 0x61, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, - 0x31, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, - 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x65, 0x74, - 0x52, 0x6f, 0x6c, 0x65, 0x47, 0x72, 0x61, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, - 0x73, 0x65, 0x22, 0x80, 0x01, 0x92, 0x41, 0x53, 0x12, 0x51, 0x53, 0x65, 0x74, 0x20, 0x67, 0x72, - 0x61, 0x6e, 0x74, 0x73, 0x20, 0x66, 0x6f, 0x72, 0x20, 0x61, 0x20, 0x52, 0x6f, 0x6c, 0x65, 0x2c, + 0x6c, 0x65, 0x47, 0x72, 0x61, 0x6e, 0x74, 0x53, 0x63, 0x6f, 0x70, 0x65, 0x73, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x1a, 0x36, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, + 0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x76, + 0x31, 0x2e, 0x53, 0x65, 0x74, 0x52, 0x6f, 0x6c, 0x65, 0x47, 0x72, 0x61, 0x6e, 0x74, 0x53, 0x63, + 0x6f, 0x70, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x92, 0x01, 0x92, + 0x41, 0x5f, 0x12, 0x5d, 0x53, 0x65, 0x74, 0x20, 0x67, 0x72, 0x61, 0x6e, 0x74, 0x20, 0x73, 0x63, + 0x6f, 0x70, 0x65, 0x73, 0x20, 0x66, 0x6f, 0x72, 0x20, 0x61, 0x20, 0x52, 0x6f, 0x6c, 0x65, 0x2c, 0x20, 0x72, 0x65, 0x6d, 0x6f, 0x76, 0x69, 0x6e, 0x67, 0x20, 0x61, 0x6e, 0x79, 0x20, 0x67, 0x72, - 0x61, 0x6e, 0x74, 0x73, 0x20, 0x74, 0x68, 0x61, 0x74, 0x20, 0x61, 0x72, 0x65, 0x20, 0x6e, 0x6f, - 0x74, 0x20, 0x73, 0x70, 0x65, 0x63, 0x69, 0x66, 0x69, 0x65, 0x64, 0x20, 0x69, 0x6e, 0x20, 0x74, - 0x68, 0x65, 0x20, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x82, 0xd3, 0xe4, 0x93, 0x02, - 0x24, 0x3a, 0x01, 0x2a, 0x62, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x22, 0x19, 0x2f, 0x76, 0x31, 0x2f, - 0x72, 0x6f, 0x6c, 0x65, 0x73, 0x2f, 0x7b, 0x69, 0x64, 0x7d, 0x3a, 0x73, 0x65, 0x74, 0x2d, 0x67, - 0x72, 0x61, 0x6e, 0x74, 0x73, 0x12, 0xcc, 0x01, 0x0a, 0x10, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, - 0x52, 0x6f, 0x6c, 0x65, 0x47, 0x72, 0x61, 0x6e, 0x74, 0x73, 0x12, 0x33, 0x2e, 0x63, 0x6f, 0x6e, - 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x73, 0x65, 0x72, 0x76, - 0x69, 0x63, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x52, 0x6f, - 0x6c, 0x65, 0x47, 0x72, 0x61, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, - 0x34, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, - 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x6d, - 0x6f, 0x76, 0x65, 0x52, 0x6f, 0x6c, 0x65, 0x47, 0x72, 0x61, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x73, - 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x4d, 0x92, 0x41, 0x1d, 0x12, 0x1b, 0x52, 0x65, 0x6d, 0x6f, - 0x76, 0x65, 0x73, 0x20, 0x67, 0x72, 0x61, 0x6e, 0x74, 0x73, 0x20, 0x66, 0x72, 0x6f, 0x6d, 0x20, - 0x61, 0x20, 0x52, 0x6f, 0x6c, 0x65, 0x2e, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x27, 0x3a, 0x01, 0x2a, - 0x62, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x22, 0x1c, 0x2f, 0x76, 0x31, 0x2f, 0x72, 0x6f, 0x6c, 0x65, - 0x73, 0x2f, 0x7b, 0x69, 0x64, 0x7d, 0x3a, 0x72, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x2d, 0x67, 0x72, - 0x61, 0x6e, 0x74, 0x73, 0x42, 0x4d, 0x5a, 0x4b, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, - 0x6f, 0x6d, 0x2f, 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x2f, 0x62, 0x6f, 0x75, - 0x6e, 0x64, 0x61, 0x72, 0x79, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2f, 0x67, - 0x65, 0x6e, 0x2f, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2f, 0x61, 0x70, - 0x69, 0x2f, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x3b, 0x73, 0x65, 0x72, 0x76, 0x69, - 0x63, 0x65, 0x73, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x61, 0x6e, 0x74, 0x20, 0x73, 0x63, 0x6f, 0x70, 0x65, 0x73, 0x20, 0x74, 0x68, 0x61, 0x74, 0x20, + 0x61, 0x72, 0x65, 0x20, 0x6e, 0x6f, 0x74, 0x20, 0x73, 0x70, 0x65, 0x63, 0x69, 0x66, 0x69, 0x65, + 0x64, 0x20, 0x69, 0x6e, 0x20, 0x74, 0x68, 0x65, 0x20, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x2e, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x2a, 0x3a, 0x01, 0x2a, 0x62, 0x04, 0x69, 0x74, 0x65, 0x6d, + 0x22, 0x1f, 0x2f, 0x76, 0x31, 0x2f, 0x72, 0x6f, 0x6c, 0x65, 0x73, 0x2f, 0x7b, 0x69, 0x64, 0x7d, + 0x3a, 0x73, 0x65, 0x74, 0x2d, 0x67, 0x72, 0x61, 0x6e, 0x74, 0x2d, 0x73, 0x63, 0x6f, 0x70, 0x65, + 0x73, 0x12, 0xe7, 0x01, 0x0a, 0x15, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x52, 0x6f, 0x6c, 0x65, + 0x47, 0x72, 0x61, 0x6e, 0x74, 0x53, 0x63, 0x6f, 0x70, 0x65, 0x73, 0x12, 0x38, 0x2e, 0x63, 0x6f, + 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x73, 0x65, 0x72, + 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x52, + 0x6f, 0x6c, 0x65, 0x47, 0x72, 0x61, 0x6e, 0x74, 0x53, 0x63, 0x6f, 0x70, 0x65, 0x73, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x39, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, + 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, + 0x76, 0x31, 0x2e, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x52, 0x6f, 0x6c, 0x65, 0x47, 0x72, 0x61, + 0x6e, 0x74, 0x53, 0x63, 0x6f, 0x70, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x22, 0x59, 0x92, 0x41, 0x23, 0x12, 0x21, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x73, 0x20, 0x67, + 0x72, 0x61, 0x6e, 0x74, 0x20, 0x73, 0x63, 0x6f, 0x70, 0x65, 0x73, 0x20, 0x66, 0x72, 0x6f, 0x6d, + 0x20, 0x61, 0x20, 0x52, 0x6f, 0x6c, 0x65, 0x2e, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x2d, 0x3a, 0x01, + 0x2a, 0x62, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x22, 0x22, 0x2f, 0x76, 0x31, 0x2f, 0x72, 0x6f, 0x6c, + 0x65, 0x73, 0x2f, 0x7b, 0x69, 0x64, 0x7d, 0x3a, 0x72, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x2d, 0x67, + 0x72, 0x61, 0x6e, 0x74, 0x2d, 0x73, 0x63, 0x6f, 0x70, 0x65, 0x73, 0x42, 0x4d, 0x5a, 0x4b, 0x67, + 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, + 0x6f, 0x72, 0x70, 0x2f, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x61, 0x72, 0x79, 0x2f, 0x69, 0x6e, 0x74, + 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2f, 0x67, 0x65, 0x6e, 0x2f, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, + 0x6c, 0x6c, 0x65, 0x72, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, + 0x73, 0x3b, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, + 0x6f, 0x33, } var ( @@ -1599,74 +2020,89 @@ func file_controller_api_services_v1_role_service_proto_rawDescGZIP() []byte { return file_controller_api_services_v1_role_service_proto_rawDescData } -var file_controller_api_services_v1_role_service_proto_msgTypes = make([]protoimpl.MessageInfo, 22) +var file_controller_api_services_v1_role_service_proto_msgTypes = make([]protoimpl.MessageInfo, 28) var file_controller_api_services_v1_role_service_proto_goTypes = []interface{}{ - (*GetRoleRequest)(nil), // 0: controller.api.services.v1.GetRoleRequest - (*GetRoleResponse)(nil), // 1: controller.api.services.v1.GetRoleResponse - (*ListRolesRequest)(nil), // 2: controller.api.services.v1.ListRolesRequest - (*ListRolesResponse)(nil), // 3: controller.api.services.v1.ListRolesResponse - (*CreateRoleRequest)(nil), // 4: controller.api.services.v1.CreateRoleRequest - (*CreateRoleResponse)(nil), // 5: controller.api.services.v1.CreateRoleResponse - (*UpdateRoleRequest)(nil), // 6: controller.api.services.v1.UpdateRoleRequest - (*UpdateRoleResponse)(nil), // 7: controller.api.services.v1.UpdateRoleResponse - (*DeleteRoleRequest)(nil), // 8: controller.api.services.v1.DeleteRoleRequest - (*DeleteRoleResponse)(nil), // 9: controller.api.services.v1.DeleteRoleResponse - (*AddRolePrincipalsRequest)(nil), // 10: controller.api.services.v1.AddRolePrincipalsRequest - (*AddRolePrincipalsResponse)(nil), // 11: controller.api.services.v1.AddRolePrincipalsResponse - (*SetRolePrincipalsRequest)(nil), // 12: controller.api.services.v1.SetRolePrincipalsRequest - (*SetRolePrincipalsResponse)(nil), // 13: controller.api.services.v1.SetRolePrincipalsResponse - (*RemoveRolePrincipalsRequest)(nil), // 14: controller.api.services.v1.RemoveRolePrincipalsRequest - (*RemoveRolePrincipalsResponse)(nil), // 15: controller.api.services.v1.RemoveRolePrincipalsResponse - (*AddRoleGrantsRequest)(nil), // 16: controller.api.services.v1.AddRoleGrantsRequest - (*AddRoleGrantsResponse)(nil), // 17: controller.api.services.v1.AddRoleGrantsResponse - (*SetRoleGrantsRequest)(nil), // 18: controller.api.services.v1.SetRoleGrantsRequest - (*SetRoleGrantsResponse)(nil), // 19: controller.api.services.v1.SetRoleGrantsResponse - (*RemoveRoleGrantsRequest)(nil), // 20: controller.api.services.v1.RemoveRoleGrantsRequest - (*RemoveRoleGrantsResponse)(nil), // 21: controller.api.services.v1.RemoveRoleGrantsResponse - (*roles.Role)(nil), // 22: controller.api.resources.roles.v1.Role - (*fieldmaskpb.FieldMask)(nil), // 23: google.protobuf.FieldMask + (*GetRoleRequest)(nil), // 0: controller.api.services.v1.GetRoleRequest + (*GetRoleResponse)(nil), // 1: controller.api.services.v1.GetRoleResponse + (*ListRolesRequest)(nil), // 2: controller.api.services.v1.ListRolesRequest + (*ListRolesResponse)(nil), // 3: controller.api.services.v1.ListRolesResponse + (*CreateRoleRequest)(nil), // 4: controller.api.services.v1.CreateRoleRequest + (*CreateRoleResponse)(nil), // 5: controller.api.services.v1.CreateRoleResponse + (*UpdateRoleRequest)(nil), // 6: controller.api.services.v1.UpdateRoleRequest + (*UpdateRoleResponse)(nil), // 7: controller.api.services.v1.UpdateRoleResponse + (*DeleteRoleRequest)(nil), // 8: controller.api.services.v1.DeleteRoleRequest + (*DeleteRoleResponse)(nil), // 9: controller.api.services.v1.DeleteRoleResponse + (*AddRolePrincipalsRequest)(nil), // 10: controller.api.services.v1.AddRolePrincipalsRequest + (*AddRolePrincipalsResponse)(nil), // 11: controller.api.services.v1.AddRolePrincipalsResponse + (*SetRolePrincipalsRequest)(nil), // 12: controller.api.services.v1.SetRolePrincipalsRequest + (*SetRolePrincipalsResponse)(nil), // 13: controller.api.services.v1.SetRolePrincipalsResponse + (*RemoveRolePrincipalsRequest)(nil), // 14: controller.api.services.v1.RemoveRolePrincipalsRequest + (*RemoveRolePrincipalsResponse)(nil), // 15: controller.api.services.v1.RemoveRolePrincipalsResponse + (*AddRoleGrantsRequest)(nil), // 16: controller.api.services.v1.AddRoleGrantsRequest + (*AddRoleGrantsResponse)(nil), // 17: controller.api.services.v1.AddRoleGrantsResponse + (*SetRoleGrantsRequest)(nil), // 18: controller.api.services.v1.SetRoleGrantsRequest + (*SetRoleGrantsResponse)(nil), // 19: controller.api.services.v1.SetRoleGrantsResponse + (*RemoveRoleGrantsRequest)(nil), // 20: controller.api.services.v1.RemoveRoleGrantsRequest + (*RemoveRoleGrantsResponse)(nil), // 21: controller.api.services.v1.RemoveRoleGrantsResponse + (*AddRoleGrantScopesRequest)(nil), // 22: controller.api.services.v1.AddRoleGrantScopesRequest + (*AddRoleGrantScopesResponse)(nil), // 23: controller.api.services.v1.AddRoleGrantScopesResponse + (*SetRoleGrantScopesRequest)(nil), // 24: controller.api.services.v1.SetRoleGrantScopesRequest + (*SetRoleGrantScopesResponse)(nil), // 25: controller.api.services.v1.SetRoleGrantScopesResponse + (*RemoveRoleGrantScopesRequest)(nil), // 26: controller.api.services.v1.RemoveRoleGrantScopesRequest + (*RemoveRoleGrantScopesResponse)(nil), // 27: controller.api.services.v1.RemoveRoleGrantScopesResponse + (*roles.Role)(nil), // 28: controller.api.resources.roles.v1.Role + (*fieldmaskpb.FieldMask)(nil), // 29: google.protobuf.FieldMask } var file_controller_api_services_v1_role_service_proto_depIdxs = []int32{ - 22, // 0: controller.api.services.v1.GetRoleResponse.item:type_name -> controller.api.resources.roles.v1.Role - 22, // 1: controller.api.services.v1.ListRolesResponse.items:type_name -> controller.api.resources.roles.v1.Role - 22, // 2: controller.api.services.v1.CreateRoleRequest.item:type_name -> controller.api.resources.roles.v1.Role - 22, // 3: controller.api.services.v1.CreateRoleResponse.item:type_name -> controller.api.resources.roles.v1.Role - 22, // 4: controller.api.services.v1.UpdateRoleRequest.item:type_name -> controller.api.resources.roles.v1.Role - 23, // 5: controller.api.services.v1.UpdateRoleRequest.update_mask:type_name -> google.protobuf.FieldMask - 22, // 6: controller.api.services.v1.UpdateRoleResponse.item:type_name -> controller.api.resources.roles.v1.Role - 22, // 7: controller.api.services.v1.AddRolePrincipalsResponse.item:type_name -> controller.api.resources.roles.v1.Role - 22, // 8: controller.api.services.v1.SetRolePrincipalsResponse.item:type_name -> controller.api.resources.roles.v1.Role - 22, // 9: controller.api.services.v1.RemoveRolePrincipalsResponse.item:type_name -> controller.api.resources.roles.v1.Role - 22, // 10: controller.api.services.v1.AddRoleGrantsResponse.item:type_name -> controller.api.resources.roles.v1.Role - 22, // 11: controller.api.services.v1.SetRoleGrantsResponse.item:type_name -> controller.api.resources.roles.v1.Role - 22, // 12: controller.api.services.v1.RemoveRoleGrantsResponse.item:type_name -> controller.api.resources.roles.v1.Role - 0, // 13: controller.api.services.v1.RoleService.GetRole:input_type -> controller.api.services.v1.GetRoleRequest - 2, // 14: controller.api.services.v1.RoleService.ListRoles:input_type -> controller.api.services.v1.ListRolesRequest - 4, // 15: controller.api.services.v1.RoleService.CreateRole:input_type -> controller.api.services.v1.CreateRoleRequest - 6, // 16: controller.api.services.v1.RoleService.UpdateRole:input_type -> controller.api.services.v1.UpdateRoleRequest - 8, // 17: controller.api.services.v1.RoleService.DeleteRole:input_type -> controller.api.services.v1.DeleteRoleRequest - 10, // 18: controller.api.services.v1.RoleService.AddRolePrincipals:input_type -> controller.api.services.v1.AddRolePrincipalsRequest - 12, // 19: controller.api.services.v1.RoleService.SetRolePrincipals:input_type -> controller.api.services.v1.SetRolePrincipalsRequest - 14, // 20: controller.api.services.v1.RoleService.RemoveRolePrincipals:input_type -> controller.api.services.v1.RemoveRolePrincipalsRequest - 16, // 21: controller.api.services.v1.RoleService.AddRoleGrants:input_type -> controller.api.services.v1.AddRoleGrantsRequest - 18, // 22: controller.api.services.v1.RoleService.SetRoleGrants:input_type -> controller.api.services.v1.SetRoleGrantsRequest - 20, // 23: controller.api.services.v1.RoleService.RemoveRoleGrants:input_type -> controller.api.services.v1.RemoveRoleGrantsRequest - 1, // 24: controller.api.services.v1.RoleService.GetRole:output_type -> controller.api.services.v1.GetRoleResponse - 3, // 25: controller.api.services.v1.RoleService.ListRoles:output_type -> controller.api.services.v1.ListRolesResponse - 5, // 26: controller.api.services.v1.RoleService.CreateRole:output_type -> controller.api.services.v1.CreateRoleResponse - 7, // 27: controller.api.services.v1.RoleService.UpdateRole:output_type -> controller.api.services.v1.UpdateRoleResponse - 9, // 28: controller.api.services.v1.RoleService.DeleteRole:output_type -> controller.api.services.v1.DeleteRoleResponse - 11, // 29: controller.api.services.v1.RoleService.AddRolePrincipals:output_type -> controller.api.services.v1.AddRolePrincipalsResponse - 13, // 30: controller.api.services.v1.RoleService.SetRolePrincipals:output_type -> controller.api.services.v1.SetRolePrincipalsResponse - 15, // 31: controller.api.services.v1.RoleService.RemoveRolePrincipals:output_type -> controller.api.services.v1.RemoveRolePrincipalsResponse - 17, // 32: controller.api.services.v1.RoleService.AddRoleGrants:output_type -> controller.api.services.v1.AddRoleGrantsResponse - 19, // 33: controller.api.services.v1.RoleService.SetRoleGrants:output_type -> controller.api.services.v1.SetRoleGrantsResponse - 21, // 34: controller.api.services.v1.RoleService.RemoveRoleGrants:output_type -> controller.api.services.v1.RemoveRoleGrantsResponse - 24, // [24:35] is the sub-list for method output_type - 13, // [13:24] is the sub-list for method input_type - 13, // [13:13] is the sub-list for extension type_name - 13, // [13:13] is the sub-list for extension extendee - 0, // [0:13] is the sub-list for field type_name + 28, // 0: controller.api.services.v1.GetRoleResponse.item:type_name -> controller.api.resources.roles.v1.Role + 28, // 1: controller.api.services.v1.ListRolesResponse.items:type_name -> controller.api.resources.roles.v1.Role + 28, // 2: controller.api.services.v1.CreateRoleRequest.item:type_name -> controller.api.resources.roles.v1.Role + 28, // 3: controller.api.services.v1.CreateRoleResponse.item:type_name -> controller.api.resources.roles.v1.Role + 28, // 4: controller.api.services.v1.UpdateRoleRequest.item:type_name -> controller.api.resources.roles.v1.Role + 29, // 5: controller.api.services.v1.UpdateRoleRequest.update_mask:type_name -> google.protobuf.FieldMask + 28, // 6: controller.api.services.v1.UpdateRoleResponse.item:type_name -> controller.api.resources.roles.v1.Role + 28, // 7: controller.api.services.v1.AddRolePrincipalsResponse.item:type_name -> controller.api.resources.roles.v1.Role + 28, // 8: controller.api.services.v1.SetRolePrincipalsResponse.item:type_name -> controller.api.resources.roles.v1.Role + 28, // 9: controller.api.services.v1.RemoveRolePrincipalsResponse.item:type_name -> controller.api.resources.roles.v1.Role + 28, // 10: controller.api.services.v1.AddRoleGrantsResponse.item:type_name -> controller.api.resources.roles.v1.Role + 28, // 11: controller.api.services.v1.SetRoleGrantsResponse.item:type_name -> controller.api.resources.roles.v1.Role + 28, // 12: controller.api.services.v1.RemoveRoleGrantsResponse.item:type_name -> controller.api.resources.roles.v1.Role + 28, // 13: controller.api.services.v1.AddRoleGrantScopesResponse.item:type_name -> controller.api.resources.roles.v1.Role + 28, // 14: controller.api.services.v1.SetRoleGrantScopesResponse.item:type_name -> controller.api.resources.roles.v1.Role + 28, // 15: controller.api.services.v1.RemoveRoleGrantScopesResponse.item:type_name -> controller.api.resources.roles.v1.Role + 0, // 16: controller.api.services.v1.RoleService.GetRole:input_type -> controller.api.services.v1.GetRoleRequest + 2, // 17: controller.api.services.v1.RoleService.ListRoles:input_type -> controller.api.services.v1.ListRolesRequest + 4, // 18: controller.api.services.v1.RoleService.CreateRole:input_type -> controller.api.services.v1.CreateRoleRequest + 6, // 19: controller.api.services.v1.RoleService.UpdateRole:input_type -> controller.api.services.v1.UpdateRoleRequest + 8, // 20: controller.api.services.v1.RoleService.DeleteRole:input_type -> controller.api.services.v1.DeleteRoleRequest + 10, // 21: controller.api.services.v1.RoleService.AddRolePrincipals:input_type -> controller.api.services.v1.AddRolePrincipalsRequest + 12, // 22: controller.api.services.v1.RoleService.SetRolePrincipals:input_type -> controller.api.services.v1.SetRolePrincipalsRequest + 14, // 23: controller.api.services.v1.RoleService.RemoveRolePrincipals:input_type -> controller.api.services.v1.RemoveRolePrincipalsRequest + 16, // 24: controller.api.services.v1.RoleService.AddRoleGrants:input_type -> controller.api.services.v1.AddRoleGrantsRequest + 18, // 25: controller.api.services.v1.RoleService.SetRoleGrants:input_type -> controller.api.services.v1.SetRoleGrantsRequest + 20, // 26: controller.api.services.v1.RoleService.RemoveRoleGrants:input_type -> controller.api.services.v1.RemoveRoleGrantsRequest + 22, // 27: controller.api.services.v1.RoleService.AddRoleGrantScopes:input_type -> controller.api.services.v1.AddRoleGrantScopesRequest + 24, // 28: controller.api.services.v1.RoleService.SetRoleGrantScopes:input_type -> controller.api.services.v1.SetRoleGrantScopesRequest + 26, // 29: controller.api.services.v1.RoleService.RemoveRoleGrantScopes:input_type -> controller.api.services.v1.RemoveRoleGrantScopesRequest + 1, // 30: controller.api.services.v1.RoleService.GetRole:output_type -> controller.api.services.v1.GetRoleResponse + 3, // 31: controller.api.services.v1.RoleService.ListRoles:output_type -> controller.api.services.v1.ListRolesResponse + 5, // 32: controller.api.services.v1.RoleService.CreateRole:output_type -> controller.api.services.v1.CreateRoleResponse + 7, // 33: controller.api.services.v1.RoleService.UpdateRole:output_type -> controller.api.services.v1.UpdateRoleResponse + 9, // 34: controller.api.services.v1.RoleService.DeleteRole:output_type -> controller.api.services.v1.DeleteRoleResponse + 11, // 35: controller.api.services.v1.RoleService.AddRolePrincipals:output_type -> controller.api.services.v1.AddRolePrincipalsResponse + 13, // 36: controller.api.services.v1.RoleService.SetRolePrincipals:output_type -> controller.api.services.v1.SetRolePrincipalsResponse + 15, // 37: controller.api.services.v1.RoleService.RemoveRolePrincipals:output_type -> controller.api.services.v1.RemoveRolePrincipalsResponse + 17, // 38: controller.api.services.v1.RoleService.AddRoleGrants:output_type -> controller.api.services.v1.AddRoleGrantsResponse + 19, // 39: controller.api.services.v1.RoleService.SetRoleGrants:output_type -> controller.api.services.v1.SetRoleGrantsResponse + 21, // 40: controller.api.services.v1.RoleService.RemoveRoleGrants:output_type -> controller.api.services.v1.RemoveRoleGrantsResponse + 23, // 41: controller.api.services.v1.RoleService.AddRoleGrantScopes:output_type -> controller.api.services.v1.AddRoleGrantScopesResponse + 25, // 42: controller.api.services.v1.RoleService.SetRoleGrantScopes:output_type -> controller.api.services.v1.SetRoleGrantScopesResponse + 27, // 43: controller.api.services.v1.RoleService.RemoveRoleGrantScopes:output_type -> controller.api.services.v1.RemoveRoleGrantScopesResponse + 30, // [30:44] is the sub-list for method output_type + 16, // [16:30] is the sub-list for method input_type + 16, // [16:16] is the sub-list for extension type_name + 16, // [16:16] is the sub-list for extension extendee + 0, // [0:16] is the sub-list for field type_name } func init() { file_controller_api_services_v1_role_service_proto_init() } @@ -1939,6 +2375,78 @@ func file_controller_api_services_v1_role_service_proto_init() { return nil } } + file_controller_api_services_v1_role_service_proto_msgTypes[22].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*AddRoleGrantScopesRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_controller_api_services_v1_role_service_proto_msgTypes[23].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*AddRoleGrantScopesResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_controller_api_services_v1_role_service_proto_msgTypes[24].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*SetRoleGrantScopesRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_controller_api_services_v1_role_service_proto_msgTypes[25].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*SetRoleGrantScopesResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_controller_api_services_v1_role_service_proto_msgTypes[26].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*RemoveRoleGrantScopesRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_controller_api_services_v1_role_service_proto_msgTypes[27].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*RemoveRoleGrantScopesResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } } type x struct{} out := protoimpl.TypeBuilder{ @@ -1946,7 +2454,7 @@ func file_controller_api_services_v1_role_service_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_controller_api_services_v1_role_service_proto_rawDesc, NumEnums: 0, - NumMessages: 22, + NumMessages: 28, NumExtensions: 0, NumServices: 1, }, diff --git a/internal/gen/controller/api/services/role_service.pb.gw.go b/internal/gen/controller/api/services/role_service.pb.gw.go index dca4e8dc27..8430796dd0 100644 --- a/internal/gen/controller/api/services/role_service.pb.gw.go +++ b/internal/gen/controller/api/services/role_service.pb.gw.go @@ -713,6 +713,210 @@ func local_request_RoleService_RemoveRoleGrants_0(ctx context.Context, marshaler } +func request_RoleService_AddRoleGrantScopes_0(ctx context.Context, marshaler runtime.Marshaler, client RoleServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq AddRoleGrantScopesRequest + var metadata runtime.ServerMetadata + + newReader, berr := utilities.IOReaderFactory(req.Body) + if berr != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr) + } + if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + var ( + val string + ok bool + err error + _ = err + ) + + val, ok = pathParams["id"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "id") + } + + protoReq.Id, err = runtime.String(val) + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "id", err) + } + + msg, err := client.AddRoleGrantScopes(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func local_request_RoleService_AddRoleGrantScopes_0(ctx context.Context, marshaler runtime.Marshaler, server RoleServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq AddRoleGrantScopesRequest + var metadata runtime.ServerMetadata + + newReader, berr := utilities.IOReaderFactory(req.Body) + if berr != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr) + } + if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + var ( + val string + ok bool + err error + _ = err + ) + + val, ok = pathParams["id"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "id") + } + + protoReq.Id, err = runtime.String(val) + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "id", err) + } + + msg, err := server.AddRoleGrantScopes(ctx, &protoReq) + return msg, metadata, err + +} + +func request_RoleService_SetRoleGrantScopes_0(ctx context.Context, marshaler runtime.Marshaler, client RoleServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq SetRoleGrantScopesRequest + var metadata runtime.ServerMetadata + + newReader, berr := utilities.IOReaderFactory(req.Body) + if berr != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr) + } + if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + var ( + val string + ok bool + err error + _ = err + ) + + val, ok = pathParams["id"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "id") + } + + protoReq.Id, err = runtime.String(val) + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "id", err) + } + + msg, err := client.SetRoleGrantScopes(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func local_request_RoleService_SetRoleGrantScopes_0(ctx context.Context, marshaler runtime.Marshaler, server RoleServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq SetRoleGrantScopesRequest + var metadata runtime.ServerMetadata + + newReader, berr := utilities.IOReaderFactory(req.Body) + if berr != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr) + } + if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + var ( + val string + ok bool + err error + _ = err + ) + + val, ok = pathParams["id"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "id") + } + + protoReq.Id, err = runtime.String(val) + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "id", err) + } + + msg, err := server.SetRoleGrantScopes(ctx, &protoReq) + return msg, metadata, err + +} + +func request_RoleService_RemoveRoleGrantScopes_0(ctx context.Context, marshaler runtime.Marshaler, client RoleServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq RemoveRoleGrantScopesRequest + var metadata runtime.ServerMetadata + + newReader, berr := utilities.IOReaderFactory(req.Body) + if berr != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr) + } + if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + var ( + val string + ok bool + err error + _ = err + ) + + val, ok = pathParams["id"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "id") + } + + protoReq.Id, err = runtime.String(val) + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "id", err) + } + + msg, err := client.RemoveRoleGrantScopes(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func local_request_RoleService_RemoveRoleGrantScopes_0(ctx context.Context, marshaler runtime.Marshaler, server RoleServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq RemoveRoleGrantScopesRequest + var metadata runtime.ServerMetadata + + newReader, berr := utilities.IOReaderFactory(req.Body) + if berr != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr) + } + if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + var ( + val string + ok bool + err error + _ = err + ) + + val, ok = pathParams["id"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "id") + } + + protoReq.Id, err = runtime.String(val) + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "id", err) + } + + msg, err := server.RemoveRoleGrantScopes(ctx, &protoReq) + return msg, metadata, err + +} + // RegisterRoleServiceHandlerServer registers the http handlers for service RoleService to "mux". // UnaryRPC :call RoleServiceServer directly. // StreamingRPC :currently unsupported pending https://github.com/grpc/grpc-go/issues/906. @@ -994,6 +1198,81 @@ func RegisterRoleServiceHandlerServer(ctx context.Context, mux *runtime.ServeMux }) + mux.Handle("POST", pattern_RoleService_AddRoleGrantScopes_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + var stream runtime.ServerTransportStream + ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + var err error + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/controller.api.services.v1.RoleService/AddRoleGrantScopes", runtime.WithHTTPPathPattern("/v1/roles/{id}:add-grant-scopes")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_RoleService_AddRoleGrantScopes_0(annotatedContext, inboundMarshaler, server, req, pathParams) + md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) + if err != nil { + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) + return + } + + forward_RoleService_AddRoleGrantScopes_0(annotatedContext, mux, outboundMarshaler, w, req, response_RoleService_AddRoleGrantScopes_0{resp}, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("POST", pattern_RoleService_SetRoleGrantScopes_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + var stream runtime.ServerTransportStream + ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + var err error + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/controller.api.services.v1.RoleService/SetRoleGrantScopes", runtime.WithHTTPPathPattern("/v1/roles/{id}:set-grant-scopes")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_RoleService_SetRoleGrantScopes_0(annotatedContext, inboundMarshaler, server, req, pathParams) + md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) + if err != nil { + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) + return + } + + forward_RoleService_SetRoleGrantScopes_0(annotatedContext, mux, outboundMarshaler, w, req, response_RoleService_SetRoleGrantScopes_0{resp}, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("POST", pattern_RoleService_RemoveRoleGrantScopes_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + var stream runtime.ServerTransportStream + ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + var err error + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/controller.api.services.v1.RoleService/RemoveRoleGrantScopes", runtime.WithHTTPPathPattern("/v1/roles/{id}:remove-grant-scopes")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_RoleService_RemoveRoleGrantScopes_0(annotatedContext, inboundMarshaler, server, req, pathParams) + md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) + if err != nil { + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) + return + } + + forward_RoleService_RemoveRoleGrantScopes_0(annotatedContext, mux, outboundMarshaler, w, req, response_RoleService_RemoveRoleGrantScopes_0{resp}, mux.GetForwardResponseOptions()...) + + }) + return nil } @@ -1277,6 +1556,72 @@ func RegisterRoleServiceHandlerClient(ctx context.Context, mux *runtime.ServeMux }) + mux.Handle("POST", pattern_RoleService_AddRoleGrantScopes_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + var err error + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/controller.api.services.v1.RoleService/AddRoleGrantScopes", runtime.WithHTTPPathPattern("/v1/roles/{id}:add-grant-scopes")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_RoleService_AddRoleGrantScopes_0(annotatedContext, inboundMarshaler, client, req, pathParams) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) + if err != nil { + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) + return + } + + forward_RoleService_AddRoleGrantScopes_0(annotatedContext, mux, outboundMarshaler, w, req, response_RoleService_AddRoleGrantScopes_0{resp}, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("POST", pattern_RoleService_SetRoleGrantScopes_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + var err error + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/controller.api.services.v1.RoleService/SetRoleGrantScopes", runtime.WithHTTPPathPattern("/v1/roles/{id}:set-grant-scopes")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_RoleService_SetRoleGrantScopes_0(annotatedContext, inboundMarshaler, client, req, pathParams) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) + if err != nil { + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) + return + } + + forward_RoleService_SetRoleGrantScopes_0(annotatedContext, mux, outboundMarshaler, w, req, response_RoleService_SetRoleGrantScopes_0{resp}, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("POST", pattern_RoleService_RemoveRoleGrantScopes_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + var err error + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/controller.api.services.v1.RoleService/RemoveRoleGrantScopes", runtime.WithHTTPPathPattern("/v1/roles/{id}:remove-grant-scopes")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_RoleService_RemoveRoleGrantScopes_0(annotatedContext, inboundMarshaler, client, req, pathParams) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) + if err != nil { + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) + return + } + + forward_RoleService_RemoveRoleGrantScopes_0(annotatedContext, mux, outboundMarshaler, w, req, response_RoleService_RemoveRoleGrantScopes_0{resp}, mux.GetForwardResponseOptions()...) + + }) + return nil } @@ -1361,6 +1706,33 @@ func (m response_RoleService_RemoveRoleGrants_0) XXX_ResponseBody() interface{} return response.Item } +type response_RoleService_AddRoleGrantScopes_0 struct { + proto.Message +} + +func (m response_RoleService_AddRoleGrantScopes_0) XXX_ResponseBody() interface{} { + response := m.Message.(*AddRoleGrantScopesResponse) + return response.Item +} + +type response_RoleService_SetRoleGrantScopes_0 struct { + proto.Message +} + +func (m response_RoleService_SetRoleGrantScopes_0) XXX_ResponseBody() interface{} { + response := m.Message.(*SetRoleGrantScopesResponse) + return response.Item +} + +type response_RoleService_RemoveRoleGrantScopes_0 struct { + proto.Message +} + +func (m response_RoleService_RemoveRoleGrantScopes_0) XXX_ResponseBody() interface{} { + response := m.Message.(*RemoveRoleGrantScopesResponse) + return response.Item +} + var ( pattern_RoleService_GetRole_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 1, 0, 4, 1, 5, 2}, []string{"v1", "roles", "id"}, "")) @@ -1383,6 +1755,12 @@ var ( pattern_RoleService_SetRoleGrants_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 1, 0, 4, 1, 5, 2}, []string{"v1", "roles", "id"}, "set-grants")) pattern_RoleService_RemoveRoleGrants_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 1, 0, 4, 1, 5, 2}, []string{"v1", "roles", "id"}, "remove-grants")) + + pattern_RoleService_AddRoleGrantScopes_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 1, 0, 4, 1, 5, 2}, []string{"v1", "roles", "id"}, "add-grant-scopes")) + + pattern_RoleService_SetRoleGrantScopes_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 1, 0, 4, 1, 5, 2}, []string{"v1", "roles", "id"}, "set-grant-scopes")) + + pattern_RoleService_RemoveRoleGrantScopes_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 1, 0, 4, 1, 5, 2}, []string{"v1", "roles", "id"}, "remove-grant-scopes")) ) var ( @@ -1407,4 +1785,10 @@ var ( forward_RoleService_SetRoleGrants_0 = runtime.ForwardResponseMessage forward_RoleService_RemoveRoleGrants_0 = runtime.ForwardResponseMessage + + forward_RoleService_AddRoleGrantScopes_0 = runtime.ForwardResponseMessage + + forward_RoleService_SetRoleGrantScopes_0 = runtime.ForwardResponseMessage + + forward_RoleService_RemoveRoleGrantScopes_0 = runtime.ForwardResponseMessage ) diff --git a/internal/gen/controller/api/services/role_service_grpc.pb.go b/internal/gen/controller/api/services/role_service_grpc.pb.go index 3b357962c3..3b42f11ea2 100644 --- a/internal/gen/controller/api/services/role_service_grpc.pb.go +++ b/internal/gen/controller/api/services/role_service_grpc.pb.go @@ -22,17 +22,20 @@ import ( const _ = grpc.SupportPackageIsVersion7 const ( - RoleService_GetRole_FullMethodName = "/controller.api.services.v1.RoleService/GetRole" - RoleService_ListRoles_FullMethodName = "/controller.api.services.v1.RoleService/ListRoles" - RoleService_CreateRole_FullMethodName = "/controller.api.services.v1.RoleService/CreateRole" - RoleService_UpdateRole_FullMethodName = "/controller.api.services.v1.RoleService/UpdateRole" - RoleService_DeleteRole_FullMethodName = "/controller.api.services.v1.RoleService/DeleteRole" - RoleService_AddRolePrincipals_FullMethodName = "/controller.api.services.v1.RoleService/AddRolePrincipals" - RoleService_SetRolePrincipals_FullMethodName = "/controller.api.services.v1.RoleService/SetRolePrincipals" - RoleService_RemoveRolePrincipals_FullMethodName = "/controller.api.services.v1.RoleService/RemoveRolePrincipals" - RoleService_AddRoleGrants_FullMethodName = "/controller.api.services.v1.RoleService/AddRoleGrants" - RoleService_SetRoleGrants_FullMethodName = "/controller.api.services.v1.RoleService/SetRoleGrants" - RoleService_RemoveRoleGrants_FullMethodName = "/controller.api.services.v1.RoleService/RemoveRoleGrants" + RoleService_GetRole_FullMethodName = "/controller.api.services.v1.RoleService/GetRole" + RoleService_ListRoles_FullMethodName = "/controller.api.services.v1.RoleService/ListRoles" + RoleService_CreateRole_FullMethodName = "/controller.api.services.v1.RoleService/CreateRole" + RoleService_UpdateRole_FullMethodName = "/controller.api.services.v1.RoleService/UpdateRole" + RoleService_DeleteRole_FullMethodName = "/controller.api.services.v1.RoleService/DeleteRole" + RoleService_AddRolePrincipals_FullMethodName = "/controller.api.services.v1.RoleService/AddRolePrincipals" + RoleService_SetRolePrincipals_FullMethodName = "/controller.api.services.v1.RoleService/SetRolePrincipals" + RoleService_RemoveRolePrincipals_FullMethodName = "/controller.api.services.v1.RoleService/RemoveRolePrincipals" + RoleService_AddRoleGrants_FullMethodName = "/controller.api.services.v1.RoleService/AddRoleGrants" + RoleService_SetRoleGrants_FullMethodName = "/controller.api.services.v1.RoleService/SetRoleGrants" + RoleService_RemoveRoleGrants_FullMethodName = "/controller.api.services.v1.RoleService/RemoveRoleGrants" + RoleService_AddRoleGrantScopes_FullMethodName = "/controller.api.services.v1.RoleService/AddRoleGrantScopes" + RoleService_SetRoleGrantScopes_FullMethodName = "/controller.api.services.v1.RoleService/SetRoleGrantScopes" + RoleService_RemoveRoleGrantScopes_FullMethodName = "/controller.api.services.v1.RoleService/RemoveRoleGrantScopes" ) // RoleServiceClient is the client API for RoleService service. @@ -100,6 +103,22 @@ type RoleServiceClient interface { // grants will be removed. If missing, malformed, or references a non-existing // resource, an error is returned. RemoveRoleGrants(ctx context.Context, in *RemoveRoleGrantsRequest, opts ...grpc.CallOption) (*RemoveRoleGrantsResponse, error) + // AddRoleGrantScopes adds grants scopes to a Role. The provided request must + // include the Role id which the grant scopes will be added to. An error is + // returned if the provided id is malformed or references a non-existing + // resource. + AddRoleGrantScopes(ctx context.Context, in *AddRoleGrantScopesRequest, opts ...grpc.CallOption) (*AddRoleGrantScopesResponse, error) + // SetRoleGrants sets the Role's grant scopes. Any existing grant scopes on + // the Role are deleted if they are not included in this request. The provided + // request must include the Role ID on which the grants will be set. If + // missing, malformed, or referencing a non-existing resource, an error is + // returned. + SetRoleGrantScopes(ctx context.Context, in *SetRoleGrantScopesRequest, opts ...grpc.CallOption) (*SetRoleGrantScopesResponse, error) + // RemoveRoleGrantScopes removes the grant scopes from the specified Role. The + // provided request must include the Role IDs from which the grants will be + // removed. If missing, malformed, or references a non-existing resource, an + // error is returned. + RemoveRoleGrantScopes(ctx context.Context, in *RemoveRoleGrantScopesRequest, opts ...grpc.CallOption) (*RemoveRoleGrantScopesResponse, error) } type roleServiceClient struct { @@ -209,6 +228,33 @@ func (c *roleServiceClient) RemoveRoleGrants(ctx context.Context, in *RemoveRole return out, nil } +func (c *roleServiceClient) AddRoleGrantScopes(ctx context.Context, in *AddRoleGrantScopesRequest, opts ...grpc.CallOption) (*AddRoleGrantScopesResponse, error) { + out := new(AddRoleGrantScopesResponse) + err := c.cc.Invoke(ctx, RoleService_AddRoleGrantScopes_FullMethodName, in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *roleServiceClient) SetRoleGrantScopes(ctx context.Context, in *SetRoleGrantScopesRequest, opts ...grpc.CallOption) (*SetRoleGrantScopesResponse, error) { + out := new(SetRoleGrantScopesResponse) + err := c.cc.Invoke(ctx, RoleService_SetRoleGrantScopes_FullMethodName, in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *roleServiceClient) RemoveRoleGrantScopes(ctx context.Context, in *RemoveRoleGrantScopesRequest, opts ...grpc.CallOption) (*RemoveRoleGrantScopesResponse, error) { + out := new(RemoveRoleGrantScopesResponse) + err := c.cc.Invoke(ctx, RoleService_RemoveRoleGrantScopes_FullMethodName, in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + // RoleServiceServer is the server API for RoleService service. // All implementations must embed UnimplementedRoleServiceServer // for forward compatibility @@ -274,6 +320,22 @@ type RoleServiceServer interface { // grants will be removed. If missing, malformed, or references a non-existing // resource, an error is returned. RemoveRoleGrants(context.Context, *RemoveRoleGrantsRequest) (*RemoveRoleGrantsResponse, error) + // AddRoleGrantScopes adds grants scopes to a Role. The provided request must + // include the Role id which the grant scopes will be added to. An error is + // returned if the provided id is malformed or references a non-existing + // resource. + AddRoleGrantScopes(context.Context, *AddRoleGrantScopesRequest) (*AddRoleGrantScopesResponse, error) + // SetRoleGrants sets the Role's grant scopes. Any existing grant scopes on + // the Role are deleted if they are not included in this request. The provided + // request must include the Role ID on which the grants will be set. If + // missing, malformed, or referencing a non-existing resource, an error is + // returned. + SetRoleGrantScopes(context.Context, *SetRoleGrantScopesRequest) (*SetRoleGrantScopesResponse, error) + // RemoveRoleGrantScopes removes the grant scopes from the specified Role. The + // provided request must include the Role IDs from which the grants will be + // removed. If missing, malformed, or references a non-existing resource, an + // error is returned. + RemoveRoleGrantScopes(context.Context, *RemoveRoleGrantScopesRequest) (*RemoveRoleGrantScopesResponse, error) mustEmbedUnimplementedRoleServiceServer() } @@ -314,6 +376,15 @@ func (UnimplementedRoleServiceServer) SetRoleGrants(context.Context, *SetRoleGra func (UnimplementedRoleServiceServer) RemoveRoleGrants(context.Context, *RemoveRoleGrantsRequest) (*RemoveRoleGrantsResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method RemoveRoleGrants not implemented") } +func (UnimplementedRoleServiceServer) AddRoleGrantScopes(context.Context, *AddRoleGrantScopesRequest) (*AddRoleGrantScopesResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method AddRoleGrantScopes not implemented") +} +func (UnimplementedRoleServiceServer) SetRoleGrantScopes(context.Context, *SetRoleGrantScopesRequest) (*SetRoleGrantScopesResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method SetRoleGrantScopes not implemented") +} +func (UnimplementedRoleServiceServer) RemoveRoleGrantScopes(context.Context, *RemoveRoleGrantScopesRequest) (*RemoveRoleGrantScopesResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method RemoveRoleGrantScopes not implemented") +} func (UnimplementedRoleServiceServer) mustEmbedUnimplementedRoleServiceServer() {} // UnsafeRoleServiceServer may be embedded to opt out of forward compatibility for this service. @@ -525,6 +596,60 @@ func _RoleService_RemoveRoleGrants_Handler(srv interface{}, ctx context.Context, return interceptor(ctx, in, info, handler) } +func _RoleService_AddRoleGrantScopes_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(AddRoleGrantScopesRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(RoleServiceServer).AddRoleGrantScopes(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: RoleService_AddRoleGrantScopes_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(RoleServiceServer).AddRoleGrantScopes(ctx, req.(*AddRoleGrantScopesRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _RoleService_SetRoleGrantScopes_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(SetRoleGrantScopesRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(RoleServiceServer).SetRoleGrantScopes(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: RoleService_SetRoleGrantScopes_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(RoleServiceServer).SetRoleGrantScopes(ctx, req.(*SetRoleGrantScopesRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _RoleService_RemoveRoleGrantScopes_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(RemoveRoleGrantScopesRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(RoleServiceServer).RemoveRoleGrantScopes(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: RoleService_RemoveRoleGrantScopes_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(RoleServiceServer).RemoveRoleGrantScopes(ctx, req.(*RemoveRoleGrantScopesRequest)) + } + return interceptor(ctx, in, info, handler) +} + // RoleService_ServiceDesc is the grpc.ServiceDesc for RoleService service. // It's only intended for direct use with grpc.RegisterService, // and not to be introspected or modified (even as a copy) @@ -576,6 +701,18 @@ var RoleService_ServiceDesc = grpc.ServiceDesc{ MethodName: "RemoveRoleGrants", Handler: _RoleService_RemoveRoleGrants_Handler, }, + { + MethodName: "AddRoleGrantScopes", + Handler: _RoleService_AddRoleGrantScopes_Handler, + }, + { + MethodName: "SetRoleGrantScopes", + Handler: _RoleService_SetRoleGrantScopes_Handler, + }, + { + MethodName: "RemoveRoleGrantScopes", + Handler: _RoleService_RemoveRoleGrantScopes_Handler, + }, }, Streams: []grpc.StreamDesc{}, Metadata: "controller/api/services/v1/role_service.proto", diff --git a/internal/iam/group.go b/internal/iam/group.go index 9b0d3b74ea..29f9055be8 100644 --- a/internal/iam/group.go +++ b/internal/iam/group.go @@ -78,7 +78,7 @@ func (g *Group) VetForWrite(ctx context.Context, r db.Reader, opType db.OpType, return nil } -func (u *Group) validScopeTypes() []scope.Type { +func (g *Group) validScopeTypes() []scope.Type { return []scope.Type{scope.Global, scope.Org, scope.Project} } diff --git a/internal/iam/options.go b/internal/iam/options.go index ec5375add2..3a9dc1eca6 100644 --- a/internal/iam/options.go +++ b/internal/iam/options.go @@ -6,6 +6,7 @@ package iam import ( "io" + "github.com/hashicorp/boundary/internal/db" "github.com/hashicorp/boundary/internal/pagination" ) @@ -13,7 +14,9 @@ import ( func getOpts(opt ...Option) options { opts := getDefaultOptions() for _, o := range opt { - o(&opts) + if o != nil { + o(&opts) + } } return opts } @@ -27,7 +30,8 @@ type options struct { withName string withDescription string withLimit int - withGrantScopeId string + withGrantScopeId *string + withGrantScopeIds []string withSkipVetForWrite bool withDisassociate bool withSkipAdminRoleCreation bool @@ -36,6 +40,8 @@ type options struct { withRandomReader io.Reader withAccountIds []string withPrimaryAuthMethodId string + withReader db.Reader + withWriter db.Writer withStartPageAfterItem pagination.Item } @@ -45,7 +51,6 @@ func getDefaultOptions() options { withName: "", withDescription: "", withLimit: 0, - withGrantScopeId: "", withSkipVetForWrite: false, } } @@ -84,7 +89,20 @@ func WithLimit(limit int) Option { // roles. func WithGrantScopeId(id string) Option { return func(o *options) { - o.withGrantScopeId = id + if o.withGrantScopeId == nil { + o.withGrantScopeId = new(string) + } + *o.withGrantScopeId = id + } +} + +// WithGrantScopeIds provides an option to specify the scope ID for grants in +// roles. In most tests this is likely the option to use, however, for tests +// that call repo functions instead of test functions the other option is still +// correct to specify a grant scope at creation time. +func WithGrantScopeIds(ids []string) Option { + return func(o *options) { + o.withGrantScopeIds = ids } } @@ -150,6 +168,18 @@ func WithPrimaryAuthMethodId(id string) Option { } } +// WithReaderWriter allows the caller to pass an inflight transaction to be used +// for all database operations. If WithReaderWriter(...) is used, then the +// caller is responsible for managing the transaction. The purpose of the +// WithReaderWriter(...) option is to allow the caller to create the scope and +// all of its keys in the same transaction. +func WithReaderWriter(r db.Reader, w db.Writer) Option { + return func(o *options) { + o.withReader = r + o.withWriter = w + } +} + // WithStartPageAfterItem is used to paginate over the results. // The next page will start after the provided item. func WithStartPageAfterItem(item pagination.Item) Option { diff --git a/internal/iam/options_test.go b/internal/iam/options_test.go index 7f6b692fc8..7c0abaaf5e 100644 --- a/internal/iam/options_test.go +++ b/internal/iam/options_test.go @@ -63,9 +63,11 @@ func Test_GetOpts(t *testing.T) { }) t.Run("WithGrantScopeId", func(t *testing.T) { assert := assert.New(t) - opts := getOpts(WithGrantScopeId("o_1234")) + str := "o_1234" + opts := getOpts(WithGrantScopeId(str)) testOpts := getDefaultOptions() - testOpts.withGrantScopeId = "o_1234" + + testOpts.withGrantScopeId = &str assert.Equal(opts, testOpts) }) t.Run("WithDisassociate", func(t *testing.T) { diff --git a/internal/iam/query.go b/internal/iam/query.go index 1b140d033b..c5f1dc5808 100644 --- a/internal/iam/query.go +++ b/internal/iam/query.go @@ -112,70 +112,131 @@ const ( order by action, member_id; ` - grantsQuery = ` - with - users (id) as ( - select public_id - from iam_user - %s -- anonUser || authUser - ), - user_groups (id) as ( - select group_id - from iam_group_member_user - where member_id in (select id from users) - ), - user_accounts (id) as ( - select public_id - from auth_account - where iam_user_id in (select id from users) - ), - user_managed_groups (id) as ( - select managed_group_id - from auth_managed_group_member_account - where member_id in (select id from user_accounts) - ), - managed_group_roles (role_id) as ( - select role_id - from iam_managed_group_role - where principal_id in (select id from user_managed_groups) - ), - group_roles (role_id) as ( - select role_id - from iam_group_role - where principal_id in (select id from user_groups) - ), - user_roles (role_id) as ( - select role_id - from iam_user_role - where principal_id in (select id from users) - ), - user_group_roles (role_id) as ( - select role_id - from group_roles - union - select role_id - from user_roles - union - select role_id - from managed_group_roles - ), - roles (role_id, grant_scope_id) as ( - select iam_role.public_id, - iam_role.grant_scope_id - from iam_role - where public_id in (select role_id from user_group_roles) - ), - final (role_id, role_scope, role_grant) as ( - select roles.role_id, - roles.grant_scope_id, - iam_role_grant.canonical_grant - from roles - inner join iam_role_grant - on roles.role_id = iam_role_grant.role_id - -- only retrieves roles with a grant! there can be roles that don't have grants (it's a valid state in boundary) - ) - select role_id as role_id, role_scope as scope_id, role_grant as grant from final; - ` + grantsForUserQuery = ` + with + users (id) as ( + select public_id + from iam_user + %s -- anonUser || authUser + ), + user_groups (id) as ( + select group_id + from iam_group_member_user + where member_id in (select id from users) + ), + user_accounts (id) as ( + select public_id + from auth_account + where iam_user_id in (select id from users) + ), + user_managed_groups (id) as ( + select managed_group_id + from auth_managed_group_member_account + where member_id in (select id from user_accounts) + ), + managed_group_roles (role_id) as ( + select role_id + from iam_managed_group_role + where principal_id in (select id from user_managed_groups) + ), + group_roles (role_id) as ( + select role_id + from iam_group_role + where principal_id in (select id from user_groups) + ), + user_roles (role_id) as ( + select role_id + from iam_user_role + where principal_id in (select id from users) + ), + user_group_roles (role_id) as ( + select role_id + from group_roles + union + select role_id + from user_roles + union + select role_id + from managed_group_roles + ), + roles (role_id, role_scope_id) as ( + select iam_role.public_id, + iam_role.scope_id + from iam_role + where public_id in (select role_id from user_group_roles) + ), + role_grant_scopes (role_id, role_scope_id, grant_scope_id) as ( + select roles.role_id, + roles.role_scope_id, + iam_role_grant_scope.scope_id_or_special + from roles + inner join iam_role_grant_scope + on roles.role_id = iam_role_grant_scope.role_id + ), + -- For all role_ids with a special scope_id of 'descendants', we want to + -- perform a cartesian product to pair the role_id with all non-global scopes. + descendant_grant_scopes (role_id, grant_scope_id) as ( + select role_grant_scopes.role_id as role_id, + iam_scope.public_id as grant_scope_id + from role_grant_scopes, + iam_scope + where iam_scope.public_id != 'global' + and role_grant_scopes.grant_scope_id = 'descendants' + ), + children_grant_scopes (role_id, grant_scope_id) as ( + select role_grant_scopes.role_id as role_id, + iam_scope.public_id as grant_scope_id + from role_grant_scopes + join iam_scope + on iam_scope.parent_id = role_grant_scopes.role_scope_id + where role_grant_scopes.grant_scope_id = 'children' + ), + this_grant_scopes (role_id, grant_scope_id) as ( + select role_grant_scopes.role_id as role_id, + role_grant_scopes.role_scope_id as grant_scope_id + from role_grant_scopes + where role_grant_scopes.grant_scope_id = 'this' + ), + direct_grant_scopes (role_id, grant_scope_id) as ( + select role_grant_scopes.role_id as role_id, + role_grant_scopes.grant_scope_id as grant_scope_id + from role_grant_scopes + where role_grant_scopes.grant_scope_id not in ('descendants', 'children', 'this') + ), + grant_scopes (role_id, grant_scope_id) as ( + select + role_id as role_id, + grant_scope_id as grant_scope_id + from descendant_grant_scopes + union + select + role_id as role_id, + grant_scope_id as grant_scope_id + from children_grant_scopes + union + select + role_id as role_id, + grant_scope_id as grant_scope_id + from this_grant_scopes + union + select + role_id as role_id, + grant_scope_id as grant_scope_id + from direct_grant_scopes + ), + final (role_id, grant_scope_id, canonical_grant) as ( + select grant_scopes.role_id, + grant_scopes.grant_scope_id, + iam_role_grant.canonical_grant + from grant_scopes + join iam_role_grant + on grant_scopes.role_id = iam_role_grant.role_id + ) + select role_id as role_id, + grant_scope_id as scope_id, + canonical_grant as grant + from final; + ` estimateCountRoles = ` select reltuples::bigint as estimate from pg_class where oid in ('iam_role'::regclass) diff --git a/internal/iam/repository.go b/internal/iam/repository.go index dd176fd972..33c2d8b69a 100644 --- a/internal/iam/repository.go +++ b/internal/iam/repository.go @@ -55,6 +55,8 @@ func NewRepository(ctx context.Context, r db.Reader, w db.Writer, kms *kms.Kms, // list will return a listing of resources and honor the WithLimit option or the // repo defaultLimit +// +// Supported options: WithLimit, WithReaderWriter func (r *Repository) list(ctx context.Context, resources any, where string, args []any, opt ...Option) error { opts := getOpts(opt...) limit := r.defaultLimit @@ -62,11 +64,17 @@ func (r *Repository) list(ctx context.Context, resources any, where string, args // non-zero signals an override of the default limit for the repo. limit = opts.withLimit } - return r.reader.SearchWhere(ctx, resources, where, args, db.WithLimit(limit)) + reader := r.reader + if opts.withReader != nil { + reader = opts.withReader + } + return reader.SearchWhere(ctx, resources, where, args, db.WithLimit(limit)) } // create will create a new iam resource in the db repository with an oplog entry -func (r *Repository) create(ctx context.Context, resource Resource, _ ...Option) (Resource, error) { +// +// Supported options: WithReaderWriter +func (r *Repository) create(ctx context.Context, resource Resource, opt ...Option) (Resource, error) { const op = "iam.(Repository).create" if resource == nil { return nil, errors.New(ctx, errors.InvalidParameter, op, "missing resource") @@ -90,24 +98,20 @@ func (r *Repository) create(ctx context.Context, resource Resource, _ ...Option) return nil, errors.Wrap(ctx, err, op, errors.WithMsg("unable to get oplog wrapper")) } - var returnedResource any - _, err = r.writer.DoTx( - ctx, - db.StdRetryCnt, - db.ExpBackoff{}, - func(_ db.Reader, w db.Writer) error { - returnedResource = resourceCloner.Clone() - err := w.Create( - ctx, - returnedResource, - db.WithOplog(oplogWrapper, metadata), - ) - if err != nil { - return errors.Wrap(ctx, err, op) - } - return nil - }, - ) + returnedResource := resourceCloner.Clone() + opts := getOpts(opt...) + if opts.withWriter != nil { + err = opts.withWriter.Create(ctx, returnedResource, db.WithOplog(oplogWrapper, metadata)) + } else { + _, err = r.writer.DoTx( + ctx, + db.StdRetryCnt, + db.ExpBackoff{}, + func(_ db.Reader, w db.Writer) error { + return w.Create(ctx, returnedResource, db.WithOplog(oplogWrapper, metadata)) + }, + ) + } if err != nil { return nil, errors.Wrap(ctx, err, op) } @@ -115,6 +119,8 @@ func (r *Repository) create(ctx context.Context, resource Resource, _ ...Option) } // update will update an iam resource in the db repository with an oplog entry +// +// Supported options: WithReaderWriter func (r *Repository) update(ctx context.Context, resource Resource, version uint32, fieldMaskPaths []string, setToNullPaths []string, opt ...Option) (Resource, int, error) { const op = "iam.(Repository).update" if version == 0 { @@ -141,6 +147,18 @@ func (r *Repository) update(ctx context.Context, resource Resource, version uint dbOpts = append(dbOpts, db.WithSkipVetForWrite(true)) } + reader := r.reader + writer := r.writer + needFreshReaderWriter := true + if opts.withReader != nil && opts.withWriter != nil { + reader = opts.withReader + writer = opts.withWriter + if !writer.IsTx(ctx) { + return nil, db.NoRowsAffected, errors.New(ctx, errors.Internal, op, "writer is not in transaction") + } + needFreshReaderWriter = false + } + var scope *Scope switch t := resource.(type) { case *Scope: @@ -159,29 +177,35 @@ func (r *Repository) update(ctx context.Context, resource Resource, version uint var rowsUpdated int var returnedResource any - _, err = r.writer.DoTx( - ctx, - db.StdRetryCnt, - db.ExpBackoff{}, - func(_ db.Reader, w db.Writer) error { - returnedResource = resourceCloner.Clone() - rowsUpdated, err = w.Update( - ctx, - returnedResource, - fieldMaskPaths, - setToNullPaths, - dbOpts..., - ) - if err != nil { - return errors.Wrap(ctx, err, op) - } - if rowsUpdated > 1 { - // return err, which will result in a rollback of the update - return errors.New(ctx, errors.MultipleRecords, op, "more than 1 resource would have been updated") - } - return nil - }, - ) + txFunc := func(rdr db.Reader, wtr db.Writer) error { + returnedResource = resourceCloner.Clone() + rowsUpdated, err = wtr.Update( + ctx, + returnedResource, + fieldMaskPaths, + setToNullPaths, + dbOpts..., + ) + if err != nil { + return errors.Wrap(ctx, err, op) + } + if rowsUpdated > 1 { + // return err, which will result in a rollback of the update + return errors.New(ctx, errors.MultipleRecords, op, "more than 1 resource would have been updated") + } + return nil + } + + if !needFreshReaderWriter { + err = txFunc(reader, writer) + } else { + _, err = r.writer.DoTx( + ctx, + db.StdRetryCnt, + db.ExpBackoff{}, + txFunc, + ) + } if err != nil { return nil, db.NoRowsAffected, errors.Wrap(ctx, err, op) } diff --git a/internal/iam/repository_grant_scope.go b/internal/iam/repository_grant_scope.go new file mode 100644 index 0000000000..d29144c031 --- /dev/null +++ b/internal/iam/repository_grant_scope.go @@ -0,0 +1,385 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package iam + +import ( + "context" + "fmt" + + "github.com/hashicorp/boundary/internal/db" + "github.com/hashicorp/boundary/internal/errors" + "github.com/hashicorp/boundary/internal/kms" + "github.com/hashicorp/boundary/internal/oplog" +) + +// AddRoleGrantScopes will add role grant scopes associated with the role ID in +// the repository. No options are currently supported. Zero is not a valid value +// for the WithVersion option and will return an error. +func (r *Repository) AddRoleGrantScopes(ctx context.Context, roleId string, roleVersion uint32, grantScopes []string, _ ...Option) ([]*RoleGrantScope, error) { + const op = "iam.(Repository).AddRoleGrantScopes" + if roleId == "" { + return nil, errors.New(ctx, errors.InvalidParameter, op, "missing role id") + } + if len(grantScopes) == 0 { + return nil, errors.New(ctx, errors.InvalidParameter, op, "missing grant scopes") + } + if roleVersion == 0 { + return nil, errors.New(ctx, errors.InvalidParameter, op, "missing version") + } + + role := allocRole() + role.PublicId = roleId + + scope, err := role.GetScope(ctx, r.reader) + if err != nil { + return nil, errors.Wrap(ctx, err, op, errors.WithMsg(fmt.Sprintf("unable to get role %s scope", roleId))) + } + role.ScopeId = scope.PublicId + + // Find existing grant scopes + roleGrantScopes := []*RoleGrantScope{} + if err := r.reader.SearchWhere(ctx, &roleGrantScopes, "role_id = ?", []any{roleId}); err != nil { + return nil, errors.Wrap(ctx, err, op, errors.WithMsg("unable to search for grant scopes")) + } + found := map[string]*RoleGrantScope{} + for _, rgs := range roleGrantScopes { + found[rgs.ScopeIdOrSpecial] = rgs + } + + // Check incoming grant scopes to see if they exist so we don't try to add + // again and cause an integrity error + addRoleGrantScopes := make([]any, 0, len(grantScopes)) + for _, grantScope := range grantScopes { + if _, ok := found[grantScope]; !ok { + addRoleGrantScopes = append(addRoleGrantScopes, grantScope) + } + } + + newRoleGrantScopes := make([]any, 0, len(addRoleGrantScopes)) + for _, grantScope := range grantScopes { + roleGrantScope, err := NewRoleGrantScope(ctx, role.GetPublicId(), grantScope) + if err != nil { + return nil, errors.Wrap(ctx, err, op, errors.WithMsg("unable to create in memory role grant scope")) + } + newRoleGrantScopes = append(newRoleGrantScopes, roleGrantScope) + } + + oplogWrapper, err := r.kms.GetWrapper(ctx, role.GetScopeId(), kms.KeyPurposeOplog) + if err != nil { + return nil, errors.Wrap(ctx, err, op, errors.WithMsg("unable to get oplog wrapper")) + } + + _, err = r.writer.DoTx( + ctx, + db.StdRetryCnt, + db.ExpBackoff{}, + func(reader db.Reader, w db.Writer) error { + msgs := make([]*oplog.Message, 0, 2) + roleTicket, err := w.GetTicket(ctx, &role) + if err != nil { + return errors.Wrap(ctx, err, op, errors.WithMsg("unable to get ticket")) + } + + // We need to update the role version as that's the aggregate + updatedRole := allocRole() + updatedRole.PublicId = role.GetPublicId() + updatedRole.Version = uint32(roleVersion + 1) + var roleOplogMsg oplog.Message + rowsUpdated, err := w.Update(ctx, &updatedRole, []string{"Version"}, nil, db.NewOplogMsg(&roleOplogMsg), db.WithVersion(&roleVersion)) + if err != nil { + return errors.Wrap(ctx, err, op, errors.WithMsg("unable to update role version")) + } + if rowsUpdated != 1 { + return errors.New(ctx, errors.MultipleRecords, op, fmt.Sprintf("updated role and %d rows updated", rowsUpdated)) + } + msgs = append(msgs, &roleOplogMsg) + roleGrantScopesOplogMsgs := make([]*oplog.Message, 0, len(newRoleGrantScopes)) + if err := w.CreateItems(ctx, newRoleGrantScopes, db.NewOplogMsgs(&roleGrantScopesOplogMsgs)); err != nil { + return errors.Wrap(ctx, err, op, errors.WithMsg("unable to add grants")) + } + msgs = append(msgs, roleGrantScopesOplogMsgs...) + + metadata := oplog.Metadata{ + "op-type": []string{oplog.OpType_OP_TYPE_CREATE.String()}, + "scope-id": []string{scope.PublicId}, + "scope-type": []string{scope.Type}, + "resource-public-id": []string{role.PublicId}, + } + if err := w.WriteOplogEntryWith(ctx, oplogWrapper, roleTicket, metadata, msgs); err != nil { + return errors.Wrap(ctx, err, op, errors.WithMsg("unable to write oplog")) + } + + return nil + }, + ) + if err != nil { + return nil, errors.Wrap(ctx, err, op) + } + roleGrantScopes = make([]*RoleGrantScope, 0, len(newRoleGrantScopes)) + for _, grantScope := range newRoleGrantScopes { + roleGrantScopes = append(roleGrantScopes, grantScope.(*RoleGrantScope)) + } + return roleGrantScopes, nil +} + +// DeleteRoleGrantScopes will delete role grant scopes associated with the role ID in +// the repository. No options are currently supported. Zero is not a valid value +// for the WithVersion option and will return an error. +func (r *Repository) DeleteRoleGrantScopes(ctx context.Context, roleId string, roleVersion uint32, grantScopes []string, _ ...Option) (int, error) { + const op = "iam.(Repository).DeleteRoleGrantScopes" + if roleId == "" { + return db.NoRowsAffected, errors.New(ctx, errors.InvalidParameter, op, "missing role id") + } + if len(grantScopes) == 0 { + return db.NoRowsAffected, errors.New(ctx, errors.InvalidParameter, op, "missing grant scopes") + } + if roleVersion == 0 { + return db.NoRowsAffected, errors.New(ctx, errors.InvalidParameter, op, "missing version") + } + + role := allocRole() + role.PublicId = roleId + + scope, err := role.GetScope(ctx, r.reader) + if err != nil { + return db.NoRowsAffected, errors.Wrap(ctx, err, op, errors.WithMsg(fmt.Sprintf("unable to get role %s scope", roleId))) + } + role.ScopeId = scope.PublicId + + oplogWrapper, err := r.kms.GetWrapper(ctx, role.GetScopeId(), kms.KeyPurposeOplog) + if err != nil { + return db.NoRowsAffected, errors.Wrap(ctx, err, op, errors.WithMsg("unable to get oplog wrapper")) + } + + var totalRowsDeleted int + _, err = r.writer.DoTx( + ctx, + db.StdRetryCnt, + db.ExpBackoff{}, + func(reader db.Reader, w db.Writer) error { + msgs := make([]*oplog.Message, 0, 2) + roleTicket, err := w.GetTicket(ctx, &role) + if err != nil { + return errors.Wrap(ctx, err, op, errors.WithMsg("unable to get ticket")) + } + + // We need to update the role version as that's the aggregate + updatedRole := allocRole() + updatedRole.PublicId = role.GetPublicId() + updatedRole.Version = uint32(roleVersion + 1) + var roleOplogMsg oplog.Message + rowsUpdated, err := w.Update(ctx, &updatedRole, []string{"Version"}, nil, db.NewOplogMsg(&roleOplogMsg), db.WithVersion(&roleVersion)) + if err != nil { + return errors.Wrap(ctx, err, op, errors.WithMsg("unable to update role version")) + } + if rowsUpdated != 1 { + return errors.New(ctx, errors.MultipleRecords, op, fmt.Sprintf("updated role and %d rows updated", rowsUpdated)) + } + msgs = append(msgs, &roleOplogMsg) + + deleteRoleGrantScopes := make([]any, 0, len(grantScopes)) + for _, grantScope := range grantScopes { + roleGrantScope, err := NewRoleGrantScope(ctx, roleId, grantScope) + if err != nil { + return errors.Wrap(ctx, err, op, errors.WithMsg("unable to create in memory role grant scope")) + } + deleteRoleGrantScopes = append(deleteRoleGrantScopes, roleGrantScope) + } + + if len(deleteRoleGrantScopes) == 0 { + return nil + } + + roleGrantScopesOplogMsgs := make([]*oplog.Message, 0, len(deleteRoleGrantScopes)) + rowsDeleted, err := w.DeleteItems(ctx, deleteRoleGrantScopes, db.NewOplogMsgs(&roleGrantScopesOplogMsgs)) + if err != nil { + return errors.Wrap(ctx, err, op, errors.WithMsg("unable to add grants")) + } + totalRowsDeleted = rowsDeleted + msgs = append(msgs, roleGrantScopesOplogMsgs...) + + metadata := oplog.Metadata{ + "op-type": []string{oplog.OpType_OP_TYPE_CREATE.String()}, + "scope-id": []string{scope.PublicId}, + "scope-type": []string{scope.Type}, + "resource-public-id": []string{role.PublicId}, + } + if err := w.WriteOplogEntryWith(ctx, oplogWrapper, roleTicket, metadata, msgs); err != nil { + return errors.Wrap(ctx, err, op, errors.WithMsg("unable to write oplog")) + } + + return nil + }, + ) + if err != nil { + return db.NoRowsAffected, errors.Wrap(ctx, err, op) + } + return totalRowsDeleted, nil +} + +// SetRoleGrantScopes sets grant scopes on a role (roleId). The role's current +// db version +// must match the roleVersion or an error will be returned. Zero is not a valid +// value for the WithVersion option and will return an error. +func (r *Repository) SetRoleGrantScopes(ctx context.Context, roleId string, roleVersion uint32, grantScopes []string, opt ...Option) ([]*RoleGrantScope, int, error) { + const op = "iam.(Repository).SetRoleGrantScopes" + if roleId == "" { + return nil, db.NoRowsAffected, errors.New(ctx, errors.InvalidParameter, op, "missing role id") + } + if roleVersion == 0 { + return nil, db.NoRowsAffected, errors.New(ctx, errors.InvalidParameter, op, "missing version") + } + + // Explicitly set to zero clears, but treat nil as a mistake + if grantScopes == nil { + return nil, db.NoRowsAffected, errors.New(ctx, errors.InvalidParameter, op, "missing grants") + } + + reader := r.reader + writer := r.writer + needFreshReaderWriter := true + opts := getOpts(opt...) + if opts.withReader != nil && opts.withWriter != nil { + reader = opts.withReader + writer = opts.withWriter + needFreshReaderWriter = false + } + + role := allocRole() + role.PublicId = roleId + + // NOTE: Set calculation can safely take place out of the transaction since + // we are using roleVersion to ensure that we end up operating on the same + // set of data from this query to the final set in the transaction function + + // Find existing grant scopes + roleGrantScopes := []*RoleGrantScope{} + if err := reader.SearchWhere(ctx, &roleGrantScopes, "role_id = ?", []any{roleId}); err != nil { + return nil, db.NoRowsAffected, errors.Wrap(ctx, err, op, errors.WithMsg("unable to search for grant scopes")) + } + found := map[string]*RoleGrantScope{} + for _, rgs := range roleGrantScopes { + found[rgs.ScopeIdOrSpecial] = rgs + } + + // Check incoming grant scopes to see if they exist and if so act appropriately + currentRoleGrantScopes := make([]*RoleGrantScope, 0, len(grantScopes)+len(found)) + addRoleGrantScopes := make([]any, 0, len(grantScopes)) + deleteRoleGrantScopes := make([]any, 0, len(grantScopes)) + for _, grantScope := range grantScopes { + rgs, ok := found[grantScope] + if ok { + // If we have an exact match, do nothing, we want to keep + // it, but remove from found + currentRoleGrantScopes = append(currentRoleGrantScopes, rgs) + delete(found, grantScope) + continue + } + + // Not found, so add + rgs, err := NewRoleGrantScope(ctx, roleId, grantScope) + if err != nil { + return nil, db.NoRowsAffected, errors.Wrap(ctx, err, op, errors.WithMsg("unable to create in memory role grant")) + } + addRoleGrantScopes = append(addRoleGrantScopes, rgs) + currentRoleGrantScopes = append(currentRoleGrantScopes, rgs) + } + + if len(found) > 0 { + for _, rgs := range found { + deleteRoleGrantScopes = append(deleteRoleGrantScopes, rgs) + } + } + + if len(addRoleGrantScopes) == 0 && len(deleteRoleGrantScopes) == 0 { + return currentRoleGrantScopes, db.NoRowsAffected, nil + } + + scope, err := role.GetScope(ctx, reader) + if err != nil { + return nil, db.NoRowsAffected, errors.Wrap(ctx, err, op, errors.WithMsg(fmt.Sprintf("unable to get role %s scope", roleId))) + } + oplogWrapper, err := r.kms.GetWrapper(ctx, scope.GetPublicId(), kms.KeyPurposeOplog) + if err != nil { + return nil, db.NoRowsAffected, errors.Wrap(ctx, err, op, errors.WithMsg("unable to get oplog wrapper")) + } + + var totalRowsDeleted int + currentRoleGrantScopes = currentRoleGrantScopes[:0] + txFunc := func(rdr db.Reader, wtr db.Writer) error { + msgs := make([]*oplog.Message, 0, 2) + roleTicket, err := wtr.GetTicket(ctx, &role) + if err != nil { + return errors.Wrap(ctx, err, op, errors.WithMsg("unable to get ticket")) + } + updatedRole := allocRole() + updatedRole.PublicId = roleId + updatedRole.Version = roleVersion + 1 + var roleOplogMsg oplog.Message + rowsUpdated, err := wtr.Update(ctx, &updatedRole, []string{"Version"}, nil, db.NewOplogMsg(&roleOplogMsg), db.WithVersion(&roleVersion)) + if err != nil { + return errors.Wrap(ctx, err, op, errors.WithMsg("unable to update role version")) + } + if rowsUpdated != 1 { + return errors.New(ctx, errors.MultipleRecords, op, fmt.Sprintf("updated role and %d rows updated", rowsUpdated)) + } + msgs = append(msgs, &roleOplogMsg) + + // Anything we didn't take out of found needs to be removed. This needs + // to come before writing in new ones because otherwise we may hit some + // validation issues. + if len(deleteRoleGrantScopes) > 0 { + roleGrantScopeOplogMsgs := make([]*oplog.Message, 0, len(deleteRoleGrantScopes)) + rowsDeleted, err := wtr.DeleteItems(ctx, deleteRoleGrantScopes, db.NewOplogMsgs(&roleGrantScopeOplogMsgs)) + if err != nil { + return errors.Wrap(ctx, err, op, errors.WithMsg("unable to delete role grant scope")) + } + if rowsDeleted != len(deleteRoleGrantScopes) { + return errors.New(ctx, errors.MultipleRecords, op, fmt.Sprintf("role grant scope deleted %d did not match request for %d", rowsDeleted, len(deleteRoleGrantScopes))) + } + totalRowsDeleted = rowsDeleted + msgs = append(msgs, roleGrantScopeOplogMsgs...) + } + + // Write the new ones in + if len(addRoleGrantScopes) > 0 { + roleGrantScopeOplogMsgs := make([]*oplog.Message, 0, len(addRoleGrantScopes)) + if err := wtr.CreateItems(ctx, addRoleGrantScopes, db.NewOplogMsgs(&roleGrantScopeOplogMsgs)); err != nil { + return errors.Wrap(ctx, err, op, errors.WithMsg("unable to add grant scope during set")) + } + msgs = append(msgs, roleGrantScopeOplogMsgs...) + } + + metadata := oplog.Metadata{ + "op-type": []string{oplog.OpType_OP_TYPE_DELETE.String(), oplog.OpType_OP_TYPE_CREATE.String()}, + "scope-id": []string{scope.PublicId}, + "scope-type": []string{scope.Type}, + "resource-public-id": []string{roleId}, + } + if err := wtr.WriteOplogEntryWith(ctx, oplogWrapper, roleTicket, metadata, msgs); err != nil { + return errors.Wrap(ctx, err, op, errors.WithMsg("unable to write oplog")) + } + + if err := r.list(ctx, ¤tRoleGrantScopes, "role_id = ?", []any{roleId}, opt...); err != nil { + return errors.Wrap(ctx, err, op, errors.WithMsg("unable to retrieve current role grant scopes after set")) + } + + return nil + } + + if !needFreshReaderWriter { + err = txFunc(reader, writer) + } else { + _, err = r.writer.DoTx( + ctx, + db.StdRetryCnt, + db.ExpBackoff{}, + txFunc, + ) + } + if err != nil { + return nil, db.NoRowsAffected, errors.Wrap(ctx, err, op) + } + return currentRoleGrantScopes, totalRowsDeleted, nil +} diff --git a/internal/iam/repository_principal_role_test.go b/internal/iam/repository_principal_role_test.go index 30e78645e3..cd0f0e2cf5 100644 --- a/internal/iam/repository_principal_role_test.go +++ b/internal/iam/repository_principal_role_test.go @@ -135,7 +135,7 @@ func TestRepository_AddPrincipalRoles(t *testing.T) { var userIds, groupIds []string for _, roleId := range []string{orgRole.PublicId, projRole.PublicId} { - origRole, _, _, err := repo.LookupRole(context.Background(), roleId) + origRole, _, _, _, err := repo.LookupRole(context.Background(), roleId) require.NoError(err) if tt.args.wantUserIds { @@ -187,7 +187,7 @@ func TestRepository_AddPrincipalRoles(t *testing.T) { assert.Equal(gotPrincipal[principalId].GetType(), r.GetType()) } - r, _, _, err := repo.LookupRole(context.Background(), roleId) + r, _, _, _, err := repo.LookupRole(context.Background(), roleId) require.NoError(err) assert.Equal(tt.args.roleVersion+1, r.Version) assert.Equal(origRole.Version, r.Version-1) @@ -646,7 +646,7 @@ func TestRepository_SetPrincipalRoles(t *testing.T) { if tt.args.addToOrigGrps { tt.args.groupIds = append(tt.args.groupIds, origGrps...) } - origRole, _, _, err := repo.LookupRole(context.Background(), tt.args.role.PublicId) + origRole, _, _, _, err := repo.LookupRole(context.Background(), tt.args.role.PublicId) require.NoError(err) principalIds := append(tt.args.userIds, tt.args.groupIds...) @@ -671,7 +671,7 @@ func TestRepository_SetPrincipalRoles(t *testing.T) { sort.Strings(gotIds) assert.Equal(wantIds, gotIds) - r, _, _, err := repo.LookupRole(context.Background(), tt.args.role.PublicId) + r, _, _, _, err := repo.LookupRole(context.Background(), tt.args.role.PublicId) require.NoError(err) if tt.name != "no change" { assert.Equalf(tt.args.roleVersion+1, r.Version, "%s unexpected version: %d/%d", tt.name, tt.args.roleVersion+1, r.Version) diff --git a/internal/iam/repository_role.go b/internal/iam/repository_role.go index f60208b462..00c8d8aa17 100644 --- a/internal/iam/repository_role.go +++ b/internal/iam/repository_role.go @@ -10,6 +10,7 @@ import ( "strings" "time" + "github.com/hashicorp/boundary/globals" "github.com/hashicorp/boundary/internal/db" "github.com/hashicorp/boundary/internal/db/timestamp" "github.com/hashicorp/boundary/internal/errors" @@ -18,34 +19,70 @@ import ( // CreateRole will create a role in the repository and return the written // role. No options are currently supported. -func (r *Repository) CreateRole(ctx context.Context, role *Role, _ ...Option) (*Role, error) { +func (r *Repository) CreateRole(ctx context.Context, role *Role, opt ...Option) (*Role, []*PrincipalRole, []*RoleGrant, []*RoleGrantScope, error) { const op = "iam.(Repository).CreateRole" if role == nil { - return nil, errors.New(ctx, errors.InvalidParameter, op, "missing role") + return nil, nil, nil, nil, errors.New(ctx, errors.InvalidParameter, op, "missing role") } if role.Role == nil { - return nil, errors.New(ctx, errors.InvalidParameter, op, "missing role store") + return nil, nil, nil, nil, errors.New(ctx, errors.InvalidParameter, op, "missing role store") } if role.PublicId != "" { - return nil, errors.New(ctx, errors.InvalidParameter, op, "public id not empty") + return nil, nil, nil, nil, errors.New(ctx, errors.InvalidParameter, op, "public id not empty") } if role.ScopeId == "" { - return nil, errors.New(ctx, errors.InvalidParameter, op, "missing scope id") + return nil, nil, nil, nil, errors.New(ctx, errors.InvalidParameter, op, "missing scope id") } id, err := newRoleId(ctx) if err != nil { - return nil, errors.Wrap(ctx, err, op) + return nil, nil, nil, nil, errors.Wrap(ctx, err, op) } c := role.Clone().(*Role) c.PublicId = id - resource, err := r.create(ctx, c) + + opts := getOpts(opt...) + + var initialScope string + switch { + case opts.withGrantScopeId == nil: + initialScope = globals.GrantScopeThis + case *opts.withGrantScopeId == "": + initialScope = globals.GrantScopeThis + default: + initialScope = *opts.withGrantScopeId + } + + var resource Resource + var pr []*PrincipalRole + var rg []*RoleGrant + var grantScopes []*RoleGrantScope + _, err = r.writer.DoTx( + ctx, + db.StdRetryCnt, + db.ExpBackoff{}, + func(reader db.Reader, writer db.Writer) error { + resource, err = r.create(ctx, c, WithReaderWriter(reader, writer)) + if err != nil { + return errors.Wrap(ctx, err, op, errors.WithMsg("while creating role")) + } + _, _, err = r.SetRoleGrantScopes(ctx, id, resource.(*Role).Version, []string{initialScope}, WithReaderWriter(reader, writer)) + if err != nil { + return errors.Wrap(ctx, err, op, errors.WithMsg("while setting grant scopes")) + } + // Do a fresh lookup to get all return values + resource, pr, rg, grantScopes, err = r.LookupRole(ctx, resource.(*Role).PublicId, WithReaderWriter(reader, writer)) + if err != nil { + return errors.Wrap(ctx, err, op) + } + return nil + }) if err != nil { if errors.IsUniqueError(err) { - return nil, errors.New(ctx, errors.NotUnique, op, fmt.Sprintf("role %s already exists in scope %s", role.Name, role.ScopeId)) + return nil, nil, nil, nil, errors.New(ctx, errors.NotUnique, op, fmt.Sprintf("role %s already exists in scope %s", role.Name, role.ScopeId)) } - return nil, errors.Wrap(ctx, err, op, errors.WithMsg(fmt.Sprintf("for %s", c.PublicId))) + return nil, nil, nil, nil, errors.Wrap(ctx, err, op, errors.WithMsg(fmt.Sprintf("for %s", c.PublicId))) } - return resource.(*Role), nil + return resource.(*Role), pr, rg, grantScopes, nil } // UpdateRole will update a role in the repository and return the written role. @@ -54,43 +91,56 @@ func (r *Repository) CreateRole(ctx context.Context, role *Role, _ ...Option) (* // included in fieldMask. Name, Description, and GrantScopeId are the only // updatable fields, If no updatable fields are included in the fieldMaskPaths, // then an error is returned. -func (r *Repository) UpdateRole(ctx context.Context, role *Role, version uint32, fieldMaskPaths []string, _ ...Option) (*Role, []*PrincipalRole, []*RoleGrant, int, error) { +func (r *Repository) UpdateRole(ctx context.Context, role *Role, version uint32, fieldMaskPaths []string, opt ...Option) (*Role, []*PrincipalRole, []*RoleGrant, []*RoleGrantScope, int, error) { const op = "iam.(Repository).UpdateRole" if role == nil { - return nil, nil, nil, db.NoRowsAffected, errors.New(ctx, errors.InvalidParameter, op, "missing role") + return nil, nil, nil, nil, db.NoRowsAffected, errors.New(ctx, errors.InvalidParameter, op, "missing role") } if role.Role == nil { - return nil, nil, nil, db.NoRowsAffected, errors.New(ctx, errors.InvalidParameter, op, "missing role store") + return nil, nil, nil, nil, db.NoRowsAffected, errors.New(ctx, errors.InvalidParameter, op, "missing role store") } if role.PublicId == "" { - return nil, nil, nil, db.NoRowsAffected, errors.New(ctx, errors.InvalidParameter, op, "missing public id") + return nil, nil, nil, nil, db.NoRowsAffected, errors.New(ctx, errors.InvalidParameter, op, "missing public id") } + for _, f := range fieldMaskPaths { switch { case strings.EqualFold("name", f): case strings.EqualFold("description", f): - case strings.EqualFold("grantscopeid", f): default: - return nil, nil, nil, db.NoRowsAffected, errors.New(ctx, errors.InvalidFieldMask, op, fmt.Sprintf("invalid field mask: %s", f)) + return nil, nil, nil, nil, db.NoRowsAffected, errors.New(ctx, errors.InvalidFieldMask, op, fmt.Sprintf("invalid field mask: %s", f)) } } + + opts := getOpts(opt...) + var grantScopeIdToSet []string + if opts.withGrantScopeId != nil { + // If the value is empty, they're trying to clear it (e.g. a + // null field), which is represented by an empty slice in + // SetRoleGrantScopes + grantScopeIdToSet = make([]string, 0, 1) + if *opts.withGrantScopeId != "" { + grantScopeIdToSet = append(grantScopeIdToSet, *opts.withGrantScopeId) + } + } + var dbMask, nullFields []string dbMask, nullFields = dbw.BuildUpdatePaths( map[string]any{ - "name": role.Name, - "description": role.Description, - "GrantScopeId": role.GrantScopeId, + "name": role.Name, + "description": role.Description, }, fieldMaskPaths, nil, ) - if len(dbMask) == 0 && len(nullFields) == 0 { - return nil, nil, nil, db.NoRowsAffected, errors.E(ctx, errors.WithCode(errors.EmptyFieldMask), errors.WithOp(op)) + if len(dbMask) == 0 && len(nullFields) == 0 && grantScopeIdToSet == nil { + return nil, nil, nil, nil, db.NoRowsAffected, errors.E(ctx, errors.WithCode(errors.EmptyFieldMask), errors.WithOp(op)) } var resource Resource var rowsUpdated int var pr []*PrincipalRole var rg []*RoleGrant + var grantScopes []*RoleGrantScope _, err := r.writer.DoTx( ctx, db.StdRetryCnt, @@ -98,19 +148,23 @@ func (r *Repository) UpdateRole(ctx context.Context, role *Role, version uint32, func(read db.Reader, w db.Writer) error { var err error c := role.Clone().(*Role) - resource, rowsUpdated, err = r.update(ctx, c, version, dbMask, nullFields) - if err != nil { - return errors.Wrap(ctx, err, op) + resource = c // If we don't have dbMask or nullFields, we'll return this + if len(dbMask) > 0 || len(nullFields) > 0 { + resource, rowsUpdated, err = r.update(ctx, c, version, dbMask, nullFields, WithReaderWriter(read, w)) + if err != nil { + return errors.Wrap(ctx, err, op) + } + version = resource.(*Role).Version } - repo, err := NewRepository(ctx, read, w, r.kms) - if err != nil { - return errors.Wrap(ctx, err, op) - } - pr, err = repo.ListPrincipalRoles(ctx, role.PublicId) - if err != nil { - return errors.Wrap(ctx, err, op) + if grantScopeIdToSet != nil { + _, _, err = r.SetRoleGrantScopes(ctx, role.PublicId, version, grantScopeIdToSet, WithReaderWriter(read, w)) + if err != nil { + return errors.Wrap(ctx, err, op, errors.WithMsg("while setting grant scopes")) + } } - rg, err = repo.ListRoleGrants(ctx, role.PublicId) + // Do a fresh lookup since version may have gone up by 1 or 2 based + // on grant scope id + resource, pr, rg, grantScopes, err = r.LookupRole(ctx, role.PublicId, WithReaderWriter(read, w)) if err != nil { return errors.Wrap(ctx, err, op) } @@ -119,54 +173,73 @@ func (r *Repository) UpdateRole(ctx context.Context, role *Role, version uint32, ) if err != nil { if errors.IsUniqueError(err) { - return nil, nil, nil, db.NoRowsAffected, errors.New(ctx, errors.NotUnique, op, fmt.Sprintf("role %s already exists in org %s", role.Name, role.ScopeId)) + return nil, nil, nil, nil, db.NoRowsAffected, errors.New(ctx, errors.NotUnique, op, fmt.Sprintf("role %s already exists in org %s", role.Name, role.ScopeId)) } - return nil, nil, nil, db.NoRowsAffected, errors.Wrap(ctx, err, op, errors.WithMsg(fmt.Sprintf("for %s", role.PublicId))) + return nil, nil, nil, nil, db.NoRowsAffected, errors.Wrap(ctx, err, op, errors.WithMsg(fmt.Sprintf("for %s", role.PublicId))) } - return resource.(*Role), pr, rg, rowsUpdated, nil + return resource.(*Role), pr, rg, grantScopes, rowsUpdated, nil } // LookupRole will look up a role in the repository. If the role is not // found, it will return nil, nil. -func (r *Repository) LookupRole(ctx context.Context, withPublicId string, _ ...Option) (*Role, []*PrincipalRole, []*RoleGrant, error) { +// +// Supported options: WithReaderWriter +func (r *Repository) LookupRole(ctx context.Context, withPublicId string, opt ...Option) (*Role, []*PrincipalRole, []*RoleGrant, []*RoleGrantScope, error) { const op = "iam.(Repository).LookupRole" if withPublicId == "" { - return nil, nil, nil, errors.New(ctx, errors.InvalidParameter, op, "missing public id") + return nil, nil, nil, nil, errors.New(ctx, errors.InvalidParameter, op, "missing public id") } + opts := getOpts(opt...) role := allocRole() role.PublicId = withPublicId var pr []*PrincipalRole var rg []*RoleGrant - _, err := r.writer.DoTx( - ctx, - db.StdRetryCnt, - db.ExpBackoff{}, - func(read db.Reader, w db.Writer) error { - if err := read.LookupByPublicId(ctx, &role); err != nil { - return errors.Wrap(ctx, err, op) - } - repo, err := NewRepository(ctx, read, w, r.kms) - if err != nil { - return errors.Wrap(ctx, err, op) - } - pr, err = repo.ListPrincipalRoles(ctx, withPublicId) - if err != nil { - return errors.Wrap(ctx, err, op) - } - rg, err = repo.ListRoleGrants(ctx, withPublicId) - if err != nil { - return errors.Wrap(ctx, err, op) - } - return nil - }, - ) + var rgs []*RoleGrantScope + + lookupFunc := func(read db.Reader, w db.Writer) error { + if err := read.LookupByPublicId(ctx, &role); err != nil { + return errors.Wrap(ctx, err, op) + } + repo, err := NewRepository(ctx, read, w, r.kms) + if err != nil { + return errors.Wrap(ctx, err, op) + } + pr, err = repo.ListPrincipalRoles(ctx, withPublicId) + if err != nil { + return errors.Wrap(ctx, err, op) + } + rg, err = repo.ListRoleGrants(ctx, withPublicId) + if err != nil { + return errors.Wrap(ctx, err, op) + } + rgs, err = repo.ListRoleGrantScopes(ctx, withPublicId) + if err != nil { + return errors.Wrap(ctx, err, op) + } + return nil + } + + var err error + if opts.withReader != nil && opts.withWriter != nil { + if !opts.withWriter.IsTx(ctx) { + return nil, nil, nil, nil, errors.New(ctx, errors.Internal, op, "writer is not in transaction") + } + err = lookupFunc(opts.withReader, opts.withWriter) + } else { + _, err = r.writer.DoTx( + ctx, + db.StdRetryCnt, + db.ExpBackoff{}, + lookupFunc, + ) + } if err != nil { if errors.IsNotFoundError(err) { - return nil, nil, nil, nil + return nil, nil, nil, nil, nil } - return nil, nil, nil, errors.Wrap(ctx, err, op, errors.WithMsg(fmt.Sprintf("for %s", withPublicId))) + return nil, nil, nil, nil, errors.Wrap(ctx, err, op, errors.WithMsg(fmt.Sprintf("for %s", withPublicId))) } - return &role, pr, rg, nil + return &role, pr, rg, rgs, nil } // DeleteRole will delete a role from the repository. diff --git a/internal/iam/repository_role_grant.go b/internal/iam/repository_role_grant.go index 8a85b4bf83..caff056610 100644 --- a/internal/iam/repository_role_grant.go +++ b/internal/iam/repository_role_grant.go @@ -389,6 +389,20 @@ func (r *Repository) ListRoleGrants(ctx context.Context, roleId string, opt ...O return roleGrants, nil } +// ListRoleGrantScopes returns the grant scopes for the roleId and supports the WithLimit +// option. +func (r *Repository) ListRoleGrantScopes(ctx context.Context, roleId string, opt ...Option) ([]*RoleGrantScope, error) { + const op = "iam.(Repository).ListRoleGrantScopes" + if roleId == "" { + return nil, errors.New(ctx, errors.InvalidParameter, op, "missing role id") + } + var roleGrantScopes []*RoleGrantScope + if err := r.list(ctx, &roleGrantScopes, "role_id = ?", []any{roleId}, opt...); err != nil { + return nil, errors.Wrap(ctx, err, op, errors.WithMsg("unable to lookup role grant scopes")) + } + return roleGrantScopes, nil +} + func (r *Repository) GrantsForUser(ctx context.Context, userId string, _ ...Option) ([]perms.GrantTuple, error) { const op = "iam.(Repository).GrantsForUser" if userId == "" { @@ -403,9 +417,9 @@ func (r *Repository) GrantsForUser(ctx context.Context, userId string, _ ...Opti var query string switch userId { case globals.AnonymousUserId: - query = fmt.Sprintf(grantsQuery, anonUser) + query = fmt.Sprintf(grantsForUserQuery, anonUser) default: - query = fmt.Sprintf(grantsQuery, authUser) + query = fmt.Sprintf(grantsForUserQuery, authUser) } var grants []perms.GrantTuple @@ -415,11 +429,9 @@ func (r *Repository) GrantsForUser(ctx context.Context, userId string, _ ...Opti } defer rows.Close() for rows.Next() { - var g perms.GrantTuple - if err := r.reader.ScanRows(ctx, rows, &g); err != nil { + if err := r.reader.ScanRows(ctx, rows, &grants); err != nil { return nil, errors.Wrap(ctx, err, op) } - grants = append(grants, g) } if err := rows.Err(); err != nil { return nil, errors.Wrap(ctx, err, op) diff --git a/internal/iam/repository_role_grant_ext_test.go b/internal/iam/repository_role_grant_ext_test.go index f6ab911a37..779bbe2ed1 100644 --- a/internal/iam/repository_role_grant_ext_test.go +++ b/internal/iam/repository_role_grant_ext_test.go @@ -10,6 +10,7 @@ import ( mathrand "math/rand" "testing" + "github.com/hashicorp/boundary/globals" "github.com/hashicorp/boundary/internal/auth/ldap" "github.com/hashicorp/boundary/internal/auth/ldap/store" "github.com/hashicorp/boundary/internal/auth/oidc" @@ -23,6 +24,43 @@ import ( ) func TestGrantsForUser(t *testing.T) { + conn, _ := db.TestSetup(t, "postgres") + wrap := db.TestWrapper(t) + + iamRepo := iam.TestRepo(t, conn, wrap) + user := iam.TestUser(t, iamRepo, "global") + org1, proj1 := iam.TestScopes( + t, + iamRepo, + iam.WithSkipAdminRoleCreation(true), + iam.WithSkipDefaultRoleCreation(true), + ) + org2, proj2 := iam.TestScopes( + t, + iamRepo, + iam.WithSkipAdminRoleCreation(true), + iam.WithSkipDefaultRoleCreation(true), + ) + t.Log("org1", org1.GetPublicId(), "proj1", proj1.GetPublicId(), "org2", org2.GetPublicId(), "proj2", proj2.GetPublicId()) + org1Proj1Role := iam.TestRole(t, conn, org1.GetPublicId(), iam.WithGrantScopeId(proj1.PublicId)) + org2Proj2Role := iam.TestRole(t, conn, org2.GetPublicId(), iam.WithGrantScopeIds([]string{globals.GrantScopeThis, globals.GrantScopeChildren})) + globalRole := iam.TestRole(t, conn, scope.Global.String(), iam.WithGrantScopeIds([]string{globals.GrantScopeDescendants})) + iam.TestUserRole(t, conn, org1Proj1Role.PublicId, user.PublicId) + iam.TestUserRole(t, conn, org2Proj2Role.PublicId, user.PublicId) + iam.TestUserRole(t, conn, globalRole.PublicId, user.PublicId) + iam.TestRoleGrant(t, conn, org1Proj1Role.PublicId, "id=*;type=*;actions=read") + iam.TestRoleGrant(t, conn, org2Proj2Role.PublicId, "id=*;type=*;actions=create") + iam.TestRoleGrant(t, conn, org2Proj2Role.PublicId, "id=*;type=*;actions=list,no-op") + iam.TestRoleGrant(t, conn, globalRole.PublicId, "id=*;type=auth-method;actions=update") + iam.TestRoleGrant(t, conn, globalRole.PublicId, "id=*;type=credential-store;actions=list,no-op") + // time.Sleep(10000 * time.Second) + // ctx := context.Background() + // grantTuples, err := iamRepo.GrantsForUser(ctx, user.PublicId) + // require.NoError(t, err) + // t.Log(pretty.Sprint(grantTuples)) +} + +func TestGrantsForUserRandomized(t *testing.T) { ctx := context.Background() conn, _ := db.TestSetup(t, "postgres") wrap := db.TestWrapper(t) diff --git a/internal/iam/repository_role_test.go b/internal/iam/repository_role_test.go index 9ffc6be4b3..e23f686a1c 100644 --- a/internal/iam/repository_role_test.go +++ b/internal/iam/repository_role_test.go @@ -151,11 +151,11 @@ func TestRepository_CreateRole(t *testing.T) { if tt.wantDup { dup, err := NewRole(ctx, org.PublicId, tt.args.opt...) assert.NoError(err) - dup, err = repo.CreateRole(context.Background(), dup, tt.args.opt...) + dup, _, _, _, err = repo.CreateRole(context.Background(), dup, tt.args.opt...) assert.NoError(err) assert.NotNil(dup) } - grp, err := repo.CreateRole(context.Background(), tt.args.role, tt.args.opt...) + grp, _, _, _, err := repo.CreateRole(context.Background(), tt.args.role, tt.args.opt...) if tt.wantErr { assert.Error(err) assert.Nil(grp) @@ -167,7 +167,7 @@ func TestRepository_CreateRole(t *testing.T) { assert.NotNil(grp.CreateTime) assert.NotNil(grp.UpdateTime) - foundGrp, _, _, err := repo.LookupRole(context.Background(), grp.PublicId) + foundGrp, _, _, _, err := repo.LookupRole(context.Background(), grp.PublicId) assert.NoError(err) assert.True(proto.Equal(foundGrp, grp)) @@ -395,7 +395,7 @@ func TestRepository_UpdateRole(t *testing.T) { _ = TestUserRole(t, conn, r.GetPublicId(), u.GetPublicId()) _ = TestRoleGrant(t, conn, r.GetPublicId(), "ids=*;type=*;actions=*") r.Name = tt.args.name - _, _, _, _, err := repo.UpdateRole(context.Background(), r, r.Version, tt.args.fieldMaskPaths, tt.args.opt...) + _, _, _, _, _, err := repo.UpdateRole(context.Background(), r, r.Version, tt.args.fieldMaskPaths, tt.args.opt...) assert.NoError(err) } @@ -427,7 +427,7 @@ func TestRepository_UpdateRole(t *testing.T) { updateRole.Name = tt.args.name updateRole.Description = tt.args.description - roleAfterUpdate, principals, grants, updatedRows, err := repo.UpdateRole(context.Background(), &updateRole, r.Version, tt.args.fieldMaskPaths, tt.args.opt...) + roleAfterUpdate, principals, grants, _, updatedRows, err := repo.UpdateRole(context.Background(), &updateRole, r.Version, tt.args.fieldMaskPaths, tt.args.opt...) if tt.wantErr { assert.Error(err) assert.True(errors.Match(errors.T(tt.wantIsError), err)) @@ -450,7 +450,7 @@ func TestRepository_UpdateRole(t *testing.T) { default: assert.NotEqual(r.UpdateTime, roleAfterUpdate.UpdateTime) } - foundRole, _, _, err := repo.LookupRole(context.Background(), r.PublicId) + foundRole, _, _, _, err := repo.LookupRole(context.Background(), r.PublicId) assert.NoError(err) assert.True(proto.Equal(roleAfterUpdate, foundRole)) underlyingDB, err := conn.SqlDB(ctx) @@ -544,7 +544,7 @@ func TestRepository_DeleteRole(t *testing.T) { } assert.NoError(err) assert.Equal(tt.wantRowsDeleted, deletedRows) - foundRole, _, _, err := repo.LookupRole(ctx, tt.args.role.PublicId) + foundRole, _, _, _, err := repo.LookupRole(ctx, tt.args.role.PublicId) assert.NoError(err) assert.Nil(foundRole) diff --git a/internal/iam/repository_scope.go b/internal/iam/repository_scope.go index 128a9ac072..dc29fb6ba2 100644 --- a/internal/iam/repository_scope.go +++ b/internal/iam/repository_scope.go @@ -209,7 +209,7 @@ func (r *Repository) CreateScope(ctx context.Context, s *Scope, userId string, o adminRole = adminRoleRaw.(*Role) - msgs := make([]*oplog.Message, 0, 3) + msgs := make([]*oplog.Message, 0, 4) roleTicket, err := w.GetTicket(ctx, adminRole) if err != nil { return errors.Wrap(ctx, err, op, errors.WithMsg("unable to get ticket")) @@ -237,6 +237,16 @@ func (r *Repository) CreateScope(ctx context.Context, s *Scope, userId string, o } msgs = append(msgs, roleGrantOplogMsgs...) + roleGrantScope, err := NewRoleGrantScope(ctx, adminRolePublicId, globals.GrantScopeThis) + if err != nil { + return errors.Wrap(ctx, err, op, errors.WithMsg("unable to create in memory role grant scope")) + } + roleGrantScopeOplogMsgs := make([]*oplog.Message, 0, 1) + if err := w.CreateItems(ctx, []any{roleGrantScope}, db.NewOplogMsgs(&roleGrantScopeOplogMsgs)); err != nil { + return errors.Wrap(ctx, err, op, errors.WithMsg("unable to add grant scope")) + } + msgs = append(msgs, roleGrantScopeOplogMsgs...) + rolePrincipal, err := NewUserRole(ctx, adminRolePublicId, userId) if err != nil { return errors.Wrap(ctx, err, op, errors.WithMsg("unable to create in memory role user")) @@ -272,7 +282,7 @@ func (r *Repository) CreateScope(ctx context.Context, s *Scope, userId string, o defaultRole = defaultRoleRaw.(*Role) - msgs := make([]*oplog.Message, 0, 6) + msgs := make([]*oplog.Message, 0, 7) roleTicket, err := w.GetTicket(ctx, defaultRole) if err != nil { return errors.Wrap(ctx, err, op, errors.WithMsg("unable to get ticket")) @@ -360,6 +370,16 @@ func (r *Repository) CreateScope(ctx context.Context, s *Scope, userId string, o msgs = append(msgs, roleUserOplogMsgs...) } + roleGrantScope, err := NewRoleGrantScope(ctx, defaultRolePublicId, globals.GrantScopeThis) + if err != nil { + return errors.Wrap(ctx, err, op, errors.WithMsg("unable to create in memory role grant scope")) + } + roleGrantScopeOplogMsgs := make([]*oplog.Message, 0, 1) + if err := w.CreateItems(ctx, []any{roleGrantScope}, db.NewOplogMsgs(&roleGrantScopeOplogMsgs)); err != nil { + return errors.Wrap(ctx, err, op, errors.WithMsg("unable to add grant scope")) + } + msgs = append(msgs, roleGrantScopeOplogMsgs...) + metadata := oplog.Metadata{ "op-type": []string{oplog.OpType_OP_TYPE_CREATE.String()}, "scope-id": []string{s.PublicId}, diff --git a/internal/iam/repository_user_test.go b/internal/iam/repository_user_test.go index 7bda995f62..abe866a578 100644 --- a/internal/iam/repository_user_test.go +++ b/internal/iam/repository_user_test.go @@ -703,7 +703,7 @@ func TestRepository_ListUsers_Multiple_Scopes(t *testing.T) { }) const numPerScope = 10 - var total int = 3 // anon, auth, recovery + total := 3 // anon, auth, recovery for i := 0; i < numPerScope; i++ { iam.TestUser(t, repo, "global") total++ diff --git a/internal/iam/role.go b/internal/iam/role.go index 41c0a16e0b..c496976006 100644 --- a/internal/iam/role.go +++ b/internal/iam/role.go @@ -34,7 +34,7 @@ var ( ) // NewRole creates a new in memory role with a scope (project/org) -// allowed options include: withDescripion, WithName, withGrantScopeId. +// allowed options include: withDescripion, WithName. func NewRole(ctx context.Context, scopeId string, opt ...Option) (*Role, error) { const op = "iam.NewRole" if scopeId == "" { @@ -43,10 +43,9 @@ func NewRole(ctx context.Context, scopeId string, opt ...Option) (*Role, error) opts := getOpts(opt...) r := &Role{ Role: &store.Role{ - ScopeId: scopeId, - Name: opts.withName, - Description: opts.withDescription, - GrantScopeId: opts.withGrantScopeId, + ScopeId: scopeId, + Name: opts.withName, + Description: opts.withDescription, }, } return r, nil @@ -59,8 +58,8 @@ func allocRole() Role { } // Clone creates a clone of the Role. -func (r *Role) Clone() any { - cp := proto.Clone(r.Role) +func (role *Role) Clone() any { + cp := proto.Clone(role.Role) return &Role{ Role: cp.(*store.Role), } @@ -78,7 +77,7 @@ func (role *Role) VetForWrite(ctx context.Context, r db.Reader, opType db.OpType return nil } -func (u *Role) validScopeTypes() []scope.Type { +func (role *Role) validScopeTypes() []scope.Type { return []scope.Type{scope.Global, scope.Org, scope.Project} } @@ -103,9 +102,9 @@ func (*Role) Actions() map[string]action.Type { } // TableName returns the tablename to override the default gorm table name. -func (r *Role) TableName() string { - if r.tableName != "" { - return r.tableName +func (role *Role) TableName() string { + if role.tableName != "" { + return role.tableName } return defaultRoleTableName } @@ -113,8 +112,8 @@ func (r *Role) TableName() string { // SetTableName sets the tablename and satisfies the ReplayableMessage // interface. If the caller attempts to set the name to "" the name will be // reset to the default name. -func (r *Role) SetTableName(n string) { - r.tableName = n +func (role *Role) SetTableName(n string) { + role.tableName = n } type deletedRole struct { diff --git a/internal/iam/role_grant_scope.go b/internal/iam/role_grant_scope.go new file mode 100644 index 0000000000..4ccde40325 --- /dev/null +++ b/internal/iam/role_grant_scope.go @@ -0,0 +1,111 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package iam + +import ( + "context" + "fmt" + + "github.com/hashicorp/boundary/globals" + "github.com/hashicorp/boundary/internal/db" + "github.com/hashicorp/boundary/internal/errors" + "github.com/hashicorp/boundary/internal/iam/store" + "github.com/hashicorp/boundary/internal/types/resource" + "github.com/hashicorp/boundary/internal/types/scope" + "google.golang.org/protobuf/proto" +) + +const defaultRoleGrantScopeTable = "iam_role_grant_scope" + +// RoleGrantScope defines the grant scopes that are assigned to a role +type RoleGrantScope struct { + *store.RoleGrantScope + tableName string `gorm:"-"` +} + +// ensure that RoleGrantScope implements the interfaces of: Cloneable and db.VetForWriter +var ( + _ Cloneable = (*RoleGrantScope)(nil) + _ db.VetForWriter = (*RoleGrantScope)(nil) +) + +// NewRoleGrantScope creates a new in memory role grant scope +func NewRoleGrantScope(ctx context.Context, roleId string, grantScope string, _ ...Option) (*RoleGrantScope, error) { + const op = "iam.NewRoleGrantScope" + if roleId == "" { + return nil, errors.New(ctx, errors.InvalidParameter, op, "missing role id") + } + if grantScope == "" { + return nil, errors.New(ctx, errors.InvalidParameter, op, "missing grant scope") + } + + switch { + case grantScope == scope.Global.String(), + grantScope == globals.GrantScopeThis, + grantScope == globals.GrantScopeChildren, + grantScope == globals.GrantScopeDescendants: + case globals.ResourceInfoFromPrefix(grantScope).Type == resource.Scope: + default: + return nil, errors.New(ctx, errors.InvalidParameter, op, fmt.Sprintf("unknown grant scope id %q", grantScope)) + } + rgs := &RoleGrantScope{ + RoleGrantScope: &store.RoleGrantScope{ + RoleId: roleId, + ScopeIdOrSpecial: grantScope, + }, + } + return rgs, nil +} + +func allocRoleGrantScope() RoleGrantScope { + return RoleGrantScope{ + RoleGrantScope: &store.RoleGrantScope{}, + } +} + +// Clone creates a clone of the RoleGrantScope +func (g *RoleGrantScope) Clone() any { + cp := proto.Clone(g.RoleGrantScope) + return &RoleGrantScope{ + RoleGrantScope: cp.(*store.RoleGrantScope), + } +} + +// VetForWrite implements db.VetForWrite() interface +func (g *RoleGrantScope) VetForWrite(ctx context.Context, _ db.Reader, _ db.OpType, _ ...db.Option) error { + const op = "iam.(RoleGrantScope).VetForWrite" + if g.RoleId == "" { + return errors.New(ctx, errors.InvalidParameter, op, "missing role id") + } + if g.ScopeIdOrSpecial == "" { + return errors.New(ctx, errors.InvalidParameter, op, "missing scope id") + } + + switch { + case g.ScopeIdOrSpecial == scope.Global.String(), + g.ScopeIdOrSpecial == globals.GrantScopeThis, + g.ScopeIdOrSpecial == globals.GrantScopeChildren, + g.ScopeIdOrSpecial == globals.GrantScopeDescendants: + case globals.ResourceInfoFromPrefix(g.ScopeIdOrSpecial).Type == resource.Scope: + default: + return errors.New(ctx, errors.InvalidParameter, op, fmt.Sprintf("unknown grant scope id %q", g.ScopeIdOrSpecial)) + } + + return nil +} + +// TableName returns the tablename to override the default gorm table name +func (g *RoleGrantScope) TableName() string { + if g.tableName != "" { + return g.tableName + } + return defaultRoleGrantScopeTable +} + +// SetTableName sets the tablename and satisfies the ReplayableMessage +// interface. If the caller attempts to set the name to "" the name will be +// reset to the default name. +func (g *RoleGrantScope) SetTableName(n string) { + g.tableName = n +} diff --git a/internal/iam/role_test.go b/internal/iam/role_test.go index 29d90961ad..b0e0993146 100644 --- a/internal/iam/role_test.go +++ b/internal/iam/role_test.go @@ -219,7 +219,6 @@ func Test_RoleUpdate(t *testing.T) { repo := TestRepo(t, conn, wrapper) id := testId(t) org, proj := TestScopes(t, repo) - org2, proj2 := TestScopes(t, repo) rw := db.New(conn) type args struct { name string @@ -227,7 +226,6 @@ func Test_RoleUpdate(t *testing.T) { fieldMaskPaths []string nullPaths []string scopeId string - grantScopeId string scopeIdOverride string opts []db.Option } @@ -325,80 +323,6 @@ func Test_RoleUpdate(t *testing.T) { wantErr: false, wantRowsUpdate: 1, }, - { - name: "set grant scope in project with same scope", - args: args{ - name: "set grant scope in project with same scope", - fieldMaskPaths: []string{"Name", "GrantScopeId"}, - scopeId: proj.PublicId, - grantScopeId: proj.PublicId, - }, - wantRowsUpdate: 1, - }, - { - name: "set grant scope in org with same scope", - args: args{ - name: "set grant scope in org with same scope", - fieldMaskPaths: []string{"Name", "GrantScopeId"}, - scopeId: org2.PublicId, - grantScopeId: org2.PublicId, - }, - wantRowsUpdate: 1, - }, - { - name: "set grant scope in project with different scope", - args: args{ - name: "set grant scope in project with different scope", - fieldMaskPaths: []string{"GrantScopeId"}, - scopeId: proj.PublicId, - grantScopeId: proj2.PublicId, - }, - wantErr: true, - wantErrMsg: "db.Update: invalid to set grant_scope_id to non-same scope_id when role scope type is project: integrity violation: error #1104", - }, - { - name: "set grant scope in org", - args: args{ - name: "set grant scope in org", - fieldMaskPaths: []string{"Name", "GrantScopeId"}, - scopeId: org2.PublicId, - grantScopeId: proj2.PublicId, - }, - wantRowsUpdate: 1, - }, - { - name: "set grant scope in external project", - args: args{ - name: "set grant scope in external project", - fieldMaskPaths: []string{"GrantScopeId"}, - scopeId: org.PublicId, - grantScopeId: proj2.PublicId, - }, - wantErr: true, - wantErrMsg: "db.Update: grant_scope_id is not a child project of the role scope: integrity violation: error #1104", - }, - { - name: "set grant scope in global", - args: args{ - name: "set grant scope in global", - fieldMaskPaths: []string{"GrantScopeId"}, - scopeId: org.PublicId, - grantScopeId: "global", - }, - wantErr: true, - wantErrMsg: "db.Update: grant_scope_id is not a child project of the role scope: integrity violation: error #1104", - }, - { - name: "set grant scope to parent", - args: args{ - name: "set grant scope to parent", - fieldMaskPaths: []string{"GrantScopeId"}, - scopeId: proj2.PublicId, - grantScopeId: org2.PublicId, - }, - wantErr: true, - wantErrMsg: "db.Update: invalid to set grant_scope_id to non-same scope_id when role scope type is project: integrity violation: error #1104", - }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { @@ -424,7 +348,6 @@ func Test_RoleUpdate(t *testing.T) { } updateRole.Name = tt.args.name updateRole.Description = tt.args.description - updateRole.GrantScopeId = tt.args.grantScopeId updatedRows, err := rw.Update(context.Background(), &updateRole, tt.args.fieldMaskPaths, tt.args.nullPaths, tt.args.opts...) if tt.wantErr { @@ -443,9 +366,6 @@ func Test_RoleUpdate(t *testing.T) { foundRole.PublicId = role.GetPublicId() err = rw.LookupByPublicId(context.Background(), &foundRole) require.NoError(err) - if tt.args.grantScopeId == "" { - updateRole.GrantScopeId = role.ScopeId - } assert.True(proto.Equal(updateRole, foundRole)) if len(tt.args.nullPaths) != 0 { underlyingDB, err := conn.SqlDB(ctx) diff --git a/internal/iam/store/role.pb.go b/internal/iam/store/role.pb.go index e6e654e393..80628dd533 100644 --- a/internal/iam/store/role.pb.go +++ b/internal/iam/store/role.pb.go @@ -53,10 +53,6 @@ type Role struct { // itself and when modifying dependent items like principal roles. // @inject_tag: `gorm:"default:null"` Version uint32 `protobuf:"varint,70,opt,name=version,proto3" json:"version,omitempty" gorm:"default:null"` - // grant_scope_id is used for delegating access; it defines a scope other than - // the role's scope that is used when compiling these grants into an ACL - // @inject_tag: `gorm:"default:null"` - GrantScopeId string `protobuf:"bytes,80,opt,name=grant_scope_id,json=grantScopeId,proto3" json:"grant_scope_id,omitempty" gorm:"default:null"` } func (x *Role) Reset() { @@ -140,13 +136,6 @@ func (x *Role) GetVersion() uint32 { return 0 } -func (x *Role) GetGrantScopeId() string { - if x != nil { - return x.GrantScopeId - } - return "" -} - var File_controller_storage_iam_store_v1_role_proto protoreflect.FileDescriptor var file_controller_storage_iam_store_v1_role_proto_rawDesc = []byte{ @@ -160,7 +149,7 @@ var file_controller_storage_iam_store_v1_role_proto_rawDesc = []byte{ 0x6f, 0x6e, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x2f, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2f, 0x73, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x2f, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x2f, 0x76, 0x31, 0x2f, 0x74, 0x69, 0x6d, 0x65, 0x73, - 0x74, 0x61, 0x6d, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xa4, 0x03, 0x0a, 0x04, 0x52, + 0x74, 0x61, 0x6d, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xe0, 0x02, 0x0a, 0x04, 0x52, 0x6f, 0x6c, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x49, 0x64, 0x12, 0x19, 0x0a, 0x08, 0x73, 0x63, 0x6f, 0x70, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x14, 0x20, 0x01, @@ -182,16 +171,11 @@ var file_controller_storage_iam_store_v1_role_proto_rawDesc = []byte{ 0x74, 0x61, 0x6d, 0x70, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x0a, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x54, 0x69, 0x6d, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x46, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x07, - 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x48, 0x0a, 0x0e, 0x67, 0x72, 0x61, 0x6e, 0x74, - 0x5f, 0x73, 0x63, 0x6f, 0x70, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x50, 0x20, 0x01, 0x28, 0x09, 0x42, - 0x22, 0xc2, 0xdd, 0x29, 0x1e, 0x0a, 0x0c, 0x47, 0x72, 0x61, 0x6e, 0x74, 0x53, 0x63, 0x6f, 0x70, - 0x65, 0x49, 0x64, 0x12, 0x0e, 0x67, 0x72, 0x61, 0x6e, 0x74, 0x5f, 0x73, 0x63, 0x6f, 0x70, 0x65, - 0x5f, 0x69, 0x64, 0x52, 0x0c, 0x67, 0x72, 0x61, 0x6e, 0x74, 0x53, 0x63, 0x6f, 0x70, 0x65, 0x49, - 0x64, 0x42, 0x38, 0x5a, 0x36, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, - 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x2f, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x61, - 0x72, 0x79, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2f, 0x69, 0x61, 0x6d, 0x2f, - 0x73, 0x74, 0x6f, 0x72, 0x65, 0x3b, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x62, 0x06, 0x70, 0x72, 0x6f, - 0x74, 0x6f, 0x33, + 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x4a, 0x04, 0x08, 0x50, 0x10, 0x51, 0x42, 0x38, 0x5a, + 0x36, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x68, 0x61, 0x73, 0x68, + 0x69, 0x63, 0x6f, 0x72, 0x70, 0x2f, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x61, 0x72, 0x79, 0x2f, 0x69, + 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2f, 0x69, 0x61, 0x6d, 0x2f, 0x73, 0x74, 0x6f, 0x72, + 0x65, 0x3b, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( diff --git a/internal/iam/store/role_grant.pb.go b/internal/iam/store/role_grant.pb.go index 215a651ae9..ae566ca786 100644 --- a/internal/iam/store/role_grant.pb.go +++ b/internal/iam/store/role_grant.pb.go @@ -33,14 +33,14 @@ type RoleGrant struct { // @inject_tag: `gorm:"default:current_timestamp"` CreateTime *timestamp.Timestamp `protobuf:"bytes,1,opt,name=create_time,json=createTime,proto3" json:"create_time,omitempty" gorm:"default:current_timestamp"` // role_id is the ID of the role this is a part of - // @inject_tag: gorm:"primary_key" + // @inject_tag: `gorm:"primary_key"` RoleId string `protobuf:"bytes,2,opt,name=role_id,json=roleId,proto3" json:"role_id,omitempty" gorm:"primary_key"` // raw_grant is the string grant value as provided by the user // @inject_tag: `gorm:"default:null"` RawGrant string `protobuf:"bytes,3,opt,name=raw_grant,json=rawGrant,proto3" json:"raw_grant,omitempty" gorm:"default:null"` // canonical_grant is the canonical string representation of the grant value. // We use this as the unique constraint. - // @inject_tag: gorm:"primary_key" + // @inject_tag: `gorm:"primary_key"` CanonicalGrant string `protobuf:"bytes,4,opt,name=canonical_grant,json=canonicalGrant,proto3" json:"canonical_grant,omitempty" gorm:"primary_key"` } diff --git a/internal/iam/store/role_grant_scope.pb.go b/internal/iam/store/role_grant_scope.pb.go new file mode 100644 index 0000000000..a28b2a7435 --- /dev/null +++ b/internal/iam/store/role_grant_scope.pb.go @@ -0,0 +1,190 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.31.0 +// protoc (unknown) +// source: controller/storage/iam/store/v1/role_grant_scope.proto + +package store + +import ( + timestamp "github.com/hashicorp/boundary/internal/db/timestamp" + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +type RoleGrantScope struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // create_time from the RDBMS + // @inject_tag: `gorm:"default:current_timestamp"` + CreateTime *timestamp.Timestamp `protobuf:"bytes,1,opt,name=create_time,json=createTime,proto3" json:"create_time,omitempty" gorm:"default:current_timestamp"` + // role_id is the ID of the role this is a part of + // @inject_tag: `gorm:"primary_key"` + RoleId string `protobuf:"bytes,2,opt,name=role_id,json=roleId,proto3" json:"role_id,omitempty" gorm:"primary_key"` + // scope_id_or_special is the string grant scope value as provided by the + // user, which may be one of a few special values as well + // + // @inject_tag: `gorm:"primary_key"` + ScopeIdOrSpecial string `protobuf:"bytes,3,opt,name=scope_id_or_special,json=scopeIdOrSpecial,proto3" json:"scope_id_or_special,omitempty" gorm:"primary_key"` +} + +func (x *RoleGrantScope) Reset() { + *x = RoleGrantScope{} + if protoimpl.UnsafeEnabled { + mi := &file_controller_storage_iam_store_v1_role_grant_scope_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *RoleGrantScope) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*RoleGrantScope) ProtoMessage() {} + +func (x *RoleGrantScope) ProtoReflect() protoreflect.Message { + mi := &file_controller_storage_iam_store_v1_role_grant_scope_proto_msgTypes[0] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use RoleGrantScope.ProtoReflect.Descriptor instead. +func (*RoleGrantScope) Descriptor() ([]byte, []int) { + return file_controller_storage_iam_store_v1_role_grant_scope_proto_rawDescGZIP(), []int{0} +} + +func (x *RoleGrantScope) GetCreateTime() *timestamp.Timestamp { + if x != nil { + return x.CreateTime + } + return nil +} + +func (x *RoleGrantScope) GetRoleId() string { + if x != nil { + return x.RoleId + } + return "" +} + +func (x *RoleGrantScope) GetScopeIdOrSpecial() string { + if x != nil { + return x.ScopeIdOrSpecial + } + return "" +} + +var File_controller_storage_iam_store_v1_role_grant_scope_proto protoreflect.FileDescriptor + +var file_controller_storage_iam_store_v1_role_grant_scope_proto_rawDesc = []byte{ + 0x0a, 0x36, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2f, 0x73, 0x74, 0x6f, + 0x72, 0x61, 0x67, 0x65, 0x2f, 0x69, 0x61, 0x6d, 0x2f, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x2f, 0x76, + 0x31, 0x2f, 0x72, 0x6f, 0x6c, 0x65, 0x5f, 0x67, 0x72, 0x61, 0x6e, 0x74, 0x5f, 0x73, 0x63, 0x6f, + 0x70, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x1f, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, + 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x73, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x2e, 0x69, 0x61, 0x6d, + 0x2e, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x2e, 0x76, 0x31, 0x1a, 0x2f, 0x63, 0x6f, 0x6e, 0x74, 0x72, + 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2f, 0x73, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x2f, 0x74, 0x69, + 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x2f, 0x76, 0x31, 0x2f, 0x74, 0x69, 0x6d, 0x65, 0x73, + 0x74, 0x61, 0x6d, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xa5, 0x01, 0x0a, 0x0e, 0x52, + 0x6f, 0x6c, 0x65, 0x47, 0x72, 0x61, 0x6e, 0x74, 0x53, 0x63, 0x6f, 0x70, 0x65, 0x12, 0x4b, 0x0a, + 0x0b, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x2a, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, + 0x73, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x2e, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, + 0x70, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x0a, + 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x54, 0x69, 0x6d, 0x65, 0x12, 0x17, 0x0a, 0x07, 0x72, 0x6f, + 0x6c, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, 0x6f, 0x6c, + 0x65, 0x49, 0x64, 0x12, 0x2d, 0x0a, 0x13, 0x73, 0x63, 0x6f, 0x70, 0x65, 0x5f, 0x69, 0x64, 0x5f, + 0x6f, 0x72, 0x5f, 0x73, 0x70, 0x65, 0x63, 0x69, 0x61, 0x6c, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x10, 0x73, 0x63, 0x6f, 0x70, 0x65, 0x49, 0x64, 0x4f, 0x72, 0x53, 0x70, 0x65, 0x63, 0x69, + 0x61, 0x6c, 0x42, 0x38, 0x5a, 0x36, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, + 0x2f, 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x2f, 0x62, 0x6f, 0x75, 0x6e, 0x64, + 0x61, 0x72, 0x79, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2f, 0x69, 0x61, 0x6d, + 0x2f, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x3b, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x62, 0x06, 0x70, 0x72, + 0x6f, 0x74, 0x6f, 0x33, +} + +var ( + file_controller_storage_iam_store_v1_role_grant_scope_proto_rawDescOnce sync.Once + file_controller_storage_iam_store_v1_role_grant_scope_proto_rawDescData = file_controller_storage_iam_store_v1_role_grant_scope_proto_rawDesc +) + +func file_controller_storage_iam_store_v1_role_grant_scope_proto_rawDescGZIP() []byte { + file_controller_storage_iam_store_v1_role_grant_scope_proto_rawDescOnce.Do(func() { + file_controller_storage_iam_store_v1_role_grant_scope_proto_rawDescData = protoimpl.X.CompressGZIP(file_controller_storage_iam_store_v1_role_grant_scope_proto_rawDescData) + }) + return file_controller_storage_iam_store_v1_role_grant_scope_proto_rawDescData +} + +var file_controller_storage_iam_store_v1_role_grant_scope_proto_msgTypes = make([]protoimpl.MessageInfo, 1) +var file_controller_storage_iam_store_v1_role_grant_scope_proto_goTypes = []interface{}{ + (*RoleGrantScope)(nil), // 0: controller.storage.iam.store.v1.RoleGrantScope + (*timestamp.Timestamp)(nil), // 1: controller.storage.timestamp.v1.Timestamp +} +var file_controller_storage_iam_store_v1_role_grant_scope_proto_depIdxs = []int32{ + 1, // 0: controller.storage.iam.store.v1.RoleGrantScope.create_time:type_name -> controller.storage.timestamp.v1.Timestamp + 1, // [1:1] is the sub-list for method output_type + 1, // [1:1] is the sub-list for method input_type + 1, // [1:1] is the sub-list for extension type_name + 1, // [1:1] is the sub-list for extension extendee + 0, // [0:1] is the sub-list for field type_name +} + +func init() { file_controller_storage_iam_store_v1_role_grant_scope_proto_init() } +func file_controller_storage_iam_store_v1_role_grant_scope_proto_init() { + if File_controller_storage_iam_store_v1_role_grant_scope_proto != nil { + return + } + if !protoimpl.UnsafeEnabled { + file_controller_storage_iam_store_v1_role_grant_scope_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*RoleGrantScope); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_controller_storage_iam_store_v1_role_grant_scope_proto_rawDesc, + NumEnums: 0, + NumMessages: 1, + NumExtensions: 0, + NumServices: 0, + }, + GoTypes: file_controller_storage_iam_store_v1_role_grant_scope_proto_goTypes, + DependencyIndexes: file_controller_storage_iam_store_v1_role_grant_scope_proto_depIdxs, + MessageInfos: file_controller_storage_iam_store_v1_role_grant_scope_proto_msgTypes, + }.Build() + File_controller_storage_iam_store_v1_role_grant_scope_proto = out.File + file_controller_storage_iam_store_v1_role_grant_scope_proto_rawDesc = nil + file_controller_storage_iam_store_v1_role_grant_scope_proto_goTypes = nil + file_controller_storage_iam_store_v1_role_grant_scope_proto_depIdxs = nil +} diff --git a/internal/iam/testing.go b/internal/iam/testing.go index d83003ee95..5bebd86c66 100644 --- a/internal/iam/testing.go +++ b/internal/iam/testing.go @@ -8,6 +8,7 @@ import ( "crypto/rand" "testing" + "github.com/hashicorp/boundary/globals" "github.com/hashicorp/boundary/internal/auth/store" "github.com/hashicorp/boundary/internal/db" dbassert "github.com/hashicorp/boundary/internal/db/assert" @@ -185,9 +186,16 @@ func TestUser(t testing.TB, repo *Repository, scopeId string, opt ...Option) *Us return user } -// TestRole creates a role suitable for testing. +// TestRole creates a role suitable for testing. It will use a default grant +// scope ID unless WithGrantScopeId is used. To prevent a grant scope from being +// created, pass in a grant scope ID option with the value "testing-none". func TestRole(t testing.TB, conn *db.DB, scopeId string, opt ...Option) *Role { t.Helper() + opts := getOpts(opt...) + if opts.withGrantScopeId != nil && *opts.withGrantScopeId != "" && len(opts.withGrantScopeIds) > 0 { + require.FailNow(t, "cannot specify both withGrantScopeId and withGrantScopeIds") + } + ctx := context.Background() require := require.New(t) rw := db.New(conn) @@ -197,11 +205,30 @@ func TestRole(t testing.TB, conn *db.DB, scopeId string, opt ...Option) *Role { id, err := newRoleId(ctx) require.NoError(err) role.PublicId = id - err = rw.Create(ctx, role) - require.NoError(err) + require.NoError(rw.Create(ctx, role)) require.NotEmpty(role.PublicId) - opts := getOpts(opt...) + grantScopeIds := opts.withGrantScopeIds + if len(grantScopeIds) == 0 { + var scpId string + switch { + case opts.withGrantScopeId == nil: + scpId = globals.GrantScopeThis + case *opts.withGrantScopeId == "": + scpId = globals.GrantScopeThis + default: + scpId = *opts.withGrantScopeId + } + grantScopeIds = []string{scpId} + } + for _, gsi := range grantScopeIds { + if gsi == "testing-none" { + continue + } + gs, err := NewRoleGrantScope(ctx, id, gsi) + require.NoError(err) + require.NoError(rw.Create(ctx, gs)) + } require.Equal(opts.withDescription, role.Description) require.Equal(opts.withName, role.Name) return role @@ -219,6 +246,17 @@ func TestRoleGrant(t testing.TB, conn *db.DB, roleId, grant string, opt ...Optio return g } +func TestRoleGrantScope(t testing.TB, conn *db.DB, roleId, grantScopeId string, opt ...Option) *RoleGrantScope { + t.Helper() + require := require.New(t) + rw := db.New(conn) + + gs, err := NewRoleGrantScope(context.Background(), roleId, grantScopeId, opt...) + require.NoError(err) + require.NoError(rw.Create(context.Background(), gs)) + return gs +} + // TestGroup creates a group suitable for testing. func TestGroup(t testing.TB, conn *db.DB, scopeId string, opt ...Option) *Group { t.Helper() diff --git a/internal/perms/acl_test.go b/internal/perms/acl_test.go index 2154b7c7c8..0cde2e6e99 100644 --- a/internal/perms/acl_test.go +++ b/internal/perms/acl_test.go @@ -932,7 +932,7 @@ func Test_AnonRestrictions(t *testing.T) { if i == resource.Controller || i == resource.Worker { continue } - for j := action.Type(1); j <= action.ReApplyStoragePolicy; j++ { + for j := action.Type(1); j <= action.RemoveGrantScopes; j++ { id := "foobar" prefixes := globals.ResourcePrefixesFromType(resource.Type(i)) if len(prefixes) > 0 { diff --git a/internal/proto/controller/api/resources/roles/v1/role.proto b/internal/proto/controller/api/resources/roles/v1/role.proto index 0bb96b1c56..e8414a54e6 100644 --- a/internal/proto/controller/api/resources/roles/v1/role.proto +++ b/internal/proto/controller/api/resources/roles/v1/role.proto @@ -88,16 +88,27 @@ message Role { // The mutation will fail if the version does not match the latest known good version. uint32 version = 80; // @gotags: `class:"public"` - // The Scope the grants will apply to. If the Role is at the global scope, this can be an org or project. If the Role is at an org scope, this can be a project within the org. It is invalid for this to be anything other than the Role's scope when the Role's scope is a project. + // The Scope the grants will apply to. If the Role is at the global scope, + // this can be an org or project. If the Role is at an org scope, this can be + // a project within the org. It is invalid for this to be anything other than + // the Role's scope when the Role's scope is a project. + // + // Deprecated: Use "grant_scope_ids" instead. google.protobuf.StringValue grant_scope_id = 90 [ json_name = "grant_scope_id", - (custom_options.v1.generate_sdk_option) = true, - (custom_options.v1.mask_mapping) = { - this: "grant_scope_id" - that: "GrantScopeId" - } + deprecated = true, + (custom_options.v1.generate_sdk_option) = true ]; // @gotags: `class:"public" eventstream:"observation"` + // Output only. The IDs of Scopes the grants will apply to. This can include + // the role's own scope ID, or "this" for the same behavior; specific IDs of + // scopes that are children of the role's scope; the value "children" to match + // all direct child scopes of the role's scope; or the value "descendants" to + // match all descendant scopes (e.g. child scopes, children of child scopes; + // only valid at "global" scope since it is the only one with children of + // children). + repeated string grant_scope_ids = 91 [json_name = "grant_scope_ids"]; // @gotags: `class:"public"` + // Output only. The IDs (only) of principals that are assigned to this role. repeated string principal_ids = 100 [json_name = "principal_ids"]; // @gotags: `class:"public" eventstream:"observation"` diff --git a/internal/proto/controller/api/services/v1/role_service.proto b/internal/proto/controller/api/services/v1/role_service.proto index e94478762a..d6a63c7ad4 100644 --- a/internal/proto/controller/api/services/v1/role_service.proto +++ b/internal/proto/controller/api/services/v1/role_service.proto @@ -152,6 +152,46 @@ service RoleService { }; option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = {summary: "Removes grants from a Role."}; } + + // AddRoleGrantScopes adds grants scopes to a Role. The provided request must + // include the Role id which the grant scopes will be added to. An error is + // returned if the provided id is malformed or references a non-existing + // resource. + rpc AddRoleGrantScopes(AddRoleGrantScopesRequest) returns (AddRoleGrantScopesResponse) { + option (google.api.http) = { + post: "/v1/roles/{id}:add-grant-scopes" + body: "*" + response_body: "item" + }; + option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = {summary: "Adds grant scopes to a Role"}; + } + + // SetRoleGrants sets the Role's grant scopes. Any existing grant scopes on + // the Role are deleted if they are not included in this request. The provided + // request must include the Role ID on which the grants will be set. If + // missing, malformed, or referencing a non-existing resource, an error is + // returned. + rpc SetRoleGrantScopes(SetRoleGrantScopesRequest) returns (SetRoleGrantScopesResponse) { + option (google.api.http) = { + post: "/v1/roles/{id}:set-grant-scopes" + body: "*" + response_body: "item" + }; + option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = {summary: "Set grant scopes for a Role, removing any grant scopes that are not specified in the request."}; + } + + // RemoveRoleGrantScopes removes the grant scopes from the specified Role. The + // provided request must include the Role IDs from which the grants will be + // removed. If missing, malformed, or references a non-existing resource, an + // error is returned. + rpc RemoveRoleGrantScopes(RemoveRoleGrantScopesRequest) returns (RemoveRoleGrantScopesResponse) { + option (google.api.http) = { + post: "/v1/roles/{id}:remove-grant-scopes" + body: "*" + response_body: "item" + }; + option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = {summary: "Removes grant scopes from a Role."}; + } } message GetRoleRequest { @@ -305,3 +345,39 @@ message RemoveRoleGrantsRequest { message RemoveRoleGrantsResponse { resources.roles.v1.Role item = 1; } + +message AddRoleGrantScopesRequest { + string id = 1; // @gotags: `class:"public"` + // Version is used to ensure this resource has not changed. + // The mutation will fail if the version does not match the latest known good version. + uint32 version = 2; // @gotags: `class:"public"` + repeated string grant_scope_ids = 3 [json_name = "grant_scope_ids"]; // @gotags: `class:"public"` +} + +message AddRoleGrantScopesResponse { + resources.roles.v1.Role item = 1; +} + +message SetRoleGrantScopesRequest { + string id = 1; // @gotags: `class:"public"` + // Version is used to ensure this resource has not changed. + // The mutation will fail if the version does not match the latest known good version. + uint32 version = 2; // @gotags: `class:"public"` + repeated string grant_scope_ids = 3 [json_name = "grant_scope_ids"]; // @gotags: `class:"public"` +} + +message SetRoleGrantScopesResponse { + resources.roles.v1.Role item = 1; +} + +message RemoveRoleGrantScopesRequest { + string id = 1; // @gotags: `class:"public"` + // Version is used to ensure this resource has not changed. + // The mutation will fail if the version does not match the latest known good version. + uint32 version = 2; // @gotags: `class:"public"` + repeated string grant_scope_ids = 3 [json_name = "grant_scope_ids"]; // @gotags: `class:"public"` +} + +message RemoveRoleGrantScopesResponse { + resources.roles.v1.Role item = 1; +} diff --git a/internal/proto/controller/storage/iam/store/v1/role.proto b/internal/proto/controller/storage/iam/store/v1/role.proto index 0ad7fd531c..9d4814c31f 100644 --- a/internal/proto/controller/storage/iam/store/v1/role.proto +++ b/internal/proto/controller/storage/iam/store/v1/role.proto @@ -47,11 +47,6 @@ message Role { // @inject_tag: `gorm:"default:null"` uint32 version = 70; - // grant_scope_id is used for delegating access; it defines a scope other than - // the role's scope that is used when compiling these grants into an ACL - // @inject_tag: `gorm:"default:null"` - string grant_scope_id = 80 [(custom_options.v1.mask_mapping) = { - this: "GrantScopeId" - that: "grant_scope_id" - }]; + // Previously grant_scope_id + reserved 80; } diff --git a/internal/proto/controller/storage/iam/store/v1/role_grant.proto b/internal/proto/controller/storage/iam/store/v1/role_grant.proto index 9617ecee87..7821366060 100644 --- a/internal/proto/controller/storage/iam/store/v1/role_grant.proto +++ b/internal/proto/controller/storage/iam/store/v1/role_grant.proto @@ -15,7 +15,7 @@ message RoleGrant { timestamp.v1.Timestamp create_time = 1; // role_id is the ID of the role this is a part of - // @inject_tag: gorm:"primary_key" + // @inject_tag: `gorm:"primary_key"` string role_id = 2; // raw_grant is the string grant value as provided by the user @@ -24,6 +24,6 @@ message RoleGrant { // canonical_grant is the canonical string representation of the grant value. // We use this as the unique constraint. - // @inject_tag: gorm:"primary_key" + // @inject_tag: `gorm:"primary_key"` string canonical_grant = 4; } diff --git a/internal/proto/controller/storage/iam/store/v1/role_grant_scope.proto b/internal/proto/controller/storage/iam/store/v1/role_grant_scope.proto new file mode 100644 index 0000000000..464b9683ba --- /dev/null +++ b/internal/proto/controller/storage/iam/store/v1/role_grant_scope.proto @@ -0,0 +1,26 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +syntax = "proto3"; + +package controller.storage.iam.store.v1; + +import "controller/storage/timestamp/v1/timestamp.proto"; + +option go_package = "github.com/hashicorp/boundary/internal/iam/store;store"; + +message RoleGrantScope { + // create_time from the RDBMS + // @inject_tag: `gorm:"default:current_timestamp"` + timestamp.v1.Timestamp create_time = 1; + + // role_id is the ID of the role this is a part of + // @inject_tag: `gorm:"primary_key"` + string role_id = 2; + + // scope_id_or_special is the string grant scope value as provided by the + // user, which may be one of a few special values as well + // + // @inject_tag: `gorm:"primary_key"` + string scope_id_or_special = 3; +} diff --git a/internal/tests/api/hostcatalogs/host_catalog_test.go b/internal/tests/api/hostcatalogs/host_catalog_test.go index 6e6fb1724e..1462ec1b3c 100644 --- a/internal/tests/api/hostcatalogs/host_catalog_test.go +++ b/internal/tests/api/hostcatalogs/host_catalog_test.go @@ -147,18 +147,23 @@ func TestCrud(t *testing.T) { hcClient := hostcatalogs.NewClient(client) hc, err := hcClient.Create(tc.Context(), "static", proj.GetPublicId(), hostcatalogs.WithName("foo")) + require.NoError(err) checkCatalog("create", hc.Item, err, "foo", 1) hc, err = hcClient.Read(tc.Context(), hc.Item.Id) + require.NoError(err) checkCatalog("read", hc.Item, err, "foo", 1) hc, err = hcClient.Update(tc.Context(), hc.Item.Id, hc.Item.Version, hostcatalogs.WithName("foo")) + require.NoError(err) checkCatalog("update", hc.Item, err, "foo", 1) hc, err = hcClient.Update(tc.Context(), hc.Item.Id, hc.Item.Version, hostcatalogs.WithName("bar")) + require.NoError(err) checkCatalog("update", hc.Item, err, "bar", 2) hc, err = hcClient.Update(tc.Context(), hc.Item.Id, hc.Item.Version, hostcatalogs.DefaultName()) + require.NoError(err) checkCatalog("update", hc.Item, err, "", 3) _, err = hcClient.Delete(tc.Context(), hc.Item.Id) diff --git a/internal/tests/api/roles/classification_test.go b/internal/tests/api/roles/classification_test.go index ec5fd9f3c7..02d289992e 100644 --- a/internal/tests/api/roles/classification_test.go +++ b/internal/tests/api/roles/classification_test.go @@ -9,6 +9,7 @@ import ( "testing" "time" + "github.com/hashicorp/boundary/globals" "github.com/hashicorp/boundary/sdk/pbs/controller/api" pb "github.com/hashicorp/boundary/sdk/pbs/controller/api/resources/roles" "github.com/hashicorp/boundary/sdk/pbs/controller/api/resources/scopes" @@ -53,6 +54,10 @@ func TestRoles(t *testing.T) { UpdatedTime: pbNow, Version: 0, GrantScopeId: &wrapperspb.StringValue{Value: "grant-scope-id"}, + GrantScopeIds: []string{ + globals.GrantScopeThis, + "grant-scope-id", + }, PrincipalIds: []string{ "principal-id", }, @@ -99,6 +104,10 @@ func TestRoles(t *testing.T) { UpdatedTime: pbNow, Version: 0, GrantScopeId: &wrapperspb.StringValue{Value: "grant-scope-id"}, + GrantScopeIds: []string{ + globals.GrantScopeThis, + "grant-scope-id", + }, PrincipalIds: []string{ "principal-id", }, diff --git a/internal/tests/api/roles/role_test.go b/internal/tests/api/roles/role_test.go index f8fbf865a2..f5341585fb 100644 --- a/internal/tests/api/roles/role_test.go +++ b/internal/tests/api/roles/role_test.go @@ -69,7 +69,7 @@ func TestCustom(t *testing.T) { require.NotNil(g) rc := roles.NewClient(client) - var version uint32 = 1 + var version uint32 = 2 r, err := rc.Create(tc.Context(), tt.scopeId, roles.WithName("foo")) require.NoError(err) @@ -241,16 +241,16 @@ func TestCrud(t *testing.T) { t.Run(tt.name, func(t *testing.T) { roleClient := roles.NewClient(client) g, err := roleClient.Create(tc.Context(), tt.scopeId, roles.WithName("foo")) - checkRole("create", g.Item, err, "foo", 1) + checkRole("create", g.Item, err, "foo", 2) g, err = roleClient.Read(tc.Context(), g.Item.Id) - checkRole("read", g.Item, err, "foo", 1) + checkRole("read", g.Item, err, "foo", 2) g, err = roleClient.Update(tc.Context(), g.Item.Id, g.Item.Version, roles.WithName("bar")) - checkRole("update", g.Item, err, "bar", 2) + checkRole("update", g.Item, err, "bar", 3) g, err = roleClient.Update(tc.Context(), g.Item.Id, g.Item.Version, roles.DefaultName()) - checkRole("update", g.Item, err, "", 3) + checkRole("update", g.Item, err, "", 4) _, err = roleClient.Delete(tc.Context(), g.Item.Id) require.NoError(err) diff --git a/internal/tests/cli/boundary/target.bats b/internal/tests/cli/boundary/target.bats index 8fc83606fe..0f59409d9e 100644 --- a/internal/tests/cli/boundary/target.bats +++ b/internal/tests/cli/boundary/target.bats @@ -32,9 +32,9 @@ load _target_host_sources [ "$status" -eq 0 ] } -@test "boundary/target: unpriv user can not read default target" { +@test "boundary/target: unpriv user can read default target" { run read_target $DEFAULT_TARGET - [ "$status" -eq 1 ] + [ "$status" -eq 0 ] } @test "boundary/login: login back in as admin user" { diff --git a/internal/tests/cluster/recursive_anon_listing_test.go b/internal/tests/cluster/recursive_anon_listing_test.go index 0c3170dba2..9ef58ad68e 100644 --- a/internal/tests/cluster/recursive_anon_listing_test.go +++ b/internal/tests/cluster/recursive_anon_listing_test.go @@ -27,6 +27,14 @@ func TestListAnonymousRecursing(t *testing.T) { rolesClient := rolesapi.NewClient(client) orgScopeId := "o_1234567890" + // Create a custom role in org scope + customRole, err := rolesClient.Create(tc.Context(), orgScopeId) + require.NoError(err) + customRole, err = rolesClient.AddPrincipals(tc.Context(), customRole.Item.Id, customRole.Item.Version, []string{"u_anon"}) + require.NoError(err) + _, err = rolesClient.AddGrants(tc.Context(), customRole.Item.Id, customRole.Item.Version, []string{"id=*;type=auth-method;actions=list,authenticate"}) + require.NoError(err) + // Create an auth method in org scope for the test am, err := amClient.Create(tc.Context(), "password", orgScopeId) require.NoError(err) @@ -49,18 +57,19 @@ func TestListAnonymousRecursing(t *testing.T) { rl, err := rolesClient.List(tc.Context(), scope.Global.String()) require.NoError(err) require.NotNil(rl) - require.Len(rl.GetItems(), 2) + require.Len(rl.GetItems(), 3) // Find the non-admin one and delete that first - adminIdx := 0 - defaultIdx := 1 - roles := rl.GetItems() - if strings.Contains(roles[0].Name, "Default") { - adminIdx, defaultIdx = 1, 0 + adminRoleId := "" + for _, role := range rl.GetItems() { + if strings.Contains(role.Name, "Admin") { + adminRoleId = role.Id + } else { + _, err = rolesClient.Delete(tc.Context(), role.Id) + require.NoError(err) + } } - _, err = rolesClient.Delete(tc.Context(), roles[defaultIdx].Id) - require.NoError(err) - _, err = rolesClient.Delete(tc.Context(), roles[adminIdx].Id) + _, err = rolesClient.Delete(tc.Context(), adminRoleId) require.NoError(err) // Make sure we can't list in global diff --git a/internal/tests/cluster/unix_listener_test.go b/internal/tests/cluster/unix_listener_test.go index a0b7902237..cf475096ad 100644 --- a/internal/tests/cluster/unix_listener_test.go +++ b/internal/tests/cluster/unix_listener_test.go @@ -4,6 +4,7 @@ package cluster import ( + "bytes" "context" "os" "path" @@ -15,6 +16,7 @@ import ( "github.com/hashicorp/boundary/internal/cmd/config" "github.com/hashicorp/boundary/internal/daemon/controller" "github.com/hashicorp/boundary/internal/daemon/worker" + "github.com/hashicorp/boundary/internal/event" "github.com/hashicorp/boundary/internal/tests/helper" "github.com/hashicorp/go-hclog" "github.com/stretchr/testify/require" @@ -22,6 +24,7 @@ import ( func TestUnixListener(t *testing.T) { require := require.New(t) + buf := new(bytes.Buffer) logger := hclog.New(&hclog.LoggerOptions{ Level: hclog.Trace, }) @@ -52,6 +55,23 @@ func TestUnixListener(t *testing.T) { Config: conf, Logger: logger.Named("c1"), DisableOidcAuthMethodCreation: true, + EventerConfig: &event.EventerConfig{ + ObservationsEnabled: true, + SysEventsEnabled: true, + Sinks: []*event.SinkConfig{ + { + Name: "output", + Type: event.WriterSink, + EventTypes: []event.Type{ + event.EveryType, + }, + WriterConfig: &event.WriterSinkTypeConfig{ + Writer: buf, + }, + Format: event.TextHclogSinkFormat, + }, + }, + }, }) defer c1.Shutdown() @@ -80,6 +100,23 @@ func TestUnixListener(t *testing.T) { Config: conf, Logger: logger.Named("c1"), DisableOidcAuthMethodCreation: true, + EventerConfig: &event.EventerConfig{ + ObservationsEnabled: true, + SysEventsEnabled: true, + Sinks: []*event.SinkConfig{ + { + Name: "output", + Type: event.WriterSink, + EventTypes: []event.Type{ + event.EveryType, + }, + WriterConfig: &event.WriterSinkTypeConfig{ + Writer: buf, + }, + Format: event.TextHclogSinkFormat, + }, + }, + }, }) defer c1.Shutdown() diff --git a/internal/types/action/action.go b/internal/types/action/action.go index 997cb8c1ba..f5045182b2 100644 --- a/internal/types/action/action.go +++ b/internal/types/action/action.go @@ -71,6 +71,9 @@ const ( AttachStoragePolicy Type = 57 DetachStoragePolicy Type = 58 ReApplyStoragePolicy Type = 59 + AddGrantScopes Type = 60 + SetGrantScopes Type = 61 + RemoveGrantScopes Type = 62 // When adding new actions, be sure to update: // @@ -138,6 +141,9 @@ var Map = map[string]Type{ AttachStoragePolicy.String(): AttachStoragePolicy, DetachStoragePolicy.String(): DetachStoragePolicy, ReApplyStoragePolicy.String(): ReApplyStoragePolicy, + AddGrantScopes.String(): AddGrantScopes, + SetGrantScopes.String(): SetGrantScopes, + RemoveGrantScopes.String(): RemoveGrantScopes, } var DeprecatedMap = map[string]Type{ @@ -211,6 +217,9 @@ func (a Type) String() string { "attach-storage-policy", "detach-storage-policy", "reapply-storage-policy", + "add-grant-scopes", + "set-grant-scopes", + "remove-grant-scopes", }[a] } diff --git a/sdk/pbs/controller/api/resources/roles/role.pb.go b/sdk/pbs/controller/api/resources/roles/role.pb.go index eef45f303d..cc45cbf67e 100644 --- a/sdk/pbs/controller/api/resources/roles/role.pb.go +++ b/sdk/pbs/controller/api/resources/roles/role.pb.go @@ -261,8 +261,23 @@ type Role struct { // Version is used in mutation requests, after the initial creation, to ensure this resource has not changed. // The mutation will fail if the version does not match the latest known good version. Version uint32 `protobuf:"varint,80,opt,name=version,proto3" json:"version,omitempty" class:"public"` // @gotags: `class:"public"` - // The Scope the grants will apply to. If the Role is at the global scope, this can be an org or project. If the Role is at an org scope, this can be a project within the org. It is invalid for this to be anything other than the Role's scope when the Role's scope is a project. + // The Scope the grants will apply to. If the Role is at the global scope, + // this can be an org or project. If the Role is at an org scope, this can be + // a project within the org. It is invalid for this to be anything other than + // the Role's scope when the Role's scope is a project. + // + // Deprecated: Use "grant_scope_ids" instead. + // + // Deprecated: Marked as deprecated in controller/api/resources/roles/v1/role.proto. GrantScopeId *wrapperspb.StringValue `protobuf:"bytes,90,opt,name=grant_scope_id,proto3" json:"grant_scope_id,omitempty" class:"public" eventstream:"observation"` // @gotags: `class:"public" eventstream:"observation"` + // Output only. The IDs of Scopes the grants will apply to. This can include + // the role's own scope ID, or "this" for the same behavior; specific IDs of + // scopes that are children of the role's scope; the value "children" to match + // all direct child scopes of the role's scope; or the value "descendants" to + // match all descendant scopes (e.g. child scopes, children of child scopes; + // only valid at "global" scope since it is the only one with children of + // children). + GrantScopeIds []string `protobuf:"bytes,91,rep,name=grant_scope_ids,proto3" json:"grant_scope_ids,omitempty" class:"public"` // @gotags: `class:"public"` // Output only. The IDs (only) of principals that are assigned to this role. PrincipalIds []string `protobuf:"bytes,100,rep,name=principal_ids,proto3" json:"principal_ids,omitempty" class:"public" eventstream:"observation"` // @gotags: `class:"public" eventstream:"observation"` // Output only. The principals that are assigned to this role. @@ -363,6 +378,7 @@ func (x *Role) GetVersion() uint32 { return 0 } +// Deprecated: Marked as deprecated in controller/api/resources/roles/v1/role.proto. func (x *Role) GetGrantScopeId() *wrapperspb.StringValue { if x != nil { return x.GrantScopeId @@ -370,6 +386,13 @@ func (x *Role) GetGrantScopeId() *wrapperspb.StringValue { return nil } +func (x *Role) GetGrantScopeIds() []string { + if x != nil { + return x.GrantScopeIds + } + return nil +} + func (x *Role) GetPrincipalIds() []string { if x != nil { return x.PrincipalIds @@ -441,7 +464,7 @@ var file_controller_api_resources_roles_v1_role_proto_rawDesc = []byte{ 0x01, 0x28, 0x0b, 0x32, 0x2c, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x2e, 0x72, 0x6f, 0x6c, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x72, 0x61, 0x6e, 0x74, 0x4a, 0x73, 0x6f, - 0x6e, 0x52, 0x04, 0x6a, 0x73, 0x6f, 0x6e, 0x22, 0xb9, 0x06, 0x0a, 0x04, 0x52, 0x6f, 0x6c, 0x65, + 0x6e, 0x52, 0x04, 0x6a, 0x73, 0x6f, 0x6e, 0x22, 0xc3, 0x06, 0x0a, 0x04, 0x52, 0x6f, 0x6c, 0x65, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x1a, 0x0a, 0x08, 0x73, 0x63, 0x6f, 0x70, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x14, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x73, 0x63, 0x6f, 0x70, 0x65, 0x5f, 0x69, 0x64, 0x12, 0x43, 0x0a, 0x05, @@ -469,36 +492,37 @@ var file_controller_api_resources_roles_v1_role_proto_rawDesc = []byte{ 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x0c, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x50, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x07, - 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x6c, 0x0a, 0x0e, 0x67, 0x72, 0x61, 0x6e, 0x74, + 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x4c, 0x0a, 0x0e, 0x67, 0x72, 0x61, 0x6e, 0x74, 0x5f, 0x73, 0x63, 0x6f, 0x70, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x5a, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, - 0x66, 0x2e, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x42, 0x26, 0xa0, - 0xda, 0x29, 0x01, 0xc2, 0xdd, 0x29, 0x1e, 0x0a, 0x0e, 0x67, 0x72, 0x61, 0x6e, 0x74, 0x5f, 0x73, - 0x63, 0x6f, 0x70, 0x65, 0x5f, 0x69, 0x64, 0x12, 0x0c, 0x47, 0x72, 0x61, 0x6e, 0x74, 0x53, 0x63, - 0x6f, 0x70, 0x65, 0x49, 0x64, 0x52, 0x0e, 0x67, 0x72, 0x61, 0x6e, 0x74, 0x5f, 0x73, 0x63, 0x6f, - 0x70, 0x65, 0x5f, 0x69, 0x64, 0x12, 0x24, 0x0a, 0x0d, 0x70, 0x72, 0x69, 0x6e, 0x63, 0x69, 0x70, - 0x61, 0x6c, 0x5f, 0x69, 0x64, 0x73, 0x18, 0x64, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0d, 0x70, 0x72, - 0x69, 0x6e, 0x63, 0x69, 0x70, 0x61, 0x6c, 0x5f, 0x69, 0x64, 0x73, 0x12, 0x4c, 0x0a, 0x0a, 0x70, - 0x72, 0x69, 0x6e, 0x63, 0x69, 0x70, 0x61, 0x6c, 0x73, 0x18, 0x6e, 0x20, 0x03, 0x28, 0x0b, 0x32, - 0x2c, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, - 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x2e, 0x72, 0x6f, 0x6c, 0x65, 0x73, - 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x72, 0x69, 0x6e, 0x63, 0x69, 0x70, 0x61, 0x6c, 0x52, 0x0a, 0x70, - 0x72, 0x69, 0x6e, 0x63, 0x69, 0x70, 0x61, 0x6c, 0x73, 0x12, 0x24, 0x0a, 0x0d, 0x67, 0x72, 0x61, - 0x6e, 0x74, 0x5f, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x73, 0x18, 0x78, 0x20, 0x03, 0x28, 0x09, - 0x52, 0x0d, 0x67, 0x72, 0x61, 0x6e, 0x74, 0x5f, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x73, 0x12, - 0x41, 0x0a, 0x06, 0x67, 0x72, 0x61, 0x6e, 0x74, 0x73, 0x18, 0x82, 0x01, 0x20, 0x03, 0x28, 0x0b, - 0x32, 0x28, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x61, 0x70, - 0x69, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x2e, 0x72, 0x6f, 0x6c, 0x65, - 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x72, 0x61, 0x6e, 0x74, 0x52, 0x06, 0x67, 0x72, 0x61, 0x6e, - 0x74, 0x73, 0x12, 0x2f, 0x0a, 0x12, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x64, - 0x5f, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0xac, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52, - 0x12, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x64, 0x5f, 0x61, 0x63, 0x74, 0x69, - 0x6f, 0x6e, 0x73, 0x42, 0x4c, 0x5a, 0x4a, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, - 0x6d, 0x2f, 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x2f, 0x62, 0x6f, 0x75, 0x6e, - 0x64, 0x61, 0x72, 0x79, 0x2f, 0x73, 0x64, 0x6b, 0x2f, 0x70, 0x62, 0x73, 0x2f, 0x63, 0x6f, 0x6e, - 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x72, 0x65, 0x73, 0x6f, - 0x75, 0x72, 0x63, 0x65, 0x73, 0x2f, 0x72, 0x6f, 0x6c, 0x65, 0x73, 0x3b, 0x72, 0x6f, 0x6c, 0x65, - 0x73, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x66, 0x2e, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x42, 0x06, 0xa0, + 0xda, 0x29, 0x01, 0x18, 0x01, 0x52, 0x0e, 0x67, 0x72, 0x61, 0x6e, 0x74, 0x5f, 0x73, 0x63, 0x6f, + 0x70, 0x65, 0x5f, 0x69, 0x64, 0x12, 0x28, 0x0a, 0x0f, 0x67, 0x72, 0x61, 0x6e, 0x74, 0x5f, 0x73, + 0x63, 0x6f, 0x70, 0x65, 0x5f, 0x69, 0x64, 0x73, 0x18, 0x5b, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0f, + 0x67, 0x72, 0x61, 0x6e, 0x74, 0x5f, 0x73, 0x63, 0x6f, 0x70, 0x65, 0x5f, 0x69, 0x64, 0x73, 0x12, + 0x24, 0x0a, 0x0d, 0x70, 0x72, 0x69, 0x6e, 0x63, 0x69, 0x70, 0x61, 0x6c, 0x5f, 0x69, 0x64, 0x73, + 0x18, 0x64, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0d, 0x70, 0x72, 0x69, 0x6e, 0x63, 0x69, 0x70, 0x61, + 0x6c, 0x5f, 0x69, 0x64, 0x73, 0x12, 0x4c, 0x0a, 0x0a, 0x70, 0x72, 0x69, 0x6e, 0x63, 0x69, 0x70, + 0x61, 0x6c, 0x73, 0x18, 0x6e, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2c, 0x2e, 0x63, 0x6f, 0x6e, 0x74, + 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, + 0x72, 0x63, 0x65, 0x73, 0x2e, 0x72, 0x6f, 0x6c, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x72, + 0x69, 0x6e, 0x63, 0x69, 0x70, 0x61, 0x6c, 0x52, 0x0a, 0x70, 0x72, 0x69, 0x6e, 0x63, 0x69, 0x70, + 0x61, 0x6c, 0x73, 0x12, 0x24, 0x0a, 0x0d, 0x67, 0x72, 0x61, 0x6e, 0x74, 0x5f, 0x73, 0x74, 0x72, + 0x69, 0x6e, 0x67, 0x73, 0x18, 0x78, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0d, 0x67, 0x72, 0x61, 0x6e, + 0x74, 0x5f, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x73, 0x12, 0x41, 0x0a, 0x06, 0x67, 0x72, 0x61, + 0x6e, 0x74, 0x73, 0x18, 0x82, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x28, 0x2e, 0x63, 0x6f, 0x6e, + 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x72, 0x65, 0x73, 0x6f, + 0x75, 0x72, 0x63, 0x65, 0x73, 0x2e, 0x72, 0x6f, 0x6c, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x47, + 0x72, 0x61, 0x6e, 0x74, 0x52, 0x06, 0x67, 0x72, 0x61, 0x6e, 0x74, 0x73, 0x12, 0x2f, 0x0a, 0x12, + 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x64, 0x5f, 0x61, 0x63, 0x74, 0x69, 0x6f, + 0x6e, 0x73, 0x18, 0xac, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52, 0x12, 0x61, 0x75, 0x74, 0x68, 0x6f, + 0x72, 0x69, 0x7a, 0x65, 0x64, 0x5f, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x42, 0x4c, 0x5a, + 0x4a, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x68, 0x61, 0x73, 0x68, + 0x69, 0x63, 0x6f, 0x72, 0x70, 0x2f, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x61, 0x72, 0x79, 0x2f, 0x73, + 0x64, 0x6b, 0x2f, 0x70, 0x62, 0x73, 0x2f, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, + 0x72, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x2f, + 0x72, 0x6f, 0x6c, 0x65, 0x73, 0x3b, 0x72, 0x6f, 0x6c, 0x65, 0x73, 0x62, 0x06, 0x70, 0x72, 0x6f, + 0x74, 0x6f, 0x33, } var (