2021-05-04 19:57:35 -05:00
// Copyright (c) HashiCorp, Inc.
2022-11-16 17:17:28 -06:00
// SPDX-License-Identifier: BUSL-1.1
2023-02-02 17:15:17 -08:00
2020-07-22 00:14:28 -04:00
package scopes
import (
"context"
"fmt"
"sort"
"strings"
2021-05-04 19:57:35 -05:00
"github.com/hashicorp/boundary/globals"
2022-05-09 12:45:10 -07:00
"github.com/hashicorp/boundary/internal/daemon/controller/auth"
"github.com/hashicorp/boundary/internal/daemon/controller/common"
"github.com/hashicorp/boundary/internal/daemon/controller/common/scopeids"
"github.com/hashicorp/boundary/internal/daemon/controller/handlers"
2024-01-31 09:54:12 -08:00
"github.com/hashicorp/boundary/internal/daemon/controller/handlers/aliases"
2022-05-09 12:45:10 -07:00
"github.com/hashicorp/boundary/internal/daemon/controller/handlers/authmethods"
"github.com/hashicorp/boundary/internal/daemon/controller/handlers/authtokens"
"github.com/hashicorp/boundary/internal/daemon/controller/handlers/credentialstores"
"github.com/hashicorp/boundary/internal/daemon/controller/handlers/groups"
"github.com/hashicorp/boundary/internal/daemon/controller/handlers/host_catalogs"
2023-11-22 18:02:26 +00:00
"github.com/hashicorp/boundary/internal/daemon/controller/handlers/policies"
2022-05-09 12:45:10 -07:00
"github.com/hashicorp/boundary/internal/daemon/controller/handlers/roles"
2023-05-05 16:18:25 -07:00
"github.com/hashicorp/boundary/internal/daemon/controller/handlers/session_recordings"
2022-05-09 12:45:10 -07:00
"github.com/hashicorp/boundary/internal/daemon/controller/handlers/sessions"
2023-04-28 22:44:49 -07:00
"github.com/hashicorp/boundary/internal/daemon/controller/handlers/storage_buckets"
2022-05-09 12:45:10 -07:00
"github.com/hashicorp/boundary/internal/daemon/controller/handlers/targets"
"github.com/hashicorp/boundary/internal/daemon/controller/handlers/users"
2022-06-05 16:43:02 -04:00
"github.com/hashicorp/boundary/internal/daemon/controller/handlers/workers"
2021-04-16 12:06:03 -05:00
"github.com/hashicorp/boundary/internal/errors"
2020-08-10 17:15:02 -04:00
pbs "github.com/hashicorp/boundary/internal/gen/controller/api/services"
"github.com/hashicorp/boundary/internal/iam"
"github.com/hashicorp/boundary/internal/iam/store"
2022-11-16 17:17:28 -06:00
"github.com/hashicorp/boundary/internal/kms"
2024-01-02 17:19:29 -08:00
"github.com/hashicorp/boundary/internal/listtoken"
"github.com/hashicorp/boundary/internal/pagination"
2021-01-20 10:53:01 -05:00
"github.com/hashicorp/boundary/internal/perms"
2021-05-04 19:57:35 -05:00
"github.com/hashicorp/boundary/internal/requests"
2020-09-04 14:48:11 -07:00
"github.com/hashicorp/boundary/internal/types/action"
"github.com/hashicorp/boundary/internal/types/resource"
2020-08-10 17:15:02 -04:00
"github.com/hashicorp/boundary/internal/types/scope"
2022-11-16 17:17:28 -06:00
"github.com/hashicorp/boundary/internal/util"
2021-08-26 10:44:41 -04:00
pb "github.com/hashicorp/boundary/sdk/pbs/controller/api/resources/scopes"
2022-11-16 17:17:28 -06:00
wrappingKms "github.com/hashicorp/go-kms-wrapping/extras/kms/v2"
2020-07-22 00:14:28 -04:00
"google.golang.org/grpc/codes"
2024-01-03 17:54:00 -08:00
"google.golang.org/grpc/status"
2022-11-16 17:17:28 -06:00
"google.golang.org/protobuf/types/known/timestamppb"
2020-07-22 00:14:28 -04:00
"google.golang.org/protobuf/types/known/wrapperspb"
)
2021-01-20 10:53:01 -05:00
var (
maskManager handlers . MaskManager
// IdActions contains the set of actions that can be performed on
// individual resources
2023-10-31 13:44:17 +00:00
IdActions = action . NewActionSet (
2021-04-23 09:13:37 -05:00
action . NoOp ,
2021-01-20 10:53:01 -05:00
action . Read ,
action . Update ,
action . Delete ,
2024-01-08 22:14:24 -08:00
action . AttachStoragePolicy ,
action . DetachStoragePolicy ,
2023-10-31 13:44:17 +00:00
)
2021-01-20 10:53:01 -05:00
// CollectionActions contains the set of actions that can be performed on
// this collection
2023-10-31 13:44:17 +00:00
CollectionActions = action . NewActionSet (
2021-01-20 10:53:01 -05:00
action . Create ,
action . List ,
2022-11-16 17:17:28 -06:00
action . ListScopeKeys ,
action . RotateScopeKeys ,
action . ListScopeKeyVersionDestructionJobs ,
action . DestroyScopeKeyVersion ,
2023-10-31 13:44:17 +00:00
)
2021-01-20 10:53:01 -05:00
2023-10-11 18:26:16 +00:00
// TODO: get this from action registry
2021-05-04 19:57:35 -05:00
scopeCollectionTypeMapMap = map [ string ] map [ resource . Type ] action . ActionSet {
scope . Global . String (): {
2024-01-31 09:54:12 -08:00
resource . Alias : aliases . CollectionActions ,
2023-05-05 16:18:25 -07:00
resource . AuthMethod : authmethods . CollectionActions ,
resource . StorageBucket : storage_buckets . CollectionActions ,
resource . AuthToken : authtokens . CollectionActions ,
resource . Group : groups . CollectionActions ,
resource . Role : roles . CollectionActions ,
resource . Scope : CollectionActions ,
resource . User : users . CollectionActions ,
resource . Worker : workers . CollectionActions ,
resource . SessionRecording : session_recordings . CollectionActions ,
2023-11-22 18:02:26 +00:00
resource . Policy : policies . CollectionActions ,
2021-05-04 19:57:35 -05:00
},
scope . Org . String (): {
2023-05-05 16:18:25 -07:00
resource . AuthMethod : authmethods . CollectionActions ,
resource . StorageBucket : storage_buckets . CollectionActions ,
resource . AuthToken : authtokens . CollectionActions ,
resource . Group : groups . CollectionActions ,
resource . Role : roles . CollectionActions ,
resource . Scope : CollectionActions ,
resource . User : users . CollectionActions ,
resource . SessionRecording : session_recordings . CollectionActions ,
2023-11-22 18:02:26 +00:00
resource . Policy : policies . CollectionActions ,
2021-05-04 19:57:35 -05:00
},
scope . Project . String (): {
2021-09-13 13:56:44 -04:00
resource . CredentialStore : credentialstores . CollectionActions ,
resource . Group : groups . CollectionActions ,
resource . HostCatalog : host_catalogs . CollectionActions ,
resource . Role : roles . CollectionActions ,
2023-10-31 13:44:17 +00:00
resource . Scope : action . NewActionSet (
action . ListScopeKeys ,
action . RotateScopeKeys ,
action . ListScopeKeyVersionDestructionJobs ,
action . DestroyScopeKeyVersion ,
), // Only Scope key actions are allowed on the project level
resource . Session : sessions . CollectionActions ,
resource . Target : targets . CollectionActions ,
2021-05-04 19:57:35 -05:00
},
2021-01-20 10:53:01 -05:00
}
)
2020-07-22 00:14:28 -04:00
func init () {
var err error
2023-06-06 17:09:08 -04:00
if maskManager , err = handlers . NewMaskManager (
context . Background (),
handlers . MaskDestination { & store . Scope {}},
handlers . MaskSource { & pb . Scope {}},
); err != nil {
2020-07-22 00:14:28 -04:00
panic ( err )
}
2023-10-11 18:26:16 +00:00
// TODO: refactor to remove IdActions and CollectionActions package variables
action . RegisterResource ( resource . Scope , IdActions , CollectionActions )
2020-07-22 00:14:28 -04:00
}
// Service handles requests as described by the pbs.ScopeServiceServer interface.
type Service struct {
2022-08-30 14:22:10 -04:00
pbs . UnsafeScopeServiceServer
2020-11-11 14:41:05 -05:00
2024-01-02 17:19:29 -08:00
repoFn common . IamRepoFactory
kmsRepo * kms . Kms
maxPageSize uint
2020-07-22 00:14:28 -04:00
}
2022-08-30 14:22:10 -04:00
var _ pbs . ScopeServiceServer = ( * Service )( nil )
2024-01-03 17:54:00 -08:00
// NewServiceFn returns a service which handles scope related requests to boundary.
var NewServiceFn = func ( ctx context . Context , repo common . IamRepoFactory , kmsRepo * kms . Kms , maxPageSize uint ) ( pbs . ScopeServiceServer , error ) {
2021-04-16 12:06:03 -05:00
const op = "scopes.(Service).NewService"
2022-11-16 17:17:28 -06:00
if util . IsNil ( repo ) {
2024-01-03 17:54:00 -08:00
return & Service {}, errors . New ( ctx , errors . InvalidParameter , op , "missing iam repository" )
2020-07-22 00:14:28 -04:00
}
2022-11-16 17:17:28 -06:00
if kmsRepo == nil {
2024-01-03 17:54:00 -08:00
return & Service {}, errors . New ( ctx , errors . InvalidParameter , op , "missing kms" )
2022-11-16 17:17:28 -06:00
}
2024-01-02 17:19:29 -08:00
if maxPageSize == 0 {
maxPageSize = uint ( globals . DefaultMaxPageSize )
}
2024-01-03 17:54:00 -08:00
return & Service { repoFn : repo , kmsRepo : kmsRepo , maxPageSize : maxPageSize }, nil
2020-07-22 00:14:28 -04:00
}
// ListScopes implements the interface pbs.ScopeServiceServer.
2024-01-03 17:54:00 -08:00
func ( s * Service ) ListScopes ( ctx context . Context , req * pbs . ListScopesRequest ) ( * pbs . ListScopesResponse , error ) {
2024-01-02 17:19:29 -08:00
const op = "scopes.(Service).ListScopes"
2020-07-22 15:10:44 -04:00
if req . GetScopeId () == "" {
2020-08-12 00:02:26 -04:00
req . ScopeId = scope . Global . String ()
2020-07-22 15:10:44 -04:00
}
2023-06-06 17:09:08 -04:00
if err := validateListRequest ( ctx , req ); err != nil {
2020-07-22 00:14:28 -04:00
return nil , err
}
2020-09-04 14:48:11 -07:00
authResults := s . authResult ( ctx , req . GetScopeId (), action . List )
if authResults . Error != nil {
2021-03-24 15:33:04 -04:00
// If it's forbidden, and it's a recursive request, and they're
// successfully authenticated but just not authorized, keep going as we
2021-09-02 12:55:04 -04:00
// may have authorization on downstream scopes. Or, if they've not
// authenticated, still process in case u_anon has permissions.
if ( authResults . Error == handlers . ForbiddenError () || authResults . Error == handlers . UnauthenticatedError ()) &&
2021-03-24 15:33:04 -04:00
req . GetRecursive () &&
2021-04-13 16:55:40 -05:00
authResults . AuthenticationFinished {
2021-03-24 15:33:04 -04:00
} else {
return nil , authResults . Error
}
2020-09-04 14:48:11 -07:00
}
2021-01-25 19:37:40 -05:00
2021-03-24 15:33:04 -04:00
scopeIds , scopeInfoMap , err := scopeids . GetListingScopeIds (
2022-04-29 09:52:01 -04:00
ctx , s . repoFn , authResults , req . GetScopeId (), resource . Scope , req . GetRecursive ())
2021-01-25 19:37:40 -05:00
if err != nil {
return nil , err
}
2024-01-02 17:19:29 -08:00
pageSize := int ( s . maxPageSize )
// Use the requested page size only if it is smaller than
// the configured max.
if req . GetPageSize () != 0 && uint ( req . GetPageSize ()) < s . maxPageSize {
pageSize = int ( req . GetPageSize ())
2020-07-22 00:14:28 -04:00
}
2024-01-02 17:19:29 -08:00
var filterItemFn func ( ctx context . Context , item * iam . Scope ) ( bool , error )
switch {
case req . GetFilter () != "" :
// Only use a filter if we need to
filter , err := handlers . NewFilter ( ctx , req . GetFilter ())
if err != nil {
return nil , err
}
filterItemFn = func ( ctx context . Context , item * iam . Scope ) ( bool , error ) {
outputOpts , ok , err := newOutputOpts ( ctx , item , authResults , scopeInfoMap )
if err != nil {
return false , err
}
if ! ok {
return false , nil
}
pbItem , err := ToProto ( ctx , item , outputOpts ... )
if err != nil {
return false , err
}
return filter . Match ( pbItem ), nil
}
default :
filterItemFn = func ( ctx context . Context , item * iam . Scope ) ( bool , error ) {
return true , nil
}
2021-05-04 19:57:35 -05:00
}
2021-01-25 19:37:40 -05:00
2024-01-02 17:19:29 -08:00
grantsHash , err := authResults . GrantsHash ( ctx )
2021-02-22 10:09:25 -07:00
if err != nil {
2024-01-02 17:19:29 -08:00
return nil , errors . Wrap ( ctx , err , op )
2021-01-20 10:53:01 -05:00
}
2021-05-04 19:57:35 -05:00
2024-01-02 17:19:29 -08:00
repo , err := s . repoFn ()
if err != nil {
return nil , errors . Wrap ( ctx , err , op )
}
2021-05-04 19:57:35 -05:00
2024-01-02 17:19:29 -08:00
var listResp * pagination . ListResponse [ * iam . Scope ]
var sortBy string
if req . GetListToken () == "" {
sortBy = "created_time"
listResp , err = iam . ListScopes ( ctx , grantsHash , pageSize , filterItemFn , repo , scopeIds )
if err != nil {
return nil , err
2021-05-04 19:57:35 -05:00
}
2024-01-02 17:19:29 -08:00
} else {
listToken , err := handlers . ParseListToken ( ctx , req . GetListToken (), resource . Scope , grantsHash )
if err != nil {
return nil , err
2021-05-04 19:57:35 -05:00
}
2024-01-02 17:19:29 -08:00
switch st := listToken . Subtype .( type ) {
case * listtoken . PaginationToken :
sortBy = "created_time"
listResp , err = iam . ListScopesPage ( ctx , grantsHash , pageSize , filterItemFn , listToken , repo , scopeIds )
if err != nil {
return nil , err
}
case * listtoken . StartRefreshToken :
sortBy = "updated_time"
listResp , err = iam . ListScopesRefresh ( ctx , grantsHash , pageSize , filterItemFn , listToken , repo , scopeIds )
if err != nil {
return nil , err
}
case * listtoken . RefreshToken :
sortBy = "updated_time"
listResp , err = iam . ListScopesRefreshPage ( ctx , grantsHash , pageSize , filterItemFn , listToken , repo , scopeIds )
2021-05-04 19:57:35 -05:00
if err != nil {
2021-01-20 10:53:01 -05:00
return nil , err
}
2024-01-02 17:19:29 -08:00
default :
return nil , handlers . ApiErrorWithCodeAndMessage ( codes . InvalidArgument , "unexpected list token subtype: %T" , st )
2021-01-20 10:53:01 -05:00
}
2024-01-02 17:19:29 -08:00
}
2020-07-22 00:14:28 -04:00
2024-01-02 17:19:29 -08:00
finalItems := make ([] * pb . Scope , 0 , len ( listResp . Items ))
for _ , item := range listResp . Items {
outputOpts , ok , err := newOutputOpts ( ctx , item , authResults , scopeInfoMap )
if err != nil {
return nil , errors . Wrap ( ctx , err , op )
}
if ! ok {
continue
}
2021-05-04 19:57:35 -05:00
item , err := ToProto ( ctx , item , outputOpts ... )
if err != nil {
2024-01-02 17:19:29 -08:00
return nil , errors . Wrap ( ctx , err , op )
2021-05-04 19:57:35 -05:00
}
2024-01-02 17:19:29 -08:00
finalItems = append ( finalItems , item )
}
respType := "delta"
if listResp . CompleteListing {
respType = "complete"
}
resp := & pbs . ListScopesResponse {
Items : finalItems ,
EstItemCount : uint32 ( listResp . EstimatedItemCount ),
RemovedIds : listResp . DeletedIds ,
ResponseType : respType ,
SortBy : sortBy ,
SortDir : "desc" ,
}
2021-05-04 19:57:35 -05:00
2024-01-02 17:19:29 -08:00
if listResp . ListToken != nil {
resp . ListToken , err = handlers . MarshalListToken ( ctx , listResp . ListToken , pbs . ResourceType_RESOURCE_TYPE_SCOPE )
if err != nil {
return nil , err
2021-05-04 19:57:35 -05:00
}
}
2024-01-02 17:19:29 -08:00
return resp , nil
2020-07-22 00:14:28 -04:00
}
// GetScopes implements the interface pbs.ScopeServiceServer.
2024-01-03 17:54:00 -08:00
func ( s * Service ) GetScope ( ctx context . Context , req * pbs . GetScopeRequest ) ( * pbs . GetScopeResponse , error ) {
2021-05-04 19:57:35 -05:00
const op = "scopes.(Service).GetScope"
2020-07-22 00:14:28 -04:00
if err := validateGetRequest ( req ); err != nil {
return nil , err
}
2020-09-04 14:48:11 -07:00
authResults := s . authResult ( ctx , req . GetId (), action . Read )
if authResults . Error != nil {
return nil , authResults . Error
}
2020-07-22 00:14:28 -04:00
p , err := s . getFromRepo ( ctx , req . GetId ())
if err != nil {
return nil , err
}
2021-05-04 19:57:35 -05:00
outputFields , ok := requests . OutputFields ( ctx )
if ! ok {
2021-07-29 18:52:56 -06:00
return nil , errors . New ( ctx , errors . Internal , op , "no request context found" )
2021-05-04 19:57:35 -05:00
}
outputOpts := make ([] handlers . Option , 0 , 3 )
2023-01-06 17:27:19 -06:00
outputOpts = append ( outputOpts , handlers . WithOutputFields ( outputFields ))
2021-05-04 19:57:35 -05:00
if outputFields . Has ( globals . ScopeField ) {
outputOpts = append ( outputOpts , handlers . WithScope ( authResults . Scope ))
}
if outputFields . Has ( globals . AuthorizedActionsField ) {
2024-01-18 17:29:53 -08:00
outputOpts = append ( outputOpts , handlers . WithAuthorizedActions ( authResults . FetchActionSetForId ( ctx , p . GetPublicId (), idActionsById ( p . GetPublicId ())). Strings ()))
2021-01-20 10:53:01 -05:00
}
2021-05-04 19:57:35 -05:00
if outputFields . Has ( globals . AuthorizedCollectionActionsField ) {
2024-09-17 10:07:43 -04:00
scopeInfo := & pb . ScopeInfo {
Id : p . GetPublicId (),
Type : p . Type ,
Name : p . GetName (),
Description : p . GetDescription (),
ParentScopeId : p . GetParentId (),
}
collectionActions , err := auth . CalculateAuthorizedCollectionActions ( ctx , authResults , scopeCollectionTypeMapMap [ p . Type ], scopeInfo , "" )
2021-05-04 19:57:35 -05:00
if err != nil {
return nil , err
}
outputOpts = append ( outputOpts , handlers . WithAuthorizedCollectionActions ( collectionActions ))
}
item , err := ToProto ( ctx , p , outputOpts ... )
if err != nil {
2021-01-20 10:53:01 -05:00
return nil , err
}
2021-05-04 19:57:35 -05:00
return & pbs . GetScopeResponse { Item : item }, nil
2020-07-22 00:14:28 -04:00
}
// CreateScope implements the interface pbs.ScopeServiceServer.
2024-01-03 17:54:00 -08:00
func ( s * Service ) CreateScope ( ctx context . Context , req * pbs . CreateScopeRequest ) ( * pbs . CreateScopeResponse , error ) {
2021-05-04 19:57:35 -05:00
const op = "scopes.(Service).CreateScope"
2020-09-03 08:02:30 -07:00
if err := validateCreateRequest ( req ); err != nil {
return nil , err
2020-07-22 15:10:44 -04:00
}
2020-09-04 14:48:11 -07:00
authResults := s . authResult ( ctx , req . GetItem (). GetScopeId (), action . Create )
2020-08-16 00:42:36 -04:00
if authResults . Error != nil {
return nil , authResults . Error
2020-07-22 00:14:28 -04:00
}
2020-08-26 23:27:25 -04:00
p , err := s . createInRepo ( ctx , authResults , req )
2020-07-22 00:14:28 -04:00
if err != nil {
return nil , err
}
2021-05-04 19:57:35 -05:00
outputFields , ok := requests . OutputFields ( ctx )
if ! ok {
2021-07-29 18:52:56 -06:00
return nil , errors . New ( ctx , errors . Internal , op , "no request context found" )
2021-05-04 19:57:35 -05:00
}
outputOpts := make ([] handlers . Option , 0 , 3 )
2023-01-06 17:27:19 -06:00
outputOpts = append ( outputOpts , handlers . WithOutputFields ( outputFields ))
2021-05-04 19:57:35 -05:00
if outputFields . Has ( globals . ScopeField ) {
outputOpts = append ( outputOpts , handlers . WithScope ( authResults . Scope ))
}
if outputFields . Has ( globals . AuthorizedActionsField ) {
2024-01-18 17:29:53 -08:00
outputOpts = append ( outputOpts , handlers . WithAuthorizedActions ( authResults . FetchActionSetForId ( ctx , p . GetPublicId (), idActionsById ( p . GetPublicId ())). Strings ()))
2021-05-04 19:57:35 -05:00
}
if outputFields . Has ( globals . AuthorizedCollectionActionsField ) {
2024-09-17 10:07:43 -04:00
scopeInfo := & pb . ScopeInfo {
Id : p . GetPublicId (),
Type : p . Type ,
Name : p . GetName (),
Description : p . GetDescription (),
ParentScopeId : p . GetParentId (),
}
collectionActions , err := auth . CalculateAuthorizedCollectionActions ( ctx , authResults , scopeCollectionTypeMapMap [ p . Type ], scopeInfo , "" )
2021-05-04 19:57:35 -05:00
if err != nil {
return nil , err
}
outputOpts = append ( outputOpts , handlers . WithAuthorizedCollectionActions ( collectionActions ))
}
item , err := ToProto ( ctx , p , outputOpts ... )
if err != nil {
2021-01-20 10:53:01 -05:00
return nil , err
}
2021-05-04 19:57:35 -05:00
return & pbs . CreateScopeResponse { Item : item , Uri : fmt . Sprintf ( "scopes/%s" , item . GetId ())}, nil
2020-07-22 00:14:28 -04:00
}
// UpdateScope implements the interface pbs.ScopeServiceServer.
2024-01-03 17:54:00 -08:00
func ( s * Service ) UpdateScope ( ctx context . Context , req * pbs . UpdateScopeRequest ) ( * pbs . UpdateScopeResponse , error ) {
2021-05-04 19:57:35 -05:00
const op = "scopes.(Service).UpdateScope"
2020-07-22 00:14:28 -04:00
if err := validateUpdateRequest ( req ); err != nil {
return nil , err
}
2020-09-04 14:48:11 -07:00
authResults := s . authResult ( ctx , req . GetId (), action . Update )
if authResults . Error != nil {
return nil , authResults . Error
}
2020-08-18 17:38:38 -07:00
p , err := s . updateInRepo ( ctx , authResults . Scope , req . GetId (), req . GetUpdateMask (). GetPaths (), req . GetItem ())
2020-07-22 00:14:28 -04:00
if err != nil {
return nil , err
}
2021-05-04 19:57:35 -05:00
outputFields , ok := requests . OutputFields ( ctx )
if ! ok {
2021-07-29 18:52:56 -06:00
return nil , errors . New ( ctx , errors . Internal , op , "no request context found" )
2021-05-04 19:57:35 -05:00
}
outputOpts := make ([] handlers . Option , 0 , 3 )
2023-01-06 17:27:19 -06:00
outputOpts = append ( outputOpts , handlers . WithOutputFields ( outputFields ))
2021-05-04 19:57:35 -05:00
if outputFields . Has ( globals . ScopeField ) {
outputOpts = append ( outputOpts , handlers . WithScope ( authResults . Scope ))
}
if outputFields . Has ( globals . AuthorizedActionsField ) {
2024-01-18 17:29:53 -08:00
outputOpts = append ( outputOpts , handlers . WithAuthorizedActions ( authResults . FetchActionSetForId ( ctx , p . GetPublicId (), idActionsById ( p . GetPublicId ())). Strings ()))
2021-05-04 19:57:35 -05:00
}
if outputFields . Has ( globals . AuthorizedCollectionActionsField ) {
2024-09-17 10:07:43 -04:00
scopeInfo := & pb . ScopeInfo {
Id : p . GetPublicId (),
Type : p . Type ,
Name : p . GetName (),
Description : p . GetDescription (),
ParentScopeId : p . GetParentId (),
}
collectionActions , err := auth . CalculateAuthorizedCollectionActions ( ctx , authResults , scopeCollectionTypeMapMap [ p . Type ], scopeInfo , "" )
2021-05-04 19:57:35 -05:00
if err != nil {
return nil , err
}
outputOpts = append ( outputOpts , handlers . WithAuthorizedCollectionActions ( collectionActions ))
}
item , err := ToProto ( ctx , p , outputOpts ... )
if err != nil {
2021-01-20 10:53:01 -05:00
return nil , err
}
2021-05-04 19:57:35 -05:00
return & pbs . UpdateScopeResponse { Item : item }, nil
2020-07-22 00:14:28 -04:00
}
// DeleteScope implements the interface pbs.ScopeServiceServer.
2024-01-03 17:54:00 -08:00
func ( s * Service ) DeleteScope ( ctx context . Context , req * pbs . DeleteScopeRequest ) ( * pbs . DeleteScopeResponse , error ) {
2020-07-22 00:14:28 -04:00
if err := validateDeleteRequest ( req ); err != nil {
return nil , err
}
2020-09-04 14:48:11 -07:00
authResults := s . authResult ( ctx , req . GetId (), action . Delete )
if authResults . Error != nil {
return nil , authResults . Error
}
2020-09-08 23:57:26 -04:00
_ , err := s . deleteFromRepo ( ctx , req . GetId ())
2020-07-22 00:14:28 -04:00
if err != nil {
return nil , err
}
2021-04-26 19:38:55 -05:00
return nil , nil
2020-07-22 00:14:28 -04:00
}
2022-11-16 17:17:28 -06:00
// ListKeys implements the interface pbs.ScopeServiceServer.
2024-01-03 17:54:00 -08:00
func ( s * Service ) ListKeys ( ctx context . Context , req * pbs . ListKeysRequest ) ( * pbs . ListKeysResponse , error ) {
2022-11-16 17:17:28 -06:00
if req . GetId () == "" {
req . Id = scope . Global . String ()
}
if err := validateListKeysRequest ( req ); err != nil {
return nil , err
}
authResults := s . authResult ( ctx , req . GetId (), action . ListScopeKeys )
if authResults . Error != nil {
return nil , authResults . Error
}
items , err := s . kmsRepo . ListKeys ( ctx , req . GetId ())
if err != nil {
if errors . IsNotFoundError ( err ) {
return nil , handlers . NotFoundErrorf ( "unknown scope_id %q" , req . Id )
}
return nil , err
}
finalItems := make ([] * pb . Key , 0 , len ( items ))
res := perms . Resource {
Type : resource . Scope ,
}
for _ , item := range items {
res . Id = item . Id
res . ScopeId = item . Scope
outputFields := authResults . FetchOutputFields ( res , action . ListScopeKeys ). SelfOrDefaults ( authResults . UserId )
outputOpts := make ([] handlers . Option , 0 , 3 )
2023-01-06 17:27:19 -06:00
outputOpts = append ( outputOpts , handlers . WithOutputFields ( outputFields ))
2022-11-16 17:17:28 -06:00
if outputFields . Has ( globals . ScopeField ) {
outputOpts = append ( outputOpts , handlers . WithScope ( authResults . Scope ))
}
protoItem , err := keyToProto ( ctx , item , outputOpts ... )
if err != nil {
return nil , err
}
finalItems = append ( finalItems , protoItem )
}
sortKeys ( finalItems )
return & pbs . ListKeysResponse { Items : finalItems }, nil
}
// RotateKeys implements the interface pbs.ScopeServiceServer.
2024-01-03 17:54:00 -08:00
func ( s * Service ) RotateKeys ( ctx context . Context , req * pbs . RotateKeysRequest ) ( * pbs . RotateKeysResponse , error ) {
2022-11-16 17:17:28 -06:00
if req . GetScopeId () == "" {
req . ScopeId = scope . Global . String ()
}
if err := validateRotateKeysRequest ( req ); err != nil {
return nil , err
}
authResults := s . authResult ( ctx , req . GetScopeId (), action . RotateScopeKeys )
if authResults . Error != nil {
return nil , authResults . Error
}
if err := s . kmsRepo . RotateKeys ( ctx , req . GetScopeId (), kms . WithRewrap ( req . GetRewrap ())); err != nil {
return nil , err
}
return nil , nil
}
// ListKeyVersionDestructionJobs implements the interface pbs.ScopeServiceServer.
2024-01-03 17:54:00 -08:00
func ( s * Service ) ListKeyVersionDestructionJobs ( ctx context . Context , req * pbs . ListKeyVersionDestructionJobsRequest ) ( * pbs . ListKeyVersionDestructionJobsResponse , error ) {
2022-11-16 17:17:28 -06:00
if req . GetScopeId () == "" {
req . ScopeId = scope . Global . String ()
}
if err := validateListKeyVersionDestructionJobsRequest ( req ); err != nil {
return nil , err
}
authResults := s . authResult ( ctx , req . GetScopeId (), action . ListScopeKeyVersionDestructionJobs )
if authResults . Error != nil {
return nil , authResults . Error
}
jobs , err := s . kmsRepo . ListDataKeyVersionDestructionJobs ( ctx , req . GetScopeId ())
if err != nil {
return nil , err
}
res := perms . Resource {
Type : resource . Scope ,
}
finalJobs := make ([] * pb . KeyVersionDestructionJob , 0 , len ( jobs ))
for _ , job := range jobs {
res . Id = job . ScopeId
res . ScopeId = job . ScopeId
outputFields := authResults . FetchOutputFields ( res , action . ListScopeKeyVersionDestructionJobs ). SelfOrDefaults ( authResults . UserId )
outputOpts := make ([] handlers . Option , 0 , 3 )
2023-01-06 17:27:19 -06:00
outputOpts = append ( outputOpts , handlers . WithOutputFields ( outputFields ))
2022-11-16 17:17:28 -06:00
if outputFields . Has ( globals . ScopeField ) {
outputOpts = append ( outputOpts , handlers . WithScope ( authResults . Scope ))
}
protoJob , err := destructionJobToProto ( ctx , job , outputOpts ... )
if err != nil {
return nil , err
}
finalJobs = append ( finalJobs , protoJob )
}
return & pbs . ListKeyVersionDestructionJobsResponse {
Items : finalJobs ,
}, nil
}
// DestroyKeyVersion implements the interface pbs.ScopeServiceServer.
2024-01-03 17:54:00 -08:00
func ( s * Service ) DestroyKeyVersion ( ctx context . Context , req * pbs . DestroyKeyVersionRequest ) ( * pbs . DestroyKeyVersionResponse , error ) {
2022-11-16 17:17:28 -06:00
if req . GetScopeId () == "" {
req . ScopeId = scope . Global . String ()
}
if err := validateDestroyKeyVersionRequest ( req ); err != nil {
return nil , err
}
authResults := s . authResult ( ctx , req . GetScopeId (), action . DestroyScopeKeyVersion )
if authResults . Error != nil {
return nil , authResults . Error
}
destroyed , err := s . kmsRepo . DestroyKeyVersion ( ctx , req . GetScopeId (), req . GetKeyVersionId ())
if err != nil {
if errors . Match ( errors . T ( errors . KeyNotFound ), err ) {
return nil , handlers . NotFoundErrorf ( "unknown key_version_id %q" , req . KeyVersionId )
}
return nil , err
}
state := "completed"
if ! destroyed {
state = "pending"
}
return & pbs . DestroyKeyVersionResponse {
State : state ,
}, nil
}
2024-01-03 17:54:00 -08:00
// AttachStoragePolicy implements the interface pbs.ScopeServiceServer.
func ( s * Service ) AttachStoragePolicy ( ctx context . Context , req * pbs . AttachStoragePolicyRequest ) ( * pbs . AttachStoragePolicyResponse , error ) {
return nil , status . Errorf ( codes . Unimplemented , "Policies are an Enterprise-only feature" )
}
// DetachStoragePolicy implements the interface pbs.ScopeServiceServer.
func ( s * Service ) DetachStoragePolicy ( ctx context . Context , req * pbs . DetachStoragePolicyRequest ) ( * pbs . DetachStoragePolicyResponse , error ) {
return nil , status . Errorf ( codes . Unimplemented , "Policies are an Enterprise-only feature" )
}
func ( s * Service ) getFromRepo ( ctx context . Context , id string ) ( * iam . Scope , error ) {
2020-09-04 14:48:11 -07:00
repo , err := s . repoFn ()
2020-07-22 00:14:28 -04:00
if err != nil {
return nil , err
}
2021-05-04 19:57:35 -05:00
out , err := repo . LookupScope ( ctx , id )
2020-07-22 00:14:28 -04:00
if err != nil {
return nil , err
}
2021-05-04 19:57:35 -05:00
if out == nil {
2020-07-22 00:14:28 -04:00
return nil , handlers . NotFoundErrorf ( "Scope %q doesn't exist." , id )
}
2021-05-04 19:57:35 -05:00
return out , nil
2020-07-22 00:14:28 -04:00
}
2024-01-03 17:54:00 -08:00
func ( s * Service ) createInRepo ( ctx context . Context , authResults auth . VerifyResults , req * pbs . CreateScopeRequest ) ( * iam . Scope , error ) {
2021-04-16 12:06:03 -05:00
const op = "scopes.(Service).createInRepo"
2020-08-26 23:27:25 -04:00
item := req . GetItem ()
2020-07-22 00:14:28 -04:00
var opts [] iam . Option
if item . GetName () != nil {
opts = append ( opts , iam . WithName ( item . GetName (). GetValue ()))
}
if item . GetDescription () != nil {
opts = append ( opts , iam . WithDescription ( item . GetDescription (). GetValue ()))
}
2020-09-29 20:38:16 -04:00
opts = append ( opts , iam . WithSkipAdminRoleCreation ( req . GetSkipAdminRoleCreation ()))
opts = append ( opts , iam . WithSkipDefaultRoleCreation ( req . GetSkipDefaultRoleCreation ()))
2020-07-22 00:14:28 -04:00
2020-08-13 17:39:48 -04:00
parentScope := authResults . Scope
2020-07-22 00:14:28 -04:00
var iamScope * iam . Scope
var err error
switch parentScope . GetType () {
case scope . Global . String ():
2023-06-06 17:09:08 -04:00
iamScope , err = iam . NewOrg ( ctx , opts ... )
2020-07-22 00:14:28 -04:00
case scope . Org . String ():
2023-06-06 17:09:08 -04:00
iamScope , err = iam . NewProject ( ctx , parentScope . GetId (), opts ... )
2020-07-22 00:14:28 -04:00
}
if err != nil {
2020-09-25 13:41:20 -07:00
return nil , handlers . ApiErrorWithCodeAndMessage ( codes . Internal , "Unable to build new scope for creation: %v." , err )
2020-07-22 00:14:28 -04:00
}
2020-09-04 14:48:11 -07:00
repo , err := s . repoFn ()
2020-07-22 00:14:28 -04:00
if err != nil {
return nil , err
}
2020-08-26 23:27:25 -04:00
out , err := repo . CreateScope ( ctx , iamScope , authResults . UserId , opts ... )
2020-07-22 00:14:28 -04:00
if err != nil {
2021-07-29 18:52:56 -06:00
return nil , errors . Wrap ( ctx , err , op , errors . WithMsg ( "unable to create scope" ))
2020-07-22 00:14:28 -04:00
}
if out == nil {
2020-09-25 13:41:20 -07:00
return nil , handlers . ApiErrorWithCodeAndMessage ( codes . Internal , "Unable to create scope but no error returned from repository." )
2020-07-22 00:14:28 -04:00
}
2021-05-04 19:57:35 -05:00
return out , nil
2020-07-22 00:14:28 -04:00
}
2024-01-03 17:54:00 -08:00
func ( s * Service ) updateInRepo ( ctx context . Context , parentScope * pb . ScopeInfo , scopeId string , mask [] string , item * pb . Scope ) ( * iam . Scope , error ) {
2021-04-16 12:06:03 -05:00
const op = "scope.(Service).updateInRepo"
2020-07-22 00:14:28 -04:00
var opts [] iam . Option
2021-04-08 20:54:32 -04:00
var scopeDesc , scopeName , scopePrimaryAuthMethodId string
2020-07-22 00:14:28 -04:00
if desc := item . GetDescription (); desc != nil {
2021-04-08 20:54:32 -04:00
scopeDesc = desc . GetValue ()
opts = append ( opts , iam . WithDescription ( scopeDesc ))
2020-07-22 00:14:28 -04:00
}
if name := item . GetName (); name != nil {
2021-04-08 20:54:32 -04:00
scopeName = name . GetValue ()
opts = append ( opts , iam . WithName ( scopeName ))
}
if primaryAuthMethodId := item . GetPrimaryAuthMethodId (); primaryAuthMethodId != nil {
2023-03-15 11:40:40 -04:00
if ! handlers . ValidId ( handlers . Id ( primaryAuthMethodId . GetValue ()), globals . PasswordAuthMethodPrefix , globals . OidcAuthMethodPrefix , globals . LdapAuthMethodPrefix ) {
2021-04-08 20:54:32 -04:00
return nil , handlers . InvalidArgumentErrorf ( "Error in provided request." , map [ string ] string { "primary_auth_method_id" : "Improperly formatted identifier" })
}
scopePrimaryAuthMethodId = primaryAuthMethodId . GetValue ()
opts = append ( opts , iam . WithPrimaryAuthMethodId ( scopePrimaryAuthMethodId ))
2020-07-22 00:14:28 -04:00
}
2020-08-18 17:38:38 -07:00
version := item . GetVersion ()
2020-07-22 00:14:28 -04:00
var iamScope * iam . Scope
var err error
2021-04-08 20:54:32 -04:00
switch {
case scopeId == scope . Global . String ():
// boundary does not allow you to create a new global scope, so
// we'll build the required scope by hand for the update.
s := iam . AllocScope ()
s . PublicId = scopeId
iamScope = & s
iamScope . Description = scopeDesc
iamScope . Name = scopeName
iamScope . PrimaryAuthMethodId = scopePrimaryAuthMethodId
case parentScope . GetType () == scope . Global . String ():
2023-06-06 17:09:08 -04:00
iamScope , err = iam . NewOrg ( ctx , opts ... )
2021-04-08 20:54:32 -04:00
case parentScope . GetType () == scope . Org . String ():
2023-06-06 17:09:08 -04:00
iamScope , err = iam . NewProject ( ctx , parentScope . GetId (), opts ... )
2020-07-22 00:14:28 -04:00
}
if err != nil {
2020-09-25 13:41:20 -07:00
return nil , handlers . ApiErrorWithCodeAndMessage ( codes . Internal , "Unable to build scope for update: %v." , err )
2020-07-22 00:14:28 -04:00
}
iamScope . PublicId = scopeId
dbMask := maskManager . Translate ( mask )
if len ( dbMask ) == 0 {
2020-08-28 00:17:09 -04:00
return nil , handlers . InvalidArgumentErrorf ( "No valid fields included in the update mask." , map [ string ] string { "update_mask" : "No valid fields provided in the update mask." })
2020-07-22 00:14:28 -04:00
}
2020-09-04 14:48:11 -07:00
repo , err := s . repoFn ()
2020-07-22 00:14:28 -04:00
if err != nil {
return nil , err
}
2020-08-07 12:55:02 -04:00
out , rowsUpdated , err := repo . UpdateScope ( ctx , iamScope , version , dbMask )
2020-07-22 00:14:28 -04:00
if err != nil {
2021-07-29 18:52:56 -06:00
return nil , errors . Wrap ( ctx , err , op , errors . WithMsg ( "unable to update project" ))
2020-07-22 00:14:28 -04:00
}
if rowsUpdated == 0 {
2020-09-25 13:41:20 -07:00
return nil , handlers . NotFoundErrorf ( "Scope %q doesn't exist or incorrect version provided." , scopeId )
2020-07-22 00:14:28 -04:00
}
2021-05-04 19:57:35 -05:00
return out , nil
2020-07-22 00:14:28 -04:00
}
2024-01-03 17:54:00 -08:00
func ( s * Service ) deleteFromRepo ( ctx context . Context , scopeId string ) ( bool , error ) {
2021-04-16 12:06:03 -05:00
const op = "scope.(Service).deleteFromRepo"
2020-09-04 14:48:11 -07:00
repo , err := s . repoFn ()
2020-07-22 00:14:28 -04:00
if err != nil {
return false , err
}
rows , err := repo . DeleteScope ( ctx , scopeId )
if err != nil {
2021-07-29 18:52:56 -06:00
return false , errors . Wrap ( ctx , err , op , errors . WithMsg ( "unable to delete scope" ))
2020-07-22 00:14:28 -04:00
}
return rows > 0 , nil
}
2022-11-16 17:17:28 -06:00
func sortKeys ( keys [] * pb . Key ) {
// We stable sort here even though the database may not return things in
// sorted order, still nice to have them as consistent as possible.
sort . SliceStable ( keys , func ( i , j int ) bool {
return keys [ i ]. GetId () < keys [ j ]. GetId ()
})
// we also want to sort the key versions by version id so they are in
// descending order (newest first)
for _ , key := range keys {
sort . Slice ( key . Versions , func ( i , j int ) bool {
return key . Versions [ i ]. Version > key . Versions [ j ]. Version
})
}
}
2020-09-04 14:48:11 -07:00
func ( s Service ) authResult ( ctx context . Context , id string , a action . Type ) auth . VerifyResults {
res := auth . VerifyResults {}
repo , err := s . repoFn ()
if err != nil {
res . Error = err
return res
}
var parentId string
opts := [] auth . Option { auth . WithType ( resource . Scope ), auth . WithAction ( a )}
switch a {
2022-11-16 17:17:28 -06:00
case action . List , action . Create , action . ListScopeKeys , action . ListScopeKeyVersionDestructionJobs , action . DestroyScopeKeyVersion :
2020-09-04 14:48:11 -07:00
parentId = id
s , err := repo . LookupScope ( ctx , parentId )
if err != nil {
res . Error = err
return res
}
if s == nil {
2020-09-08 23:57:26 -04:00
res . Error = handlers . NotFoundError ()
2020-09-04 14:48:11 -07:00
return res
}
default :
s , err := repo . LookupScope ( ctx , id )
if err != nil {
res . Error = err
return res
}
if s == nil {
2020-09-08 23:57:26 -04:00
res . Error = handlers . NotFoundError ()
2020-09-04 14:48:11 -07:00
return res
}
parentId = s . GetParentId ()
opts = append ( opts , auth . WithId ( id ))
}
opts = append ( opts , auth . WithScopeId ( parentId ))
return auth . Verify ( ctx , opts ... )
}
2021-05-04 19:57:35 -05:00
func ToProto ( ctx context . Context , in * iam . Scope , opt ... handlers . Option ) ( * pb . Scope , error ) {
opts := handlers . GetOpts ( opt ... )
if opts . WithOutputFields == nil {
return nil , handlers . ApiErrorWithCodeAndMessage ( codes . Internal , "output fields not found when building scope proto" )
2020-07-22 00:14:28 -04:00
}
2021-05-04 19:57:35 -05:00
outputFields := * opts . WithOutputFields
out := pb . Scope {}
if outputFields . Has ( globals . IdField ) {
out . Id = in . GetPublicId ()
2020-07-22 00:14:28 -04:00
}
2021-05-04 19:57:35 -05:00
if outputFields . Has ( globals . ScopeIdField ) {
out . ScopeId = in . GetParentId ()
2020-07-22 00:14:28 -04:00
}
2021-05-04 19:57:35 -05:00
if outputFields . Has ( globals . TypeField ) {
out . Type = in . GetType ()
2021-04-08 20:54:32 -04:00
}
2021-05-04 19:57:35 -05:00
if outputFields . Has ( globals . DescriptionField ) && in . GetDescription () != "" {
out . Description = wrapperspb . String ( in . GetDescription ())
}
if outputFields . Has ( globals . NameField ) && in . GetName () != "" {
out . Name = wrapperspb . String ( in . GetName ())
}
if outputFields . Has ( globals . CreatedTimeField ) {
2021-04-14 16:19:02 -05:00
out . CreatedTime = in . GetCreateTime (). GetTimestamp ()
2021-05-04 19:57:35 -05:00
}
if outputFields . Has ( globals . UpdatedTimeField ) {
2021-04-14 16:19:02 -05:00
out . UpdatedTime = in . GetUpdateTime (). GetTimestamp ()
2021-05-04 19:57:35 -05:00
}
if outputFields . Has ( globals . VersionField ) {
2021-04-14 16:19:02 -05:00
out . Version = in . GetVersion ()
}
2021-05-04 19:57:35 -05:00
if outputFields . Has ( globals . ScopeField ) {
out . Scope = opts . WithScope
}
if outputFields . Has ( globals . AuthorizedActionsField ) {
out . AuthorizedActions = opts . WithAuthorizedActions
}
if outputFields . Has ( globals . AuthorizedCollectionActionsField ) {
out . AuthorizedCollectionActions = opts . WithAuthorizedCollectionActions
}
2021-08-03 14:16:02 -04:00
if outputFields . Has ( globals . PrimaryAuthMethodIdField ) && in . GetPrimaryAuthMethodId () != "" {
2021-05-04 19:57:35 -05:00
out . PrimaryAuthMethodId = & wrapperspb . StringValue { Value : in . GetPrimaryAuthMethodId ()}
}
2024-01-03 17:54:00 -08:00
if outputFields . Has ( globals . StoragePolicyIdField ) {
out . StoragePolicyId = in . GetStoragePolicyId ()
}
2021-04-08 20:54:32 -04:00
2021-05-04 19:57:35 -05:00
return & out , nil
2020-07-22 00:14:28 -04:00
}
2022-11-16 17:17:28 -06:00
func keyToProto ( ctx context . Context , in wrappingKms . Key , opt ... handlers . Option ) ( * pb . Key , error ) {
opts := handlers . GetOpts ( opt ... )
if opts . WithOutputFields == nil {
return nil , handlers . ApiErrorWithCodeAndMessage ( codes . Internal , "output fields not found when building key proto" )
}
outputFields := * opts . WithOutputFields
out := pb . Key {}
if outputFields . Has ( globals . IdField ) {
out . Id = in . Id
}
if outputFields . Has ( globals . ScopeField ) {
out . Scope = opts . WithScope
}
if outputFields . Has ( globals . KeyPurposeField ) {
out . Purpose = string ( in . Purpose )
}
if outputFields . Has ( globals . CreatedTimeField ) {
out . CreatedTime = timestamppb . New ( in . CreateTime )
}
if outputFields . Has ( globals . TypeField ) {
out . Type = string ( in . Type )
}
if outputFields . Has ( globals . KeyVersionsField ) {
for _ , keyVersion := range in . Versions {
out . Versions = append ( out . Versions , & pb . KeyVersion {
Id : keyVersion . Id ,
Version : uint32 ( keyVersion . Version ),
CreatedTime : timestamppb . New ( keyVersion . CreateTime ),
})
}
}
return & out , nil
}
func destructionJobToProto ( ctx context . Context , in * kms . DataKeyVersionDestructionJobProgress , opt ... handlers . Option ) ( * pb . KeyVersionDestructionJob , error ) {
opts := handlers . GetOpts ( opt ... )
if opts . WithOutputFields == nil {
return nil , handlers . ApiErrorWithCodeAndMessage ( codes . Internal , "output fields not found when building data key version destruction job proto" )
}
outputFields := * opts . WithOutputFields
out := pb . KeyVersionDestructionJob {}
if outputFields . Has ( globals . KeyVersionIdField ) {
out . KeyVersionId = in . KeyId
}
if outputFields . Has ( globals . ScopeField ) {
out . Scope = opts . WithScope
}
if outputFields . Has ( globals . CreatedTimeField ) {
out . CreatedTime = in . CreateTime . Timestamp
}
if outputFields . Has ( globals . CompletedCountField ) {
out . CompletedCount = in . CompletedCount
}
if outputFields . Has ( globals . TotalCountField ) {
out . TotalCount = in . TotalCount
}
if outputFields . Has ( globals . StatusField ) {
out . Status = in . Status
}
return & out , nil
}
2020-07-22 00:14:28 -04:00
// A validateX method should exist for each method above. These methods do not make calls to any backing service but enforce
// requirements on the structure of the request. They verify that:
2022-08-25 09:12:24 -07:00
// - The path passed in is correctly formatted
// - All required parameters are set
// - There are no conflicting parameters provided
2020-07-22 00:14:28 -04:00
func validateGetRequest ( req * pbs . GetScopeRequest ) error {
badFields := map [ string ] string {}
id := req . GetId ()
switch {
2021-11-04 09:34:01 -04:00
case id == scope . Global . String ():
2020-07-22 00:14:28 -04:00
case strings . HasPrefix ( id , scope . Org . Prefix ()):
2021-04-08 20:54:32 -04:00
if ! handlers . ValidId ( handlers . Id ( id ), scope . Org . Prefix ()) {
2020-07-22 00:14:28 -04:00
badFields [ "id" ] = "Invalidly formatted scope id."
}
case strings . HasPrefix ( id , scope . Project . Prefix ()):
2021-04-08 20:54:32 -04:00
if ! handlers . ValidId ( handlers . Id ( id ), scope . Project . Prefix ()) {
2020-07-22 00:14:28 -04:00
badFields [ "id" ] = "Invalidly formatted scope id."
}
default :
badFields [ "id" ] = "Invalidly formatted scope id."
}
if len ( badFields ) > 0 {
2020-09-25 13:41:20 -07:00
return handlers . InvalidArgumentErrorf ( "Error in provided request." , badFields )
2020-07-22 00:14:28 -04:00
}
return nil
}
func validateCreateRequest ( req * pbs . CreateScopeRequest ) error {
2023-05-25 15:43:52 +01:00
return handlers . ValidateCreateRequest ( req . GetItem (), func () map [ string ] string {
badFields := map [ string ] string {}
item := req . GetItem ()
if item . GetScopeId () == "" {
badFields [ "scope_id" ] = "Missing value for scope_id."
}
switch item . GetType () {
case scope . Global . String ():
badFields [ "type" ] = "Cannot create a global scope."
case scope . Org . String ():
if ! strings . EqualFold ( scope . Global . String (), item . GetScopeId ()) {
badFields [ "type" ] = "Org scopes can only be created under the global scope."
}
case scope . Project . String ():
if ! handlers . ValidId ( handlers . Id ( item . GetScopeId ()), scope . Org . Prefix ()) {
badFields [ "type" ] = "Project scopes can only be created under an org scope."
}
2020-09-08 16:04:19 -07:00
}
2023-05-25 15:43:52 +01:00
return badFields
})
2020-07-22 00:14:28 -04:00
}
func validateUpdateRequest ( req * pbs . UpdateScopeRequest ) error {
badFields := map [ string ] string {}
id := req . GetId ()
switch {
2021-11-04 09:34:01 -04:00
case id == scope . Global . String ():
2020-07-22 00:14:28 -04:00
case strings . HasPrefix ( id , scope . Org . Prefix ()):
2021-04-08 20:54:32 -04:00
if ! handlers . ValidId ( handlers . Id ( id ), scope . Org . Prefix ()) {
2020-07-22 00:14:28 -04:00
badFields [ "id" ] = "Invalidly formatted scope id."
}
2020-09-08 16:04:19 -07:00
if req . GetItem (). GetType () != "" && ! strings . EqualFold ( scope . Org . String (), req . GetItem (). GetType ()) {
badFields [ "type" ] = "Cannot modify the resource type."
}
2020-07-22 00:14:28 -04:00
case strings . HasPrefix ( id , scope . Project . Prefix ()):
2021-04-08 20:54:32 -04:00
if ! handlers . ValidId ( handlers . Id ( id ), scope . Project . Prefix ()) {
2020-07-22 00:14:28 -04:00
badFields [ "id" ] = "Invalidly formatted scope id."
}
2020-09-08 16:04:19 -07:00
if req . GetItem (). GetType () != "" && ! strings . EqualFold ( scope . Project . String (), req . GetItem (). GetType ()) {
badFields [ "type" ] = "Cannot modify the resource type."
}
2020-07-22 00:14:28 -04:00
default :
badFields [ "id" ] = "Invalidly formatted scope id."
}
if req . GetUpdateMask () == nil {
badFields [ "update_mask" ] = "UpdateMask not provided but is required to update a project."
}
item := req . GetItem ()
if item == nil {
if len ( badFields ) > 0 {
2020-09-25 13:41:20 -07:00
return handlers . InvalidArgumentErrorf ( "Error in provided request." , badFields )
2020-07-22 00:14:28 -04:00
}
// It is legitimate for no item to be specified in an update request as it indicates all fields provided in
// the mask will be marked as unset.
return nil
}
2020-08-18 17:38:38 -07:00
if item . GetVersion () == 0 {
badFields [ "version" ] = "Existing resource version is required for an update."
}
2020-07-22 00:14:28 -04:00
if item . GetId () != "" {
badFields [ "id" ] = "This is a read only field and cannot be specified in an update request."
}
if item . GetCreatedTime () != nil {
badFields [ "created_time" ] = "This is a read only field and cannot be specified in an update request."
}
if item . GetUpdatedTime () != nil {
badFields [ "updated_time" ] = "This is a read only field and cannot be specified in an update request."
}
2023-03-15 11:40:40 -04:00
if item . GetPrimaryAuthMethodId (). GetValue () != "" && ! handlers . ValidId ( handlers . Id ( item . GetPrimaryAuthMethodId (). GetValue ()), globals . PasswordAuthMethodPrefix , globals . OidcAuthMethodPrefix , globals . LdapAuthMethodPrefix ) {
2021-04-08 20:54:32 -04:00
badFields [ "primary_auth_method_id" ] = "Improperly formatted identifier."
}
2023-05-25 15:43:52 +01:00
if item . GetName () != nil {
trimmed := strings . TrimSpace ( item . GetName (). GetValue ())
switch {
case trimmed == "" :
badFields [ "name" ] = "Cannot set empty string as name"
2024-06-06 13:03:42 -04:00
case ! handlers . ValidName ( trimmed ):
2023-05-25 15:43:52 +01:00
badFields [ "name" ] = "Name contains unprintable characters"
default :
item . GetName (). Value = trimmed
}
}
if item . GetDescription () != nil {
trimmed := strings . TrimSpace ( item . GetDescription (). GetValue ())
switch {
case trimmed == "" :
badFields [ "description" ] = "Cannot set empty string as description"
2024-06-06 13:03:42 -04:00
case ! handlers . ValidDescription ( trimmed ):
2023-05-25 15:43:52 +01:00
badFields [ "description" ] = "Description contains unprintable characters"
default :
item . GetDescription (). Value = trimmed
}
}
2020-07-22 00:14:28 -04:00
if len ( badFields ) > 0 {
2020-09-25 13:41:20 -07:00
return handlers . InvalidArgumentErrorf ( "Error in provided request." , badFields )
2020-07-22 00:14:28 -04:00
}
return nil
}
func validateDeleteRequest ( req * pbs . DeleteScopeRequest ) error {
badFields := map [ string ] string {}
id := req . GetId ()
switch {
2021-11-04 09:34:01 -04:00
case id == scope . Global . String ():
2020-07-22 00:14:28 -04:00
badFields [ "id" ] = "Invalid to delete the global scope."
case strings . HasPrefix ( id , scope . Org . Prefix ()):
2021-04-08 20:54:32 -04:00
if ! handlers . ValidId ( handlers . Id ( id ), scope . Org . Prefix ()) {
2020-07-22 00:14:28 -04:00
badFields [ "id" ] = "Invalidly formatted scope id."
}
case strings . HasPrefix ( id , scope . Project . Prefix ()):
2021-04-08 20:54:32 -04:00
if ! handlers . ValidId ( handlers . Id ( id ), scope . Project . Prefix ()) {
2020-07-22 00:14:28 -04:00
badFields [ "id" ] = "Invalidly formatted scope id."
}
default :
badFields [ "id" ] = "Invalidly formatted scope id."
}
if len ( badFields ) > 0 {
2020-09-25 13:41:20 -07:00
return handlers . InvalidArgumentErrorf ( "Error in provided request." , badFields )
2020-07-22 00:14:28 -04:00
}
return nil
}
2023-06-06 17:09:08 -04:00
func validateListRequest ( ctx context . Context , req * pbs . ListScopesRequest ) error {
2020-07-22 00:14:28 -04:00
badFields := map [ string ] string {}
2021-04-08 20:54:32 -04:00
if req . GetScopeId () != scope . Global . String () && ! handlers . ValidId ( handlers . Id ( req . GetScopeId ()), scope . Org . Prefix ()) {
2020-10-07 17:43:12 -07:00
badFields [ "scope_id" ] = "Must be 'global' or a valid org scope id when listing."
}
2023-06-06 17:09:08 -04:00
if _ , err := handlers . NewFilter ( ctx , req . GetFilter ()); err != nil {
2021-02-22 10:09:25 -07:00
badFields [ "filter" ] = fmt . Sprintf ( "This field could not be parsed. %v" , err )
}
2020-07-22 00:14:28 -04:00
if len ( badFields ) > 0 {
2020-09-25 13:41:20 -07:00
return handlers . InvalidArgumentErrorf ( "Error in provided request." , badFields )
2020-07-22 00:14:28 -04:00
}
return nil
}
2022-11-16 17:17:28 -06:00
func validateListKeysRequest ( req * pbs . ListKeysRequest ) error {
badFields := map [ string ] string {}
if req . GetId () != scope . Global . String () && ! handlers . ValidId ( handlers . Id ( req . GetId ()), scope . Org . Prefix ()) && ! handlers . ValidId ( handlers . Id ( req . GetId ()), scope . Project . Prefix ()) {
badFields [ "id" ] = "Must be 'global', a valid org scope id or a valid project scope id when listing keys."
}
if len ( badFields ) > 0 {
return handlers . InvalidArgumentErrorf ( "Error in provided request." , badFields )
}
return nil
}
func validateRotateKeysRequest ( req * pbs . RotateKeysRequest ) error {
badFields := map [ string ] string {}
if req . GetScopeId () != scope . Global . String () && ! handlers . ValidId ( handlers . Id ( req . GetScopeId ()), scope . Org . Prefix ()) && ! handlers . ValidId ( handlers . Id ( req . GetScopeId ()), scope . Project . Prefix ()) {
badFields [ "id" ] = "Must be 'global', a valid org scope id or a valid project scope id when listing keys."
}
// other field is just a bool so can't be validated
if len ( badFields ) > 0 {
return handlers . InvalidArgumentErrorf ( "Error in provided request." , badFields )
}
return nil
}
func validateListKeyVersionDestructionJobsRequest ( req * pbs . ListKeyVersionDestructionJobsRequest ) error {
badFields := map [ string ] string {}
if req . GetScopeId () != scope . Global . String () && ! handlers . ValidId ( handlers . Id ( req . GetScopeId ()), scope . Org . Prefix ()) && ! handlers . ValidId ( handlers . Id ( req . GetScopeId ()), scope . Project . Prefix ()) {
badFields [ "scope_id" ] = "Must be 'global', a valid org scope id or a valid project scope id when listing key version destruction jobs."
}
if len ( badFields ) > 0 {
return handlers . InvalidArgumentErrorf ( "Error in provided request." , badFields )
}
return nil
}
func validateDestroyKeyVersionRequest ( req * pbs . DestroyKeyVersionRequest ) error {
badFields := map [ string ] string {}
if req . GetScopeId () != scope . Global . String () && ! handlers . ValidId ( handlers . Id ( req . GetScopeId ()), scope . Org . Prefix ()) && ! handlers . ValidId ( handlers . Id ( req . GetScopeId ()), scope . Project . Prefix ()) {
badFields [ "scope_id" ] = "Must be 'global', a valid org scope id or a valid project scope id when destroying a key version."
}
if ! handlers . ValidId ( handlers . Id ( req . GetKeyVersionId ()), "kdkv" , "krkv" ) {
badFields [ "key_version_id" ] = "Must be a valid KEK or DEK version ID."
}
if len ( badFields ) > 0 {
return handlers . InvalidArgumentErrorf ( "Error in provided request." , badFields )
}
return nil
}
2024-01-02 17:19:29 -08:00
func newOutputOpts ( ctx context . Context , item * iam . Scope , authResults auth . VerifyResults , scopeInfoMap map [ string ] * pb . ScopeInfo ) ([] handlers . Option , bool , error ) {
res := perms . Resource {
2025-01-24 10:23:33 -08:00
Type : resource . Scope ,
Id : item . GetPublicId (),
ScopeId : item . GetParentId (),
ParentScopeId : scopeInfoMap [ item . GetParentId ()]. GetParentScopeId (),
2024-01-02 17:19:29 -08:00
}
2024-01-18 17:29:53 -08:00
authorizedActions := authResults . FetchActionSetForId ( ctx , item . GetPublicId (), idActionsById ( item . GetPublicId ()), auth . WithResource ( & res )). Strings ()
2024-01-02 17:19:29 -08:00
if len ( authorizedActions ) == 0 {
2024-04-26 11:55:30 -04:00
return nil , false , nil
2024-01-02 17:19:29 -08:00
}
outputFields := authResults . FetchOutputFields ( res , action . List ). SelfOrDefaults ( authResults . UserId )
outputOpts := make ([] handlers . Option , 0 , 3 )
outputOpts = append ( outputOpts , handlers . WithOutputFields ( outputFields ))
if outputFields . Has ( globals . ScopeField ) {
outputOpts = append ( outputOpts , handlers . WithScope ( scopeInfoMap [ item . GetParentId ()]))
}
if outputFields . Has ( globals . AuthorizedActionsField ) {
outputOpts = append ( outputOpts , handlers . WithAuthorizedActions ( authorizedActions ))
}
if outputFields . Has ( globals . AuthorizedCollectionActionsField ) {
2024-09-17 10:07:43 -04:00
scopeInfo := & pb . ScopeInfo {
Id : item . GetPublicId (),
Type : item . Type ,
Name : item . GetName (),
Description : item . GetDescription (),
ParentScopeId : item . GetParentId (),
}
collectionActions , err := auth . CalculateAuthorizedCollectionActions ( ctx , authResults , scopeCollectionTypeMapMap [ item . Type ], scopeInfo , "" )
2024-01-02 17:19:29 -08:00
if err != nil {
return nil , false , err
}
outputOpts = append ( outputOpts , handlers . WithAuthorizedCollectionActions ( collectionActions ))
}
return outputOpts , true , nil
}
2024-01-18 17:29:53 -08:00
func idActionsById ( id string ) action . ActionSet {
act := IdActions
switch {
case id == scope . Global . String ():
// Can't delete global so elide it
act = action . Difference ( act , action . NewActionSet ( action . Delete ))
case strings . HasPrefix ( id , fmt . Sprintf ( "%s_" , scope . Project . Prefix ())):
// Can't attach/detach storage policy to projects
act = action . Difference ( act , action . NewActionSet ( action . AttachStoragePolicy , action . DetachStoragePolicy ))
}
return act
}