2021-05-04 19:57:35 -05:00
// Copyright (c) HashiCorp, Inc.
2020-06-22 10:46:45 -07:00
// SPDX-License-Identifier: BUSL-1.1
2023-02-02 17:15:17 -08:00
2020-06-15 15:22:41 -07:00
package groups
import (
"context"
"fmt"
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"
2020-11-05 16:54: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"
2023-12-21 12:54:27 -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-09-02 19:43:41 -07:00
"github.com/hashicorp/boundary/internal/types/scope"
2021-08-26 10:44:41 -04:00
pb "github.com/hashicorp/boundary/sdk/pbs/controller/api/resources/groups"
2023-12-21 12:54:27 -08:00
"github.com/hashicorp/boundary/sdk/pbs/controller/api/resources/scopes"
2021-07-14 17:37:40 -07:00
"github.com/hashicorp/go-secure-stdlib/strutil"
2020-06-15 15:22:41 -07:00
"google.golang.org/grpc/codes"
"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 ,
action . AddMembers ,
action . SetMembers ,
action . RemoveMembers ,
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 ,
2023-10-31 13:44:17 +00:00
)
2021-01-20 10:53:01 -05:00
)
2020-06-15 15:22:41 -07:00
2020-07-20 11:17:09 -07:00
func init () {
var err error
2023-06-06 17:09:08 -04:00
if maskManager , err = handlers . NewMaskManager (
context . Background (),
handlers . MaskDestination { & store . Group {}},
handlers . MaskSource { & pb . Group {}},
); err != nil {
2020-07-20 11:17:09 -07:00
panic ( err )
}
2023-10-11 18:26:16 +00:00
// TODO: refactor to remove IdActionsMap and CollectionActions package variables
action . RegisterResource ( resource . Group , IdActions , CollectionActions )
2020-07-20 11:17:09 -07:00
}
2020-06-15 15:22:41 -07:00
// Service handles request as described by the pbs.GroupServiceServer interface.
type Service struct {
2022-08-30 14:22:10 -04:00
pbs . UnsafeGroupServiceServer
2020-11-11 14:41:05 -05:00
2023-12-21 12:54:27 -08:00
repoFn common . IamRepoFactory
maxPageSize uint
2020-06-15 15:22:41 -07:00
}
2022-08-30 14:22:10 -04:00
var _ pbs . GroupServiceServer = ( * Service )( nil )
2020-08-10 17:15:02 -04:00
// NewService returns a group service which handles group related requests to boundary.
2023-12-21 12:54:27 -08:00
func NewService ( ctx context . Context , repo common . IamRepoFactory , maxPageSize uint ) ( Service , error ) {
2021-04-16 12:06:03 -05:00
const op = "groups.NewService"
2020-06-15 15:22:41 -07:00
if repo == nil {
2023-06-06 17:09:08 -04:00
return Service {}, errors . New ( ctx , errors . InvalidParameter , op , "missing iam repository" )
2020-06-15 15:22:41 -07:00
}
2023-12-21 12:54:27 -08:00
if maxPageSize == 0 {
maxPageSize = uint ( globals . DefaultMaxPageSize )
}
return Service { repoFn : repo , maxPageSize : maxPageSize }, nil
2020-06-15 15:22:41 -07:00
}
2020-06-22 10:46:45 -07:00
// ListGroups implements the interface pbs.GroupServiceServer.
func ( s Service ) ListGroups ( ctx context . Context , req * pbs . ListGroupsRequest ) ( * pbs . ListGroupsResponse , error ) {
2023-12-21 12:54:27 -08:00
const op = "groups.(Service).ListGroups"
2023-06-06 17:09:08 -04:00
if err := validateListRequest ( ctx , req ); err != nil {
2020-06-22 10:46:45 -07: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 . Group , req . GetRecursive ())
2021-01-25 19:37:40 -05:00
if err != nil {
return nil , err
}
2023-12-21 12:54:27 -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-06-22 10:46:45 -07:00
}
2023-12-21 12:54:27 -08:00
var filterItemFn func ( ctx context . Context , item * iam . Group ) ( 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 . Group ) ( bool , error ) {
outputOpts , ok := newOutputOpts ( ctx , item , scopeInfoMap , authResults )
if ! ok {
return false , nil
}
pbItem , err := toProto ( ctx , item , nil , outputOpts ... )
if err != nil {
return false , err
}
return filter . Match ( pbItem ), nil
}
default :
filterItemFn = func ( ctx context . Context , item * iam . Group ) ( bool , error ) {
return true , nil
}
2021-05-04 19:57:35 -05:00
}
2023-12-21 12:54:27 -08:00
grantsHash , err := authResults . GrantsHash ( ctx )
2021-02-22 10:09:25 -07:00
if err != nil {
2023-12-21 12:54:27 -08:00
return nil , errors . Wrap ( ctx , err , op )
2021-02-22 10:09:25 -07:00
}
2023-12-21 12:54:27 -08:00
repo , err := s . repoFn ()
if err != nil {
return nil , errors . Wrap ( ctx , err , op )
2021-01-20 10:53:01 -05:00
}
2023-12-21 12:54:27 -08:00
var listResp * pagination . ListResponse [ * iam . Group ]
var sortBy string
if req . GetListToken () == "" {
sortBy = "created_time"
listResp , err = iam . ListGroups ( ctx , grantsHash , pageSize , filterItemFn , repo , scopeIds )
if err != nil {
return nil , err
2021-02-22 10:09:25 -07:00
}
2023-12-21 12:54:27 -08:00
} else {
listToken , err := handlers . ParseListToken ( ctx , req . GetListToken (), resource . Group , grantsHash )
if err != nil {
return nil , err
2021-05-04 19:57:35 -05:00
}
2023-12-21 12:54:27 -08:00
switch st := listToken . Subtype .( type ) {
case * listtoken . PaginationToken :
sortBy = "created_time"
listResp , err = iam . ListGroupsPage ( ctx , grantsHash , pageSize , filterItemFn , listToken , repo , scopeIds )
if err != nil {
return nil , err
}
case * listtoken . StartRefreshToken :
sortBy = "updated_time"
listResp , err = iam . ListGroupsRefresh ( ctx , grantsHash , pageSize , filterItemFn , listToken , repo , scopeIds )
if err != nil {
return nil , err
}
case * listtoken . RefreshToken :
sortBy = "updated_time"
listResp , err = iam . ListGroupsRefreshPage ( ctx , grantsHash , pageSize , filterItemFn , listToken , repo , scopeIds )
if err != nil {
return nil , err
}
default :
return nil , handlers . ApiErrorWithCodeAndMessage ( codes . InvalidArgument , "unexpected list token subtype: %T" , st )
2021-05-04 19:57:35 -05:00
}
2023-12-21 12:54:27 -08:00
}
2021-05-04 19:57:35 -05:00
2023-12-21 12:54:27 -08:00
finalItems := make ([] * pb . Group , 0 , len ( listResp . Items ))
for _ , item := range listResp . Items {
outputOpts , ok := newOutputOpts ( ctx , item , scopeInfoMap , authResults )
if ! ok {
continue
}
2021-05-04 19:57:35 -05:00
item , err := toProto ( ctx , item , nil , outputOpts ... )
if err != nil {
2023-12-21 12:54:27 -08:00
return nil , errors . Wrap ( ctx , err , op )
2021-05-04 19:57:35 -05:00
}
2023-12-21 12:54:27 -08:00
finalItems = append ( finalItems , item )
}
respType := "delta"
if listResp . CompleteListing {
respType = "complete"
}
resp := & pbs . ListGroupsResponse {
Items : finalItems ,
EstItemCount : uint32 ( listResp . EstimatedItemCount ),
RemovedIds : listResp . DeletedIds ,
ResponseType : respType ,
SortBy : sortBy ,
SortDir : "desc" ,
}
if listResp . ListToken != nil {
resp . ListToken , err = handlers . MarshalListToken ( ctx , listResp . ListToken , pbs . ResourceType_RESOURCE_TYPE_GROUP )
if err != nil {
return nil , err
2021-01-20 10:53:01 -05:00
}
2020-07-22 00:14:28 -04:00
}
2023-12-21 12:54:27 -08:00
return resp , nil
2020-06-15 15:22:41 -07:00
}
// GetGroups implements the interface pbs.GroupServiceServer.
func ( s Service ) GetGroup ( ctx context . Context , req * pbs . GetGroupRequest ) ( * pbs . GetGroupResponse , error ) {
2021-05-04 19:57:35 -05:00
const op = "groups.(Service).GetGroup"
2020-06-15 15:22:41 -07: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
}
2021-05-04 19:57:35 -05:00
u , members , err := s . getFromRepo ( ctx , req . GetId ())
if err != nil {
return nil , err
}
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 ) {
outputOpts = append ( outputOpts , handlers . WithAuthorizedActions ( authResults . FetchActionSetForId ( ctx , u . GetPublicId (), IdActions ). Strings ()))
}
item , err := toProto ( ctx , u , members , outputOpts ... )
2020-06-15 15:22:41 -07:00
if err != nil {
return nil , err
}
2021-05-04 19:57:35 -05:00
return & pbs . GetGroupResponse { Item : item }, nil
2020-06-15 15:22:41 -07:00
}
// CreateGroup implements the interface pbs.GroupServiceServer.
func ( s Service ) CreateGroup ( ctx context . Context , req * pbs . CreateGroupRequest ) ( * pbs . CreateGroupResponse , error ) {
2021-05-04 19:57:35 -05:00
const op = "groups.(Service).CreateGroup"
2020-06-15 15:22:41 -07:00
if err := validateCreateRequest ( req ); err != nil {
return nil , err
}
2020-09-04 14:48:11 -07:00
authResults := s . authResult ( ctx , req . GetItem (). GetScopeId (), action . Create )
if authResults . Error != nil {
return nil , authResults . Error
}
u , err := s . createInRepo ( ctx , authResults . Scope . GetId (), req . GetItem ())
2020-06-15 15:22:41 -07: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 ) {
outputOpts = append ( outputOpts , handlers . WithAuthorizedActions ( authResults . FetchActionSetForId ( ctx , u . GetPublicId (), IdActions ). Strings ()))
}
item , err := toProto ( ctx , u , nil , outputOpts ... )
if err != nil {
return nil , err
}
return & pbs . CreateGroupResponse { Item : item , Uri : fmt . Sprintf ( "groups/%s" , item . GetId ())}, nil
2020-06-15 15:22:41 -07:00
}
// UpdateGroup implements the interface pbs.GroupServiceServer.
func ( s Service ) UpdateGroup ( ctx context . Context , req * pbs . UpdateGroupRequest ) ( * pbs . UpdateGroupResponse , error ) {
2021-05-04 19:57:35 -05:00
const op = "groups.(Service).UpdateGroup"
2020-06-15 15:22:41 -07: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
}
2021-05-04 19:57:35 -05:00
u , members , err := s . updateInRepo ( ctx , authResults . Scope . GetId (), req . GetId (), req . GetUpdateMask (). GetPaths (), req . GetItem ())
if err != nil {
return nil , err
}
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 ) {
outputOpts = append ( outputOpts , handlers . WithAuthorizedActions ( authResults . FetchActionSetForId ( ctx , u . GetPublicId (), IdActions ). Strings ()))
}
item , err := toProto ( ctx , u , members , outputOpts ... )
2020-06-15 15:22:41 -07:00
if err != nil {
return nil , err
}
2021-05-04 19:57:35 -05:00
return & pbs . UpdateGroupResponse { Item : item }, nil
2020-06-15 15:22:41 -07:00
}
// DeleteGroup implements the interface pbs.GroupServiceServer.
func ( s Service ) DeleteGroup ( ctx context . Context , req * pbs . DeleteGroupRequest ) ( * pbs . DeleteGroupResponse , error ) {
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-06-15 15:22:41 -07:00
if err != nil {
return nil , err
}
2021-04-26 19:38:55 -05:00
return nil , nil
2020-06-15 15:22:41 -07:00
}
2020-07-22 15:10:21 -07:00
// AddGroupMembers implements the interface pbs.GroupServiceServer.
func ( s Service ) AddGroupMembers ( ctx context . Context , req * pbs . AddGroupMembersRequest ) ( * pbs . AddGroupMembersResponse , error ) {
2021-05-04 19:57:35 -05:00
const op = "groups.(Service).AddGroupMembers"
2020-07-22 15:10:21 -07:00
if err := validateAddGroupMembersRequest ( req ); err != nil {
return nil , err
}
2020-09-04 14:48:11 -07:00
authResults := s . authResult ( ctx , req . GetId (), action . AddMembers )
if authResults . Error != nil {
return nil , authResults . Error
}
2021-05-04 19:57:35 -05:00
g , members , err := s . addMembersInRepo ( ctx , req . GetId (), req . GetMemberIds (), req . GetVersion ())
2020-07-22 15:10:21 -07: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 ) {
outputOpts = append ( outputOpts , handlers . WithAuthorizedActions ( authResults . FetchActionSetForId ( ctx , g . GetPublicId (), IdActions ). Strings ()))
}
item , err := toProto ( ctx , g , members , outputOpts ... )
if err != nil {
return nil , err
}
return & pbs . AddGroupMembersResponse { Item : item }, nil
2020-07-22 15:10:21 -07:00
}
// SetGroupMembers implements the interface pbs.GroupServiceServer.
func ( s Service ) SetGroupMembers ( ctx context . Context , req * pbs . SetGroupMembersRequest ) ( * pbs . SetGroupMembersResponse , error ) {
2021-05-04 19:57:35 -05:00
const op = "groups.(Service).SetGroupMembers"
2020-07-22 15:10:21 -07:00
if err := validateSetGroupMembersRequest ( req ); err != nil {
return nil , err
}
2020-09-04 14:48:11 -07:00
authResults := s . authResult ( ctx , req . GetId (), action . SetMembers )
if authResults . Error != nil {
return nil , authResults . Error
}
2021-05-04 19:57:35 -05:00
g , members , err := s . setMembersInRepo ( ctx , req . GetId (), req . GetMemberIds (), req . GetVersion ())
if err != nil {
return nil , err
}
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 ) {
outputOpts = append ( outputOpts , handlers . WithAuthorizedActions ( authResults . FetchActionSetForId ( ctx , g . GetPublicId (), IdActions ). Strings ()))
}
item , err := toProto ( ctx , g , members , outputOpts ... )
2020-07-22 15:10:21 -07:00
if err != nil {
return nil , err
}
2021-05-04 19:57:35 -05:00
return & pbs . SetGroupMembersResponse { Item : item }, nil
2020-07-22 15:10:21 -07:00
}
// RemoveGroupMembers implements the interface pbs.GroupServiceServer.
func ( s Service ) RemoveGroupMembers ( ctx context . Context , req * pbs . RemoveGroupMembersRequest ) ( * pbs . RemoveGroupMembersResponse , error ) {
2021-05-04 19:57:35 -05:00
const op = "groups.(Service).RemoveGroupMembers"
2020-07-22 15:10:21 -07:00
if err := validateRemoveGroupMembersRequest ( req ); err != nil {
return nil , err
}
2020-09-04 14:48:11 -07:00
authResults := s . authResult ( ctx , req . GetId (), action . RemoveMembers )
if authResults . Error != nil {
return nil , authResults . Error
}
2021-05-04 19:57:35 -05:00
g , members , err := s . removeMembersInRepo ( ctx , req . GetId (), req . GetMemberIds (), req . GetVersion ())
2020-07-22 15:10:21 -07: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 ) {
outputOpts = append ( outputOpts , handlers . WithAuthorizedActions ( authResults . FetchActionSetForId ( ctx , g . GetPublicId (), IdActions ). Strings ()))
}
item , err := toProto ( ctx , g , members , outputOpts ... )
if err != nil {
return nil , err
}
return & pbs . RemoveGroupMembersResponse { Item : item }, nil
2020-07-22 15:10:21 -07:00
}
2021-05-04 19:57:35 -05:00
func ( s Service ) getFromRepo ( ctx context . Context , id string ) ( * iam . Group , [] * iam . GroupMember , error ) {
2021-04-16 12:06:03 -05:00
const op = "groups.(Service).getFromRepo"
2020-06-15 15:22:41 -07:00
repo , err := s . repoFn ()
if err != nil {
2021-07-29 18:52:56 -06:00
return nil , nil , errors . Wrap ( ctx , err , op )
2020-06-15 15:22:41 -07:00
}
2020-07-22 15:10:21 -07:00
g , m , err := repo . LookupGroup ( ctx , id )
2021-04-16 12:06:03 -05:00
if err != nil && ! errors . IsNotFoundError ( err ) {
2021-07-29 18:52:56 -06:00
return nil , nil , errors . Wrap ( ctx , err , op )
2020-06-15 15:22:41 -07:00
}
2020-07-22 15:10:21 -07:00
if g == nil {
2021-07-29 18:52:56 -06:00
return nil , nil , errors . New ( ctx , errors . InvalidParameter , op , fmt . Sprintf ( "group %q not found" , id ))
2020-06-15 15:22:41 -07:00
}
2021-05-04 19:57:35 -05:00
return g , m , err
2020-06-15 15:22:41 -07:00
}
2021-05-04 19:57:35 -05:00
func ( s Service ) createInRepo ( ctx context . Context , scopeId string , item * pb . Group ) ( * iam . Group , error ) {
2021-04-16 12:06:03 -05:00
const op = "groups.(Service).createInRepo"
2020-06-15 15:22:41 -07: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 ()))
}
2023-06-06 17:09:08 -04:00
u , err := iam . NewGroup ( ctx , scopeId , opts ... )
2020-06-15 15:22:41 -07:00
if err != nil {
2020-09-25 13:41:20 -07:00
return nil , handlers . ApiErrorWithCodeAndMessage ( codes . Internal , "Unable to build group for creation: %v." , err )
2020-06-15 15:22:41 -07:00
}
repo , err := s . repoFn ()
if err != nil {
2021-07-29 18:52:56 -06:00
return nil , errors . Wrap ( ctx , err , op )
2020-06-15 15:22:41 -07:00
}
out , err := repo . CreateGroup ( ctx , u )
if err != nil {
2021-07-29 18:52:56 -06:00
return nil , errors . Wrap ( ctx , err , op )
2020-06-15 15:22:41 -07:00
}
if out == nil {
2020-09-25 13:41:20 -07:00
return nil , handlers . ApiErrorWithCodeAndMessage ( codes . Internal , "Unable to create group but no error returned from repository." )
2020-06-15 15:22:41 -07:00
}
2021-05-04 19:57:35 -05:00
return out , nil
2020-06-15 15:22:41 -07:00
}
2021-05-04 19:57:35 -05:00
func ( s Service ) updateInRepo ( ctx context . Context , scopeId , id string , mask [] string , item * pb . Group ) ( * iam . Group , [] * iam . GroupMember , error ) {
2021-04-16 12:06:03 -05:00
const op = "groups.(Service).updateInRepo"
2020-06-15 15:22:41 -07:00
var opts [] iam . Option
if desc := item . GetDescription (); desc != nil {
opts = append ( opts , iam . WithDescription ( desc . GetValue ()))
}
if name := item . GetName (); name != nil {
opts = append ( opts , iam . WithName ( name . GetValue ()))
}
2020-08-18 17:38:38 -07:00
version := item . GetVersion ()
2023-06-06 17:09:08 -04:00
g , err := iam . NewGroup ( ctx , scopeId , opts ... )
2020-06-15 15:22:41 -07:00
if err != nil {
2021-05-04 19:57:35 -05:00
return nil , nil , handlers . ApiErrorWithCodeAndMessage ( codes . Internal , "Unable to build group for update: %v." , err )
2020-06-15 15:22:41 -07:00
}
2020-08-07 12:55:02 -04:00
g . PublicId = id
2020-07-20 11:17:09 -07:00
dbMask := maskManager . Translate ( mask )
2020-06-15 15:22:41 -07:00
if len ( dbMask ) == 0 {
2021-05-04 19:57:35 -05:00
return 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." })
2020-06-15 15:22:41 -07:00
}
repo , err := s . repoFn ()
if err != nil {
2021-07-29 18:52:56 -06:00
return nil , nil , errors . Wrap ( ctx , err , op )
2020-06-15 15:22:41 -07:00
}
2020-08-07 12:55:02 -04:00
out , m , rowsUpdated , err := repo . UpdateGroup ( ctx , g , version , dbMask )
2020-06-15 15:22:41 -07:00
if err != nil {
2021-07-29 18:52:56 -06:00
return nil , nil , errors . Wrap ( ctx , err , op )
2020-06-15 15:22:41 -07:00
}
if rowsUpdated == 0 {
2021-05-04 19:57:35 -05:00
return nil , nil , handlers . NotFoundErrorf ( "Group %q doesn't exist or incorrect version provided." , id )
2020-06-15 15:22:41 -07:00
}
2021-05-04 19:57:35 -05:00
return out , m , nil
2020-06-15 15:22:41 -07:00
}
func ( s Service ) deleteFromRepo ( ctx context . Context , id string ) ( bool , error ) {
2021-04-16 12:06:03 -05:00
const op = "groups.(Service).deleteFromRepo"
2020-06-15 15:22:41 -07:00
repo , err := s . repoFn ()
if err != nil {
return false , err
}
rows , err := repo . DeleteGroup ( ctx , id )
if err != nil {
2020-12-21 10:07:34 -08:00
if errors . IsNotFoundError ( err ) {
2020-06-15 15:22:41 -07:00
return false , nil
}
2021-07-29 18:52:56 -06:00
return false , errors . Wrap ( ctx , err , op , errors . WithMsg ( "unable to delete group" ))
2020-06-15 15:22:41 -07:00
}
return rows > 0 , nil
}
2021-05-04 19:57:35 -05:00
func ( s Service ) addMembersInRepo ( ctx context . Context , groupId string , userIds [] string , version uint32 ) ( * iam . Group , [] * iam . GroupMember , error ) {
2021-04-16 12:06:03 -05:00
const op = "groups.(Service).addMembersInRepo"
2020-07-22 15:10:21 -07:00
repo , err := s . repoFn ()
if err != nil {
2021-07-29 18:52:56 -06:00
return nil , nil , errors . Wrap ( ctx , err , op )
2020-07-22 15:10:21 -07:00
}
2020-10-02 16:45:32 -07:00
_ , err = repo . AddGroupMembers ( ctx , groupId , version , strutil . RemoveDuplicates ( userIds , false ))
2020-07-22 15:10:21 -07:00
if err != nil {
2020-09-25 13:41:20 -07:00
// TODO: Figure out a way to surface more helpful error info beyond the Internal error.
2021-05-04 19:57:35 -05:00
return nil , nil , handlers . ApiErrorWithCodeAndMessage ( codes . Internal , "Unable to add members to group: %v." , err )
2020-07-22 15:10:21 -07:00
}
out , m , err := repo . LookupGroup ( ctx , groupId )
if err != nil {
2021-07-29 18:52:56 -06:00
return nil , nil , errors . Wrap ( ctx , err , op , errors . WithMsg ( "unable to look up group after adding memebers" ))
2020-07-22 15:10:21 -07:00
}
if out == nil {
2021-05-04 19:57:35 -05:00
return nil , nil , handlers . ApiErrorWithCodeAndMessage ( codes . Internal , "Unable to lookup group after adding member to it." )
2020-07-22 15:10:21 -07:00
}
2021-05-04 19:57:35 -05:00
return out , m , nil
2020-07-22 15:10:21 -07:00
}
2021-05-04 19:57:35 -05:00
func ( s Service ) setMembersInRepo ( ctx context . Context , groupId string , userIds [] string , version uint32 ) ( * iam . Group , [] * iam . GroupMember , error ) {
2021-04-16 12:06:03 -05:00
const op = "groups.(Service).setMembersInRepo"
2020-07-22 15:10:21 -07:00
repo , err := s . repoFn ()
if err != nil {
2021-07-29 18:52:56 -06:00
return nil , nil , errors . Wrap ( ctx , err , op )
2020-07-22 15:10:21 -07:00
}
2020-10-02 16:45:32 -07:00
_ , _ , err = repo . SetGroupMembers ( ctx , groupId , version , strutil . RemoveDuplicates ( userIds , false ))
2020-07-22 15:10:21 -07:00
if err != nil {
2020-09-25 13:41:20 -07:00
// TODO: Figure out a way to surface more helpful error info beyond the Internal error.
2021-05-04 19:57:35 -05:00
return nil , nil , handlers . ApiErrorWithCodeAndMessage ( codes . Internal , "Unable to set members on group: %v." , err )
2020-07-22 15:10:21 -07:00
}
out , m , err := repo . LookupGroup ( ctx , groupId )
if err != nil {
2021-07-29 18:52:56 -06:00
return nil , nil , errors . Wrap ( ctx , err , op , errors . WithMsg ( "unable to look up group after setting members" ))
2020-07-22 15:10:21 -07:00
}
if out == nil {
2021-05-04 19:57:35 -05:00
return nil , nil , handlers . ApiErrorWithCodeAndMessage ( codes . Internal , "Unable to lookup group after setting members for it." )
2020-07-22 15:10:21 -07:00
}
2021-05-04 19:57:35 -05:00
return out , m , nil
2020-07-22 15:10:21 -07:00
}
2021-05-04 19:57:35 -05:00
func ( s Service ) removeMembersInRepo ( ctx context . Context , groupId string , userIds [] string , version uint32 ) ( * iam . Group , [] * iam . GroupMember , error ) {
2021-04-16 12:06:03 -05:00
const op = "groups.(Service).removeMembersInRepo"
2020-07-22 15:10:21 -07:00
repo , err := s . repoFn ()
if err != nil {
2021-07-29 18:52:56 -06:00
return nil , nil , errors . Wrap ( ctx , err , op )
2020-07-22 15:10:21 -07:00
}
2020-10-02 16:45:32 -07:00
_ , err = repo . DeleteGroupMembers ( ctx , groupId , version , strutil . RemoveDuplicates ( userIds , false ))
2020-07-22 15:10:21 -07:00
if err != nil {
2020-09-25 13:41:20 -07:00
// TODO: Figure out a way to surface more helpful error info beyond the Internal error.
2021-05-04 19:57:35 -05:00
return nil , nil , handlers . ApiErrorWithCodeAndMessage ( codes . Internal , "Unable to remove members from group: %v." , err )
2020-07-22 15:10:21 -07:00
}
out , m , err := repo . LookupGroup ( ctx , groupId )
if err != nil {
2021-07-29 18:52:56 -06:00
return nil , nil , errors . Wrap ( ctx , err , op , errors . WithMsg ( "unable to look up group after removing members" ))
2020-07-22 15:10:21 -07:00
}
if out == nil {
2021-05-04 19:57:35 -05:00
return nil , nil , handlers . ApiErrorWithCodeAndMessage ( codes . Internal , "Unable to lookup group after removing members from it." )
2020-07-22 15:10:21 -07:00
}
2021-05-04 19:57:35 -05:00
return out , m , nil
2020-07-22 15:10:21 -07:00
}
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 . Group ), auth . WithAction ( a )}
switch a {
case action . List , action . Create :
parentId = id
scp , err := repo . LookupScope ( ctx , parentId )
if err != nil {
res . Error = err
return res
}
if scp == nil {
2020-09-08 23:57:26 -04:00
res . Error = handlers . NotFoundError ()
2020-09-04 14:48:11 -07:00
return res
}
default :
grp , _ , err := repo . LookupGroup ( ctx , id )
if err != nil {
res . Error = err
return res
}
if grp == nil {
2020-09-08 23:57:26 -04:00
res . Error = handlers . NotFoundError ()
2020-09-04 14:48:11 -07:00
return res
}
parentId = grp . GetScopeId ()
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 . Group , members [] * iam . GroupMember , opt ... handlers . Option ) ( * pb . Group , error ) {
opts := handlers . GetOpts ( opt ... )
if opts . WithOutputFields == nil {
return nil , handlers . ApiErrorWithCodeAndMessage ( codes . Internal , "output fields not found when building group proto" )
2020-06-15 15:22:41 -07:00
}
2021-05-04 19:57:35 -05:00
outputFields := * opts . WithOutputFields
out := pb . Group {}
if outputFields . Has ( globals . IdField ) {
out . Id = in . GetPublicId ()
}
if outputFields . Has ( globals . ScopeIdField ) {
out . ScopeId = in . GetScopeId ()
}
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 ) {
out . CreatedTime = in . GetCreateTime (). GetTimestamp ()
}
if outputFields . Has ( globals . UpdatedTimeField ) {
out . UpdatedTime = in . GetUpdateTime (). GetTimestamp ()
2020-06-15 15:22:41 -07:00
}
2021-05-04 19:57:35 -05:00
if outputFields . Has ( globals . VersionField ) {
out . Version = in . GetVersion ()
2020-06-15 15:22:41 -07:00
}
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 . MemberIdsField ) {
for _ , m := range members {
out . MemberIds = append ( out . MemberIds , m . GetMemberId ())
}
}
if outputFields . Has ( globals . MembersField ) {
for _ , m := range members {
out . Members = append ( out . Members , & pb . Member {
Id : m . GetMemberId (),
ScopeId : m . GetMemberScopeId (),
})
}
2020-07-22 15:10:21 -07:00
}
2021-05-04 19:57:35 -05:00
return & out , nil
2020-06-15 15:22:41 -07: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-06-15 15:22:41 -07:00
func validateGetRequest ( req * pbs . GetGroupRequest ) error {
2023-03-10 15:06:39 -05:00
return handlers . ValidateGetRequest ( handlers . NoopValidatorFn , req , globals . GroupPrefix )
2020-06-15 15:22:41 -07:00
}
func validateCreateRequest ( req * pbs . CreateGroupRequest ) error {
2020-09-02 19:43:41 -07:00
return handlers . ValidateCreateRequest ( req . GetItem (), func () map [ string ] string {
badFields := map [ string ] string {}
2021-04-08 20:54:32 -04:00
if ! handlers . ValidId ( handlers . Id ( req . GetItem (). GetScopeId ()), scope . Org . Prefix ()) &&
! handlers . ValidId ( handlers . Id ( req . GetItem (). GetScopeId ()), scope . Project . Prefix ()) &&
2020-09-03 08:02:30 -07:00
scope . Global . String () != req . GetItem (). GetScopeId () {
badFields [ "scope_id" ] = "This field is missing or improperly formatted."
2020-09-02 19:43:41 -07:00
}
return badFields
})
2020-06-15 15:22:41 -07:00
}
func validateUpdateRequest ( req * pbs . UpdateGroupRequest ) error {
2023-03-10 15:06:39 -05:00
return handlers . ValidateUpdateRequest ( req , req . GetItem (), handlers . NoopValidatorFn , globals . GroupPrefix )
2020-06-15 15:22:41 -07:00
}
func validateDeleteRequest ( req * pbs . DeleteGroupRequest ) error {
2023-03-10 15:06:39 -05:00
return handlers . ValidateDeleteRequest ( handlers . NoopValidatorFn , req , globals . GroupPrefix )
2020-06-15 15:22:41 -07:00
}
2023-06-06 17:09:08 -04:00
func validateListRequest ( ctx context . Context , req * pbs . ListGroupsRequest ) error {
2020-07-22 00:14:28 -04:00
badFields := map [ string ] string {}
2021-04-08 20:54:32 -04:00
if ! handlers . ValidId ( handlers . Id ( req . GetScopeId ()), scope . Org . Prefix ()) &&
! handlers . ValidId ( handlers . Id ( req . GetScopeId ()), scope . Project . Prefix ()) &&
2020-09-08 23:57:26 -04:00
req . GetScopeId () != scope . Global . String () {
2020-09-02 19:43:41 -07:00
badFields [ "scope_id" ] = "Incorrectly formatted identifier."
}
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-06-22 10:46:45 -07:00
if len ( badFields ) > 0 {
return handlers . InvalidArgumentErrorf ( "Improperly formatted identifier." , badFields )
}
return nil
}
2020-07-22 15:10:21 -07:00
func validateAddGroupMembersRequest ( req * pbs . AddGroupMembersRequest ) error {
badFields := map [ string ] string {}
2023-03-10 15:06:39 -05:00
if ! handlers . ValidId ( handlers . Id ( req . GetId ()), globals . GroupPrefix ) {
2020-07-22 15:10:21 -07:00
badFields [ "id" ] = "Incorrectly formatted identifier."
}
2020-08-07 12:55:02 -04:00
if req . GetVersion () == 0 {
2020-07-22 15:10:21 -07:00
badFields [ "version" ] = "Required field."
}
2020-08-18 17:38:38 -07:00
if len ( req . GetMemberIds ()) == 0 {
2020-07-22 15:10:21 -07:00
badFields [ "member_ids" ] = "Must be non-empty."
}
2020-08-20 13:34:03 -04:00
for _ , id := range req . GetMemberIds () {
2023-03-10 15:06:39 -05:00
if ! handlers . ValidId ( handlers . Id ( id ), globals . UserPrefix ) {
2020-10-08 12:36:01 -07:00
badFields [ "member_ids" ] = fmt . Sprintf ( "Must only contain valid user ids but found %q." , id )
2020-10-02 16:45:32 -07:00
break
}
2022-10-28 15:52:06 -04:00
if id == globals . RecoveryUserId {
2020-10-02 16:45:32 -07:00
badFields [ "member_ids" ] = "u_recovery cannot be assigned to a group."
break
2020-08-20 13:34:03 -04:00
}
}
2020-07-22 15:10:21 -07:00
if len ( badFields ) > 0 {
return handlers . InvalidArgumentErrorf ( "Errors in provided fields." , badFields )
}
return nil
}
func validateSetGroupMembersRequest ( req * pbs . SetGroupMembersRequest ) error {
badFields := map [ string ] string {}
2023-03-10 15:06:39 -05:00
if ! handlers . ValidId ( handlers . Id ( req . GetId ()), globals . GroupPrefix ) {
2020-07-22 15:10:21 -07:00
badFields [ "id" ] = "Incorrectly formatted identifier."
}
2020-08-07 12:55:02 -04:00
if req . GetVersion () == 0 {
2020-07-22 15:10:21 -07:00
badFields [ "version" ] = "Required field."
}
2020-08-20 13:34:03 -04:00
for _ , id := range req . GetMemberIds () {
2023-03-10 15:06:39 -05:00
if ! handlers . ValidId ( handlers . Id ( id ), globals . UserPrefix ) {
2020-10-08 12:36:01 -07:00
badFields [ "member_ids" ] = fmt . Sprintf ( "Must only contain valid user ids but found %q." , id )
2020-10-02 16:45:32 -07:00
break
}
2022-10-28 15:52:06 -04:00
if id == globals . RecoveryUserId {
2020-10-02 16:45:32 -07:00
badFields [ "member_ids" ] = "u_recovery cannot be assigned to a group."
break
2020-08-20 13:34:03 -04:00
}
}
2020-07-22 15:10:21 -07:00
if len ( badFields ) > 0 {
return handlers . InvalidArgumentErrorf ( "Errors in provided fields." , badFields )
}
return nil
}
func validateRemoveGroupMembersRequest ( req * pbs . RemoveGroupMembersRequest ) error {
badFields := map [ string ] string {}
2023-03-10 15:06:39 -05:00
if ! handlers . ValidId ( handlers . Id ( req . GetId ()), globals . GroupPrefix ) {
2020-07-22 15:10:21 -07:00
badFields [ "id" ] = "Incorrectly formatted identifier."
}
2020-08-07 12:55:02 -04:00
if req . GetVersion () == 0 {
2020-07-22 15:10:21 -07:00
badFields [ "version" ] = "Required field."
}
2020-08-18 17:38:38 -07:00
if len ( req . GetMemberIds ()) == 0 {
2020-07-22 15:10:21 -07:00
badFields [ "member_ids" ] = "Must be non-empty."
}
2020-10-02 16:45:32 -07:00
for _ , id := range req . GetMemberIds () {
2023-03-10 15:06:39 -05:00
if ! handlers . ValidId ( handlers . Id ( id ), globals . UserPrefix ) {
2020-10-08 12:36:01 -07:00
badFields [ "member_ids" ] = fmt . Sprintf ( "Must only contain valid user ids but found %q." , id )
2020-10-02 16:45:32 -07:00
break
}
}
2020-07-22 15:10:21 -07:00
if len ( badFields ) > 0 {
return handlers . InvalidArgumentErrorf ( "Errors in provided fields." , badFields )
}
return nil
}
2023-12-21 12:54:27 -08:00
func newOutputOpts ( ctx context . Context , item * iam . Group , scopeInfoMap map [ string ] * scopes . ScopeInfo , authResults auth . VerifyResults ) ([] handlers . Option , bool ) {
res := perms . Resource {
Type : resource . Group ,
}
res . Id = item . GetPublicId ()
res . ScopeId = item . GetScopeId ()
2025-01-24 10:23:33 -08:00
res . ParentScopeId = scopeInfoMap [ item . GetScopeId ()]. GetParentScopeId ()
2023-12-21 12:54:27 -08:00
authorizedActions := authResults . FetchActionSetForId ( ctx , item . GetPublicId (), IdActions , auth . WithResource ( & res ))
if len ( authorizedActions ) == 0 {
return nil , false
}
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 . GetScopeId ()]))
}
if outputFields . Has ( globals . AuthorizedActionsField ) {
outputOpts = append ( outputOpts , handlers . WithAuthorizedActions ( authorizedActions . Strings ()))
}
return outputOpts , true
}