Post-merge some issues were discovered with the ways typing is used for proto reflection. This will be reworked without moving the subtypes around.
907 lines
30 KiB
Go
907 lines
30 KiB
Go
// Copyright (c) HashiCorp, Inc.
|
|
// SPDX-License-Identifier: BUSL-1.1
|
|
|
|
package managed_groups
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
|
|
"github.com/hashicorp/boundary/globals"
|
|
"github.com/hashicorp/boundary/internal/auth"
|
|
"github.com/hashicorp/boundary/internal/auth/ldap"
|
|
ldapstore "github.com/hashicorp/boundary/internal/auth/ldap/store"
|
|
"github.com/hashicorp/boundary/internal/auth/oidc"
|
|
oidcstore "github.com/hashicorp/boundary/internal/auth/oidc/store"
|
|
requestauth "github.com/hashicorp/boundary/internal/daemon/controller/auth"
|
|
"github.com/hashicorp/boundary/internal/daemon/controller/common"
|
|
"github.com/hashicorp/boundary/internal/daemon/controller/handlers"
|
|
"github.com/hashicorp/boundary/internal/errors"
|
|
pbs "github.com/hashicorp/boundary/internal/gen/controller/api/services"
|
|
"github.com/hashicorp/boundary/internal/perms"
|
|
"github.com/hashicorp/boundary/internal/requests"
|
|
"github.com/hashicorp/boundary/internal/types/action"
|
|
"github.com/hashicorp/boundary/internal/types/resource"
|
|
"github.com/hashicorp/boundary/internal/types/subtypes"
|
|
pb "github.com/hashicorp/boundary/sdk/pbs/controller/api/resources/managedgroups"
|
|
"github.com/hashicorp/go-bexpr"
|
|
"google.golang.org/grpc/codes"
|
|
"google.golang.org/protobuf/types/known/wrapperspb"
|
|
)
|
|
|
|
const (
|
|
// oidc field names
|
|
attrFilterField = "attributes.filter"
|
|
attrGroupNamesField = "attributes.group_names"
|
|
|
|
domain = "auth"
|
|
)
|
|
|
|
var (
|
|
oidcMaskManager handlers.MaskManager
|
|
ldapMaskManager handlers.MaskManager
|
|
|
|
// IdActions contains the set of actions that can be performed on
|
|
// individual resources
|
|
IdActions = map[subtypes.Subtype]action.ActionSet{
|
|
oidc.Subtype: {
|
|
action.NoOp,
|
|
action.Read,
|
|
action.Update,
|
|
action.Delete,
|
|
},
|
|
ldap.Subtype: {
|
|
action.NoOp,
|
|
action.Read,
|
|
action.Update,
|
|
action.Delete,
|
|
},
|
|
}
|
|
|
|
// CollectionActions contains the set of actions that can be performed on
|
|
// this collection
|
|
CollectionActions = action.ActionSet{
|
|
action.Create,
|
|
action.List,
|
|
}
|
|
)
|
|
|
|
func init() {
|
|
var err error
|
|
if oidcMaskManager, err = handlers.NewMaskManager(
|
|
context.Background(),
|
|
handlers.MaskDestination{&oidcstore.ManagedGroup{}},
|
|
handlers.MaskSource{&pb.ManagedGroup{}, &pb.OidcManagedGroupAttributes{}},
|
|
); err != nil {
|
|
panic(err)
|
|
}
|
|
if ldapMaskManager, err = handlers.NewMaskManager(
|
|
context.Background(),
|
|
handlers.MaskDestination{&ldapstore.ManagedGroup{}},
|
|
handlers.MaskSource{&pb.ManagedGroup{}, &pb.LdapManagedGroupAttributes{}},
|
|
); err != nil {
|
|
panic(err)
|
|
}
|
|
}
|
|
|
|
// Service handles request as described by the pbs.ManagedGroupServiceServer interface.
|
|
type Service struct {
|
|
pbs.UnsafeManagedGroupServiceServer
|
|
|
|
oidcRepoFn common.OidcAuthRepoFactory
|
|
ldapRepoFn common.LdapAuthRepoFactory
|
|
}
|
|
|
|
var _ pbs.ManagedGroupServiceServer = (*Service)(nil)
|
|
|
|
// NewService returns a managed group service which handles managed group related requests to boundary.
|
|
func NewService(ctx context.Context, oidcRepo common.OidcAuthRepoFactory, ldapRepo common.LdapAuthRepoFactory) (Service, error) {
|
|
const op = "managed_groups.NewService"
|
|
switch {
|
|
case oidcRepo == nil:
|
|
return Service{}, errors.New(ctx, errors.InvalidParameter, op, "missing oidc repository provided")
|
|
case ldapRepo == nil:
|
|
return Service{}, errors.New(ctx, errors.InvalidParameter, op, "missing ldap repository provided")
|
|
}
|
|
return Service{oidcRepoFn: oidcRepo, ldapRepoFn: ldapRepo}, nil
|
|
}
|
|
|
|
// ListManagedGroups implements the interface pbs.ManagedGroupsServiceServer.
|
|
func (s Service) ListManagedGroups(ctx context.Context, req *pbs.ListManagedGroupsRequest) (*pbs.ListManagedGroupsResponse, error) {
|
|
if err := validateListRequest(ctx, req); err != nil {
|
|
return nil, err
|
|
}
|
|
_, authResults := s.parentAndAuthResult(ctx, req.GetAuthMethodId(), action.List)
|
|
if authResults.Error != nil {
|
|
return nil, authResults.Error
|
|
}
|
|
ul, err := s.listFromRepo(ctx, req.GetAuthMethodId())
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if len(ul) == 0 {
|
|
return &pbs.ListManagedGroupsResponse{}, nil
|
|
}
|
|
|
|
filter, err := handlers.NewFilter(ctx, req.GetFilter())
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
finalItems := make([]*pb.ManagedGroup, 0, len(ul))
|
|
|
|
res := perms.Resource{
|
|
ScopeId: authResults.Scope.Id,
|
|
Type: resource.ManagedGroup,
|
|
Pin: req.GetAuthMethodId(),
|
|
}
|
|
for _, mg := range ul {
|
|
res.Id = mg.GetPublicId()
|
|
authorizedActions := authResults.FetchActionSetForId(ctx, mg.GetPublicId(), IdActions[subtypes.SubtypeFromId(domain, mg.GetPublicId())], requestauth.WithResource(&res)).Strings()
|
|
if len(authorizedActions) == 0 {
|
|
continue
|
|
}
|
|
|
|
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(authResults.Scope))
|
|
}
|
|
if outputFields.Has(globals.AuthorizedActionsField) {
|
|
outputOpts = append(outputOpts, handlers.WithAuthorizedActions(authorizedActions))
|
|
}
|
|
|
|
item, err := toProto(ctx, mg, outputOpts...)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// This comes last so that we can use item fields in the filter after
|
|
// the allowed fields are populated above
|
|
filterable, err := subtypes.Filterable(item)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if filter.Match(filterable) {
|
|
finalItems = append(finalItems, item)
|
|
}
|
|
}
|
|
return &pbs.ListManagedGroupsResponse{Items: finalItems}, nil
|
|
}
|
|
|
|
// GetManagedGroup implements the interface pbs.ManagedGroupServiceServer.
|
|
func (s Service) GetManagedGroup(ctx context.Context, req *pbs.GetManagedGroupRequest) (*pbs.GetManagedGroupResponse, error) {
|
|
const op = "managed_groups.(Service).GetManagedGroup"
|
|
|
|
if err := validateGetRequest(ctx, req); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
_, authResults := s.parentAndAuthResult(ctx, req.GetId(), action.Read)
|
|
if authResults.Error != nil {
|
|
return nil, authResults.Error
|
|
}
|
|
mg, memberIds, err := s.getFromRepo(ctx, req.GetId())
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
outputFields, ok := requests.OutputFields(ctx)
|
|
if !ok {
|
|
return nil, errors.New(ctx, errors.Internal, op, "no request context found")
|
|
}
|
|
|
|
outputOpts := make([]handlers.Option, 0, 3)
|
|
outputOpts = append(outputOpts, handlers.WithOutputFields(outputFields))
|
|
if outputFields.Has(globals.ScopeField) {
|
|
outputOpts = append(outputOpts, handlers.WithScope(authResults.Scope))
|
|
}
|
|
if outputFields.Has(globals.AuthorizedActionsField) {
|
|
outputOpts = append(outputOpts, handlers.WithAuthorizedActions(authResults.FetchActionSetForId(ctx, mg.GetPublicId(), IdActions[subtypes.SubtypeFromId(domain, mg.GetPublicId())]).Strings()))
|
|
}
|
|
if outputFields.Has(globals.MemberIdsField) {
|
|
outputOpts = append(outputOpts, handlers.WithMemberIds(memberIds))
|
|
}
|
|
|
|
item, err := toProto(ctx, mg, outputOpts...)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &pbs.GetManagedGroupResponse{Item: item}, nil
|
|
}
|
|
|
|
// CreateManagedGroup implements the interface pbs.ManagedGroupServiceServer.
|
|
func (s Service) CreateManagedGroup(ctx context.Context, req *pbs.CreateManagedGroupRequest) (*pbs.CreateManagedGroupResponse, error) {
|
|
const op = "managed_groups.(Service).CreateManagedGroup"
|
|
|
|
if err := validateCreateRequest(ctx, req); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
authMeth, authResults := s.parentAndAuthResult(ctx, req.GetItem().GetAuthMethodId(), action.Create)
|
|
if authResults.Error != nil {
|
|
return nil, authResults.Error
|
|
}
|
|
mg, err := s.createInRepo(ctx, authMeth, req.GetItem())
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
outputFields, ok := requests.OutputFields(ctx)
|
|
if !ok {
|
|
return nil, errors.New(ctx, errors.Internal, op, "no request context found")
|
|
}
|
|
|
|
outputOpts := make([]handlers.Option, 0, 3)
|
|
outputOpts = append(outputOpts, handlers.WithOutputFields(outputFields))
|
|
if outputFields.Has(globals.ScopeField) {
|
|
outputOpts = append(outputOpts, handlers.WithScope(authResults.Scope))
|
|
}
|
|
if outputFields.Has(globals.AuthorizedActionsField) {
|
|
outputOpts = append(outputOpts, handlers.WithAuthorizedActions(authResults.FetchActionSetForId(ctx, mg.GetPublicId(), IdActions[subtypes.SubtypeFromId(domain, mg.GetPublicId())]).Strings()))
|
|
}
|
|
|
|
item, err := toProto(ctx, mg, outputOpts...)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &pbs.CreateManagedGroupResponse{Item: item, Uri: fmt.Sprintf("managed-groups/%s", item.GetId())}, nil
|
|
}
|
|
|
|
// UpdateManagedGroup implements the interface pbs.ManagedGroupServiceServer.
|
|
func (s Service) UpdateManagedGroup(ctx context.Context, req *pbs.UpdateManagedGroupRequest) (*pbs.UpdateManagedGroupResponse, error) {
|
|
const op = "managed_groups.(Service).UpdateManagedGroup"
|
|
|
|
if err := validateUpdateRequest(ctx, req); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
authMeth, authResults := s.parentAndAuthResult(ctx, req.GetId(), action.Update)
|
|
if authResults.Error != nil {
|
|
return nil, authResults.Error
|
|
}
|
|
mg, err := s.updateInRepo(ctx, authResults.Scope.GetId(), authMeth.GetPublicId(), req)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
outputFields, ok := requests.OutputFields(ctx)
|
|
if !ok {
|
|
return nil, errors.New(ctx, errors.Internal, op, "no request context found")
|
|
}
|
|
|
|
outputOpts := make([]handlers.Option, 0, 3)
|
|
outputOpts = append(outputOpts, handlers.WithOutputFields(outputFields))
|
|
if outputFields.Has(globals.ScopeField) {
|
|
outputOpts = append(outputOpts, handlers.WithScope(authResults.Scope))
|
|
}
|
|
if outputFields.Has(globals.AuthorizedActionsField) {
|
|
outputOpts = append(outputOpts, handlers.WithAuthorizedActions(authResults.FetchActionSetForId(ctx, mg.GetPublicId(), IdActions[subtypes.SubtypeFromId(domain, mg.GetPublicId())]).Strings()))
|
|
}
|
|
|
|
item, err := toProto(ctx, mg, outputOpts...)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &pbs.UpdateManagedGroupResponse{Item: item}, nil
|
|
}
|
|
|
|
// DeleteManagedGroup implements the interface pbs.ManagedGroupServiceServer.
|
|
func (s Service) DeleteManagedGroup(ctx context.Context, req *pbs.DeleteManagedGroupRequest) (*pbs.DeleteManagedGroupResponse, error) {
|
|
if err := validateDeleteRequest(ctx, req); err != nil {
|
|
return nil, err
|
|
}
|
|
_, authResults := s.parentAndAuthResult(ctx, req.GetId(), action.Delete)
|
|
if authResults.Error != nil {
|
|
return nil, authResults.Error
|
|
}
|
|
_, err := s.deleteFromRepo(ctx, authResults.Scope.GetId(), req.GetId())
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return nil, nil
|
|
}
|
|
|
|
func (s Service) getFromRepo(ctx context.Context, id string) (auth.ManagedGroup, []string, error) {
|
|
var out auth.ManagedGroup
|
|
var memberIds []string
|
|
switch subtypes.SubtypeFromId(domain, id) {
|
|
case oidc.Subtype:
|
|
repo, err := s.oidcRepoFn()
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
mg, err := repo.LookupManagedGroup(ctx, id)
|
|
if err != nil {
|
|
if errors.IsNotFoundError(err) {
|
|
return nil, nil, handlers.NotFoundErrorf("ManagedGroup %q doesn't exist.", id)
|
|
}
|
|
return nil, nil, err
|
|
}
|
|
ids, err := repo.ListManagedGroupMembershipsByGroup(ctx, mg.GetPublicId())
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
if len(ids) > 0 {
|
|
memberIds = make([]string, len(ids))
|
|
for i, v := range ids {
|
|
memberIds[i] = v.MemberId
|
|
}
|
|
}
|
|
out = mg
|
|
case ldap.Subtype:
|
|
repo, err := s.ldapRepoFn()
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
mg, err := repo.LookupManagedGroup(ctx, id)
|
|
if err != nil {
|
|
if errors.IsNotFoundError(err) {
|
|
return nil, nil, handlers.NotFoundErrorf("LDAP ManagedGroup %q doesn't exist.", id)
|
|
}
|
|
return nil, nil, err
|
|
}
|
|
ids, err := repo.ListManagedGroupMembershipsByGroup(ctx, mg.GetPublicId())
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
if len(ids) > 0 {
|
|
memberIds = make([]string, len(ids))
|
|
for i, v := range ids {
|
|
memberIds[i] = v.MemberId
|
|
}
|
|
}
|
|
out = mg
|
|
default:
|
|
return nil, nil, handlers.NotFoundErrorf("Unrecognized id.")
|
|
}
|
|
return out, memberIds, nil
|
|
}
|
|
|
|
func (s Service) createOidcInRepo(ctx context.Context, am auth.AuthMethod, item *pb.ManagedGroup) (*oidc.ManagedGroup, error) {
|
|
const op = "managed_groups.(Service).createOidcInRepo"
|
|
if item == nil {
|
|
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing item")
|
|
}
|
|
var opts []oidc.Option
|
|
if item.GetName() != nil {
|
|
opts = append(opts, oidc.WithName(item.GetName().GetValue()))
|
|
}
|
|
if item.GetDescription() != nil {
|
|
opts = append(opts, oidc.WithDescription(item.GetDescription().GetValue()))
|
|
}
|
|
attrs := item.GetOidcManagedGroupAttributes()
|
|
mg, err := oidc.NewManagedGroup(ctx, am.GetPublicId(), attrs.GetFilter(), opts...)
|
|
if err != nil {
|
|
return nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "Unable to build managed group for creation: %v.", err)
|
|
}
|
|
repo, err := s.oidcRepoFn()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
out, err := repo.CreateManagedGroup(ctx, am.GetScopeId(), mg)
|
|
if err != nil {
|
|
return nil, errors.Wrap(ctx, err, op, errors.WithMsg("unable to create managed group"))
|
|
}
|
|
if out == nil {
|
|
return nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "Unable to create managed group but no error returned from repository.")
|
|
}
|
|
return out, nil
|
|
}
|
|
|
|
func (s Service) createLdapInRepo(ctx context.Context, am auth.AuthMethod, item *pb.ManagedGroup) (*ldap.ManagedGroup, error) {
|
|
const op = "managed_groups.(Service).createLdapInRepo"
|
|
if item == nil {
|
|
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing item")
|
|
}
|
|
var opts []ldap.Option
|
|
if item.GetName() != nil {
|
|
opts = append(opts, ldap.WithName(ctx, item.GetName().GetValue()))
|
|
}
|
|
if item.GetDescription() != nil {
|
|
opts = append(opts, ldap.WithDescription(ctx, item.GetDescription().GetValue()))
|
|
}
|
|
attrs := item.GetLdapManagedGroupAttributes()
|
|
mg, err := ldap.NewManagedGroup(ctx, am.GetPublicId(), attrs.GetGroupNames(), opts...)
|
|
if err != nil {
|
|
return nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "Unable to build managed group for creation: %v.", err)
|
|
}
|
|
repo, err := s.ldapRepoFn()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
out, err := repo.CreateManagedGroup(ctx, am.GetScopeId(), mg)
|
|
if err != nil {
|
|
return nil, errors.Wrap(ctx, err, op, errors.WithMsg("unable to create managed group"))
|
|
}
|
|
if out == nil {
|
|
return nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "Unable to create managed group but no error returned from repository.")
|
|
}
|
|
return out, nil
|
|
}
|
|
|
|
func (s Service) createInRepo(ctx context.Context, am auth.AuthMethod, item *pb.ManagedGroup) (auth.ManagedGroup, error) {
|
|
const op = "managed_groups.(Service).createInRepo"
|
|
if item == nil {
|
|
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing item")
|
|
}
|
|
var out auth.ManagedGroup
|
|
switch subtypes.SubtypeFromId(domain, am.GetPublicId()) {
|
|
case oidc.Subtype:
|
|
am, err := s.createOidcInRepo(ctx, am, item)
|
|
if err != nil {
|
|
return nil, errors.Wrap(ctx, err, op)
|
|
}
|
|
if am == nil {
|
|
return nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "Unable to create managed group but no error returned from repository.")
|
|
}
|
|
out = am
|
|
case ldap.Subtype:
|
|
am, err := s.createLdapInRepo(ctx, am, item)
|
|
if err != nil {
|
|
return nil, errors.Wrap(ctx, err, op)
|
|
}
|
|
if am == nil {
|
|
return nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "Unable to create ldap managed group but no error returned from repository.")
|
|
}
|
|
out = am
|
|
}
|
|
return out, nil
|
|
}
|
|
|
|
func (s Service) updateOidcInRepo(ctx context.Context, scopeId, amId, id string, mask []string, item *pb.ManagedGroup) (*oidc.ManagedGroup, error) {
|
|
const op = "managed_groups.(Service).updateOidcInRepo"
|
|
if item == nil {
|
|
return nil, errors.New(ctx, errors.InvalidParameter, op, "nil managed group.")
|
|
}
|
|
mg := oidc.AllocManagedGroup()
|
|
mg.PublicId = id
|
|
if item.GetName() != nil {
|
|
mg.Name = item.GetName().GetValue()
|
|
}
|
|
if item.GetDescription() != nil {
|
|
mg.Description = item.GetDescription().GetValue()
|
|
}
|
|
// Set this regardless; it'll only take effect if the masks contain the value
|
|
mg.Filter = item.GetOidcManagedGroupAttributes().GetFilter()
|
|
|
|
version := item.GetVersion()
|
|
|
|
dbMask := oidcMaskManager.Translate(mask)
|
|
if len(dbMask) == 0 {
|
|
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."})
|
|
}
|
|
repo, err := s.oidcRepoFn()
|
|
if err != nil {
|
|
return nil, errors.Wrap(ctx, err, op)
|
|
}
|
|
out, rowsUpdated, err := repo.UpdateManagedGroup(ctx, scopeId, mg, version, dbMask)
|
|
if err != nil {
|
|
return nil, errors.Wrap(ctx, err, op, errors.WithMsg("unable to update managed group"))
|
|
}
|
|
if rowsUpdated == 0 {
|
|
return nil, handlers.NotFoundErrorf("Managed Group %q doesn't exist or incorrect version provided.", id)
|
|
}
|
|
return out, nil
|
|
}
|
|
|
|
func (s Service) updateLdapInRepo(ctx context.Context, scopeId, amId, id string, mask []string, item *pb.ManagedGroup) (*ldap.ManagedGroup, error) {
|
|
const op = "managed_groups.(Service).updateLdapInRepo"
|
|
if item == nil {
|
|
return nil, errors.New(ctx, errors.InvalidParameter, op, "nil managed group.")
|
|
}
|
|
mg := ldap.AllocManagedGroup()
|
|
mg.PublicId = id
|
|
if item.GetName() != nil {
|
|
mg.Name = item.GetName().GetValue()
|
|
}
|
|
if item.GetDescription() != nil {
|
|
mg.Description = item.GetDescription().GetValue()
|
|
}
|
|
// Set this regardless; it'll only take effect if the masks contain the value
|
|
encodedGroupNames, err := json.Marshal(item.GetLdapManagedGroupAttributes().GetGroupNames())
|
|
if err != nil {
|
|
return nil, errors.Wrap(ctx, err, op, errors.WithMsg("unable to encode group names"))
|
|
}
|
|
mg.GroupNames = string(encodedGroupNames)
|
|
|
|
version := item.GetVersion()
|
|
|
|
dbMask := ldapMaskManager.Translate(mask)
|
|
if len(dbMask) == 0 {
|
|
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."})
|
|
}
|
|
repo, err := s.ldapRepoFn()
|
|
if err != nil {
|
|
return nil, errors.Wrap(ctx, err, op)
|
|
}
|
|
out, rowsUpdated, err := repo.UpdateManagedGroup(ctx, scopeId, mg, version, dbMask)
|
|
if err != nil {
|
|
return nil, errors.Wrap(ctx, err, op, errors.WithMsg("unable to update managed group"))
|
|
}
|
|
if rowsUpdated == 0 {
|
|
return nil, handlers.NotFoundErrorf("Managed Group %q doesn't exist or incorrect version provided.", id)
|
|
}
|
|
return out, nil
|
|
}
|
|
|
|
func (s Service) updateInRepo(ctx context.Context, scopeId, authMethodId string, req *pbs.UpdateManagedGroupRequest) (auth.ManagedGroup, error) {
|
|
const op = "managed_groups.(Service).updateInRepo"
|
|
var out auth.ManagedGroup
|
|
switch subtypes.SubtypeFromId(domain, req.GetId()) {
|
|
case oidc.Subtype:
|
|
mg, err := s.updateOidcInRepo(ctx, scopeId, authMethodId, req.GetId(), req.GetUpdateMask().GetPaths(), req.GetItem())
|
|
if err != nil {
|
|
return nil, errors.Wrap(ctx, err, op)
|
|
}
|
|
if mg == nil {
|
|
return nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "Unable to update managed group but no error returned from repository.")
|
|
}
|
|
out = mg
|
|
case ldap.Subtype:
|
|
mg, err := s.updateLdapInRepo(ctx, scopeId, authMethodId, req.GetId(), req.GetUpdateMask().GetPaths(), req.GetItem())
|
|
if err != nil {
|
|
return nil, errors.Wrap(ctx, err, op)
|
|
}
|
|
if mg == nil {
|
|
return nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "Unable to update managed group but no error returned from repository.")
|
|
}
|
|
out = mg
|
|
}
|
|
return out, nil
|
|
}
|
|
|
|
func (s Service) deleteFromRepo(ctx context.Context, scopeId, id string) (bool, error) {
|
|
const op = "managed_groups.(Service).deleteFromRepo"
|
|
var rows int
|
|
var err error
|
|
switch subtypes.SubtypeFromId(domain, id) {
|
|
case oidc.Subtype:
|
|
repo, iErr := s.oidcRepoFn()
|
|
if iErr != nil {
|
|
return false, iErr
|
|
}
|
|
rows, err = repo.DeleteManagedGroup(ctx, scopeId, id)
|
|
case ldap.Subtype:
|
|
repo, iErr := s.ldapRepoFn()
|
|
if iErr != nil {
|
|
return false, iErr
|
|
}
|
|
rows, err = repo.DeleteManagedGroup(ctx, scopeId, id)
|
|
}
|
|
if err != nil {
|
|
if errors.IsNotFoundError(err) {
|
|
return false, nil
|
|
}
|
|
return false, errors.Wrap(ctx, err, op)
|
|
}
|
|
return rows > 0, nil
|
|
}
|
|
|
|
func (s Service) listFromRepo(ctx context.Context, authMethodId string) ([]auth.ManagedGroup, error) {
|
|
const op = "managed_groups.(Service).listFromRepo"
|
|
|
|
var outUl []auth.ManagedGroup
|
|
switch subtypes.SubtypeFromId(domain, authMethodId) {
|
|
case oidc.Subtype:
|
|
oidcRepo, err := s.oidcRepoFn()
|
|
if err != nil {
|
|
return nil, errors.Wrap(ctx, err, op)
|
|
}
|
|
oidcl, err := oidcRepo.ListManagedGroups(ctx, authMethodId, oidc.WithLimit(-1))
|
|
if err != nil {
|
|
return nil, errors.Wrap(ctx, err, op)
|
|
}
|
|
for _, a := range oidcl {
|
|
outUl = append(outUl, a)
|
|
}
|
|
case ldap.Subtype:
|
|
ldapRepo, err := s.ldapRepoFn()
|
|
if err != nil {
|
|
return nil, errors.Wrap(ctx, err, op)
|
|
}
|
|
oidcl, err := ldapRepo.ListManagedGroups(ctx, authMethodId, ldap.WithLimit(ctx, -1))
|
|
if err != nil {
|
|
return nil, errors.Wrap(ctx, err, op)
|
|
}
|
|
for _, a := range oidcl {
|
|
outUl = append(outUl, a)
|
|
}
|
|
}
|
|
return outUl, nil
|
|
}
|
|
|
|
func (s Service) parentAndAuthResult(ctx context.Context, id string, a action.Type) (auth.AuthMethod, requestauth.VerifyResults) {
|
|
const op = "managed_groups.(Service)."
|
|
res := requestauth.VerifyResults{}
|
|
oidcRepo, err := s.oidcRepoFn()
|
|
if err != nil {
|
|
res.Error = err
|
|
return nil, res
|
|
}
|
|
ldapRepo, err := s.ldapRepoFn()
|
|
if err != nil {
|
|
res.Error = err
|
|
return nil, res
|
|
}
|
|
|
|
var parentId string
|
|
opts := []requestauth.Option{requestauth.WithType(resource.ManagedGroup), requestauth.WithAction(a)}
|
|
switch a {
|
|
case action.List, action.Create:
|
|
parentId = id
|
|
default:
|
|
switch subtypes.SubtypeFromId(domain, id) {
|
|
case oidc.Subtype:
|
|
grp, err := oidcRepo.LookupManagedGroup(ctx, id)
|
|
if err != nil {
|
|
res.Error = err
|
|
return nil, res
|
|
}
|
|
if grp == nil {
|
|
res.Error = handlers.NotFoundError()
|
|
return nil, res
|
|
}
|
|
parentId = grp.GetAuthMethodId()
|
|
case ldap.Subtype:
|
|
grp, err := ldapRepo.LookupManagedGroup(ctx, id)
|
|
if err != nil {
|
|
res.Error = err
|
|
return nil, res
|
|
}
|
|
if grp == nil {
|
|
res.Error = handlers.NotFoundError()
|
|
return nil, res
|
|
}
|
|
parentId = grp.GetAuthMethodId()
|
|
default:
|
|
res.Error = errors.New(ctx, errors.InvalidPublicId, op, "unrecognized managed group subtype")
|
|
return nil, res
|
|
}
|
|
opts = append(opts, requestauth.WithId(id))
|
|
}
|
|
|
|
var authMeth auth.AuthMethod
|
|
switch subtypes.SubtypeFromId(domain, parentId) {
|
|
case oidc.Subtype:
|
|
am, err := oidcRepo.LookupAuthMethod(ctx, parentId)
|
|
if err != nil {
|
|
res.Error = err
|
|
return nil, res
|
|
}
|
|
if am == nil {
|
|
res.Error = handlers.NotFoundError()
|
|
return nil, res
|
|
}
|
|
authMeth = am
|
|
opts = append(opts, requestauth.WithScopeId(am.GetScopeId()))
|
|
case ldap.Subtype:
|
|
am, err := ldapRepo.LookupAuthMethod(ctx, parentId)
|
|
if err != nil {
|
|
res.Error = err
|
|
return nil, res
|
|
}
|
|
if am == nil {
|
|
res.Error = handlers.NotFoundError()
|
|
return nil, res
|
|
}
|
|
authMeth = am
|
|
opts = append(opts, requestauth.WithScopeId(am.GetScopeId()))
|
|
default:
|
|
res.Error = errors.New(ctx, errors.InvalidPublicId, op, "unrecognized auth method subtype")
|
|
return nil, res
|
|
}
|
|
opts = append(opts, requestauth.WithPin(parentId))
|
|
return authMeth, requestauth.Verify(ctx, opts...)
|
|
}
|
|
|
|
func toProto(ctx context.Context, in auth.ManagedGroup, opt ...handlers.Option) (*pb.ManagedGroup, error) {
|
|
opts := handlers.GetOpts(opt...)
|
|
if opts.WithOutputFields == nil {
|
|
return nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "output fields not found when building managed group proto")
|
|
}
|
|
outputFields := *opts.WithOutputFields
|
|
|
|
out := pb.ManagedGroup{}
|
|
if outputFields.Has(globals.IdField) {
|
|
out.Id = in.GetPublicId()
|
|
}
|
|
if outputFields.Has(globals.AuthMethodIdField) {
|
|
out.AuthMethodId = in.GetAuthMethodId()
|
|
}
|
|
if outputFields.Has(globals.DescriptionField) && in.GetDescription() != "" {
|
|
out.Description = &wrapperspb.StringValue{Value: in.GetDescription()}
|
|
}
|
|
if outputFields.Has(globals.NameField) && in.GetName() != "" {
|
|
out.Name = &wrapperspb.StringValue{Value: in.GetName()}
|
|
}
|
|
if outputFields.Has(globals.CreatedTimeField) {
|
|
out.CreatedTime = in.GetCreateTime().GetTimestamp()
|
|
}
|
|
if outputFields.Has(globals.UpdatedTimeField) {
|
|
out.UpdatedTime = in.GetUpdateTime().GetTimestamp()
|
|
}
|
|
if outputFields.Has(globals.VersionField) {
|
|
out.Version = in.GetVersion()
|
|
}
|
|
if outputFields.Has(globals.ScopeField) {
|
|
out.Scope = opts.WithScope
|
|
}
|
|
if outputFields.Has(globals.AuthorizedActionsField) {
|
|
out.AuthorizedActions = opts.WithAuthorizedActions
|
|
}
|
|
if outputFields.Has(globals.MemberIdsField) {
|
|
out.MemberIds = opts.WithMemberIds
|
|
}
|
|
switch i := in.(type) {
|
|
case *oidc.ManagedGroup:
|
|
if outputFields.Has(globals.TypeField) {
|
|
out.Type = oidc.Subtype.String()
|
|
}
|
|
if !outputFields.Has(globals.AttributesField) {
|
|
break
|
|
}
|
|
attrs := &pb.OidcManagedGroupAttributes{
|
|
Filter: i.GetFilter(),
|
|
}
|
|
out.Attrs = &pb.ManagedGroup_OidcManagedGroupAttributes{
|
|
OidcManagedGroupAttributes: attrs,
|
|
}
|
|
case *ldap.ManagedGroup:
|
|
if outputFields.Has(globals.TypeField) {
|
|
out.Type = ldap.Subtype.String()
|
|
}
|
|
if !outputFields.Has(globals.AttributesField) {
|
|
break
|
|
}
|
|
|
|
var grpNames []string
|
|
if err := json.Unmarshal([]byte(i.GetGroupNames()), &grpNames); err != nil {
|
|
return nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "unable to unmarshal group names")
|
|
}
|
|
|
|
attrs := &pb.LdapManagedGroupAttributes{
|
|
GroupNames: grpNames,
|
|
}
|
|
out.Attrs = &pb.ManagedGroup_LdapManagedGroupAttributes{
|
|
LdapManagedGroupAttributes: attrs,
|
|
}
|
|
}
|
|
return &out, nil
|
|
}
|
|
|
|
// 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:
|
|
// - The path passed in is correctly formatted
|
|
// - All required parameters are set
|
|
// - There are no conflicting parameters provided
|
|
func validateGetRequest(ctx context.Context, req *pbs.GetManagedGroupRequest) error {
|
|
const op = "managed_groups.validateGetRequest"
|
|
if req == nil {
|
|
return errors.New(ctx, errors.InvalidParameter, op, "nil request")
|
|
}
|
|
return handlers.ValidateGetRequest(handlers.NoopValidatorFn, req, globals.OidcManagedGroupPrefix, globals.LdapManagedGroupPrefix)
|
|
}
|
|
|
|
func validateCreateRequest(ctx context.Context, req *pbs.CreateManagedGroupRequest) error {
|
|
const op = "managed_groups.validateCreateRequest"
|
|
if req == nil {
|
|
return errors.New(ctx, errors.InvalidParameter, op, "nil request")
|
|
}
|
|
return handlers.ValidateCreateRequest(req.GetItem(), func() map[string]string {
|
|
badFields := map[string]string{}
|
|
if req.GetItem().GetAuthMethodId() == "" {
|
|
badFields[globals.AuthMethodIdField] = "This field is required."
|
|
}
|
|
switch subtypes.SubtypeFromId(domain, req.GetItem().GetAuthMethodId()) {
|
|
case oidc.Subtype:
|
|
if req.GetItem().GetType() != "" && req.GetItem().GetType() != oidc.Subtype.String() {
|
|
badFields[globals.TypeField] = "Doesn't match the parent resource's type."
|
|
}
|
|
attrs := req.GetItem().GetOidcManagedGroupAttributes()
|
|
if attrs == nil {
|
|
badFields[globals.AttributesField] = "Attribute fields is required."
|
|
} else {
|
|
if attrs.Filter == "" {
|
|
badFields[attrFilterField] = "This field is required."
|
|
} else {
|
|
if _, err := bexpr.CreateEvaluator(attrs.Filter); err != nil {
|
|
badFields[attrFilterField] = fmt.Sprintf("Error evaluating submitted filter expression: %v.", err)
|
|
}
|
|
}
|
|
}
|
|
case ldap.Subtype:
|
|
if req.GetItem().GetType() != "" && req.GetItem().GetType() != ldap.Subtype.String() {
|
|
badFields[globals.TypeField] = "Doesn't match the parent resource's type."
|
|
}
|
|
attrs := req.GetItem().GetLdapManagedGroupAttributes()
|
|
if attrs == nil {
|
|
badFields[globals.AttributesField] = "Attribute fields is required."
|
|
} else {
|
|
if len(attrs.GroupNames) == 0 {
|
|
badFields[attrGroupNamesField] = "This field is required."
|
|
}
|
|
}
|
|
default:
|
|
badFields[globals.AuthMethodIdField] = "Unknown auth method type from ID."
|
|
}
|
|
return badFields
|
|
})
|
|
}
|
|
|
|
func validateUpdateRequest(ctx context.Context, req *pbs.UpdateManagedGroupRequest) error {
|
|
const op = "managed_groups.validateUpdateRequest"
|
|
if req == nil {
|
|
return errors.New(ctx, errors.InvalidParameter, op, "nil request")
|
|
}
|
|
return handlers.ValidateUpdateRequest(req, req.GetItem(), func() map[string]string {
|
|
badFields := map[string]string{}
|
|
switch subtypes.SubtypeFromId(domain, req.GetId()) {
|
|
case oidc.Subtype:
|
|
if req.GetItem().GetType() != "" && req.GetItem().GetType() != oidc.Subtype.String() {
|
|
badFields[globals.TypeField] = "Cannot modify the resource type."
|
|
}
|
|
attrs := req.GetItem().GetOidcManagedGroupAttributes()
|
|
if handlers.MaskContains(req.GetUpdateMask().GetPaths(), attrFilterField) {
|
|
switch {
|
|
case attrs == nil:
|
|
badFields["attributes"] = "Attributes field not supplied request"
|
|
default:
|
|
if attrs.Filter == "" {
|
|
badFields[attrFilterField] = "Field cannot be empty."
|
|
} else {
|
|
if _, err := bexpr.CreateEvaluator(attrs.Filter); err != nil {
|
|
badFields[attrFilterField] = fmt.Sprintf("Error evaluating submitted filter expression: %v.", err)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
case ldap.Subtype:
|
|
if req.GetItem().GetType() != "" && req.GetItem().GetType() != ldap.Subtype.String() {
|
|
badFields[globals.TypeField] = "Cannot modify the resource type."
|
|
}
|
|
attrs := req.GetItem().GetLdapManagedGroupAttributes()
|
|
if handlers.MaskContains(req.GetUpdateMask().GetPaths(), attrGroupNamesField) {
|
|
if len(attrs.GroupNames) == 0 {
|
|
badFields[attrFilterField] = "Field cannot be empty."
|
|
}
|
|
}
|
|
default:
|
|
badFields[globals.IdField] = "Unrecognized resource type."
|
|
}
|
|
return badFields
|
|
}, globals.OidcManagedGroupPrefix, globals.LdapManagedGroupPrefix)
|
|
}
|
|
|
|
func validateDeleteRequest(ctx context.Context, req *pbs.DeleteManagedGroupRequest) error {
|
|
const op = "managed_groups.validateDeleteRequest"
|
|
if req == nil {
|
|
return errors.New(ctx, errors.InvalidParameter, op, "nil request")
|
|
}
|
|
return handlers.ValidateDeleteRequest(handlers.NoopValidatorFn, req, globals.OidcManagedGroupPrefix, globals.LdapManagedGroupPrefix)
|
|
}
|
|
|
|
func validateListRequest(ctx context.Context, req *pbs.ListManagedGroupsRequest) error {
|
|
const op = "managed_groups.validateListRequest"
|
|
if req == nil {
|
|
return errors.New(ctx, errors.InvalidParameter, op, "nil request")
|
|
}
|
|
badFields := map[string]string{}
|
|
if !handlers.ValidId(handlers.Id(req.GetAuthMethodId()), globals.OidcAuthMethodPrefix, globals.LdapAuthMethodPrefix) {
|
|
badFields[globals.AuthMethodIdField] = "Invalid formatted identifier."
|
|
}
|
|
if _, err := handlers.NewFilter(ctx, req.GetFilter()); err != nil {
|
|
badFields[globals.FilterField] = fmt.Sprintf("This field could not be parsed. %v", err)
|
|
}
|
|
if len(badFields) > 0 {
|
|
return handlers.InvalidArgumentErrorf("Error in provided request.", badFields)
|
|
}
|
|
return nil
|
|
}
|