Add Output Fields support (#1192)

This commit is contained in:
Jeff Mitchell authored and GitHub committed 2021-05-04 20:57:35 -04:00
1 parent 1948984ca2
commit 22d5c88a0b
84 files changed
+5688 -2122

No files matched your search

+13
View File
@@ -17,6 +17,7 @@ import (
"github.com/hashicorp/boundary/internal/auth"
"github.com/hashicorp/boundary/internal/auth/oidc"
"github.com/hashicorp/boundary/internal/gen/controller/api/services"
"github.com/hashicorp/boundary/internal/requests"
"github.com/hashicorp/boundary/internal/servers/controller/handlers/accounts"
"github.com/hashicorp/boundary/internal/servers/controller/handlers/authmethods"
"github.com/hashicorp/boundary/internal/servers/controller/handlers/host_sets"
@@ -228,6 +229,18 @@ func wrapHandlerWithCommonFuncs(h http.Handler, c *Controller, props HandlerProp
requestInfo.PublicId, requestInfo.EncryptedToken, requestInfo.TokenFormat = auth.GetTokenFromRequest(c.logger, c.kms, r)
ctx = auth.NewVerifierContext(ctx, c.logger, c.IamRepoFn, c.AuthTokenRepoFn, c.ServersRepoFn, c.kms, requestInfo)
// Add general request information to the context. The information from
// the auth verifier context is pretty specifically curated to
// authentication/authorization verification so this is more
// general-purpose.
//
// We could use requests.NewRequestContext but this saves an immediate
// lookup.
ctx = context.WithValue(ctx, requests.ContextRequestInformationKey, &requests.RequestContext{
Path: r.URL.Path,
Method: r.Method,
})
// Set the context back on the request
r = r.WithContext(ctx)
@@ -6,6 +6,7 @@ import (
"net/url"
"strings"
"github.com/hashicorp/boundary/globals"
"github.com/hashicorp/boundary/internal/auth"
"github.com/hashicorp/boundary/internal/auth/oidc"
oidcstore "github.com/hashicorp/boundary/internal/auth/oidc/store"
@@ -15,6 +16,7 @@ import (
pb "github.com/hashicorp/boundary/internal/gen/controller/api/resources/accounts"
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/servers/controller/common"
"github.com/hashicorp/boundary/internal/servers/controller/handlers"
"github.com/hashicorp/boundary/internal/types/action"
@@ -120,22 +122,45 @@ func (s Service) ListAccounts(ctx context.Context, req *pbs.ListAccountsRequest)
if err != nil {
return nil, err
}
if len(ul) == 0 {
return &pbs.ListAccountsResponse{}, nil
}
filter, err := handlers.NewFilter(req.GetFilter())
if err != nil {
return nil, err
}
finalItems := make([]*pb.Account, 0, len(ul))
res := &perms.Resource{
res := perms.Resource{
ScopeId: authResults.Scope.Id,
Type: resource.Account,
Pin: req.GetAuthMethodId(),
}
for _, item := range ul {
item.Scope = authResults.Scope
item.AuthorizedActions = authResults.FetchActionSetForId(ctx, item.Id, IdActions[auth.SubtypeFromId(item.Id)], auth.WithResource(res)).Strings()
if len(item.AuthorizedActions) == 0 {
for _, acct := range ul {
res.Id = acct.GetPublicId()
authorizedActions := authResults.FetchActionSetForId(ctx, acct.GetPublicId(), IdActions[auth.SubtypeFromId(acct.GetPublicId())], auth.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, acct, 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
if filter.Match(item) {
finalItems = append(finalItems, item)
}
@@ -145,56 +170,119 @@ func (s Service) ListAccounts(ctx context.Context, req *pbs.ListAccountsRequest)
// GetAccount implements the interface pbs.AccountServiceServer.
func (s Service) GetAccount(ctx context.Context, req *pbs.GetAccountRequest) (*pbs.GetAccountResponse, error) {
const op = "accounts.(Service).GetAccount"
if err := validateGetRequest(req); err != nil {
return nil, err
}
_, authResults := s.parentAndAuthResult(ctx, req.GetId(), action.Read)
if authResults.Error != nil {
return nil, authResults.Error
}
u, err := s.getFromRepo(ctx, req.GetId())
acct, err := s.getFromRepo(ctx, req.GetId())
if err != nil {
return nil, err
}
u.Scope = authResults.Scope
u.AuthorizedActions = authResults.FetchActionSetForId(ctx, u.Id, IdActions[auth.SubtypeFromId(u.Id)]).Strings()
return &pbs.GetAccountResponse{Item: u}, nil
outputFields, ok := requests.OutputFields(ctx)
if !ok {
return nil, errors.New(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, acct.GetPublicId(), IdActions[auth.SubtypeFromId(acct.GetPublicId())]).Strings()))
}
item, err := toProto(ctx, acct, outputOpts...)
if err != nil {
return nil, err
}
return &pbs.GetAccountResponse{Item: item}, nil
}
// CreateAccount implements the interface pbs.AccountServiceServer.
func (s Service) CreateAccount(ctx context.Context, req *pbs.CreateAccountRequest) (*pbs.CreateAccountResponse, error) {
const op = "accounts.(Service).CreateAccount"
if err := validateCreateRequest(req); err != nil {
return nil, err
}
authMeth, authResults := s.parentAndAuthResult(ctx, req.GetItem().GetAuthMethodId(), action.Create)
if authResults.Error != nil {
return nil, authResults.Error
}
u, err := s.createInRepo(ctx, authMeth, req.GetItem())
acct, err := s.createInRepo(ctx, authMeth, req.GetItem())
if err != nil {
return nil, err
}
u.Scope = authResults.Scope
u.AuthorizedActions = authResults.FetchActionSetForId(ctx, u.Id, IdActions[auth.SubtypeFromId(req.GetItem().GetAuthMethodId())]).Strings()
return &pbs.CreateAccountResponse{Item: u, Uri: fmt.Sprintf("accounts/%s", u.GetId())}, nil
outputFields, ok := requests.OutputFields(ctx)
if !ok {
return nil, errors.New(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, acct.GetPublicId(), IdActions[auth.SubtypeFromId(acct.GetPublicId())]).Strings()))
}
item, err := toProto(ctx, acct, outputOpts...)
if err != nil {
return nil, err
}
return &pbs.CreateAccountResponse{Item: item, Uri: fmt.Sprintf("accounts/%s", item.GetId())}, nil
}
// UpdateAccount implements the interface pbs.AccountServiceServer.
func (s Service) UpdateAccount(ctx context.Context, req *pbs.UpdateAccountRequest) (*pbs.UpdateAccountResponse, error) {
const op = "accounts.(Service).UpdateAccount"
if err := validateUpdateRequest(req); err != nil {
return nil, err
}
authMeth, authResults := s.parentAndAuthResult(ctx, req.GetId(), action.Update)
if authResults.Error != nil {
return nil, authResults.Error
}
u, err := s.updateInRepo(ctx, authResults.Scope.GetId(), authMeth.GetPublicId(), req)
acct, err := s.updateInRepo(ctx, authResults.Scope.GetId(), authMeth.GetPublicId(), req)
if err != nil {
return nil, err
}
u.Scope = authResults.Scope
u.AuthorizedActions = authResults.FetchActionSetForId(ctx, u.Id, IdActions[auth.SubtypeFromId(u.Id)]).Strings()
return &pbs.UpdateAccountResponse{Item: u}, nil
outputFields, ok := requests.OutputFields(ctx)
if !ok {
return nil, errors.New(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, acct.GetPublicId(), IdActions[auth.SubtypeFromId(acct.GetPublicId())]).Strings()))
}
item, err := toProto(ctx, acct, outputOpts...)
if err != nil {
return nil, err
}
return &pbs.UpdateAccountResponse{Item: item}, nil
}
// DeleteAccount implements the interface pbs.AccountServiceServer.
@@ -215,41 +303,83 @@ func (s Service) DeleteAccount(ctx context.Context, req *pbs.DeleteAccountReques
// ChangePassword implements the interface pbs.AccountServiceServer.
func (s Service) ChangePassword(ctx context.Context, req *pbs.ChangePasswordRequest) (*pbs.ChangePasswordResponse, error) {
const op = "accounts.(Service).ChangePassword"
if err := validateChangePasswordRequest(req); err != nil {
return nil, err
}
_, authResults := s.parentAndAuthResult(ctx, req.GetId(), action.ChangePassword)
if authResults.Error != nil {
return nil, authResults.Error
}
u, err := s.changePasswordInRepo(ctx, authResults.Scope.GetId(), req.GetId(), req.GetVersion(), req.GetCurrentPassword(), req.GetNewPassword())
acct, err := s.changePasswordInRepo(ctx, authResults.Scope.GetId(), req.GetId(), req.GetVersion(), req.GetCurrentPassword(), req.GetNewPassword())
if err != nil {
return nil, err
}
u.Scope = authResults.Scope
u.AuthorizedActions = authResults.FetchActionSetForId(ctx, u.Id, IdActions[auth.SubtypeFromId(u.Id)]).Strings()
return &pbs.ChangePasswordResponse{Item: u}, nil
outputFields, ok := requests.OutputFields(ctx)
if !ok {
return nil, errors.New(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, acct.GetPublicId(), IdActions[auth.SubtypeFromId(acct.GetPublicId())]).Strings()))
}
item, err := toProto(ctx, acct, outputOpts...)
if err != nil {
return nil, err
}
return &pbs.ChangePasswordResponse{Item: item}, nil
}
// SetPassword implements the interface pbs.AccountServiceServer.
func (s Service) SetPassword(ctx context.Context, req *pbs.SetPasswordRequest) (*pbs.SetPasswordResponse, error) {
const op = "accounts.(Service).SetPassword"
if err := validateSetPasswordRequest(req); err != nil {
return nil, err
}
_, authResults := s.parentAndAuthResult(ctx, req.GetId(), action.SetPassword)
if authResults.Error != nil {
return nil, authResults.Error
}
u, err := s.setPasswordInRepo(ctx, authResults.Scope.GetId(), req.GetId(), req.GetVersion(), req.GetPassword())
acct, err := s.setPasswordInRepo(ctx, authResults.Scope.GetId(), req.GetId(), req.GetVersion(), req.GetPassword())
if err != nil {
return nil, err
}
u.Scope = authResults.Scope
u.AuthorizedActions = authResults.FetchActionSetForId(ctx, u.Id, IdActions[auth.SubtypeFromId(u.Id)]).Strings()
return &pbs.SetPasswordResponse{Item: u}, nil
outputFields, ok := requests.OutputFields(ctx)
if !ok {
return nil, errors.New(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, acct.GetPublicId(), IdActions[auth.SubtypeFromId(acct.GetPublicId())]).Strings()))
}
item, err := toProto(ctx, acct, outputOpts...)
if err != nil {
return nil, err
}
return &pbs.SetPasswordResponse{Item: item}, nil
}
func (s Service) getFromRepo(ctx context.Context, id string) (*pb.Account, error) {
func (s Service) getFromRepo(ctx context.Context, id string) (auth.Account, error) {
var acct auth.Account
switch auth.SubtypeFromId(id) {
case auth.PasswordSubtype:
@@ -281,7 +411,7 @@ func (s Service) getFromRepo(ctx context.Context, id string) (*pb.Account, error
default:
return nil, handlers.NotFoundErrorf("Unrecognized id.")
}
return toProto(acct)
return acct, nil
}
func (s Service) createPwInRepo(ctx context.Context, am auth.AuthMethod, item *pb.Account) (*password.Account, error) {
@@ -367,7 +497,7 @@ func (s Service) createOidcInRepo(ctx context.Context, am auth.AuthMethod, item
return out, nil
}
func (s Service) createInRepo(ctx context.Context, am auth.AuthMethod, item *pb.Account) (*pb.Account, error) {
func (s Service) createInRepo(ctx context.Context, am auth.AuthMethod, item *pb.Account) (auth.Account, error) {
const op = "accounts.(Service).createInRepo"
if item == nil {
return nil, errors.New(errors.InvalidParameter, op, "missing item")
@@ -393,7 +523,7 @@ func (s Service) createInRepo(ctx context.Context, am auth.AuthMethod, item *pb.
}
out = am
}
return toProto(out)
return out, nil
}
func (s Service) updatePwInRepo(ctx context.Context, scopeId, authMethId, id string, mask []string, item *pb.Account) (*password.Account, error) {
@@ -463,7 +593,7 @@ func (s Service) updateOidcInRepo(ctx context.Context, scopeId, amId, id string,
return out, nil
}
func (s Service) updateInRepo(ctx context.Context, scopeId, authMethodId string, req *pbs.UpdateAccountRequest) (*pb.Account, error) {
func (s Service) updateInRepo(ctx context.Context, scopeId, authMethodId string, req *pbs.UpdateAccountRequest) (auth.Account, error) {
const op = "accounts.(Service).updateInRepo"
var out auth.Account
switch auth.SubtypeFromId(req.GetId()) {
@@ -486,7 +616,7 @@ func (s Service) updateInRepo(ctx context.Context, scopeId, authMethodId string,
}
out = a
}
return toProto(out)
return out, nil
}
func (s Service) deleteFromRepo(ctx context.Context, scopeId, id string) (bool, error) {
@@ -516,46 +646,40 @@ func (s Service) deleteFromRepo(ctx context.Context, scopeId, id string) (bool,
return rows > 0, nil
}
func (s Service) listFromRepo(ctx context.Context, authMethodId string) ([]*pb.Account, error) {
var outUl []*pb.Account
func (s Service) listFromRepo(ctx context.Context, authMethodId string) ([]auth.Account, error) {
const op = "accounts.(Service).listFromRepo"
var outUl []auth.Account
switch auth.SubtypeFromId(authMethodId) {
case auth.PasswordSubtype:
pwRepo, err := s.pwRepoFn()
if err != nil {
return nil, err
return nil, errors.Wrap(err, op)
}
pwl, err := pwRepo.ListAccounts(ctx, authMethodId)
if err != nil {
return nil, err
return nil, errors.Wrap(err, op)
}
for _, u := range pwl {
ou, err := toProto(u)
if err != nil {
return nil, err
}
outUl = append(outUl, ou)
for _, a := range pwl {
outUl = append(outUl, a)
}
case auth.OidcSubtype:
oidcRepo, err := s.oidcRepoFn()
if err != nil {
return nil, err
return nil, errors.Wrap(err, op)
}
oidcl, err := oidcRepo.ListAccounts(ctx, authMethodId)
if err != nil {
return nil, err
return nil, errors.Wrap(err, op)
}
for _, u := range oidcl {
ou, err := toProto(u)
if err != nil {
return nil, err
}
outUl = append(outUl, ou)
for _, a := range oidcl {
outUl = append(outUl, a)
}
}
return outUl, nil
}
func (s Service) changePasswordInRepo(ctx context.Context, scopeId, id string, version uint32, currentPassword, newPassword string) (*pb.Account, error) {
func (s Service) changePasswordInRepo(ctx context.Context, scopeId, id string, version uint32, currentPassword, newPassword string) (auth.Account, error) {
const op = "account.(Service).changePasswordInRepo"
repo, err := s.pwRepoFn()
if err != nil {
@@ -578,10 +702,10 @@ func (s Service) changePasswordInRepo(ctx context.Context, scopeId, id string, v
if out == nil {
return nil, handlers.ApiErrorWithCodeAndMessage(codes.PermissionDenied, "Failed to change password.")
}
return toProto(out)
return out, nil
}
func (s Service) setPasswordInRepo(ctx context.Context, scopeId, id string, version uint32, pw string) (*pb.Account, error) {
func (s Service) setPasswordInRepo(ctx context.Context, scopeId, id string, version uint32, pw string) (auth.Account, error) {
const op = "accounts.(Service).setPasswordInRepo"
repo, err := s.pwRepoFn()
@@ -599,7 +723,7 @@ func (s Service) setPasswordInRepo(ctx context.Context, scopeId, id string, vers
}
return nil, errors.Wrap(err, op)
}
return toProto(out)
return out, nil
}
func (s Service) parentAndAuthResult(ctx context.Context, id string, a action.Type) (auth.AuthMethod, auth.VerifyResults) {
@@ -677,30 +801,61 @@ func (s Service) parentAndAuthResult(ctx context.Context, id string, a action.Ty
return authMeth, auth.Verify(ctx, opts...)
}
func toProto(in auth.Account) (*pb.Account, error) {
out := pb.Account{
Id: in.GetPublicId(),
CreatedTime: in.GetCreateTime().GetTimestamp(),
UpdatedTime: in.GetUpdateTime().GetTimestamp(),
AuthMethodId: in.GetAuthMethodId(),
Version: in.GetVersion(),
func toProto(ctx context.Context, in auth.Account, opt ...handlers.Option) (*pb.Account, error) {
opts := handlers.GetOpts(opt...)
if opts.WithOutputFields == nil {
return nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "output fields not found when building account proto")
}
if in.GetDescription() != "" {
outputFields := *opts.WithOutputFields
out := pb.Account{}
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 in.GetName() != "" {
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
}
switch i := in.(type) {
case *password.Account:
out.Type = auth.PasswordSubtype.String()
if outputFields.Has(globals.TypeField) {
out.Type = auth.PasswordSubtype.String()
}
if !outputFields.Has(globals.AttributesField) {
break
}
st, err := handlers.ProtoToStruct(&pb.PasswordAccountAttributes{LoginName: i.GetLoginName()})
if err != nil {
return nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "failed building password attribute struct: %v", err)
}
out.Attributes = st
case *oidc.Account:
out.Type = auth.OidcSubtype.String()
if outputFields.Has(globals.TypeField) {
out.Type = auth.OidcSubtype.String()
}
if !outputFields.Has(globals.AttributesField) {
break
}
attrs := &pb.OidcAccountAttributes{
Issuer: i.GetIssuer(),
Subject: i.GetSubject(),
@@ -9,6 +9,7 @@ import (
"testing"
"github.com/google/go-cmp/cmp"
"github.com/hashicorp/boundary/globals"
"github.com/hashicorp/boundary/internal/auth"
"github.com/hashicorp/boundary/internal/auth/oidc"
"github.com/hashicorp/boundary/internal/auth/password"
@@ -268,10 +269,11 @@ func TestListPassword(t *testing.T) {
}
cases := []struct {
name string
req *pbs.ListAccountsRequest
res *pbs.ListAccountsResponse
err error
name string
req *pbs.ListAccountsRequest
res *pbs.ListAccountsResponse
err error
skipAnon bool
}{
{
name: "List Some Accounts",
@@ -299,7 +301,8 @@ func TestListPassword(t *testing.T) {
AuthMethodId: amSomeAccounts.GetPublicId(),
Filter: fmt.Sprintf(`"/item/attributes/login_name"==%q`, wantSomeAccounts[1].Attributes.AsMap()["login_name"]),
},
res: &pbs.ListAccountsResponse{Items: wantSomeAccounts[1:2]},
res: &pbs.ListAccountsResponse{Items: wantSomeAccounts[1:2]},
skipAnon: true,
},
{
name: "Filter All Accounts",
@@ -321,6 +324,7 @@ func TestListPassword(t *testing.T) {
s, err := accounts.NewService(pwRepoFn, oidcRepoFn)
require.NoError(err, "Couldn't create new user service.")
// Test non-anon first
got, gErr := s.ListAccounts(auth.DisabledAuthTestContext(iamRepoFn, o.GetPublicId()), tc.req)
if tc.err != nil {
require.Error(gErr)
@@ -330,6 +334,20 @@ func TestListPassword(t *testing.T) {
require.NoError(gErr)
}
assert.Empty(cmp.Diff(got, tc.res, protocmp.Transform(), protocmp.SortRepeatedFields(got)), "ListAccounts() with scope %q got response %q, wanted %q", tc.req, got, tc.res)
// Now test with anon
if tc.skipAnon {
return
}
got, gErr = s.ListAccounts(auth.DisabledAuthTestContext(iamRepoFn, o.GetPublicId(), auth.WithUserId(auth.AnonymousUserId)), tc.req)
require.NoError(gErr)
assert.Len(got.Items, len(tc.res.Items))
for _, g := range got.GetItems() {
assert.Nil(g.Attributes)
assert.Nil(g.CreatedTime)
assert.Nil(g.UpdatedTime)
assert.Empty(g.Version)
}
})
}
}
@@ -401,10 +419,11 @@ func TestListOidc(t *testing.T) {
}
cases := []struct {
name string
req *pbs.ListAccountsRequest
res *pbs.ListAccountsResponse
err error
name string
req *pbs.ListAccountsRequest
res *pbs.ListAccountsResponse
err error
skipAnon bool
}{
{
name: "List Some Accounts",
@@ -432,7 +451,8 @@ func TestListOidc(t *testing.T) {
AuthMethodId: amSomeAccounts.GetPublicId(),
Filter: fmt.Sprintf(`"/item/attributes/subject"==%q`, wantSomeAccounts[1].Attributes.AsMap()["subject"]),
},
res: &pbs.ListAccountsResponse{Items: wantSomeAccounts[1:2]},
res: &pbs.ListAccountsResponse{Items: wantSomeAccounts[1:2]},
skipAnon: true,
},
{
name: "Filter All Accounts",
@@ -467,6 +487,20 @@ func TestListOidc(t *testing.T) {
got.Items[j].GetAttributes().GetFields()["subject"].GetStringValue()) < 0
})
assert.Empty(cmp.Diff(got, tc.res, protocmp.Transform()), "ListAccounts() with scope %q got response %q, wanted %q", tc.req, got, tc.res)
// Now test with anon
if tc.skipAnon {
return
}
got, gErr = s.ListAccounts(auth.DisabledAuthTestContext(iamRepoFn, o.GetPublicId(), auth.WithUserId(auth.AnonymousUserId)), tc.req)
require.NoError(gErr)
assert.Len(got.Items, len(tc.res.Items))
for _, g := range got.GetItems() {
assert.Nil(g.Attributes)
assert.Nil(g.CreatedTime)
assert.Nil(g.UpdatedTime)
assert.Empty(g.Version)
}
})
}
}
@@ -1067,7 +1101,7 @@ func TestUpdatePassword(t *testing.T) {
name: "Update an Existing AuthMethod",
req: &pbs.UpdateAccountRequest{
UpdateMask: &field_mask.FieldMask{
Paths: []string{"name", "description"},
Paths: []string{globals.NameField, globals.DescriptionField},
},
Item: &pb.Account{
Name: &wrapperspb.StringValue{Value: "new"},
@@ -1126,7 +1160,7 @@ func TestUpdatePassword(t *testing.T) {
name: "Cant change type",
req: &pbs.UpdateAccountRequest{
UpdateMask: &field_mask.FieldMask{
Paths: []string{"name"},
Paths: []string{globals.NameField},
},
Item: &pb.Account{
Name: &wrapperspb.StringValue{Value: ""},
@@ -1163,7 +1197,7 @@ func TestUpdatePassword(t *testing.T) {
name: "Unset Name",
req: &pbs.UpdateAccountRequest{
UpdateMask: &field_mask.FieldMask{
Paths: []string{"name"},
Paths: []string{globals.NameField},
},
Item: &pb.Account{
Description: &wrapperspb.StringValue{Value: "ignored"},
@@ -1185,7 +1219,7 @@ func TestUpdatePassword(t *testing.T) {
name: "Update Only Name",
req: &pbs.UpdateAccountRequest{
UpdateMask: &field_mask.FieldMask{
Paths: []string{"name"},
Paths: []string{globals.NameField},
},
Item: &pb.Account{
Name: &wrapperspb.StringValue{Value: "updated"},
@@ -1209,7 +1243,7 @@ func TestUpdatePassword(t *testing.T) {
name: "Update Only Description",
req: &pbs.UpdateAccountRequest{
UpdateMask: &field_mask.FieldMask{
Paths: []string{"description"},
Paths: []string{globals.DescriptionField},
},
Item: &pb.Account{
Name: &wrapperspb.StringValue{Value: "ignored"},
@@ -1258,7 +1292,7 @@ func TestUpdatePassword(t *testing.T) {
req: &pbs.UpdateAccountRequest{
Id: password.AccountPrefix + "_DoesntExis",
UpdateMask: &field_mask.FieldMask{
Paths: []string{"description"},
Paths: []string{globals.DescriptionField},
},
Item: &pb.Account{
Name: &wrapperspb.StringValue{Value: "new"},
@@ -1434,7 +1468,7 @@ func TestUpdateOidc(t *testing.T) {
name: "Update an Existing AuthMethod",
req: &pbs.UpdateAccountRequest{
UpdateMask: &field_mask.FieldMask{
Paths: []string{"name", "description"},
Paths: []string{globals.NameField, globals.DescriptionField},
},
Item: &pb.Account{
Name: &wrapperspb.StringValue{Value: "new"},
@@ -1493,7 +1527,7 @@ func TestUpdateOidc(t *testing.T) {
name: "Cant change type",
req: &pbs.UpdateAccountRequest{
UpdateMask: &field_mask.FieldMask{
Paths: []string{"name"},
Paths: []string{globals.NameField},
},
Item: &pb.Account{
Name: &wrapperspb.StringValue{Value: ""},
@@ -1530,7 +1564,7 @@ func TestUpdateOidc(t *testing.T) {
name: "Unset Name",
req: &pbs.UpdateAccountRequest{
UpdateMask: &field_mask.FieldMask{
Paths: []string{"name"},
Paths: []string{globals.NameField},
},
Item: &pb.Account{
Description: &wrapperspb.StringValue{Value: "ignored"},
@@ -1552,7 +1586,7 @@ func TestUpdateOidc(t *testing.T) {
name: "Update Only Name",
req: &pbs.UpdateAccountRequest{
UpdateMask: &field_mask.FieldMask{
Paths: []string{"name"},
Paths: []string{globals.NameField},
},
Item: &pb.Account{
Name: &wrapperspb.StringValue{Value: "updated"},
@@ -1576,7 +1610,7 @@ func TestUpdateOidc(t *testing.T) {
name: "Update Only Description",
req: &pbs.UpdateAccountRequest{
UpdateMask: &field_mask.FieldMask{
Paths: []string{"description"},
Paths: []string{globals.DescriptionField},
},
Item: &pb.Account{
Name: &wrapperspb.StringValue{Value: "ignored"},
@@ -1615,7 +1649,7 @@ func TestUpdateOidc(t *testing.T) {
req: &pbs.UpdateAccountRequest{
Id: password.AccountPrefix + "_DoesntExis",
UpdateMask: &field_mask.FieldMask{
Paths: []string{"description"},
Paths: []string{globals.DescriptionField},
},
Item: &pb.Account{
Name: &wrapperspb.StringValue{Value: "new"},
@@ -1864,7 +1898,7 @@ func TestSetPassword(t *testing.T) {
password: "anewpassword",
},
{
name: "password to short",
name: "password too short",
accountId: defaultAcct.GetId(),
version: defaultAcct.GetVersion(),
password: "123",
@@ -2027,7 +2061,7 @@ func TestChangePassword(t *testing.T) {
newPW: "anewpassword",
},
{
name: "new password to short",
name: "new password too short",
authMethodId: defaultAcct.GetAuthMethodId(),
accountId: defaultAcct.GetId(),
version: defaultAcct.GetVersion(),
@@ -6,6 +6,7 @@ import (
"net/url"
"strings"
"github.com/hashicorp/boundary/globals"
"github.com/hashicorp/boundary/internal/auth"
"github.com/hashicorp/boundary/internal/auth/oidc"
"github.com/hashicorp/boundary/internal/auth/password"
@@ -17,6 +18,7 @@ import (
pbs "github.com/hashicorp/boundary/internal/gen/controller/api/services"
"github.com/hashicorp/boundary/internal/kms"
"github.com/hashicorp/boundary/internal/perms"
"github.com/hashicorp/boundary/internal/requests"
"github.com/hashicorp/boundary/internal/servers/controller/common"
"github.com/hashicorp/boundary/internal/servers/controller/common/scopeids"
"github.com/hashicorp/boundary/internal/servers/controller/handlers"
@@ -61,33 +63,6 @@ var (
}
)
func populateCollectionAuthorizedActions(ctx context.Context,
authResults auth.VerifyResults,
item *pb.AuthMethod) error {
res := &perms.Resource{
ScopeId: authResults.Scope.Id,
Pin: item.Id,
}
// Range over the defined collections and check permissions against those
// collections. We use the ID of this scope being returned, not its parent,
// hence passing in a resource here.
for k, v := range collectionTypeMap {
res.Type = k
acts := authResults.FetchActionSetForType(ctx, k, v, auth.WithResource(res)).Strings()
if len(acts) > 0 {
if item.AuthorizedCollectionActions == nil {
item.AuthorizedCollectionActions = make(map[string]*structpb.ListValue)
}
lv, err := structpb.NewList(strutil.StringListToInterfaceList(acts))
if err != nil {
return err
}
item.AuthorizedCollectionActions[k.String()+"s"] = lv
}
}
return nil
}
// Service handles request as described by the pbs.AuthMethodServiceServer interface.
type Service struct {
pbs.UnimplementedAuthMethodServiceServer
@@ -157,30 +132,54 @@ func (s Service) ListAuthMethods(ctx context.Context, req *pbs.ListAuthMethodsRe
return &pbs.ListAuthMethodsResponse{}, nil
}
ul, err := s.listFromRepo(ctx, scopeIds, authResults.UserId == auth.AnonymousUserId)
ul, err := s.listFromRepo(ctx, scopeIds, authResults)
if err != nil {
return nil, err
}
if len(ul) == 0 {
return &pbs.ListAuthMethodsResponse{}, nil
}
filter, err := handlers.NewFilter(req.GetFilter())
if err != nil {
return nil, err
}
finalItems := make([]*pb.AuthMethod, 0, len(ul))
res := &perms.Resource{
res := perms.Resource{
Type: resource.AuthMethod,
}
for _, item := range ul {
item.Scope = scopeInfoMap[item.GetScopeId()]
res.ScopeId = item.Scope.Id
item.AuthorizedActions = authResults.FetchActionSetForId(ctx, item.Id, IdActions[auth.SubtypeFromId(item.Id)], auth.WithResource(res)).Strings()
if len(item.AuthorizedActions) == 0 {
for _, am := range ul {
res.Id = am.GetPublicId()
res.ScopeId = am.GetScopeId()
authorizedActions := authResults.FetchActionSetForId(ctx, am.GetPublicId(), IdActions[auth.SubtypeFromId(am.GetPublicId())], auth.WithResource(&res)).Strings()
if len(authorizedActions) == 0 {
continue
}
if filter.Match(item) {
finalItems = append(finalItems, item)
if err := populateCollectionAuthorizedActions(ctx, authResults, item); err != nil {
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[am.GetScopeId()]))
}
if outputFields.Has(globals.AuthorizedActionsField) {
outputOpts = append(outputOpts, handlers.WithAuthorizedActions(authorizedActions))
}
if outputFields.Has(globals.AuthorizedCollectionActionsField) {
collectionActions, err := auth.CalculateAuthorizedCollectionActions(ctx, authResults, collectionTypeMap, authResults.Scope.Id, am.GetPublicId())
if err != nil {
return nil, err
}
outputOpts = append(outputOpts, handlers.WithAuthorizedCollectionActions(collectionActions))
}
item, err := toAuthMethodProto(ctx, am, outputOpts...)
if err != nil {
return nil, err
}
if filter.Match(item) {
finalItems = append(finalItems, item)
}
}
return &pbs.ListAuthMethodsResponse{Items: finalItems}, nil
@@ -188,6 +187,8 @@ func (s Service) ListAuthMethods(ctx context.Context, req *pbs.ListAuthMethodsRe
// GetAuthMethod implements the interface pbs.AuthMethodServiceServer.
func (s Service) GetAuthMethod(ctx context.Context, req *pbs.GetAuthMethodRequest) (*pbs.GetAuthMethodResponse, error) {
const op = "authmethods.(Service).GetAuthMethod"
if err := validateGetRequest(req); err != nil {
return nil, err
}
@@ -195,20 +196,44 @@ func (s Service) GetAuthMethod(ctx context.Context, req *pbs.GetAuthMethodReques
if authResults.Error != nil {
return nil, authResults.Error
}
u, err := s.getFromRepo(ctx, req.GetId())
am, err := s.getFromRepo(ctx, req.GetId())
if err != nil {
return nil, err
}
u.Scope = authResults.Scope
u.AuthorizedActions = authResults.FetchActionSetForId(ctx, u.Id, IdActions[auth.SubtypeFromId(u.Id)]).Strings()
if err := populateCollectionAuthorizedActions(ctx, authResults, u); err != nil {
outputFields, ok := requests.OutputFields(ctx)
if !ok {
return nil, errors.New(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, am.GetPublicId(), IdActions[auth.SubtypeFromId(am.GetPublicId())]).Strings()))
}
if outputFields.Has(globals.AuthorizedCollectionActionsField) {
collectionActions, err := auth.CalculateAuthorizedCollectionActions(ctx, authResults, collectionTypeMap, authResults.Scope.Id, am.GetPublicId())
if err != nil {
return nil, err
}
outputOpts = append(outputOpts, handlers.WithAuthorizedCollectionActions(collectionActions))
}
item, err := toAuthMethodProto(ctx, am, outputOpts...)
if err != nil {
return nil, err
}
return &pbs.GetAuthMethodResponse{Item: u}, nil
return &pbs.GetAuthMethodResponse{Item: item}, nil
}
// CreateAuthMethod implements the interface pbs.AuthMethodServiceServer.
func (s Service) CreateAuthMethod(ctx context.Context, req *pbs.CreateAuthMethodRequest) (*pbs.CreateAuthMethodResponse, error) {
const op = "authmethods.(Service).CreateAuthMethod"
if err := validateCreateRequest(req); err != nil {
return nil, err
}
@@ -216,20 +241,44 @@ func (s Service) CreateAuthMethod(ctx context.Context, req *pbs.CreateAuthMethod
if authResults.Error != nil {
return nil, authResults.Error
}
u, err := s.createInRepo(ctx, authResults.Scope.GetId(), req.GetItem())
am, err := s.createInRepo(ctx, authResults.Scope.GetId(), req.GetItem())
if err != nil {
return nil, err
}
u.Scope = authResults.Scope
u.AuthorizedActions = authResults.FetchActionSetForId(ctx, u.Id, IdActions[auth.SubtypeFromId(u.Id)]).Strings()
if err := populateCollectionAuthorizedActions(ctx, authResults, u); err != nil {
outputFields, ok := requests.OutputFields(ctx)
if !ok {
return nil, errors.New(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, am.GetPublicId(), IdActions[auth.SubtypeFromId(am.GetPublicId())]).Strings()))
}
if outputFields.Has(globals.AuthorizedCollectionActionsField) {
collectionActions, err := auth.CalculateAuthorizedCollectionActions(ctx, authResults, collectionTypeMap, authResults.Scope.Id, am.GetPublicId())
if err != nil {
return nil, err
}
outputOpts = append(outputOpts, handlers.WithAuthorizedCollectionActions(collectionActions))
}
item, err := toAuthMethodProto(ctx, am, outputOpts...)
if err != nil {
return nil, err
}
return &pbs.CreateAuthMethodResponse{Item: u, Uri: fmt.Sprintf("auth-methods/%s", u.GetId())}, nil
return &pbs.CreateAuthMethodResponse{Item: item, Uri: fmt.Sprintf("auth-methods/%s", item.GetId())}, nil
}
// UpdateAuthMethod implements the interface pbs.AuthMethodServiceServer.
func (s Service) UpdateAuthMethod(ctx context.Context, req *pbs.UpdateAuthMethodRequest) (*pbs.UpdateAuthMethodResponse, error) {
const op = "authmethods.(Service).UpdateAuthMethod"
if err := validateUpdateRequest(req); err != nil {
return nil, err
}
@@ -237,7 +286,7 @@ func (s Service) UpdateAuthMethod(ctx context.Context, req *pbs.UpdateAuthMethod
if authResults.Error != nil {
return nil, authResults.Error
}
u, err := s.updateInRepo(ctx, authResults.Scope.GetId(), req)
am, dryRun, err := s.updateInRepo(ctx, authResults.Scope.GetId(), req)
if err != nil {
switch {
case errors.Match(errors.T(errors.InvalidParameter), err):
@@ -246,16 +295,44 @@ func (s Service) UpdateAuthMethod(ctx context.Context, req *pbs.UpdateAuthMethod
return nil, err
}
}
u.Scope = authResults.Scope
u.AuthorizedActions = authResults.FetchActionSetForId(ctx, u.Id, IdActions[auth.SubtypeFromId(u.Id)]).Strings()
if err := populateCollectionAuthorizedActions(ctx, authResults, u); err != nil {
outputFields, ok := requests.OutputFields(ctx)
if !ok {
return nil, errors.New(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, am.GetPublicId(), IdActions[auth.SubtypeFromId(am.GetPublicId())]).Strings()))
}
if outputFields.Has(globals.AuthorizedCollectionActionsField) {
collectionActions, err := auth.CalculateAuthorizedCollectionActions(ctx, authResults, collectionTypeMap, authResults.Scope.Id, am.GetPublicId())
if err != nil {
return nil, err
}
outputOpts = append(outputOpts, handlers.WithAuthorizedCollectionActions(collectionActions))
}
item, err := toAuthMethodProto(ctx, am, outputOpts...)
if err != nil {
return nil, err
}
return &pbs.UpdateAuthMethodResponse{Item: u}, nil
if item.GetAttributes() != nil && dryRun {
item.GetAttributes().Fields["dry_run"] = structpb.NewBoolValue(true)
}
return &pbs.UpdateAuthMethodResponse{Item: item}, nil
}
// ChangeState implements the interface pbs.AuthMethodServiceServer.
func (s Service) ChangeState(ctx context.Context, req *pbs.ChangeStateRequest) (*pbs.ChangeStateResponse, error) {
const op = "authmethods.(Service).ChangeState"
if err := validateChangeStateRequest(req); err != nil {
return nil, err
}
@@ -272,12 +349,34 @@ func (s Service) ChangeState(ctx context.Context, req *pbs.ChangeStateRequest) (
return nil, err
}
}
am.Scope = authResults.Scope
am.AuthorizedActions = authResults.FetchActionSetForId(ctx, am.Id, IdActions[auth.OidcSubtype]).Strings()
if err := populateCollectionAuthorizedActions(ctx, authResults, am); err != nil {
outputFields, ok := requests.OutputFields(ctx)
if !ok {
return nil, errors.New(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, am.GetPublicId(), IdActions[auth.SubtypeFromId(am.GetPublicId())]).Strings()))
}
if outputFields.Has(globals.AuthorizedCollectionActionsField) {
collectionActions, err := auth.CalculateAuthorizedCollectionActions(ctx, authResults, collectionTypeMap, authResults.Scope.Id, am.GetPublicId())
if err != nil {
return nil, err
}
outputOpts = append(outputOpts, handlers.WithAuthorizedCollectionActions(collectionActions))
}
item, err := toAuthMethodProto(ctx, am, outputOpts...)
if err != nil {
return nil, err
}
return &pbs.ChangeStateResponse{Item: am}, nil
return &pbs.ChangeStateResponse{Item: item}, nil
}
// DeleteAuthMethod implements the interface pbs.AuthMethodServiceServer.
@@ -351,7 +450,7 @@ func (s Service) AuthenticateLogin(ctx context.Context, req *pbs.AuthenticateLog
return &pbs.AuthenticateLoginResponse{Item: tok, TokenType: req.GetTokenType()}, nil
}
func (s Service) getFromRepo(ctx context.Context, id string) (*pb.AuthMethod, error) {
func (s Service) getFromRepo(ctx context.Context, id string) (auth.AuthMethod, error) {
var lookupErr error
var am auth.AuthMethod
switch auth.SubtypeFromId(id) {
@@ -383,26 +482,28 @@ func (s Service) getFromRepo(ctx context.Context, id string) (*pb.AuthMethod, er
return nil, handlers.NotFoundErrorf("AuthMethod %q doesn't exist.", id)
}
return toAuthMethodProto(am)
return am, nil
}
func (s Service) listFromRepo(ctx context.Context, scopeIds []string, anonUser bool) ([]*pb.AuthMethod, error) {
func (s Service) listFromRepo(ctx context.Context, scopeIds []string, authResults auth.VerifyResults) ([]auth.AuthMethod, error) {
const op = "authmethods.(Service).listFromRepo"
reqCtx, ok := requests.RequestContextFromCtx(ctx)
if !ok {
return nil, errors.New(errors.Internal, op, "no request context found")
}
var outUl []auth.AuthMethod
oidcRepo, err := s.oidcRepoFn()
if err != nil {
return nil, err
return nil, errors.Wrap(err, op)
}
ol, err := oidcRepo.ListAuthMethods(ctx, scopeIds, oidc.WithUnauthenticatedUser(anonUser))
ol, err := oidcRepo.ListAuthMethods(ctx, scopeIds, oidc.WithUnauthenticatedUser(reqCtx.UserId == auth.AnonymousUserId))
if err != nil {
return nil, err
return nil, errors.Wrap(err, op)
}
var outUl []*pb.AuthMethod
for _, u := range ol {
ou, err := toAuthMethodProto(u, handlers.WithAnonymousListing(anonUser))
if err != nil {
return nil, err
}
outUl = append(outUl, ou)
for _, item := range ol {
outUl = append(outUl, item)
}
repo, err := s.pwRepoFn()
@@ -413,17 +514,13 @@ func (s Service) listFromRepo(ctx context.Context, scopeIds []string, anonUser b
if err != nil {
return nil, errors.Wrap(err, op)
}
for _, u := range pl {
ou, err := toAuthMethodProto(u, handlers.WithAnonymousListing(anonUser))
if err != nil {
return nil, errors.Wrap(err, op)
}
outUl = append(outUl, ou)
for _, item := range pl {
outUl = append(outUl, item)
}
return outUl, nil
}
func (s Service) createInRepo(ctx context.Context, scopeId string, item *pb.AuthMethod) (*pb.AuthMethod, error) {
func (s Service) createInRepo(ctx context.Context, scopeId string, item *pb.AuthMethod) (auth.AuthMethod, error) {
const op = "authmethods.(Service).createInRepo"
var out auth.AuthMethod
switch auth.SubtypeFromType(item.GetType()) {
@@ -446,51 +543,39 @@ func (s Service) createInRepo(ctx context.Context, scopeId string, item *pb.Auth
}
out = am
}
return toAuthMethodProto(out)
return out, nil
}
func (s Service) updateInRepo(ctx context.Context, scopeId string, req *pbs.UpdateAuthMethodRequest) (*pb.AuthMethod, error) {
func (s Service) updateInRepo(ctx context.Context, scopeId string, req *pbs.UpdateAuthMethodRequest) (auth.AuthMethod, bool, error) {
const op = "authmethods.(Service).updateInRepo"
storageToWire := toAuthMethodProto
var out auth.AuthMethod
var am auth.AuthMethod
var dryRun bool
switch auth.SubtypeFromId(req.GetId()) {
case auth.PasswordSubtype:
am, err := s.updatePwInRepo(ctx, scopeId, req.GetId(), req.GetUpdateMask().GetPaths(), req.GetItem())
pam, err := s.updatePwInRepo(ctx, scopeId, req.GetId(), req.GetUpdateMask().GetPaths(), req.GetItem())
if err != nil {
return nil, errors.Wrap(err, op)
return nil, false, errors.Wrap(err, op)
}
if am == nil {
return nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "Unable to update auth method but no error returned from repository.")
if pam == nil {
return nil, false, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "Unable to update auth method but no error returned from repository.")
}
out = am
am = pam
case auth.OidcSubtype:
am, dryRun, err := s.updateOidcInRepo(ctx, scopeId, req)
oam, dr, err := s.updateOidcInRepo(ctx, scopeId, req)
if err != nil {
return nil, errors.Wrap(err, op)
return nil, false, errors.Wrap(err, op)
}
if am == nil {
return nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "Unable to update auth method but no error returned from repository.")
if oam == nil {
return nil, false, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "Unable to update auth method but no error returned from repository.")
}
if dryRun {
storageToWire = func(in auth.AuthMethod, opt ...handlers.Option) (*pb.AuthMethod, error) {
am, err := toAuthMethodProto(in)
if err != nil {
return nil, errors.Wrap(err, op)
}
attrs := &pb.OidcAuthMethodAttributes{}
if err := handlers.StructToProto(am.GetAttributes(), attrs); err != nil {
return nil, errors.Wrap(err, op, errors.WithMsg("can't convert from attribute struct to proto"))
}
attrs.DryRun = true
if am.Attributes, err = handlers.ProtoToStruct(attrs); err != nil {
return nil, errors.Wrap(err, op, errors.WithMsg("can't convert from attribute proto to struct"))
}
return am, nil
}
}
out = am
am = oam
dryRun = dr
}
return storageToWire(out)
return am, dryRun, nil
}
func (s Service) deleteFromRepo(ctx context.Context, scopeId, id string) (bool, error) {
@@ -523,7 +608,7 @@ func (s Service) deleteFromRepo(ctx context.Context, scopeId, id string) (bool,
return rows > 0, nil
}
func (s Service) changeStateInRepo(ctx context.Context, req *pbs.ChangeStateRequest) (*pb.AuthMethod, error) {
func (s Service) changeStateInRepo(ctx context.Context, req *pbs.ChangeStateRequest) (auth.AuthMethod, error) {
const op = "authmethod_service.(Service).changeStateInRepo"
switch auth.SubtypeFromId(req.GetId()) {
@@ -558,7 +643,7 @@ func (s Service) changeStateInRepo(ctx context.Context, req *pbs.ChangeStateRequ
return nil, err
}
return toAuthMethodProto(am)
return am, nil
}
return nil, errors.New(errors.InvalidParameter, op, "Given auth method type does not support changing state")
@@ -633,28 +718,53 @@ func (s Service) authResult(ctx context.Context, id string, a action.Type) auth.
return auth.Verify(ctx, opts...)
}
func toAuthMethodProto(in auth.AuthMethod, opt ...handlers.Option) (*pb.AuthMethod, error) {
anonListing := handlers.GetOpts(opt...).WithAnonymousListing
out := &pb.AuthMethod{
Id: in.GetPublicId(),
ScopeId: in.GetScopeId(),
IsPrimary: in.GetIsPrimaryAuthMethod(),
func toAuthMethodProto(ctx context.Context, in auth.AuthMethod, opt ...handlers.Option) (*pb.AuthMethod, error) {
opts := handlers.GetOpts(opt...)
if opts.WithOutputFields == nil {
return nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "output fields not found when building auth method proto")
}
if in.GetDescription() != "" {
outputFields := *opts.WithOutputFields
out := pb.AuthMethod{}
if outputFields.Has(globals.IdField) {
out.Id = in.GetPublicId()
}
if outputFields.Has(globals.ScopeIdField) {
out.ScopeId = in.GetScopeId()
}
if outputFields.Has(globals.IsPrimaryField) {
out.IsPrimary = in.GetIsPrimaryAuthMethod()
}
if outputFields.Has(globals.DescriptionField) && in.GetDescription() != "" {
out.Description = wrapperspb.String(in.GetDescription())
}
if in.GetName() != "" {
if outputFields.Has(globals.NameField) && in.GetName() != "" {
out.Name = wrapperspb.String(in.GetName())
}
if !anonListing {
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.AuthorizedCollectionActionsField) {
out.AuthorizedCollectionActions = opts.WithAuthorizedCollectionActions
}
switch i := in.(type) {
case *password.AuthMethod:
out.Type = auth.PasswordSubtype.String()
if anonListing {
if outputFields.Has(globals.TypeField) {
out.Type = auth.PasswordSubtype.String()
}
if !outputFields.Has(globals.AttributesField) {
break
}
st, err := handlers.ProtoToStruct(&pb.PasswordAuthMethodAttributes{
@@ -666,8 +776,10 @@ func toAuthMethodProto(in auth.AuthMethod, opt ...handlers.Option) (*pb.AuthMeth
}
out.Attributes = st
case *oidc.AuthMethod:
out.Type = auth.OidcSubtype.String()
if anonListing {
if outputFields.Has(globals.TypeField) {
out.Type = auth.OidcSubtype.String()
}
if !outputFields.Has(globals.AttributesField) {
break
}
attrs := &pb.OidcAuthMethodAttributes{
@@ -704,7 +816,7 @@ func toAuthMethodProto(in auth.AuthMethod, opt ...handlers.Option) (*pb.AuthMeth
}
out.Attributes = st
}
return out, nil
return &out, nil
}
func toAuthTokenProto(t *authtoken.AuthToken) *pba.AuthToken {
@@ -1080,8 +1192,8 @@ func validateAuthenticateLoginRequest(req *pbs.AuthenticateLoginRequest) error {
return nil
}
func (s Service) convertInternalAuthTokenToApiAuthToken(ctx context.Context, tok *authtoken.AuthToken) (*pba.AuthToken, error) {
const op = "authmethod.convertInternalAuthTokenToApiAuthToken"
func (s Service) ConvertInternalAuthTokenToApiAuthToken(ctx context.Context, tok *authtoken.AuthToken) (*pba.AuthToken, error) {
const op = "authmethod.ConvertInternalAuthTokenToApiAuthToken"
iamRepo, err := s.iamRepoFn()
if err != nil {
return nil, err
@@ -353,7 +353,7 @@ func TestList(t *testing.T) {
require.NoError(err, "Couldn't create new auth_method service.")
// First check with non-anonymous user
got, gErr := s.ListAuthMethods(auth.DisabledAuthTestContext(iamRepoFn, tc.req.GetScopeId(), auth.WithUserId("u_auth")), tc.req)
got, gErr := s.ListAuthMethods(auth.DisabledAuthTestContext(iamRepoFn, tc.req.GetScopeId()), tc.req)
if tc.err != nil {
require.Error(gErr)
assert.True(errors.Is(gErr, tc.err), "ListAuthMethods() for scope %q got error %v, wanted %v", tc.req.GetScopeId(), gErr, tc.err)
@@ -370,8 +370,9 @@ func TestList(t *testing.T) {
assert.Empty(cmp.Diff(got, tc.res, protocmp.Transform()), "ListAuthMethods() for scope %q got response %q, wanted %q", tc.req.GetScopeId(), got, tc.res)
// Now check with anonymous user
got, gErr = s.ListAuthMethods(auth.DisabledAuthTestContext(iamRepoFn, tc.req.GetScopeId()), tc.req)
got, gErr = s.ListAuthMethods(auth.DisabledAuthTestContext(iamRepoFn, tc.req.GetScopeId(), auth.WithUserId(auth.AnonymousUserId)), tc.req)
require.NoError(gErr)
assert.Len(got.Items, len(tc.res.Items))
for _, g := range got.GetItems() {
assert.Nil(g.Attributes)
assert.Nil(g.CreatedTime)
@@ -324,7 +324,7 @@ func (s Service) authenticateOidcToken(ctx context.Context, req *pbs.Authenticat
}, nil
}
responseToken, err := s.convertInternalAuthTokenToApiAuthToken(
responseToken, err := s.ConvertInternalAuthTokenToApiAuthToken(
ctx,
token,
)
@@ -24,6 +24,7 @@ import (
pbs "github.com/hashicorp/boundary/internal/gen/controller/api/services"
"github.com/hashicorp/boundary/internal/iam"
"github.com/hashicorp/boundary/internal/kms"
"github.com/hashicorp/boundary/internal/requests"
"github.com/hashicorp/boundary/internal/servers"
"github.com/hashicorp/boundary/internal/servers/controller/common"
"github.com/hashicorp/boundary/internal/servers/controller/handlers"
@@ -153,6 +154,7 @@ func TestList_FilterNonPublic(t *testing.T) {
serversRepoFn := func() (*servers.Repository, error) {
return servers.NewRepository(rw, rw, kmsCache)
}
iamRepo := iam.TestRepo(t, conn, wrapper)
o, _ := iam.TestScopes(t, iamRepo)
@@ -194,14 +196,14 @@ func TestList_FilterNonPublic(t *testing.T) {
}{
{
name: "unauthenticated",
reqCtx: auth.DisabledAuthTestContext(iamRepoFn, o.GetPublicId()),
reqCtx: auth.DisabledAuthTestContext(iamRepoFn, o.GetPublicId(), auth.WithUserId(auth.AnonymousUserId)),
respCount: 1,
},
{
name: "authenticated",
reqCtx: func() context.Context {
at := authtoken.TestAuthToken(t, conn, kmsCache, o.GetPublicId())
return auth.NewVerifierContext(context.Background(),
return auth.NewVerifierContext(requests.NewRequestContext(context.Background()),
nil,
iamRepoFn,
authTokenRepoFn,
@@ -127,7 +127,7 @@ func (s Service) authenticateWithPwRepo(ctx context.Context, scopeId, authMethod
return nil, err
}
return s.convertInternalAuthTokenToApiAuthToken(
return s.ConvertInternalAuthTokenToApiAuthToken(
ctx,
tok,
)
@@ -4,18 +4,21 @@ import (
"context"
"fmt"
"github.com/hashicorp/boundary/globals"
"github.com/hashicorp/boundary/internal/auth"
"github.com/hashicorp/boundary/internal/authtoken"
"github.com/hashicorp/boundary/internal/errors"
pb "github.com/hashicorp/boundary/internal/gen/controller/api/resources/authtokens"
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/servers/controller/common"
"github.com/hashicorp/boundary/internal/servers/controller/common/scopeids"
"github.com/hashicorp/boundary/internal/servers/controller/handlers"
"github.com/hashicorp/boundary/internal/types/action"
"github.com/hashicorp/boundary/internal/types/resource"
"github.com/hashicorp/boundary/internal/types/scope"
"google.golang.org/grpc/codes"
)
var (
@@ -90,37 +93,57 @@ func (s Service) ListAuthTokens(ctx context.Context, req *pbs.ListAuthTokensRequ
if err != nil {
return nil, err
}
if len(ul) == 0 {
return &pbs.ListAuthTokensResponse{}, nil
}
filter, err := handlers.NewFilter(req.GetFilter())
if err != nil {
return nil, err
}
finalItems := make([]*pb.AuthToken, 0, len(ul))
res := &perms.Resource{
res := perms.Resource{
Type: resource.AuthToken,
}
for _, item := range ul {
item.Scope = scopeInfoMap[item.GetScopeId()]
res.ScopeId = item.Scope.Id
authorizedActions := authResults.FetchActionSetForId(ctx, item.Id, IdActions, auth.WithResource(res))
for _, at := range ul {
res.Id = at.GetPublicId()
res.ScopeId = at.GetScopeId()
authorizedActions := authResults.FetchActionSetForId(ctx, at.GetPublicId(), IdActions, auth.WithResource(&res))
if len(authorizedActions) == 0 {
continue
}
if authorizedActions.OnlySelf() && item.GetUserId() != authResults.UserId {
if authorizedActions.OnlySelf() && at.GetIamUserId() != authResults.UserId {
continue
}
item.AuthorizedActions = authorizedActions.Strings()
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[at.GetScopeId()]))
}
if outputFields.Has(globals.AuthorizedActionsField) {
outputOpts = append(outputOpts, handlers.WithAuthorizedActions(authorizedActions.Strings()))
}
item, err := toProto(ctx, at, outputOpts...)
if err != nil {
return nil, err
}
if filter.Match(item) {
finalItems = append(finalItems, item)
}
}
return &pbs.ListAuthTokensResponse{Items: finalItems}, nil
}
// GetAuthToken implements the interface pbs.AuthTokenServiceServer.
func (s Service) GetAuthToken(ctx context.Context, req *pbs.GetAuthTokenRequest) (*pbs.GetAuthTokenResponse, error) {
const op = "authtokens.(Service).GetAuthToken"
if err := validateGetRequest(req); err != nil {
return nil, err
}
@@ -128,22 +151,47 @@ func (s Service) GetAuthToken(ctx context.Context, req *pbs.GetAuthTokenRequest)
if authResults.Error != nil {
return nil, authResults.Error
}
u, err := s.getFromRepo(ctx, req.GetId())
at, err := s.getFromRepo(ctx, req.GetId())
if err != nil {
return nil, err
}
authzdActions := authResults.FetchActionSetForId(ctx, u.Id, IdActions)
var outputFields perms.OutputFieldsMap
authorizedActions := authResults.FetchActionSetForId(ctx, at.GetPublicId(), IdActions)
// Check to see if we need to verify Read vs. just ReadSelf
if u.GetUserId() != authResults.UserId {
if !authzdActions.HasAction(action.Read) {
if at.GetIamUserId() != authResults.UserId {
if !authorizedActions.HasAction(action.Read) {
return nil, handlers.ForbiddenError()
}
outputFields = authResults.FetchOutputFields(perms.Resource{
Id: at.GetPublicId(),
ScopeId: at.GetScopeId(),
Type: resource.AuthToken,
}, action.Read).SelfOrDefaults(authResults.UserId)
} else {
var ok bool
outputFields, ok = requests.OutputFields(ctx)
if !ok {
return nil, errors.New(errors.Internal, op, "no request context found")
}
}
u.Scope = authResults.Scope
u.AuthorizedActions = authResults.FetchActionSetForId(ctx, u.Id, IdActions).Strings()
return &pbs.GetAuthTokenResponse{Item: u}, nil
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.Strings()))
}
item, err := toProto(ctx, at, outputOpts...)
if err != nil {
return nil, err
}
return &pbs.GetAuthTokenResponse{Item: item}, nil
}
// DeleteAuthToken implements the interface pbs.AuthTokenServiceServer.
@@ -160,10 +208,12 @@ func (s Service) DeleteAuthToken(ctx context.Context, req *pbs.DeleteAuthTokenRe
if err != nil {
return nil, err
}
authzdActions := authResults.FetchActionSetForId(ctx, at.Id, IdActions)
authorizedActions := authResults.FetchActionSetForId(ctx, at.GetPublicId(), IdActions)
// Check to see if we need to verify Delete vs. just DeleteSelf
if at.GetUserId() != authResults.UserId {
if !authzdActions.HasAction(action.Delete) {
if at.GetIamUserId() != authResults.UserId {
if !authorizedActions.HasAction(action.Delete) {
return nil, handlers.ForbiddenError()
}
}
@@ -175,20 +225,20 @@ func (s Service) DeleteAuthToken(ctx context.Context, req *pbs.DeleteAuthTokenRe
return nil, nil
}
func (s Service) getFromRepo(ctx context.Context, id string) (*pb.AuthToken, error) {
func (s Service) getFromRepo(ctx context.Context, id string) (*authtoken.AuthToken, error) {
const op = "authtokens.(Service).getFromRepo"
repo, err := s.repoFn()
if err != nil {
return nil, errors.Wrap(err, op)
}
u, err := repo.LookupAuthToken(ctx, id)
at, err := repo.LookupAuthToken(ctx, id)
if err != nil && !errors.IsNotFoundError(err) {
return nil, errors.Wrap(err, op)
}
if u == nil {
if at == nil {
return nil, errors.New(errors.InvalidParameter, op, fmt.Sprintf("AuthToken %q not found", id))
}
return toProto(u), nil
return at, nil
}
func (s Service) deleteFromRepo(ctx context.Context, id string) (bool, error) {
@@ -207,7 +257,7 @@ func (s Service) deleteFromRepo(ctx context.Context, id string) (bool, error) {
return rows > 0, nil
}
func (s Service) listFromRepo(ctx context.Context, scopeIds []string) ([]*pb.AuthToken, error) {
func (s Service) listFromRepo(ctx context.Context, scopeIds []string) ([]*authtoken.AuthToken, error) {
repo, err := s.repoFn()
_ = repo
if err != nil {
@@ -217,11 +267,7 @@ func (s Service) listFromRepo(ctx context.Context, scopeIds []string) ([]*pb.Aut
if err != nil {
return nil, err
}
var outUl []*pb.AuthToken
for _, u := range ul {
outUl = append(outUl, toProto(u))
}
return outUl, nil
return ul, nil
}
func (s Service) authResult(ctx context.Context, id string, a action.Type) auth.VerifyResults {
@@ -268,19 +314,49 @@ func (s Service) authResult(ctx context.Context, id string, a action.Type) auth.
return auth.Verify(ctx, opts...)
}
func toProto(in *authtoken.AuthToken) *pb.AuthToken {
out := pb.AuthToken{
Id: in.GetPublicId(),
ScopeId: in.GetScopeId(),
CreatedTime: in.GetCreateTime().GetTimestamp(),
UpdatedTime: in.GetUpdateTime().GetTimestamp(),
ApproximateLastUsedTime: in.GetApproximateLastAccessTime().GetTimestamp(),
ExpirationTime: in.GetExpirationTime().GetTimestamp(),
UserId: in.GetIamUserId(),
AuthMethodId: in.GetAuthMethodId(),
AccountId: in.GetAuthAccountId(),
func toProto(ctx context.Context, in *authtoken.AuthToken, opt ...handlers.Option) (*pb.AuthToken, error) {
opts := handlers.GetOpts(opt...)
if opts.WithOutputFields == nil {
return nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "output fields not found when building auth token proto")
}
return &out
outputFields := *opts.WithOutputFields
out := pb.AuthToken{}
if outputFields.Has(globals.IdField) {
out.Id = in.GetPublicId()
}
if outputFields.Has(globals.ScopeIdField) {
out.ScopeId = in.GetScopeId()
}
if outputFields.Has(globals.UserIdField) {
out.UserId = in.GetIamUserId()
}
if outputFields.Has(globals.AuthMethodIdField) {
out.AuthMethodId = in.GetAuthMethodId()
}
if outputFields.Has(globals.AccountIdField) {
out.AccountId = in.GetAuthAccountId()
}
if outputFields.Has(globals.CreatedTimeField) {
out.CreatedTime = in.GetCreateTime().GetTimestamp()
}
if outputFields.Has(globals.UpdatedTimeField) {
out.UpdatedTime = in.GetUpdateTime().GetTimestamp()
}
if outputFields.Has(globals.ApproximateLastUsedTimeField) {
out.ApproximateLastUsedTime = in.GetApproximateLastAccessTime().GetTimestamp()
}
if outputFields.Has(globals.ExpirationTimeField) {
out.ExpirationTime = in.GetExpirationTime().GetTimestamp()
}
if outputFields.Has(globals.ScopeField) {
out.Scope = opts.WithScope
}
if outputFields.Has(globals.AuthorizedActionsField) {
out.AuthorizedActions = opts.WithAuthorizedActions
}
return &out, nil
}
// A validateX method should exist for each method above. These methods do not make calls to any backing service but enforce
@@ -16,6 +16,7 @@ import (
pbs "github.com/hashicorp/boundary/internal/gen/controller/api/services"
"github.com/hashicorp/boundary/internal/iam"
"github.com/hashicorp/boundary/internal/kms"
"github.com/hashicorp/boundary/internal/requests"
"github.com/hashicorp/boundary/internal/servers"
"github.com/hashicorp/boundary/internal/servers/controller/handlers"
"github.com/hashicorp/boundary/internal/servers/controller/handlers/authtokens"
@@ -97,6 +98,7 @@ func TestGetSelf(t *testing.T) {
}
ctx := auth.NewVerifierContext(context.Background(), logger, iamRepoFn, tokenRepoFn, serversRepoFn, kms, requestInfo)
ctx = context.WithValue(ctx, requests.ContextRequestInformationKey, &requests.RequestContext{})
got, err := a.GetAuthToken(ctx, &pbs.GetAuthTokenRequest{Id: tc.readId})
if tc.err != nil {
require.EqualError(err, tc.err.Error())
@@ -105,9 +107,9 @@ func TestGetSelf(t *testing.T) {
}
require.NoError(err)
require.NotNil(got)
assert.Equal(got.GetItem().GetId(), tc.token.GetPublicId())
assert.Equal(tc.token.GetPublicId(), got.GetItem().GetId())
// Ensure we didn't simply have e.g. read on all tokens
assert.Equal(got.Item.GetAuthorizedActions(), []string{"read:self", "delete:self"})
assert.Equal([]string{"read:self", "delete:self"}, got.Item.GetAuthorizedActions())
})
}
}
@@ -384,16 +386,30 @@ func TestList(t *testing.T) {
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
s, err := authtokens.NewService(repoFn, iamRepoFn)
require.NoError(t, err, "Couldn't create new user service.")
assert, require := assert.New(t), require.New(t)
require.NoError(err, "Couldn't create new user service.")
got, gErr := s.ListAuthTokens(auth.DisabledAuthTestContext(iamRepoFn, tc.req.GetScopeId()), tc.req)
// Check non-anon listing
got, gErr := s.ListAuthTokens(auth.DisabledAuthTestContext(iamRepoFn, tc.req.GetScopeId(), auth.WithUserId("u_auth")), tc.req)
if tc.err != nil {
require.Error(t, gErr)
assert.True(t, errors.Is(gErr, tc.err), "ListAuthTokens() with scope %q got error %v, wanted %v", tc.req.GetScopeId(), gErr, tc.err)
require.Error(gErr)
assert.True(errors.Is(gErr, tc.err), "ListAuthTokens() with scope %q got error %v, wanted %v", tc.req.GetScopeId(), gErr, tc.err)
return
} else {
require.NoError(t, gErr)
require.NoError(gErr)
}
assert.Empty(cmp.Diff(got, tc.res, protocmp.Transform(), protocmp.SortRepeatedFields(got)), "ListAuthTokens() with scope %q got response %q, wanted %q", tc.req.GetScopeId(), got, tc.res)
// Now check anon listing
got, gErr = s.ListAuthTokens(auth.DisabledAuthTestContext(iamRepoFn, tc.req.GetScopeId(), auth.WithUserId(auth.AnonymousUserId)), tc.req)
require.NoError(gErr)
assert.Len(got.Items, len(tc.res.Items))
for _, item := range got.GetItems() {
require.Nil(item.CreatedTime)
require.Nil(item.ExpirationTime)
require.Nil(item.UpdatedTime)
require.Nil(item.ApproximateLastUsedTime)
}
assert.Empty(t, cmp.Diff(got, tc.res, protocmp.Transform(), protocmp.SortRepeatedFields(got)), "ListAuthTokens() with scope %q got response %q, wanted %q", tc.req.GetScopeId(), got, tc.res)
})
}
}
@@ -4,6 +4,7 @@ import (
"context"
"fmt"
"github.com/hashicorp/boundary/globals"
"github.com/hashicorp/boundary/internal/auth"
"github.com/hashicorp/boundary/internal/errors"
pb "github.com/hashicorp/boundary/internal/gen/controller/api/resources/groups"
@@ -11,6 +12,7 @@ import (
"github.com/hashicorp/boundary/internal/iam"
"github.com/hashicorp/boundary/internal/iam/store"
"github.com/hashicorp/boundary/internal/perms"
"github.com/hashicorp/boundary/internal/requests"
"github.com/hashicorp/boundary/internal/servers/controller/common"
"github.com/hashicorp/boundary/internal/servers/controller/common/scopeids"
"github.com/hashicorp/boundary/internal/servers/controller/handlers"
@@ -102,21 +104,41 @@ func (s Service) ListGroups(ctx context.Context, req *pbs.ListGroupsRequest) (*p
if err != nil {
return nil, err
}
if len(gl) == 0 {
return &pbs.ListGroupsResponse{}, nil
}
filter, err := handlers.NewFilter(req.GetFilter())
if err != nil {
return nil, err
}
finalItems := make([]*pb.Group, 0, len(gl))
res := &perms.Resource{
res := perms.Resource{
Type: resource.Group,
}
for _, item := range gl {
item.Scope = scopeInfoMap[item.GetScopeId()]
res.ScopeId = item.Scope.Id
item.AuthorizedActions = authResults.FetchActionSetForId(ctx, item.Id, IdActions, auth.WithResource(res)).Strings()
if len(item.AuthorizedActions) == 0 {
res.Id = item.GetPublicId()
res.ScopeId = item.GetScopeId()
authorizedActions := authResults.FetchActionSetForId(ctx, item.GetPublicId(), IdActions, auth.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(scopeInfoMap[item.GetScopeId()]))
}
if outputFields.Has(globals.AuthorizedActionsField) {
outputOpts = append(outputOpts, handlers.WithAuthorizedActions(authorizedActions))
}
item, err := toProto(ctx, item, nil, outputOpts...)
if err != nil {
return nil, err
}
if filter.Match(item) {
finalItems = append(finalItems, item)
}
@@ -126,6 +148,8 @@ func (s Service) ListGroups(ctx context.Context, req *pbs.ListGroupsRequest) (*p
// GetGroups implements the interface pbs.GroupServiceServer.
func (s Service) GetGroup(ctx context.Context, req *pbs.GetGroupRequest) (*pbs.GetGroupResponse, error) {
const op = "groups.(Service).GetGroup"
if err := validateGetRequest(req); err != nil {
return nil, err
}
@@ -133,17 +157,37 @@ func (s Service) GetGroup(ctx context.Context, req *pbs.GetGroupRequest) (*pbs.G
if authResults.Error != nil {
return nil, authResults.Error
}
u, err := s.getFromRepo(ctx, req.GetId())
u, members, err := s.getFromRepo(ctx, req.GetId())
if err != nil {
return nil, err
}
u.Scope = authResults.Scope
u.AuthorizedActions = authResults.FetchActionSetForId(ctx, u.Id, IdActions).Strings()
return &pbs.GetGroupResponse{Item: u}, nil
outputFields, ok := requests.OutputFields(ctx)
if !ok {
return nil, errors.New(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, u.GetPublicId(), IdActions).Strings()))
}
item, err := toProto(ctx, u, members, outputOpts...)
if err != nil {
return nil, err
}
return &pbs.GetGroupResponse{Item: item}, nil
}
// CreateGroup implements the interface pbs.GroupServiceServer.
func (s Service) CreateGroup(ctx context.Context, req *pbs.CreateGroupRequest) (*pbs.CreateGroupResponse, error) {
const op = "groups.(Service).CreateGroup"
if err := validateCreateRequest(req); err != nil {
return nil, err
}
@@ -155,13 +199,33 @@ func (s Service) CreateGroup(ctx context.Context, req *pbs.CreateGroupRequest) (
if err != nil {
return nil, err
}
u.Scope = authResults.Scope
u.AuthorizedActions = authResults.FetchActionSetForId(ctx, u.Id, IdActions).Strings()
return &pbs.CreateGroupResponse{Item: u, Uri: fmt.Sprintf("groups/%s", u.GetId())}, nil
outputFields, ok := requests.OutputFields(ctx)
if !ok {
return nil, errors.New(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, 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
}
// UpdateGroup implements the interface pbs.GroupServiceServer.
func (s Service) UpdateGroup(ctx context.Context, req *pbs.UpdateGroupRequest) (*pbs.UpdateGroupResponse, error) {
const op = "groups.(Service).UpdateGroup"
if err := validateUpdateRequest(req); err != nil {
return nil, err
}
@@ -169,13 +233,31 @@ func (s Service) UpdateGroup(ctx context.Context, req *pbs.UpdateGroupRequest) (
if authResults.Error != nil {
return nil, authResults.Error
}
u, err := s.updateInRepo(ctx, authResults.Scope.GetId(), req.GetId(), req.GetUpdateMask().GetPaths(), req.GetItem())
u, members, err := s.updateInRepo(ctx, authResults.Scope.GetId(), req.GetId(), req.GetUpdateMask().GetPaths(), req.GetItem())
if err != nil {
return nil, err
}
u.Scope = authResults.Scope
u.AuthorizedActions = authResults.FetchActionSetForId(ctx, u.Id, IdActions).Strings()
return &pbs.UpdateGroupResponse{Item: u}, nil
outputFields, ok := requests.OutputFields(ctx)
if !ok {
return nil, errors.New(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, u.GetPublicId(), IdActions).Strings()))
}
item, err := toProto(ctx, u, members, outputOpts...)
if err != nil {
return nil, err
}
return &pbs.UpdateGroupResponse{Item: item}, nil
}
// DeleteGroup implements the interface pbs.GroupServiceServer.
@@ -196,6 +278,8 @@ func (s Service) DeleteGroup(ctx context.Context, req *pbs.DeleteGroupRequest) (
// AddGroupMembers implements the interface pbs.GroupServiceServer.
func (s Service) AddGroupMembers(ctx context.Context, req *pbs.AddGroupMembersRequest) (*pbs.AddGroupMembersResponse, error) {
const op = "groups.(Service).AddGroupMembers"
if err := validateAddGroupMembersRequest(req); err != nil {
return nil, err
}
@@ -203,17 +287,37 @@ func (s Service) AddGroupMembers(ctx context.Context, req *pbs.AddGroupMembersRe
if authResults.Error != nil {
return nil, authResults.Error
}
g, err := s.addMembersInRepo(ctx, req.GetId(), req.GetMemberIds(), req.GetVersion())
g, members, err := s.addMembersInRepo(ctx, req.GetId(), req.GetMemberIds(), req.GetVersion())
if err != nil {
return nil, err
}
g.Scope = authResults.Scope
g.AuthorizedActions = authResults.FetchActionSetForId(ctx, g.Id, IdActions).Strings()
return &pbs.AddGroupMembersResponse{Item: g}, nil
outputFields, ok := requests.OutputFields(ctx)
if !ok {
return nil, errors.New(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, g.GetPublicId(), IdActions).Strings()))
}
item, err := toProto(ctx, g, members, outputOpts...)
if err != nil {
return nil, err
}
return &pbs.AddGroupMembersResponse{Item: item}, nil
}
// SetGroupMembers implements the interface pbs.GroupServiceServer.
func (s Service) SetGroupMembers(ctx context.Context, req *pbs.SetGroupMembersRequest) (*pbs.SetGroupMembersResponse, error) {
const op = "groups.(Service).SetGroupMembers"
if err := validateSetGroupMembersRequest(req); err != nil {
return nil, err
}
@@ -221,17 +325,37 @@ func (s Service) SetGroupMembers(ctx context.Context, req *pbs.SetGroupMembersRe
if authResults.Error != nil {
return nil, authResults.Error
}
g, err := s.setMembersInRepo(ctx, req.GetId(), req.GetMemberIds(), req.GetVersion())
g, members, err := s.setMembersInRepo(ctx, req.GetId(), req.GetMemberIds(), req.GetVersion())
if err != nil {
return nil, err
}
g.Scope = authResults.Scope
g.AuthorizedActions = authResults.FetchActionSetForId(ctx, g.Id, IdActions).Strings()
return &pbs.SetGroupMembersResponse{Item: g}, nil
outputFields, ok := requests.OutputFields(ctx)
if !ok {
return nil, errors.New(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, g.GetPublicId(), IdActions).Strings()))
}
item, err := toProto(ctx, g, members, outputOpts...)
if err != nil {
return nil, err
}
return &pbs.SetGroupMembersResponse{Item: item}, nil
}
// RemoveGroupMembers implements the interface pbs.GroupServiceServer.
func (s Service) RemoveGroupMembers(ctx context.Context, req *pbs.RemoveGroupMembersRequest) (*pbs.RemoveGroupMembersResponse, error) {
const op = "groups.(Service).RemoveGroupMembers"
if err := validateRemoveGroupMembersRequest(req); err != nil {
return nil, err
}
@@ -239,32 +363,50 @@ func (s Service) RemoveGroupMembers(ctx context.Context, req *pbs.RemoveGroupMem
if authResults.Error != nil {
return nil, authResults.Error
}
g, err := s.removeMembersInRepo(ctx, req.GetId(), req.GetMemberIds(), req.GetVersion())
g, members, err := s.removeMembersInRepo(ctx, req.GetId(), req.GetMemberIds(), req.GetVersion())
if err != nil {
return nil, err
}
g.Scope = authResults.Scope
g.AuthorizedActions = authResults.FetchActionSetForId(ctx, g.Id, IdActions).Strings()
return &pbs.RemoveGroupMembersResponse{Item: g}, nil
outputFields, ok := requests.OutputFields(ctx)
if !ok {
return nil, errors.New(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, g.GetPublicId(), IdActions).Strings()))
}
item, err := toProto(ctx, g, members, outputOpts...)
if err != nil {
return nil, err
}
return &pbs.RemoveGroupMembersResponse{Item: item}, nil
}
func (s Service) getFromRepo(ctx context.Context, id string) (*pb.Group, error) {
func (s Service) getFromRepo(ctx context.Context, id string) (*iam.Group, []*iam.GroupMember, error) {
const op = "groups.(Service).getFromRepo"
repo, err := s.repoFn()
if err != nil {
return nil, errors.Wrap(err, op)
return nil, nil, errors.Wrap(err, op)
}
g, m, err := repo.LookupGroup(ctx, id)
if err != nil && !errors.IsNotFoundError(err) {
return nil, errors.Wrap(err, op)
return nil, nil, errors.Wrap(err, op)
}
if g == nil {
return nil, errors.New(errors.InvalidParameter, op, fmt.Sprintf("group %q not found", id))
return nil, nil, errors.New(errors.InvalidParameter, op, fmt.Sprintf("group %q not found", id))
}
return toProto(g, m), nil
return g, m, err
}
func (s Service) createInRepo(ctx context.Context, scopeId string, item *pb.Group) (*pb.Group, error) {
func (s Service) createInRepo(ctx context.Context, scopeId string, item *pb.Group) (*iam.Group, error) {
const op = "groups.(Service).createInRepo"
var opts []iam.Option
if item.GetName() != nil {
@@ -288,10 +430,10 @@ func (s Service) createInRepo(ctx context.Context, scopeId string, item *pb.Grou
if out == nil {
return nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "Unable to create group but no error returned from repository.")
}
return toProto(out, nil), nil
return out, nil
}
func (s Service) updateInRepo(ctx context.Context, scopeId, id string, mask []string, item *pb.Group) (*pb.Group, error) {
func (s Service) updateInRepo(ctx context.Context, scopeId, id string, mask []string, item *pb.Group) (*iam.Group, []*iam.GroupMember, error) {
const op = "groups.(Service).updateInRepo"
var opts []iam.Option
if desc := item.GetDescription(); desc != nil {
@@ -303,25 +445,25 @@ func (s Service) updateInRepo(ctx context.Context, scopeId, id string, mask []st
version := item.GetVersion()
g, err := iam.NewGroup(scopeId, opts...)
if err != nil {
return nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "Unable to build group for update: %v.", err)
return nil, nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "Unable to build group for update: %v.", err)
}
g.PublicId = id
dbMask := maskManager.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."})
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."})
}
repo, err := s.repoFn()
if err != nil {
return nil, errors.Wrap(err, op)
return nil, nil, errors.Wrap(err, op)
}
out, m, rowsUpdated, err := repo.UpdateGroup(ctx, g, version, dbMask)
if err != nil {
return nil, errors.Wrap(err, op)
return nil, nil, errors.Wrap(err, op)
}
if rowsUpdated == 0 {
return nil, handlers.NotFoundErrorf("Group %q doesn't exist or incorrect version provided.", id)
return nil, nil, handlers.NotFoundErrorf("Group %q doesn't exist or incorrect version provided.", id)
}
return toProto(out, m), nil
return out, m, nil
}
func (s Service) deleteFromRepo(ctx context.Context, id string) (bool, error) {
@@ -340,7 +482,7 @@ func (s Service) deleteFromRepo(ctx context.Context, id string) (bool, error) {
return rows > 0, nil
}
func (s Service) listFromRepo(ctx context.Context, scopeIds []string) ([]*pb.Group, error) {
func (s Service) listFromRepo(ctx context.Context, scopeIds []string) ([]*iam.Group, error) {
const op = "groups.(Service).listFromRepo"
repo, err := s.repoFn()
if err != nil {
@@ -350,74 +492,70 @@ func (s Service) listFromRepo(ctx context.Context, scopeIds []string) ([]*pb.Gro
if err != nil {
return nil, errors.Wrap(err, op)
}
var outGl []*pb.Group
for _, g := range gl {
outGl = append(outGl, toProto(g, nil))
}
return outGl, nil
return gl, nil
}
func (s Service) addMembersInRepo(ctx context.Context, groupId string, userIds []string, version uint32) (*pb.Group, error) {
func (s Service) addMembersInRepo(ctx context.Context, groupId string, userIds []string, version uint32) (*iam.Group, []*iam.GroupMember, error) {
const op = "groups.(Service).addMembersInRepo"
repo, err := s.repoFn()
if err != nil {
return nil, errors.Wrap(err, op)
return nil, nil, errors.Wrap(err, op)
}
_, err = repo.AddGroupMembers(ctx, groupId, version, strutil.RemoveDuplicates(userIds, false))
if err != nil {
// TODO: Figure out a way to surface more helpful error info beyond the Internal error.
return nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "Unable to add members to group: %v.", err)
return nil, nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "Unable to add members to group: %v.", err)
}
out, m, err := repo.LookupGroup(ctx, groupId)
if err != nil {
return nil, errors.Wrap(err, op, errors.WithMsg("unable to look up group after adding memebers"))
return nil, nil, errors.Wrap(err, op, errors.WithMsg("unable to look up group after adding memebers"))
}
if out == nil {
return nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "Unable to lookup group after adding member to it.")
return nil, nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "Unable to lookup group after adding member to it.")
}
return toProto(out, m), nil
return out, m, nil
}
func (s Service) setMembersInRepo(ctx context.Context, groupId string, userIds []string, version uint32) (*pb.Group, error) {
func (s Service) setMembersInRepo(ctx context.Context, groupId string, userIds []string, version uint32) (*iam.Group, []*iam.GroupMember, error) {
const op = "groups.(Service).setMembersInRepo"
repo, err := s.repoFn()
if err != nil {
return nil, errors.Wrap(err, op)
return nil, nil, errors.Wrap(err, op)
}
_, _, err = repo.SetGroupMembers(ctx, groupId, version, strutil.RemoveDuplicates(userIds, false))
if err != nil {
// TODO: Figure out a way to surface more helpful error info beyond the Internal error.
return nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "Unable to set members on group: %v.", err)
return nil, nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "Unable to set members on group: %v.", err)
}
out, m, err := repo.LookupGroup(ctx, groupId)
if err != nil {
return nil, errors.Wrap(err, op, errors.WithMsg("unable to look up group after setting members"))
return nil, nil, errors.Wrap(err, op, errors.WithMsg("unable to look up group after setting members"))
}
if out == nil {
return nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "Unable to lookup group after setting members for it.")
return nil, nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "Unable to lookup group after setting members for it.")
}
return toProto(out, m), nil
return out, m, nil
}
func (s Service) removeMembersInRepo(ctx context.Context, groupId string, userIds []string, version uint32) (*pb.Group, error) {
func (s Service) removeMembersInRepo(ctx context.Context, groupId string, userIds []string, version uint32) (*iam.Group, []*iam.GroupMember, error) {
const op = "groups.(Service).removeMembersInRepo"
repo, err := s.repoFn()
if err != nil {
return nil, errors.Wrap(err, op)
return nil, nil, errors.Wrap(err, op)
}
_, err = repo.DeleteGroupMembers(ctx, groupId, version, strutil.RemoveDuplicates(userIds, false))
if err != nil {
// TODO: Figure out a way to surface more helpful error info beyond the Internal error.
return nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "Unable to remove members from group: %v.", err)
return nil, nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "Unable to remove members from group: %v.", err)
}
out, m, err := repo.LookupGroup(ctx, groupId)
if err != nil {
return nil, errors.Wrap(err, op, errors.WithMsg("unable to look up group after removing members"))
return nil, nil, errors.Wrap(err, op, errors.WithMsg("unable to look up group after removing members"))
}
if out == nil {
return nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "Unable to lookup group after removing members from it.")
return nil, nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "Unable to lookup group after removing members from it.")
}
return toProto(out, m), nil
return out, m, nil
}
func (s Service) authResult(ctx context.Context, id string, a action.Type) auth.VerifyResults {
@@ -459,28 +597,55 @@ func (s Service) authResult(ctx context.Context, id string, a action.Type) auth.
return auth.Verify(ctx, opts...)
}
func toProto(in *iam.Group, members []*iam.GroupMember) *pb.Group {
out := pb.Group{
Id: in.GetPublicId(),
ScopeId: in.GetScopeId(),
CreatedTime: in.GetCreateTime().GetTimestamp(),
UpdatedTime: in.GetUpdateTime().GetTimestamp(),
Version: in.Version,
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")
}
if in.GetDescription() != "" {
out.Description = &wrapperspb.StringValue{Value: in.GetDescription()}
outputFields := *opts.WithOutputFields
out := pb.Group{}
if outputFields.Has(globals.IdField) {
out.Id = in.GetPublicId()
}
if in.GetName() != "" {
out.Name = &wrapperspb.StringValue{Value: in.GetName()}
if outputFields.Has(globals.ScopeIdField) {
out.ScopeId = in.GetScopeId()
}
for _, m := range members {
out.MemberIds = append(out.MemberIds, m.GetMemberId())
out.Members = append(out.Members, &pb.Member{
Id: m.GetMemberId(),
ScopeId: m.GetMemberScopeId(),
})
if outputFields.Has(globals.DescriptionField) && in.GetDescription() != "" {
out.Description = wrapperspb.String(in.GetDescription())
}
return &out
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()
}
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) {
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(),
})
}
}
return &out, nil
}
// A validateX method should exist for each method above. These methods do not make calls to any backing service but enforce
@@ -319,12 +319,26 @@ func TestList(t *testing.T) {
s, err := groups.NewService(repoFn)
require.NoError(err, "Couldn't create new group service.")
// Test with a non-anon user
got, gErr := s.ListGroups(auth.DisabledAuthTestContext(repoFn, tc.req.GetScopeId()), tc.req)
if tc.err != nil {
require.Error(gErr)
assert.True(errors.Is(gErr, tc.err), "ListGroups(%q) got error %v, wanted %v", tc.req.GetScopeId(), gErr, tc.err)
return
}
require.NoError(gErr)
assert.Empty(cmp.Diff(got, tc.res, protocmp.Transform()), "ListGroups(%q) got response %q, wanted %q", tc.req.GetScopeId(), got, tc.res)
// Test the anon case
got, gErr = s.ListGroups(auth.DisabledAuthTestContext(repoFn, tc.req.GetScopeId(), auth.WithUserId(auth.AnonymousUserId)), tc.req)
require.NoError(gErr)
assert.Len(got.Items, len(tc.res.Items))
for _, item := range got.GetItems() {
require.Nil(item.CreatedTime)
require.Nil(item.Members)
require.Nil(item.UpdatedTime)
require.Zero(item.Version)
}
})
}
}
@@ -4,6 +4,7 @@ import (
"context"
"fmt"
"github.com/hashicorp/boundary/globals"
"github.com/hashicorp/boundary/internal/auth"
"github.com/hashicorp/boundary/internal/errors"
pb "github.com/hashicorp/boundary/internal/gen/controller/api/resources/hostcatalogs"
@@ -12,6 +13,7 @@ import (
"github.com/hashicorp/boundary/internal/host/static"
"github.com/hashicorp/boundary/internal/host/static/store"
"github.com/hashicorp/boundary/internal/perms"
"github.com/hashicorp/boundary/internal/requests"
"github.com/hashicorp/boundary/internal/servers/controller/common"
"github.com/hashicorp/boundary/internal/servers/controller/common/scopeids"
"github.com/hashicorp/boundary/internal/servers/controller/handlers"
@@ -20,9 +22,7 @@ import (
"github.com/hashicorp/boundary/internal/types/action"
"github.com/hashicorp/boundary/internal/types/resource"
"github.com/hashicorp/boundary/internal/types/scope"
"github.com/hashicorp/boundary/sdk/strutil"
"google.golang.org/grpc/codes"
"google.golang.org/protobuf/types/known/structpb"
"google.golang.org/protobuf/types/known/wrapperspb"
)
@@ -67,33 +67,6 @@ type Service struct {
var _ pbs.HostCatalogServiceServer = Service{}
func populateCollectionAuthorizedActions(ctx context.Context,
authResults auth.VerifyResults,
item *pb.HostCatalog) error {
res := &perms.Resource{
ScopeId: authResults.Scope.Id,
Pin: item.Id,
}
// Range over the defined collections and check permissions against those
// collections. We use the ID of this scope being returned, not its parent,
// hence passing in a resource here.
for k, v := range collectionTypeMap {
res.Type = k
acts := authResults.FetchActionSetForType(ctx, k, v, auth.WithResource(res)).Strings()
if len(acts) > 0 {
if item.AuthorizedCollectionActions == nil {
item.AuthorizedCollectionActions = make(map[string]*structpb.ListValue)
}
lv, err := structpb.NewList(strutil.StringListToInterfaceList(acts))
if err != nil {
return err
}
item.AuthorizedCollectionActions[k.String()+"s"] = lv
}
}
return nil
}
// NewService returns a host catalog Service which handles host catalog related requests to boundary and uses the provided
// repositories for storage and retrieval.
func NewService(repoFn common.StaticRepoFactory, iamRepoFn common.IamRepoFactory) (Service, error) {
@@ -138,27 +111,50 @@ func (s Service) ListHostCatalogs(ctx context.Context, req *pbs.ListHostCatalogs
if err != nil {
return nil, err
}
if len(ul) == 0 {
return &pbs.ListHostCatalogsResponse{}, nil
}
filter, err := handlers.NewFilter(req.GetFilter())
if err != nil {
return nil, err
}
finalItems := make([]*pb.HostCatalog, 0, len(ul))
res := &perms.Resource{
res := perms.Resource{
Type: resource.HostCatalog,
}
for _, item := range ul {
item.Scope = scopeInfoMap[item.GetScopeId()]
res.ScopeId = item.Scope.Id
item.AuthorizedActions = authResults.FetchActionSetForId(ctx, item.Id, IdActions, auth.WithResource(res)).Strings()
if len(item.AuthorizedActions) == 0 {
res.Id = item.GetPublicId()
res.ScopeId = item.GetScopeId()
authorizedActions := authResults.FetchActionSetForId(ctx, item.GetPublicId(), IdActions, auth.WithResource(&res)).Strings()
if len(authorizedActions) == 0 {
continue
}
if filter.Match(item) {
finalItems = append(finalItems, item)
if err := populateCollectionAuthorizedActions(ctx, authResults, item); err != nil {
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))
}
if outputFields.Has(globals.AuthorizedCollectionActionsField) {
collectionActions, err := auth.CalculateAuthorizedCollectionActions(ctx, authResults, collectionTypeMap, authResults.Scope.Id, item.GetPublicId())
if err != nil {
return nil, err
}
outputOpts = append(outputOpts, handlers.WithAuthorizedCollectionActions(collectionActions))
}
item, err := toProto(ctx, item, outputOpts...)
if err != nil {
return nil, err
}
if filter.Match(item) {
finalItems = append(finalItems, item)
}
}
return &pbs.ListHostCatalogsResponse{Items: finalItems}, nil
@@ -166,6 +162,8 @@ func (s Service) ListHostCatalogs(ctx context.Context, req *pbs.ListHostCatalogs
// GetHostCatalog implements the interface pbs.HostCatalogServiceServer.
func (s Service) GetHostCatalog(ctx context.Context, req *pbs.GetHostCatalogRequest) (*pbs.GetHostCatalogResponse, error) {
const op = "host_catalogs.(Service).GetostCatalog"
if err := validateGetRequest(req); err != nil {
return nil, err
}
@@ -177,16 +175,40 @@ func (s Service) GetHostCatalog(ctx context.Context, req *pbs.GetHostCatalogRequ
if err != nil {
return nil, err
}
hc.Scope = authResults.Scope
hc.AuthorizedActions = authResults.FetchActionSetForId(ctx, hc.Id, IdActions).Strings()
if err := populateCollectionAuthorizedActions(ctx, authResults, hc); err != nil {
outputFields, ok := requests.OutputFields(ctx)
if !ok {
return nil, errors.New(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, hc.GetPublicId(), IdActions).Strings()))
}
if outputFields.Has(globals.AuthorizedCollectionActionsField) {
collectionActions, err := auth.CalculateAuthorizedCollectionActions(ctx, authResults, collectionTypeMap, authResults.Scope.Id, hc.GetPublicId())
if err != nil {
return nil, err
}
outputOpts = append(outputOpts, handlers.WithAuthorizedCollectionActions(collectionActions))
}
item, err := toProto(ctx, hc, outputOpts...)
if err != nil {
return nil, err
}
return &pbs.GetHostCatalogResponse{Item: hc}, nil
return &pbs.GetHostCatalogResponse{Item: item}, nil
}
// CreateHostCatalog implements the interface pbs.HostCatalogServiceServer.
func (s Service) CreateHostCatalog(ctx context.Context, req *pbs.CreateHostCatalogRequest) (*pbs.CreateHostCatalogResponse, error) {
const op = "host_catalogs.(Service).CreateHostCatalog"
if err := validateCreateRequest(req); err != nil {
return nil, err
}
@@ -198,19 +220,43 @@ func (s Service) CreateHostCatalog(ctx context.Context, req *pbs.CreateHostCatal
if err != nil {
return nil, err
}
hc.Scope = authResults.Scope
hc.AuthorizedActions = authResults.FetchActionSetForId(ctx, hc.Id, IdActions).Strings()
if err := populateCollectionAuthorizedActions(ctx, authResults, hc); err != nil {
outputFields, ok := requests.OutputFields(ctx)
if !ok {
return nil, errors.New(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, hc.GetPublicId(), IdActions).Strings()))
}
if outputFields.Has(globals.AuthorizedCollectionActionsField) {
collectionActions, err := auth.CalculateAuthorizedCollectionActions(ctx, authResults, collectionTypeMap, authResults.Scope.Id, hc.GetPublicId())
if err != nil {
return nil, err
}
outputOpts = append(outputOpts, handlers.WithAuthorizedCollectionActions(collectionActions))
}
item, err := toProto(ctx, hc, outputOpts...)
if err != nil {
return nil, err
}
return &pbs.CreateHostCatalogResponse{
Item: hc,
Uri: fmt.Sprintf("host-catalogs/%s", hc.GetId()),
Item: item,
Uri: fmt.Sprintf("host-catalogs/%s", item.GetId()),
}, nil
}
// UpdateHostCatalog implements the interface pbs.HostCatalogServiceServer.
func (s Service) UpdateHostCatalog(ctx context.Context, req *pbs.UpdateHostCatalogRequest) (*pbs.UpdateHostCatalogResponse, error) {
const op = "host_catalogs.(Service).UpdateHostCatalog"
if err := validateUpdateRequest(req); err != nil {
return nil, err
}
@@ -222,12 +268,34 @@ func (s Service) UpdateHostCatalog(ctx context.Context, req *pbs.UpdateHostCatal
if err != nil {
return nil, err
}
hc.Scope = authResults.Scope
hc.AuthorizedActions = authResults.FetchActionSetForId(ctx, hc.Id, IdActions).Strings()
if err := populateCollectionAuthorizedActions(ctx, authResults, hc); err != nil {
outputFields, ok := requests.OutputFields(ctx)
if !ok {
return nil, errors.New(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, hc.GetPublicId(), IdActions).Strings()))
}
if outputFields.Has(globals.AuthorizedCollectionActionsField) {
collectionActions, err := auth.CalculateAuthorizedCollectionActions(ctx, authResults, collectionTypeMap, authResults.Scope.Id, hc.GetPublicId())
if err != nil {
return nil, err
}
outputOpts = append(outputOpts, handlers.WithAuthorizedCollectionActions(collectionActions))
}
item, err := toProto(ctx, hc, outputOpts...)
if err != nil {
return nil, err
}
return &pbs.UpdateHostCatalogResponse{Item: hc}, nil
return &pbs.UpdateHostCatalogResponse{Item: item}, nil
}
// DeleteHostCatalog implements the interface pbs.HostCatalogServiceServer.
@@ -246,7 +314,7 @@ func (s Service) DeleteHostCatalog(ctx context.Context, req *pbs.DeleteHostCatal
return nil, nil
}
func (s Service) getFromRepo(ctx context.Context, id string) (*pb.HostCatalog, error) {
func (s Service) getFromRepo(ctx context.Context, id string) (*static.HostCatalog, error) {
repo, err := s.staticRepoFn()
if err != nil {
return nil, err
@@ -258,10 +326,10 @@ func (s Service) getFromRepo(ctx context.Context, id string) (*pb.HostCatalog, e
if hc == nil {
return nil, handlers.NotFoundErrorf("Host Catalog %q doesn't exist.", id)
}
return toProto(hc), nil
return hc, nil
}
func (s Service) listFromRepo(ctx context.Context, scopeIds []string) ([]*pb.HostCatalog, error) {
func (s Service) listFromRepo(ctx context.Context, scopeIds []string) ([]*static.HostCatalog, error) {
repo, err := s.staticRepoFn()
if err != nil {
return nil, err
@@ -270,14 +338,10 @@ func (s Service) listFromRepo(ctx context.Context, scopeIds []string) ([]*pb.Hos
if err != nil {
return nil, err
}
var outUl []*pb.HostCatalog
for _, u := range ul {
outUl = append(outUl, toProto(u))
}
return outUl, nil
return ul, nil
}
func (s Service) createInRepo(ctx context.Context, projId string, item *pb.HostCatalog) (*pb.HostCatalog, error) {
func (s Service) createInRepo(ctx context.Context, projId string, item *pb.HostCatalog) (*static.HostCatalog, error) {
const op = "host_catalogs.(Servivce).createInRepo"
var opts []static.Option
if item.GetName() != nil {
@@ -301,11 +365,11 @@ func (s Service) createInRepo(ctx context.Context, projId string, item *pb.HostC
if out == nil {
return nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "Unable to create host catalog but no error returned from repository.")
}
return toProto(out), nil
return out, nil
}
func (s Service) updateInRepo(ctx context.Context, projId, id string, mask []string, item *pb.HostCatalog) (*pb.HostCatalog, error) {
const op = "service.(Service).updateInRepo"
func (s Service) updateInRepo(ctx context.Context, projId, id string, mask []string, item *pb.HostCatalog) (*static.HostCatalog, error) {
const op = "host_catalogs.(Service).updateInRepo"
var opts []static.Option
if desc := item.GetDescription(); desc != nil {
opts = append(opts, static.WithDescription(desc.GetValue()))
@@ -334,11 +398,11 @@ func (s Service) updateInRepo(ctx context.Context, projId, id string, mask []str
if rowsUpdated == 0 {
return nil, handlers.NotFoundErrorf("Host Catalog %q doesn't exist or incorrect version provided.", id)
}
return toProto(out), nil
return out, nil
}
func (s Service) deleteFromRepo(ctx context.Context, id string) (bool, error) {
const op = "service.(Service).deleteFromRepo"
const op = "host_catalogs.(Service).deleteFromRepo"
repo, err := s.staticRepoFn()
if err != nil {
return false, errors.Wrap(err, op)
@@ -394,22 +458,48 @@ func (s Service) authResult(ctx context.Context, id string, a action.Type) auth.
return auth.Verify(ctx, opts...)
}
func toProto(in *static.HostCatalog) *pb.HostCatalog {
out := pb.HostCatalog{
Id: in.GetPublicId(),
ScopeId: in.GetScopeId(),
CreatedTime: in.GetCreateTime().GetTimestamp(),
UpdatedTime: in.GetUpdateTime().GetTimestamp(),
Version: in.GetVersion(),
Type: host.StaticSubtype.String(),
func toProto(ctx context.Context, in *static.HostCatalog, opt ...handlers.Option) (*pb.HostCatalog, error) {
opts := handlers.GetOpts(opt...)
if opts.WithOutputFields == nil {
return nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "output fields not found when building auth method proto")
}
if in.GetDescription() != "" {
out.Description = &wrapperspb.StringValue{Value: in.GetDescription()}
outputFields := *opts.WithOutputFields
out := pb.HostCatalog{}
if outputFields.Has(globals.IdField) {
out.Id = in.GetPublicId()
}
if in.GetName() != "" {
out.Name = &wrapperspb.StringValue{Value: in.GetName()}
if outputFields.Has(globals.ScopeIdField) {
out.ScopeId = in.GetScopeId()
}
return &out
if outputFields.Has(globals.TypeField) {
out.Type = host.StaticSubtype.String()
}
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()
}
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.AuthorizedCollectionActionsField) {
out.AuthorizedCollectionActions = opts.WithAuthorizedCollectionActions
}
return &out, nil
}
// A validateX method should exist for each method above. These methods do not make calls to any backing service but enforce
@@ -259,12 +259,24 @@ func TestList(t *testing.T) {
s, err := host_catalogs.NewService(repoFn, iamRepoFn)
require.NoError(err, "Couldn't create new auth_method service.")
// Test with non-anon user
got, gErr := s.ListHostCatalogs(auth.DisabledAuthTestContext(iamRepoFn, tc.req.GetScopeId()), tc.req)
if tc.err != nil {
require.Error(gErr)
assert.True(errors.Is(gErr, tc.err), "ListHostCatalogs() for scope %q got error %v, wanted %v", tc.req.GetScopeId(), gErr, tc.err)
return
}
assert.Empty(cmp.Diff(got, tc.res, protocmp.Transform()), "ListHostCatalogs() for scope %q got response %q, wanted %q", tc.req.GetScopeId(), got, tc.res)
// Test with anon user
got, gErr = s.ListHostCatalogs(auth.DisabledAuthTestContext(iamRepoFn, tc.req.GetScopeId(), auth.WithUserId("u_anon")), tc.req)
require.NoError(gErr)
assert.Len(got.Items, len(tc.res.Items))
for _, item := range got.GetItems() {
require.Nil(item.CreatedTime)
require.Nil(item.UpdatedTime)
require.Zero(item.Version)
}
})
}
}
@@ -4,6 +4,7 @@ import (
"context"
"fmt"
"github.com/hashicorp/boundary/globals"
"github.com/hashicorp/boundary/internal/auth"
"github.com/hashicorp/boundary/internal/errors"
pb "github.com/hashicorp/boundary/internal/gen/controller/api/resources/hostsets"
@@ -12,6 +13,7 @@ import (
"github.com/hashicorp/boundary/internal/host/static"
"github.com/hashicorp/boundary/internal/host/static/store"
"github.com/hashicorp/boundary/internal/perms"
"github.com/hashicorp/boundary/internal/requests"
"github.com/hashicorp/boundary/internal/servers/controller/common"
"github.com/hashicorp/boundary/internal/servers/controller/handlers"
"github.com/hashicorp/boundary/internal/types/action"
@@ -81,22 +83,43 @@ func (s Service) ListHostSets(ctx context.Context, req *pbs.ListHostSetsRequest)
if err != nil {
return nil, err
}
if len(hl) == 0 {
return &pbs.ListHostSetsResponse{}, nil
}
filter, err := handlers.NewFilter(req.GetFilter())
if err != nil {
return nil, err
}
finalItems := make([]*pb.HostSet, 0, len(hl))
res := &perms.Resource{
res := perms.Resource{
ScopeId: authResults.Scope.Id,
Type: resource.HostSet,
Pin: req.GetHostCatalogId(),
}
for _, item := range hl {
item.Scope = authResults.Scope
item.AuthorizedActions = authResults.FetchActionSetForId(ctx, item.Id, IdActions, auth.WithResource(res)).Strings()
if len(item.AuthorizedActions) == 0 {
res.Id = item.GetPublicId()
authorizedActions := authResults.FetchActionSetForId(ctx, item.GetPublicId(), IdActions, auth.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, item, nil, outputOpts...)
if err != nil {
return nil, err
}
if filter.Match(item) {
finalItems = append(finalItems, item)
}
@@ -106,6 +129,8 @@ func (s Service) ListHostSets(ctx context.Context, req *pbs.ListHostSetsRequest)
// GetHostSet implements the interface pbs.HostSetServiceServer.
func (s Service) GetHostSet(ctx context.Context, req *pbs.GetHostSetRequest) (*pbs.GetHostSetResponse, error) {
const op = "host_sets.(Service).GetHostSet"
if err := validateGetRequest(req); err != nil {
return nil, err
}
@@ -113,17 +138,37 @@ func (s Service) GetHostSet(ctx context.Context, req *pbs.GetHostSetRequest) (*p
if authResults.Error != nil {
return nil, authResults.Error
}
hc, err := s.getFromRepo(ctx, req.GetId())
hs, hosts, err := s.getFromRepo(ctx, req.GetId())
if err != nil {
return nil, err
}
hc.Scope = authResults.Scope
hc.AuthorizedActions = authResults.FetchActionSetForId(ctx, hc.Id, IdActions).Strings()
return &pbs.GetHostSetResponse{Item: hc}, nil
outputFields, ok := requests.OutputFields(ctx)
if !ok {
return nil, errors.New(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, hs.GetPublicId(), IdActions).Strings()))
}
item, err := toProto(ctx, hs, hosts, outputOpts...)
if err != nil {
return nil, err
}
return &pbs.GetHostSetResponse{Item: item}, nil
}
// CreateHostSet implements the interface pbs.HostSetServiceServer.
func (s Service) CreateHostSet(ctx context.Context, req *pbs.CreateHostSetRequest) (*pbs.CreateHostSetResponse, error) {
const op = "host_sets.(Service).CreateHostSet"
if err := validateCreateRequest(req); err != nil {
return nil, err
}
@@ -131,20 +176,40 @@ func (s Service) CreateHostSet(ctx context.Context, req *pbs.CreateHostSetReques
if authResults.Error != nil {
return nil, authResults.Error
}
h, err := s.createInRepo(ctx, authResults.Scope.GetId(), cat.GetPublicId(), req.GetItem())
hs, err := s.createInRepo(ctx, authResults.Scope.GetId(), cat.GetPublicId(), req.GetItem())
if err != nil {
return nil, err
}
h.Scope = authResults.Scope
h.AuthorizedActions = authResults.FetchActionSetForId(ctx, h.Id, IdActions).Strings()
outputFields, ok := requests.OutputFields(ctx)
if !ok {
return nil, errors.New(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, hs.GetPublicId(), IdActions).Strings()))
}
item, err := toProto(ctx, hs, nil, outputOpts...)
if err != nil {
return nil, err
}
return &pbs.CreateHostSetResponse{
Item: h,
Uri: fmt.Sprintf("host-sets/%s", h.GetId()),
Item: item,
Uri: fmt.Sprintf("host-sets/%s", item.GetId()),
}, nil
}
// UpdateHostSet implements the interface pbs.HostSetServiceServer.
func (s Service) UpdateHostSet(ctx context.Context, req *pbs.UpdateHostSetRequest) (*pbs.UpdateHostSetResponse, error) {
const op = "host_sets.(Service).UpdateHostSet"
if err := validateUpdateRequest(req); err != nil {
return nil, err
}
@@ -152,13 +217,31 @@ func (s Service) UpdateHostSet(ctx context.Context, req *pbs.UpdateHostSetReques
if authResults.Error != nil {
return nil, authResults.Error
}
hc, err := s.updateInRepo(ctx, authResults.Scope.GetId(), cat.GetPublicId(), req.GetId(), req.GetUpdateMask().GetPaths(), req.GetItem())
hs, hosts, err := s.updateInRepo(ctx, authResults.Scope.GetId(), cat.GetPublicId(), req.GetId(), req.GetUpdateMask().GetPaths(), req.GetItem())
if err != nil {
return nil, err
}
hc.Scope = authResults.Scope
hc.AuthorizedActions = authResults.FetchActionSetForId(ctx, hc.Id, IdActions).Strings()
return &pbs.UpdateHostSetResponse{Item: hc}, nil
outputFields, ok := requests.OutputFields(ctx)
if !ok {
return nil, errors.New(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, hs.GetPublicId(), IdActions).Strings()))
}
item, err := toProto(ctx, hs, hosts, outputOpts...)
if err != nil {
return nil, err
}
return &pbs.UpdateHostSetResponse{Item: item}, nil
}
// DeleteHostSet implements the interface pbs.HostSetServiceServer.
@@ -179,6 +262,8 @@ func (s Service) DeleteHostSet(ctx context.Context, req *pbs.DeleteHostSetReques
// AddHostSetHosts implements the interface pbs.HostSetServiceServer.
func (s Service) AddHostSetHosts(ctx context.Context, req *pbs.AddHostSetHostsRequest) (*pbs.AddHostSetHostsResponse, error) {
const op = "host_sets.(Service).AddHostSetHosts"
if err := validateAddRequest(req); err != nil {
return nil, err
}
@@ -186,17 +271,37 @@ func (s Service) AddHostSetHosts(ctx context.Context, req *pbs.AddHostSetHostsRe
if authResults.Error != nil {
return nil, authResults.Error
}
g, err := s.addInRepo(ctx, authResults.Scope.GetId(), req.GetId(), req.GetHostIds(), req.GetVersion())
hs, hosts, err := s.addInRepo(ctx, authResults.Scope.GetId(), req.GetId(), req.GetHostIds(), req.GetVersion())
if err != nil {
return nil, err
}
g.Scope = authResults.Scope
g.AuthorizedActions = authResults.FetchActionSetForId(ctx, g.Id, IdActions).Strings()
return &pbs.AddHostSetHostsResponse{Item: g}, nil
outputFields, ok := requests.OutputFields(ctx)
if !ok {
return nil, errors.New(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, hs.GetPublicId(), IdActions).Strings()))
}
item, err := toProto(ctx, hs, hosts, outputOpts...)
if err != nil {
return nil, err
}
return &pbs.AddHostSetHostsResponse{Item: item}, nil
}
// SetHostSetHosts implements the interface pbs.HostSetServiceServer.
func (s Service) SetHostSetHosts(ctx context.Context, req *pbs.SetHostSetHostsRequest) (*pbs.SetHostSetHostsResponse, error) {
const op = "host_sets.(Service).SetHostSetHosts"
if err := validateSetRequest(req); err != nil {
return nil, err
}
@@ -204,17 +309,36 @@ func (s Service) SetHostSetHosts(ctx context.Context, req *pbs.SetHostSetHostsRe
if authResults.Error != nil {
return nil, authResults.Error
}
g, err := s.setInRepo(ctx, authResults.Scope.GetId(), req.GetId(), req.GetHostIds(), req.GetVersion())
hs, hosts, err := s.setInRepo(ctx, authResults.Scope.GetId(), req.GetId(), req.GetHostIds(), req.GetVersion())
if err != nil {
return nil, err
}
g.Scope = authResults.Scope
g.AuthorizedActions = authResults.FetchActionSetForId(ctx, g.Id, IdActions).Strings()
return &pbs.SetHostSetHostsResponse{Item: g}, nil
outputFields, ok := requests.OutputFields(ctx)
if !ok {
return nil, errors.New(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, hs.GetPublicId(), IdActions).Strings()))
}
item, err := toProto(ctx, hs, hosts, outputOpts...)
if err != nil {
return nil, err
}
return &pbs.SetHostSetHostsResponse{Item: item}, nil
}
// RemoveHostSetHosts implements the interface pbs.HostSetServiceServer.
func (s Service) RemoveHostSetHosts(ctx context.Context, req *pbs.RemoveHostSetHostsRequest) (*pbs.RemoveHostSetHostsResponse, error) {
const op = "host_sets.(Service).RemoveHostSetHosts"
if err := validateRemoveRequest(req); err != nil {
return nil, err
}
@@ -222,31 +346,49 @@ func (s Service) RemoveHostSetHosts(ctx context.Context, req *pbs.RemoveHostSetH
if authResults.Error != nil {
return nil, authResults.Error
}
g, err := s.removeInRepo(ctx, authResults.Scope.GetId(), req.GetId(), req.GetHostIds(), req.GetVersion())
hs, hosts, err := s.removeInRepo(ctx, authResults.Scope.GetId(), req.GetId(), req.GetHostIds(), req.GetVersion())
if err != nil {
return nil, err
}
g.Scope = authResults.Scope
g.AuthorizedActions = authResults.FetchActionSetForId(ctx, g.Id, IdActions).Strings()
return &pbs.RemoveHostSetHostsResponse{Item: g}, nil
}
func (s Service) getFromRepo(ctx context.Context, id string) (*pb.HostSet, error) {
repo, err := s.staticRepoFn()
outputFields, ok := requests.OutputFields(ctx)
if !ok {
return nil, errors.New(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, hs.GetPublicId(), IdActions).Strings()))
}
item, err := toProto(ctx, hs, hosts, outputOpts...)
if err != nil {
return nil, err
}
return &pbs.RemoveHostSetHostsResponse{Item: item}, nil
}
func (s Service) getFromRepo(ctx context.Context, id string) (*static.HostSet, []*static.Host, error) {
repo, err := s.staticRepoFn()
if err != nil {
return nil, nil, err
}
h, m, err := repo.LookupSet(ctx, id)
if err != nil {
return nil, err
return nil, nil, err
}
if h == nil {
return nil, handlers.NotFoundErrorf("Host Set %q doesn't exist.", id)
return nil, nil, handlers.NotFoundErrorf("Host Set %q doesn't exist.", id)
}
return toProto(h, m), nil
return h, m, nil
}
func (s Service) createInRepo(ctx context.Context, scopeId, catalogId string, item *pb.HostSet) (*pb.HostSet, error) {
func (s Service) createInRepo(ctx context.Context, scopeId, catalogId string, item *pb.HostSet) (*static.HostSet, error) {
const op = "host_sets.(Service).createInRepo"
var opts []static.Option
if item.GetName() != nil {
@@ -270,10 +412,10 @@ func (s Service) createInRepo(ctx context.Context, scopeId, catalogId string, it
if out == nil {
return nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "Unable to create host set but no error returned from repository.")
}
return toProto(out, nil), nil
return out, nil
}
func (s Service) updateInRepo(ctx context.Context, scopeId, catalogId, id string, mask []string, item *pb.HostSet) (*pb.HostSet, error) {
func (s Service) updateInRepo(ctx context.Context, scopeId, catalogId, id string, mask []string, item *pb.HostSet) (*static.HostSet, []*static.Host, error) {
const op = "host_sets.(Service).updateInRepo"
var opts []static.Option
if desc := item.GetDescription(); desc != nil {
@@ -284,25 +426,25 @@ func (s Service) updateInRepo(ctx context.Context, scopeId, catalogId, id string
}
h, err := static.NewHostSet(catalogId, opts...)
if err != nil {
return nil, errors.Wrap(err, op, errors.WithMsg("Unable to build host set for update"))
return nil, nil, errors.Wrap(err, op, errors.WithMsg("Unable to build host set for update"))
}
h.PublicId = id
dbMask := maskManager.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."})
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."})
}
repo, err := s.staticRepoFn()
if err != nil {
return nil, err
return nil, nil, err
}
out, m, rowsUpdated, err := repo.UpdateSet(ctx, scopeId, h, item.GetVersion(), dbMask)
if err != nil {
return nil, errors.Wrap(err, op, errors.WithMsg("unable to update host set"))
return nil, nil, errors.Wrap(err, op, errors.WithMsg("unable to update host set"))
}
if rowsUpdated == 0 {
return nil, handlers.NotFoundErrorf("Host Set %q doesn't exist or incorrect version provided.", id)
return nil, nil, handlers.NotFoundErrorf("Host Set %q doesn't exist or incorrect version provided.", id)
}
return toProto(out, m), nil
return out, m, nil
}
func (s Service) deleteFromRepo(ctx context.Context, scopeId, id string) (bool, error) {
@@ -318,7 +460,7 @@ func (s Service) deleteFromRepo(ctx context.Context, scopeId, id string) (bool,
return rows > 0, nil
}
func (s Service) listFromRepo(ctx context.Context, catalogId string) ([]*pb.HostSet, error) {
func (s Service) listFromRepo(ctx context.Context, catalogId string) ([]*static.HostSet, error) {
repo, err := s.staticRepoFn()
if err != nil {
return nil, err
@@ -327,72 +469,68 @@ func (s Service) listFromRepo(ctx context.Context, catalogId string) ([]*pb.Host
if err != nil {
return nil, err
}
var outH []*pb.HostSet
for _, h := range hl {
outH = append(outH, toProto(h, nil))
}
return outH, nil
return hl, nil
}
func (s Service) addInRepo(ctx context.Context, scopeId, setId string, hostIds []string, version uint32) (*pb.HostSet, error) {
func (s Service) addInRepo(ctx context.Context, scopeId, setId string, hostIds []string, version uint32) (*static.HostSet, []*static.Host, error) {
const op = "host_sets.(Service).addInRepo"
repo, err := s.staticRepoFn()
if err != nil {
return nil, err
return nil, nil, err
}
_, err = repo.AddSetMembers(ctx, scopeId, setId, version, strutil.RemoveDuplicates(hostIds, false))
if err != nil {
return nil, errors.Wrap(err, op, errors.WithMsg("Unable to add hosts to host set"))
return nil, nil, errors.Wrap(err, op, errors.WithMsg("Unable to add hosts to host set"))
}
out, m, err := repo.LookupSet(ctx, setId)
if err != nil {
return nil, errors.Wrap(err, op, errors.WithMsg("unable to look up host set after adding hosts"))
return nil, nil, errors.Wrap(err, op, errors.WithMsg("unable to look up host set after adding hosts"))
}
if out == nil {
return nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "Unable to lookup host set after adding hosts to it.")
return nil, nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "Unable to lookup host set after adding hosts to it.")
}
return toProto(out, m), nil
return out, m, nil
}
func (s Service) setInRepo(ctx context.Context, scopeId, setId string, hostIds []string, version uint32) (*pb.HostSet, error) {
func (s Service) setInRepo(ctx context.Context, scopeId, setId string, hostIds []string, version uint32) (*static.HostSet, []*static.Host, error) {
const op = "host_sets.(Service).setInRepo"
repo, err := s.staticRepoFn()
if err != nil {
return nil, errors.Wrap(err, op)
return nil, nil, errors.Wrap(err, op)
}
_, _, err = repo.SetSetMembers(ctx, scopeId, setId, version, strutil.RemoveDuplicates(hostIds, false))
if err != nil {
return nil, errors.Wrap(err, op, errors.WithMsg("Unable to set hosts in host set"))
return nil, nil, errors.Wrap(err, op, errors.WithMsg("Unable to set hosts in host set"))
}
out, m, err := repo.LookupSet(ctx, setId)
if err != nil {
return nil, errors.Wrap(err, op, errors.WithMsg("unable to look up host set after setting hosts"))
return nil, nil, errors.Wrap(err, op, errors.WithMsg("unable to look up host set after setting hosts"))
}
if out == nil {
return nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "Unable to lookup host set after setting hosts for it.")
return nil, nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "Unable to lookup host set after setting hosts for it.")
}
return toProto(out, m), nil
return out, m, nil
}
func (s Service) removeInRepo(ctx context.Context, scopeId, setId string, hostIds []string, version uint32) (*pb.HostSet, error) {
func (s Service) removeInRepo(ctx context.Context, scopeId, setId string, hostIds []string, version uint32) (*static.HostSet, []*static.Host, error) {
const op = "host_sets.(Service).removeInRepo"
repo, err := s.staticRepoFn()
if err != nil {
return nil, errors.Wrap(err, op)
return nil, nil, errors.Wrap(err, op)
}
_, err = repo.DeleteSetMembers(ctx, scopeId, setId, version, strutil.RemoveDuplicates(hostIds, false))
if err != nil {
return nil, errors.Wrap(err, op, errors.WithMsg("Unable to remove hosts from host set"))
return nil, nil, errors.Wrap(err, op, errors.WithMsg("Unable to remove hosts from host set"))
}
out, m, err := repo.LookupSet(ctx, setId)
if err != nil {
return nil, errors.Wrap(err, op, errors.WithMsg("unable to look up host set"))
return nil, nil, errors.Wrap(err, op, errors.WithMsg("unable to look up host set"))
}
if out == nil {
return nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "Unable to lookup host set after removing hosts from it.")
return nil, nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "Unable to lookup host set after removing hosts from it.")
}
return toProto(out, m), nil
return out, m, nil
}
func (s Service) parentAndAuthResult(ctx context.Context, id string, a action.Type) (*static.HostCatalog, auth.VerifyResults) {
@@ -435,25 +573,50 @@ func (s Service) parentAndAuthResult(ctx context.Context, id string, a action.Ty
return cat, auth.Verify(ctx, opts...)
}
func toProto(in *static.HostSet, hs []*static.Host) *pb.HostSet {
out := pb.HostSet{
Id: in.GetPublicId(),
HostCatalogId: in.GetCatalogId(),
Type: host.StaticSubtype.String(),
CreatedTime: in.GetCreateTime().GetTimestamp(),
UpdatedTime: in.GetUpdateTime().GetTimestamp(),
Version: in.GetVersion(),
func toProto(ctx context.Context, in *static.HostSet, hosts []*static.Host, opt ...handlers.Option) (*pb.HostSet, error) {
opts := handlers.GetOpts(opt...)
if opts.WithOutputFields == nil {
return nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "output fields not found when building hostset proto")
}
if in.GetDescription() != "" {
out.Description = wrapperspb.String(in.GetDescription())
outputFields := *opts.WithOutputFields
out := pb.HostSet{}
if outputFields.Has(globals.IdField) {
out.Id = in.GetPublicId()
}
if in.GetName() != "" {
out.Name = wrapperspb.String(in.GetName())
if outputFields.Has(globals.HostCatalogIdField) {
out.HostCatalogId = in.GetCatalogId()
}
for _, h := range hs {
out.HostIds = append(out.HostIds, h.GetPublicId())
if outputFields.Has(globals.TypeField) {
out.Type = host.StaticSubtype.String()
}
return &out
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.HostIdsField) {
for _, h := range hosts {
out.HostIds = append(out.HostIds, h.GetPublicId())
}
}
return &out, nil
}
// A validateX method should exist for each method above. These methods do not make calls to any backing service but enforce
@@ -194,14 +194,26 @@ func TestList(t *testing.T) {
s, err := host_sets.NewService(repoFn)
require.NoError(err, "Couldn't create new host set service.")
// Test with non-anon user
got, gErr := s.ListHostSets(auth.DisabledAuthTestContext(iamRepoFn, proj.GetPublicId()), tc.req)
if tc.err != nil {
require.Error(gErr)
assert.True(errors.Is(gErr, tc.err), "ListHostSets(%q) got error %v, wanted %v", tc.req, gErr, tc.err)
} else {
require.NoError(gErr)
return
}
require.NoError(gErr)
assert.Empty(cmp.Diff(got, tc.res, protocmp.Transform()), "ListHostSets(%q) got response %q, wanted %q", tc.req, got, tc.res)
// Test with anon user
got, gErr = s.ListHostSets(auth.DisabledAuthTestContext(iamRepoFn, proj.GetPublicId(), auth.WithUserId(auth.AnonymousUserId)), tc.req)
require.NoError(gErr)
assert.Len(got.Items, len(tc.res.Items))
for _, item := range got.GetItems() {
require.Nil(item.CreatedTime)
require.Nil(item.UpdatedTime)
require.Zero(item.Version)
require.Empty(item.HostIds)
}
})
}
}
@@ -6,6 +6,7 @@ import (
"net"
"strings"
"github.com/hashicorp/boundary/globals"
"github.com/hashicorp/boundary/internal/auth"
"github.com/hashicorp/boundary/internal/errors"
pb "github.com/hashicorp/boundary/internal/gen/controller/api/resources/hosts"
@@ -14,6 +15,7 @@ import (
"github.com/hashicorp/boundary/internal/host/static"
"github.com/hashicorp/boundary/internal/host/static/store"
"github.com/hashicorp/boundary/internal/perms"
"github.com/hashicorp/boundary/internal/requests"
"github.com/hashicorp/boundary/internal/servers/controller/common"
"github.com/hashicorp/boundary/internal/servers/controller/handlers"
"github.com/hashicorp/boundary/internal/types/action"
@@ -79,22 +81,43 @@ func (s Service) ListHosts(ctx context.Context, req *pbs.ListHostsRequest) (*pbs
if err != nil {
return nil, err
}
if len(hl) == 0 {
return &pbs.ListHostsResponse{}, nil
}
filter, err := handlers.NewFilter(req.GetFilter())
if err != nil {
return nil, err
}
finalItems := make([]*pb.Host, 0, len(hl))
res := &perms.Resource{
res := perms.Resource{
ScopeId: authResults.Scope.Id,
Type: resource.Host,
Pin: req.GetHostCatalogId(),
}
for _, item := range hl {
item.Scope = authResults.Scope
item.AuthorizedActions = authResults.FetchActionSetForId(ctx, item.Id, IdActions, auth.WithResource(res)).Strings()
if len(item.AuthorizedActions) == 0 {
res.Id = item.GetPublicId()
authorizedActions := authResults.FetchActionSetForId(ctx, item.GetPublicId(), IdActions, auth.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, item, nil, outputOpts...)
if err != nil {
return nil, err
}
if filter.Match(item) {
finalItems = append(finalItems, item)
}
@@ -104,6 +127,8 @@ func (s Service) ListHosts(ctx context.Context, req *pbs.ListHostsRequest) (*pbs
// GetHost implements the interface pbs.HostServiceServer.
func (s Service) GetHost(ctx context.Context, req *pbs.GetHostRequest) (*pbs.GetHostResponse, error) {
const op = "hosts.(Service).GetHost"
if err := validateGetRequest(req); err != nil {
return nil, err
}
@@ -111,17 +136,37 @@ func (s Service) GetHost(ctx context.Context, req *pbs.GetHostRequest) (*pbs.Get
if authResults.Error != nil {
return nil, authResults.Error
}
hc, err := s.getFromRepo(ctx, req.GetId())
h, err := s.getFromRepo(ctx, req.GetId())
if err != nil {
return nil, err
}
hc.Scope = authResults.Scope
hc.AuthorizedActions = authResults.FetchActionSetForId(ctx, hc.Id, IdActions).Strings()
return &pbs.GetHostResponse{Item: hc}, nil
outputFields, ok := requests.OutputFields(ctx)
if !ok {
return nil, errors.New(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, h.GetPublicId(), IdActions).Strings()))
}
item, err := toProto(ctx, h, nil, outputOpts...)
if err != nil {
return nil, err
}
return &pbs.GetHostResponse{Item: item}, nil
}
// CreateHost implements the interface pbs.HostServiceServer.
func (s Service) CreateHost(ctx context.Context, req *pbs.CreateHostRequest) (*pbs.CreateHostResponse, error) {
const op = "hosts.(Service).CreateHost"
if err := validateCreateRequest(req); err != nil {
return nil, err
}
@@ -133,16 +178,36 @@ func (s Service) CreateHost(ctx context.Context, req *pbs.CreateHostRequest) (*p
if err != nil {
return nil, err
}
h.Scope = authResults.Scope
h.AuthorizedActions = authResults.FetchActionSetForId(ctx, h.Id, IdActions).Strings()
outputFields, ok := requests.OutputFields(ctx)
if !ok {
return nil, errors.New(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, h.GetPublicId(), IdActions).Strings()))
}
item, err := toProto(ctx, h, nil, outputOpts...)
if err != nil {
return nil, err
}
return &pbs.CreateHostResponse{
Item: h,
Uri: fmt.Sprintf("hosts/%s", h.GetId()),
Item: item,
Uri: fmt.Sprintf("hosts/%s", item.GetId()),
}, nil
}
// UpdateHost implements the interface pbs.HostServiceServer.
func (s Service) UpdateHost(ctx context.Context, req *pbs.UpdateHostRequest) (*pbs.UpdateHostResponse, error) {
const op = "hosts.(Service).UpdateHost"
if err := validateUpdateRequest(req); err != nil {
return nil, err
}
@@ -150,13 +215,31 @@ func (s Service) UpdateHost(ctx context.Context, req *pbs.UpdateHostRequest) (*p
if authResults.Error != nil {
return nil, authResults.Error
}
hc, err := s.updateInRepo(ctx, authResults.Scope.GetId(), cat.GetPublicId(), req.GetId(), req.GetUpdateMask().GetPaths(), req.GetItem())
h, err := s.updateInRepo(ctx, authResults.Scope.GetId(), cat.GetPublicId(), req.GetId(), req.GetUpdateMask().GetPaths(), req.GetItem())
if err != nil {
return nil, err
}
hc.Scope = authResults.Scope
hc.AuthorizedActions = authResults.FetchActionSetForId(ctx, hc.Id, IdActions).Strings()
return &pbs.UpdateHostResponse{Item: hc}, nil
outputFields, ok := requests.OutputFields(ctx)
if !ok {
return nil, errors.New(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, h.GetPublicId(), IdActions).Strings()))
}
item, err := toProto(ctx, h, nil, outputOpts...)
if err != nil {
return nil, err
}
return &pbs.UpdateHostResponse{Item: item}, nil
}
// DeleteHost implements the interface pbs.HostServiceServer.
@@ -175,7 +258,7 @@ func (s Service) DeleteHost(ctx context.Context, req *pbs.DeleteHostRequest) (*p
return nil, nil
}
func (s Service) getFromRepo(ctx context.Context, id string) (*pb.Host, error) {
func (s Service) getFromRepo(ctx context.Context, id string) (*static.Host, error) {
repo, err := s.staticRepoFn()
if err != nil {
return nil, err
@@ -187,10 +270,10 @@ func (s Service) getFromRepo(ctx context.Context, id string) (*pb.Host, error) {
if h == nil {
return nil, handlers.NotFoundErrorf("Host %q doesn't exist.", id)
}
return toProto(h, nil)
return h, nil
}
func (s Service) createInRepo(ctx context.Context, scopeId, catalogId string, item *pb.Host) (*pb.Host, error) {
func (s Service) createInRepo(ctx context.Context, scopeId, catalogId string, item *pb.Host) (*static.Host, error) {
const op = "hosts.(Service).createInRepo"
ha := &pb.StaticHostAttributes{}
if err := handlers.StructToProto(item.GetAttributes(), ha); err != nil {
@@ -222,10 +305,10 @@ func (s Service) createInRepo(ctx context.Context, scopeId, catalogId string, it
if out == nil {
return nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "Unable to create host but no error returned from repository.")
}
return toProto(out, nil)
return out, nil
}
func (s Service) updateInRepo(ctx context.Context, scopeId, catalogId, id string, mask []string, item *pb.Host) (*pb.Host, error) {
func (s Service) updateInRepo(ctx context.Context, scopeId, catalogId, id string, mask []string, item *pb.Host) (*static.Host, error) {
const op = "hosts.(Service).updateInRepo"
ha := &pb.StaticHostAttributes{}
if err := handlers.StructToProto(item.GetAttributes(), ha); err != nil {
@@ -261,7 +344,7 @@ func (s Service) updateInRepo(ctx context.Context, scopeId, catalogId, id string
if rowsUpdated == 0 {
return nil, handlers.NotFoundErrorf("Host %q doesn't exist or incorrect version provided.", id)
}
return toProto(out, nil)
return out, nil
}
func (s Service) deleteFromRepo(ctx context.Context, scopeId, id string) (bool, error) {
@@ -277,7 +360,7 @@ func (s Service) deleteFromRepo(ctx context.Context, scopeId, id string) (bool,
return rows > 0, nil
}
func (s Service) listFromRepo(ctx context.Context, catalogId string) ([]*pb.Host, error) {
func (s Service) listFromRepo(ctx context.Context, catalogId string) ([]*static.Host, error) {
repo, err := s.staticRepoFn()
if err != nil {
return nil, err
@@ -286,15 +369,7 @@ func (s Service) listFromRepo(ctx context.Context, catalogId string) ([]*pb.Host
if err != nil {
return nil, err
}
var outHl []*pb.Host
for _, h := range hl {
p, err := toProto(h, nil)
if err != nil {
return nil, err
}
outHl = append(outHl, p)
}
return outHl, nil
return hl, nil
}
func (s Service) parentAndAuthResult(ctx context.Context, id string, a action.Type) (*static.HostCatalog, auth.VerifyResults) {
@@ -337,29 +412,56 @@ func (s Service) parentAndAuthResult(ctx context.Context, id string, a action.Ty
return cat, auth.Verify(ctx, opts...)
}
func toProto(in *static.Host, members []*static.HostSet) (*pb.Host, error) {
out := pb.Host{
Id: in.GetPublicId(),
HostCatalogId: in.GetCatalogId(),
Type: host.StaticSubtype.String(),
CreatedTime: in.GetCreateTime().GetTimestamp(),
UpdatedTime: in.GetUpdateTime().GetTimestamp(),
Version: in.GetVersion(),
func toProto(ctx context.Context, in *static.Host, hostSets []*static.HostSet, opt ...handlers.Option) (*pb.Host, error) {
opts := handlers.GetOpts(opt...)
if opts.WithOutputFields == nil {
return nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "output fields not found when building host proto")
}
if in.GetDescription() != "" {
out.Description = wrapperspb.String(in.GetDescription())
outputFields := *opts.WithOutputFields
out := pb.Host{}
if outputFields.Has(globals.IdField) {
out.Id = in.GetPublicId()
}
if in.GetName() != "" {
out.Name = wrapperspb.String(in.GetName())
if outputFields.Has(globals.HostCatalogIdField) {
out.HostCatalogId = in.GetCatalogId()
}
for _, m := range members {
out.HostSetIds = append(out.HostSetIds, m.GetPublicId())
if outputFields.Has(globals.TypeField) {
out.Type = host.StaticSubtype.String()
}
st, err := handlers.ProtoToStruct(&pb.StaticHostAttributes{Address: wrapperspb.String(in.GetAddress())})
if err != nil {
return nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "Unable to convert static attribute to struct: %s", err)
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.HostSetIdsField) {
for _, hs := range hostSets {
out.HostSetIds = append(out.HostSetIds, hs.GetPublicId())
}
}
if outputFields.Has(globals.AttributesField) {
st, err := handlers.ProtoToStruct(&pb.StaticHostAttributes{Address: wrapperspb.String(in.GetAddress())})
if err != nil {
return nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "Unable to convert static attribute to struct: %s", err)
}
out.Attributes = st
}
out.Attributes = st
return &out, nil
}
@@ -189,14 +189,25 @@ func TestList(t *testing.T) {
s, err := hosts.NewService(repoFn)
require.NoError(err, "Couldn't create new host set service.")
// Test non-anonymous listing
got, gErr := s.ListHosts(auth.DisabledAuthTestContext(iamRepoFn, proj.GetPublicId()), tc.req)
if tc.err != nil {
require.Error(gErr)
assert.True(errors.Is(gErr, tc.err), "ListHosts(%q) got error %v, wanted %v", tc.req, gErr, tc.err)
} else {
require.NoError(gErr)
return
}
require.NoError(gErr)
assert.Empty(cmp.Diff(got, tc.res, protocmp.Transform()), "ListHosts(%q) got response %q, wanted %q", tc.req, got, tc.res)
// Test anonymous listing
got, gErr = s.ListHosts(auth.DisabledAuthTestContext(iamRepoFn, proj.GetPublicId(), auth.WithUserId(auth.AnonymousUserId)), tc.req)
require.NoError(gErr)
assert.Len(got.Items, len(tc.res.Items))
for _, item := range got.GetItems() {
require.Nil(item.CreatedTime)
require.Nil(item.UpdatedTime)
require.Zero(item.Version)
}
})
}
}
@@ -3,8 +3,10 @@ package handlers
import (
"testing"
"github.com/hashicorp/boundary/internal/perms"
"github.com/hashicorp/go-hclog"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
// Test_GetOpts provides unit tests for GetOpts and all the options
@@ -36,14 +38,26 @@ func Test_GetOpts(t *testing.T) {
opts = GetOpts(WithLogger(hclog.New(nil)))
assert.NotNil(opts.WithLogger)
})
t.Run("WithAnonymousListing", func(t *testing.T) {
t.Run("WithUserIsAnonymous", func(t *testing.T) {
assert := assert.New(t)
opts := GetOpts()
assert.False(opts.WithAnonymousListing)
assert.False(opts.WithUserIsAnonymous)
opts = GetOpts(WithAnonymousListing(true))
assert.True(opts.WithAnonymousListing)
opts = GetOpts(WithUserIsAnonymous(true))
assert.True(opts.WithUserIsAnonymous)
})
t.Run("WithOutputFields", func(t *testing.T) {
assert := assert.New(t)
require := require.New(t)
opts := GetOpts()
assert.Nil(opts.WithOutputFields)
var out perms.OutputFieldsMap
opts = GetOpts(WithOutputFields(&out))
require.NotNil(opts.WithOutputFields)
assert.Nil(*opts.WithOutputFields)
})
}
@@ -1,6 +1,11 @@
package handlers
import "github.com/hashicorp/go-hclog"
import (
"github.com/hashicorp/boundary/internal/gen/controller/api/resources/scopes"
"github.com/hashicorp/boundary/internal/perms"
"github.com/hashicorp/go-hclog"
"google.golang.org/protobuf/types/known/structpb"
)
// GetOpts - iterate the inbound Options and return a struct
func GetOpts(opt ...Option) options {
@@ -18,9 +23,13 @@ type Option func(*options)
// options = how options are represented
type options struct {
withDiscardUnknownFields bool
WithLogger hclog.Logger
WithAnonymousListing bool
withDiscardUnknownFields bool
WithLogger hclog.Logger
WithUserIsAnonymous bool
WithOutputFields *perms.OutputFieldsMap
WithScope *scopes.ScopeInfo
WithAuthorizedActions []string
WithAuthorizedCollectionActions map[string]*structpb.ListValue
}
func getDefaultOptions() options {
@@ -43,10 +52,42 @@ func WithLogger(logger hclog.Logger) Option {
}
}
// WithAnonymousListing provides an option when creating responses to only include those
// desired for listing to anonymous users.
func WithAnonymousListing(anonListing bool) Option {
// DEPRECATED: Superceded by WithOutputFields. Will be removed once all handlers
// have been migrated to that.
func WithUserIsAnonymous(anonListing bool) Option {
return func(o *options) {
o.WithAnonymousListing = anonListing
o.WithUserIsAnonymous = anonListing
}
}
// WithOutputFields provides an option when creating responses to only include
// specific fields
func WithOutputFields(fields *perms.OutputFieldsMap) Option {
return func(o *options) {
o.WithOutputFields = fields
}
}
// WithScope provides an option when creating responses to include the given
// scope if allowed
func WithScope(scp *scopes.ScopeInfo) Option {
return func(o *options) {
o.WithScope = scp
}
}
// WithAuthorizedActions provides an option when creating responses to include the given
// authorized actions if allowed
func WithAuthorizedActions(acts []string) Option {
return func(o *options) {
o.WithAuthorizedActions = acts
}
}
// WithAuthorizedCollectionActions provides an option when creating responses to include the given
// authorized collection actions if allowed
func WithAuthorizedCollectionActions(colActs map[string]*structpb.ListValue) Option {
return func(o *options) {
o.WithAuthorizedCollectionActions = colActs
}
}
@@ -4,6 +4,7 @@ import (
"context"
"fmt"
"github.com/hashicorp/boundary/globals"
"github.com/hashicorp/boundary/internal/auth"
"github.com/hashicorp/boundary/internal/errors"
pb "github.com/hashicorp/boundary/internal/gen/controller/api/resources/roles"
@@ -11,6 +12,7 @@ import (
"github.com/hashicorp/boundary/internal/iam"
"github.com/hashicorp/boundary/internal/iam/store"
"github.com/hashicorp/boundary/internal/perms"
"github.com/hashicorp/boundary/internal/requests"
"github.com/hashicorp/boundary/internal/servers/controller/common"
"github.com/hashicorp/boundary/internal/servers/controller/common/scopeids"
"github.com/hashicorp/boundary/internal/servers/controller/handlers"
@@ -105,22 +107,41 @@ func (s Service) ListRoles(ctx context.Context, req *pbs.ListRolesRequest) (*pbs
if err != nil {
return nil, err
}
if len(items) == 0 {
return &pbs.ListRolesResponse{}, nil
}
filter, err := handlers.NewFilter(req.GetFilter())
if err != nil {
return nil, err
}
finalItems := make([]*pb.Role, 0, len(items))
res := &perms.Resource{
res := perms.Resource{
Type: resource.Role,
}
for _, item := range items {
item.Scope = scopeInfoMap[item.GetScopeId()]
res.ScopeId = item.Scope.Id
item.AuthorizedActions = authResults.FetchActionSetForId(ctx, item.Id, IdActions, auth.WithResource(res)).Strings()
if len(item.AuthorizedActions) == 0 {
res.Id = item.GetPublicId()
res.ScopeId = item.GetScopeId()
authorizedActions := authResults.FetchActionSetForId(ctx, item.GetPublicId(), IdActions, auth.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(scopeInfoMap[item.GetScopeId()]))
}
if outputFields.Has(globals.AuthorizedActionsField) {
outputOpts = append(outputOpts, handlers.WithAuthorizedActions(authorizedActions))
}
item, err := toProto(ctx, item, nil, nil, outputOpts...)
if err != nil {
return nil, err
}
if filter.Match(item) {
finalItems = append(finalItems, item)
}
@@ -130,6 +151,8 @@ func (s Service) ListRoles(ctx context.Context, req *pbs.ListRolesRequest) (*pbs
// GetRoles implements the interface pbs.RoleServiceServer.
func (s Service) GetRole(ctx context.Context, req *pbs.GetRoleRequest) (*pbs.GetRoleResponse, error) {
const op = "roles.(Service).GetRole"
if err := validateGetRequest(req); err != nil {
return nil, err
}
@@ -137,17 +160,37 @@ func (s Service) GetRole(ctx context.Context, req *pbs.GetRoleRequest) (*pbs.Get
if authResults.Error != nil {
return nil, authResults.Error
}
u, err := s.getFromRepo(ctx, req.GetId())
r, prs, rgs, err := s.getFromRepo(ctx, req.GetId())
if err != nil {
return nil, err
}
u.Scope = authResults.Scope
u.AuthorizedActions = authResults.FetchActionSetForId(ctx, u.Id, IdActions).Strings()
return &pbs.GetRoleResponse{Item: u}, nil
outputFields, ok := requests.OutputFields(ctx)
if !ok {
return nil, errors.New(errors.Internal, op, "no request context found")
}
outputOpts := make([]handlers.Option, 0, 3)
outputOpts = append(outputOpts, handlers.WithOutputFields(&outputFields))
if outputFields.Has(globals.ScopeField) {
outputOpts = append(outputOpts, handlers.WithScope(authResults.Scope))
}
if outputFields.Has(globals.AuthorizedActionsField) {
outputOpts = append(outputOpts, handlers.WithAuthorizedActions(authResults.FetchActionSetForId(ctx, r.GetPublicId(), IdActions).Strings()))
}
item, err := toProto(ctx, r, prs, rgs, outputOpts...)
if err != nil {
return nil, err
}
return &pbs.GetRoleResponse{Item: item}, nil
}
// CreateRole implements the interface pbs.RoleServiceServer.
func (s Service) CreateRole(ctx context.Context, req *pbs.CreateRoleRequest) (*pbs.CreateRoleResponse, error) {
const op = "roles.(Service).CreateRole"
if err := validateCreateRequest(req); err != nil {
return nil, err
}
@@ -159,13 +202,33 @@ func (s Service) CreateRole(ctx context.Context, req *pbs.CreateRoleRequest) (*p
if err != nil {
return nil, err
}
r.Scope = authResults.Scope
r.AuthorizedActions = authResults.FetchActionSetForId(ctx, r.Id, IdActions).Strings()
return &pbs.CreateRoleResponse{Item: r, Uri: fmt.Sprintf("roles/%s", r.GetId())}, nil
outputFields, ok := requests.OutputFields(ctx)
if !ok {
return nil, errors.New(errors.Internal, op, "no request context found")
}
outputOpts := make([]handlers.Option, 0, 3)
outputOpts = append(outputOpts, handlers.WithOutputFields(&outputFields))
if outputFields.Has(globals.ScopeField) {
outputOpts = append(outputOpts, handlers.WithScope(authResults.Scope))
}
if outputFields.Has(globals.AuthorizedActionsField) {
outputOpts = append(outputOpts, handlers.WithAuthorizedActions(authResults.FetchActionSetForId(ctx, r.GetPublicId(), IdActions).Strings()))
}
item, err := toProto(ctx, r, nil, nil, outputOpts...)
if err != nil {
return nil, err
}
return &pbs.CreateRoleResponse{Item: item, Uri: fmt.Sprintf("roles/%s", item.GetId())}, nil
}
// UpdateRole implements the interface pbs.RoleServiceServer.
func (s Service) UpdateRole(ctx context.Context, req *pbs.UpdateRoleRequest) (*pbs.UpdateRoleResponse, error) {
const op = "roles.(Service).UpdateRole"
if err := validateUpdateRequest(req); err != nil {
return nil, err
}
@@ -173,13 +236,31 @@ func (s Service) UpdateRole(ctx context.Context, req *pbs.UpdateRoleRequest) (*p
if authResults.Error != nil {
return nil, authResults.Error
}
u, err := s.updateInRepo(ctx, authResults.Scope.GetId(), req.GetId(), req.GetUpdateMask().GetPaths(), req.GetItem())
r, prs, rgs, err := s.updateInRepo(ctx, authResults.Scope.GetId(), req.GetId(), req.GetUpdateMask().GetPaths(), req.GetItem())
if err != nil {
return nil, err
}
u.Scope = authResults.Scope
u.AuthorizedActions = authResults.FetchActionSetForId(ctx, u.Id, IdActions).Strings()
return &pbs.UpdateRoleResponse{Item: u}, nil
outputFields, ok := requests.OutputFields(ctx)
if !ok {
return nil, errors.New(errors.Internal, op, "no request context found")
}
outputOpts := make([]handlers.Option, 0, 3)
outputOpts = append(outputOpts, handlers.WithOutputFields(&outputFields))
if outputFields.Has(globals.ScopeField) {
outputOpts = append(outputOpts, handlers.WithScope(authResults.Scope))
}
if outputFields.Has(globals.AuthorizedActionsField) {
outputOpts = append(outputOpts, handlers.WithAuthorizedActions(authResults.FetchActionSetForId(ctx, r.GetPublicId(), IdActions).Strings()))
}
item, err := toProto(ctx, r, prs, rgs, outputOpts...)
if err != nil {
return nil, err
}
return &pbs.UpdateRoleResponse{Item: item}, nil
}
// DeleteRole implements the interface pbs.RoleServiceServer.
@@ -200,6 +281,8 @@ func (s Service) DeleteRole(ctx context.Context, req *pbs.DeleteRoleRequest) (*p
// AddRolePrincipals implements the interface pbs.RoleServiceServer.
func (s Service) AddRolePrincipals(ctx context.Context, req *pbs.AddRolePrincipalsRequest) (*pbs.AddRolePrincipalsResponse, error) {
const op = "roles.(Service).AddRolePrincipals"
if err := validateAddRolePrincipalsRequest(req); err != nil {
return nil, err
}
@@ -207,17 +290,37 @@ func (s Service) AddRolePrincipals(ctx context.Context, req *pbs.AddRolePrincipa
if authResults.Error != nil {
return nil, authResults.Error
}
r, err := s.addPrinciplesInRepo(ctx, req.GetId(), req.GetPrincipalIds(), req.GetVersion())
r, prs, rgs, err := s.addPrinciplesInRepo(ctx, req.GetId(), req.GetPrincipalIds(), req.GetVersion())
if err != nil {
return nil, err
}
r.Scope = authResults.Scope
r.AuthorizedActions = authResults.FetchActionSetForId(ctx, r.Id, IdActions).Strings()
return &pbs.AddRolePrincipalsResponse{Item: r}, nil
outputFields, ok := requests.OutputFields(ctx)
if !ok {
return nil, errors.New(errors.Internal, op, "no request context found")
}
outputOpts := make([]handlers.Option, 0, 3)
outputOpts = append(outputOpts, handlers.WithOutputFields(&outputFields))
if outputFields.Has(globals.ScopeField) {
outputOpts = append(outputOpts, handlers.WithScope(authResults.Scope))
}
if outputFields.Has(globals.AuthorizedActionsField) {
outputOpts = append(outputOpts, handlers.WithAuthorizedActions(authResults.FetchActionSetForId(ctx, r.GetPublicId(), IdActions).Strings()))
}
item, err := toProto(ctx, r, prs, rgs, outputOpts...)
if err != nil {
return nil, err
}
return &pbs.AddRolePrincipalsResponse{Item: item}, nil
}
// SetRolePrincipals implements the interface pbs.RoleServiceServer.
func (s Service) SetRolePrincipals(ctx context.Context, req *pbs.SetRolePrincipalsRequest) (*pbs.SetRolePrincipalsResponse, error) {
const op = "roles.(Service).SetRolePrincipals"
if err := validateSetRolePrincipalsRequest(req); err != nil {
return nil, err
}
@@ -225,17 +328,37 @@ func (s Service) SetRolePrincipals(ctx context.Context, req *pbs.SetRolePrincipa
if authResults.Error != nil {
return nil, authResults.Error
}
r, err := s.setPrinciplesInRepo(ctx, req.GetId(), req.GetPrincipalIds(), req.GetVersion())
r, prs, rgs, err := s.setPrinciplesInRepo(ctx, req.GetId(), req.GetPrincipalIds(), req.GetVersion())
if err != nil {
return nil, err
}
r.Scope = authResults.Scope
r.AuthorizedActions = authResults.FetchActionSetForId(ctx, r.Id, IdActions).Strings()
return &pbs.SetRolePrincipalsResponse{Item: r}, nil
outputFields, ok := requests.OutputFields(ctx)
if !ok {
return nil, errors.New(errors.Internal, op, "no request context found")
}
outputOpts := make([]handlers.Option, 0, 3)
outputOpts = append(outputOpts, handlers.WithOutputFields(&outputFields))
if outputFields.Has(globals.ScopeField) {
outputOpts = append(outputOpts, handlers.WithScope(authResults.Scope))
}
if outputFields.Has(globals.AuthorizedActionsField) {
outputOpts = append(outputOpts, handlers.WithAuthorizedActions(authResults.FetchActionSetForId(ctx, r.GetPublicId(), IdActions).Strings()))
}
item, err := toProto(ctx, r, prs, rgs, outputOpts...)
if err != nil {
return nil, err
}
return &pbs.SetRolePrincipalsResponse{Item: item}, nil
}
// RemoveRolePrincipals implements the interface pbs.RoleServiceServer.
func (s Service) RemoveRolePrincipals(ctx context.Context, req *pbs.RemoveRolePrincipalsRequest) (*pbs.RemoveRolePrincipalsResponse, error) {
const op = "roles.(Service).RemoveRolePrincipals"
if err := validateRemoveRolePrincipalsRequest(req); err != nil {
return nil, err
}
@@ -243,17 +366,37 @@ func (s Service) RemoveRolePrincipals(ctx context.Context, req *pbs.RemoveRolePr
if authResults.Error != nil {
return nil, authResults.Error
}
r, err := s.removePrinciplesInRepo(ctx, req.GetId(), req.GetPrincipalIds(), req.GetVersion())
r, prs, rgs, err := s.removePrinciplesInRepo(ctx, req.GetId(), req.GetPrincipalIds(), req.GetVersion())
if err != nil {
return nil, err
}
r.Scope = authResults.Scope
r.AuthorizedActions = authResults.FetchActionSetForId(ctx, r.Id, IdActions).Strings()
return &pbs.RemoveRolePrincipalsResponse{Item: r}, nil
outputFields, ok := requests.OutputFields(ctx)
if !ok {
return nil, errors.New(errors.Internal, op, "no request context found")
}
outputOpts := make([]handlers.Option, 0, 3)
outputOpts = append(outputOpts, handlers.WithOutputFields(&outputFields))
if outputFields.Has(globals.ScopeField) {
outputOpts = append(outputOpts, handlers.WithScope(authResults.Scope))
}
if outputFields.Has(globals.AuthorizedActionsField) {
outputOpts = append(outputOpts, handlers.WithAuthorizedActions(authResults.FetchActionSetForId(ctx, r.GetPublicId(), IdActions).Strings()))
}
item, err := toProto(ctx, r, prs, rgs, outputOpts...)
if err != nil {
return nil, err
}
return &pbs.RemoveRolePrincipalsResponse{Item: item}, nil
}
// AddRoleGrants implements the interface pbs.RoleServiceServer.
func (s Service) AddRoleGrants(ctx context.Context, req *pbs.AddRoleGrantsRequest) (*pbs.AddRoleGrantsResponse, error) {
const op = "roles.(Service).AddRoleGrants"
if err := validateAddRoleGrantsRequest(req); err != nil {
return nil, err
}
@@ -261,17 +404,37 @@ func (s Service) AddRoleGrants(ctx context.Context, req *pbs.AddRoleGrantsReques
if authResults.Error != nil {
return nil, authResults.Error
}
r, err := s.addGrantsInRepo(ctx, req.GetId(), req.GetGrantStrings(), req.GetVersion())
r, prs, rgs, err := s.addGrantsInRepo(ctx, req.GetId(), req.GetGrantStrings(), req.GetVersion())
if err != nil {
return nil, err
}
r.Scope = authResults.Scope
r.AuthorizedActions = authResults.FetchActionSetForId(ctx, r.Id, IdActions).Strings()
return &pbs.AddRoleGrantsResponse{Item: r}, nil
outputFields, ok := requests.OutputFields(ctx)
if !ok {
return nil, errors.New(errors.Internal, op, "no request context found")
}
outputOpts := make([]handlers.Option, 0, 3)
outputOpts = append(outputOpts, handlers.WithOutputFields(&outputFields))
if outputFields.Has(globals.ScopeField) {
outputOpts = append(outputOpts, handlers.WithScope(authResults.Scope))
}
if outputFields.Has(globals.AuthorizedActionsField) {
outputOpts = append(outputOpts, handlers.WithAuthorizedActions(authResults.FetchActionSetForId(ctx, r.GetPublicId(), IdActions).Strings()))
}
item, err := toProto(ctx, r, prs, rgs, outputOpts...)
if err != nil {
return nil, err
}
return &pbs.AddRoleGrantsResponse{Item: item}, nil
}
// SetRoleGrants implements the interface pbs.RoleServiceServer.
func (s Service) SetRoleGrants(ctx context.Context, req *pbs.SetRoleGrantsRequest) (*pbs.SetRoleGrantsResponse, error) {
const op = "roles.(Service).SetRoleGrants"
if err := validateSetRoleGrantsRequest(req); err != nil {
return nil, err
}
@@ -279,17 +442,37 @@ func (s Service) SetRoleGrants(ctx context.Context, req *pbs.SetRoleGrantsReques
if authResults.Error != nil {
return nil, authResults.Error
}
r, err := s.setGrantsInRepo(ctx, req.GetId(), req.GetGrantStrings(), req.GetVersion())
r, prs, rgs, err := s.setGrantsInRepo(ctx, req.GetId(), req.GetGrantStrings(), req.GetVersion())
if err != nil {
return nil, err
}
r.Scope = authResults.Scope
r.AuthorizedActions = authResults.FetchActionSetForId(ctx, r.Id, IdActions).Strings()
return &pbs.SetRoleGrantsResponse{Item: r}, nil
outputFields, ok := requests.OutputFields(ctx)
if !ok {
return nil, errors.New(errors.Internal, op, "no request context found")
}
outputOpts := make([]handlers.Option, 0, 3)
outputOpts = append(outputOpts, handlers.WithOutputFields(&outputFields))
if outputFields.Has(globals.ScopeField) {
outputOpts = append(outputOpts, handlers.WithScope(authResults.Scope))
}
if outputFields.Has(globals.AuthorizedActionsField) {
outputOpts = append(outputOpts, handlers.WithAuthorizedActions(authResults.FetchActionSetForId(ctx, r.GetPublicId(), IdActions).Strings()))
}
item, err := toProto(ctx, r, prs, rgs, outputOpts...)
if err != nil {
return nil, err
}
return &pbs.SetRoleGrantsResponse{Item: item}, nil
}
// RemoveRoleGrants implements the interface pbs.RoleServiceServer.
func (s Service) RemoveRoleGrants(ctx context.Context, req *pbs.RemoveRoleGrantsRequest) (*pbs.RemoveRoleGrantsResponse, error) {
const op = "roles.(Service).RemoveRoleGrants"
if err := validateRemoveRoleGrantsRequest(req); err != nil {
return nil, err
}
@@ -297,34 +480,52 @@ func (s Service) RemoveRoleGrants(ctx context.Context, req *pbs.RemoveRoleGrants
if authResults.Error != nil {
return nil, authResults.Error
}
r, err := s.removeGrantsInRepo(ctx, req.GetId(), req.GetGrantStrings(), req.GetVersion())
r, prs, rgs, err := s.removeGrantsInRepo(ctx, req.GetId(), req.GetGrantStrings(), req.GetVersion())
if err != nil {
return nil, err
}
r.Scope = authResults.Scope
r.AuthorizedActions = authResults.FetchActionSetForId(ctx, r.Id, IdActions).Strings()
return &pbs.RemoveRoleGrantsResponse{Item: r}, nil
}
func (s Service) getFromRepo(ctx context.Context, id string) (*pb.Role, error) {
repo, err := s.repoFn()
outputFields, ok := requests.OutputFields(ctx)
if !ok {
return nil, errors.New(errors.Internal, op, "no request context found")
}
outputOpts := make([]handlers.Option, 0, 3)
outputOpts = append(outputOpts, handlers.WithOutputFields(&outputFields))
if outputFields.Has(globals.ScopeField) {
outputOpts = append(outputOpts, handlers.WithScope(authResults.Scope))
}
if outputFields.Has(globals.AuthorizedActionsField) {
outputOpts = append(outputOpts, handlers.WithAuthorizedActions(authResults.FetchActionSetForId(ctx, r.GetPublicId(), IdActions).Strings()))
}
item, err := toProto(ctx, r, prs, rgs, outputOpts...)
if err != nil {
return nil, err
}
return &pbs.RemoveRoleGrantsResponse{Item: item}, nil
}
func (s Service) getFromRepo(ctx context.Context, id string) (*iam.Role, []iam.PrincipalRole, []*iam.RoleGrant, error) {
repo, err := s.repoFn()
if err != nil {
return nil, nil, nil, err
}
out, pr, roleGrants, err := repo.LookupRole(ctx, id)
if err != nil {
if errors.IsNotFoundError(err) {
return nil, handlers.NotFoundErrorf("Role %q doesn't exist.", id)
return nil, nil, nil, handlers.NotFoundErrorf("Role %q doesn't exist.", id)
}
return nil, err
return nil, nil, nil, err
}
if out == nil {
return nil, handlers.NotFoundErrorf("Role %q doesn't exist.", id)
return nil, nil, nil, handlers.NotFoundErrorf("Role %q doesn't exist.", id)
}
return toProto(out, pr, roleGrants), nil
return out, pr, roleGrants, nil
}
func (s Service) createInRepo(ctx context.Context, scopeId string, item *pb.Role) (*pb.Role, error) {
func (s Service) createInRepo(ctx context.Context, scopeId string, item *pb.Role) (*iam.Role, error) {
const op = "roles.(Service).createInRepo"
var opts []iam.Option
if item.GetName() != nil {
@@ -351,10 +552,10 @@ func (s Service) createInRepo(ctx context.Context, scopeId string, item *pb.Role
if out == nil {
return nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "Unable to create role but no error returned from repository.")
}
return toProto(out, nil, nil), nil
return out, nil
}
func (s Service) updateInRepo(ctx context.Context, scopeId, id string, mask []string, item *pb.Role) (*pb.Role, error) {
func (s Service) updateInRepo(ctx context.Context, scopeId, id string, mask []string, item *pb.Role) (*iam.Role, []iam.PrincipalRole, []*iam.RoleGrant, error) {
const op = "roles.(Service).updateInRepo"
var opts []iam.Option
if desc := item.GetDescription(); desc != nil {
@@ -370,25 +571,25 @@ func (s Service) updateInRepo(ctx context.Context, scopeId, id string, mask []st
u, err := iam.NewRole(scopeId, opts...)
if err != nil {
return nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "Unable to build role for update: %v.", err)
return nil, nil, nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "Unable to build role for update: %v.", err)
}
u.PublicId = id
dbMask := maskManager.Translate(mask)
if len(dbMask) == 0 {
return nil, handlers.InvalidArgumentErrorf("No valid fields included in the update mask.", map[string]string{"update_mask": "No valid fields provided in the update mask."})
return nil, nil, nil, handlers.InvalidArgumentErrorf("No valid fields included in the update mask.", map[string]string{"update_mask": "No valid fields provided in the update mask."})
}
repo, err := s.repoFn()
if err != nil {
return nil, err
return nil, nil, nil, err
}
out, pr, gr, rowsUpdated, err := repo.UpdateRole(ctx, u, version, dbMask)
if err != nil {
return nil, errors.Wrap(err, op)
return nil, nil, nil, errors.Wrap(err, op)
}
if rowsUpdated == 0 {
return nil, handlers.NotFoundErrorf("Role %q doesn't exist or incorrect version provided.", id)
return nil, nil, nil, handlers.NotFoundErrorf("Role %q doesn't exist or incorrect version provided.", id)
}
return toProto(out, pr, gr), nil
return out, pr, gr, nil
}
func (s Service) deleteFromRepo(ctx context.Context, id string) (bool, error) {
@@ -407,7 +608,7 @@ func (s Service) deleteFromRepo(ctx context.Context, id string) (bool, error) {
return rows > 0, nil
}
func (s Service) listFromRepo(ctx context.Context, scopeIds []string) ([]*pb.Role, error) {
func (s Service) listFromRepo(ctx context.Context, scopeIds []string) ([]*iam.Role, error) {
repo, err := s.repoFn()
if err != nil {
return nil, err
@@ -416,103 +617,98 @@ func (s Service) listFromRepo(ctx context.Context, scopeIds []string) ([]*pb.Rol
if err != nil {
return nil, err
}
var outRl []*pb.Role
for _, g := range rl {
// TODO: Attach principals and grants to ListRoles response.
outRl = append(outRl, toProto(g, nil, nil))
}
return outRl, nil
return rl, nil
}
func (s Service) addPrinciplesInRepo(ctx context.Context, roleId string, principalIds []string, version uint32) (*pb.Role, error) {
func (s Service) addPrinciplesInRepo(ctx context.Context, roleId string, principalIds []string, version uint32) (*iam.Role, []iam.PrincipalRole, []*iam.RoleGrant, error) {
const op = "roles.(Service).addPrincpleInRepo"
repo, err := s.repoFn()
if err != nil {
return nil, err
return nil, nil, nil, err
}
_, err = repo.AddPrincipalRoles(ctx, roleId, version, strutil.RemoveDuplicates(principalIds, false))
if err != nil {
// TODO: Figure out a way to surface more helpful error info beyond the Internal error.
return nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "Unable to add principals to role: %v.", err)
return nil, nil, nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "Unable to add principals to role: %v.", err)
}
out, pr, roleGrants, err := repo.LookupRole(ctx, roleId)
if err != nil {
return nil, errors.Wrap(err, op, errors.WithMsg("unable to look up role after adding principals"))
return nil, nil, nil, errors.Wrap(err, op, errors.WithMsg("unable to look up role after adding principals"))
}
if out == nil {
return nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "Unable to lookup role after adding principals to it.")
return nil, nil, nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "Unable to lookup role after adding principals to it.")
}
return toProto(out, pr, roleGrants), nil
return out, pr, roleGrants, nil
}
func (s Service) setPrinciplesInRepo(ctx context.Context, roleId string, principalIds []string, version uint32) (*pb.Role, error) {
func (s Service) setPrinciplesInRepo(ctx context.Context, roleId string, principalIds []string, version uint32) (*iam.Role, []iam.PrincipalRole, []*iam.RoleGrant, error) {
const op = "roles.(Service).setPrinciplesInRepo"
repo, err := s.repoFn()
if err != nil {
return nil, err
return nil, nil, nil, err
}
_, _, err = repo.SetPrincipalRoles(ctx, roleId, version, strutil.RemoveDuplicates(principalIds, false))
if err != nil {
// TODO: Figure out a way to surface more helpful error info beyond the Internal error.
return nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "Unable to set principals on role: %v.", err)
return nil, nil, nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "Unable to set principals on role: %v.", err)
}
out, pr, roleGrants, err := repo.LookupRole(ctx, roleId)
if err != nil {
return nil, errors.Wrap(err, op, errors.WithMsg("unable to look up role after setting principals"))
return nil, nil, nil, errors.Wrap(err, op, errors.WithMsg("unable to look up role after setting principals"))
}
if out == nil {
return nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "Unable to lookup role after setting principals for it.")
return nil, nil, nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "Unable to lookup role after setting principals for it.")
}
return toProto(out, pr, roleGrants), nil
return out, pr, roleGrants, nil
}
func (s Service) removePrinciplesInRepo(ctx context.Context, roleId string, principalIds []string, version uint32) (*pb.Role, error) {
func (s Service) removePrinciplesInRepo(ctx context.Context, roleId string, principalIds []string, version uint32) (*iam.Role, []iam.PrincipalRole, []*iam.RoleGrant, error) {
const op = "roles.(Service).removePrinciplesInRepo"
repo, err := s.repoFn()
if err != nil {
return nil, err
return nil, nil, nil, err
}
_, err = repo.DeletePrincipalRoles(ctx, roleId, version, strutil.RemoveDuplicates(principalIds, false))
if err != nil {
// TODO: Figure out a way to surface more helpful error info beyond the Internal error.
return nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "Unable to remove principals from role: %v.", err)
return nil, nil, nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "Unable to remove principals from role: %v.", err)
}
out, pr, roleGrants, err := repo.LookupRole(ctx, roleId)
if err != nil {
return nil, errors.Wrap(err, op, errors.WithMsg("unable to look up role after removing principals"))
return nil, nil, nil, errors.Wrap(err, op, errors.WithMsg("unable to look up role after removing principals"))
}
if out == nil {
return nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "Unable to lookup role after removing principals from it.")
return nil, nil, nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "Unable to lookup role after removing principals from it.")
}
return toProto(out, pr, roleGrants), nil
return out, pr, roleGrants, nil
}
func (s Service) addGrantsInRepo(ctx context.Context, roleId string, grants []string, version uint32) (*pb.Role, error) {
func (s Service) addGrantsInRepo(ctx context.Context, roleId string, grants []string, version uint32) (*iam.Role, []iam.PrincipalRole, []*iam.RoleGrant, error) {
const op = "service.(Service).addGrantsInRepo"
repo, err := s.repoFn()
if err != nil {
return nil, err
return nil, nil, nil, err
}
_, err = repo.AddRoleGrants(ctx, roleId, version, strutil.RemoveDuplicates(grants, false))
if err != nil {
// TODO: Figure out a way to surface more helpful error info beyond the Internal error.
return nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "Unable to add grants to role: %v.", err)
return nil, nil, nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "Unable to add grants to role: %v.", err)
}
out, pr, roleGrants, err := repo.LookupRole(ctx, roleId)
if err != nil {
return nil, errors.Wrap(err, op, errors.WithMsg("unable to look up role after adding grants"))
return nil, nil, nil, errors.Wrap(err, op, errors.WithMsg("unable to look up role after adding grants"))
}
if out == nil {
return nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "Unable to lookup role after adding grants to it.")
return nil, nil, nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "Unable to lookup role after adding grants to it.")
}
return toProto(out, pr, roleGrants), nil
return out, pr, roleGrants, nil
}
func (s Service) setGrantsInRepo(ctx context.Context, roleId string, grants []string, version uint32) (*pb.Role, error) {
func (s Service) setGrantsInRepo(ctx context.Context, roleId string, grants []string, version uint32) (*iam.Role, []iam.PrincipalRole, []*iam.RoleGrant, error) {
const op = "roles.(Service).setGrantsInRepo"
repo, err := s.repoFn()
if err != nil {
return nil, err
return nil, nil, nil, err
}
// If no grant was provided, we clear the grants.
if grants == nil {
@@ -521,37 +717,37 @@ func (s Service) setGrantsInRepo(ctx context.Context, roleId string, grants []st
_, _, err = repo.SetRoleGrants(ctx, roleId, version, strutil.RemoveDuplicates(grants, false))
if err != nil {
// TODO: Figure out a way to surface more helpful error info beyond the Internal error.
return nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "Unable to set grants on role: %v.", err)
return nil, nil, nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "Unable to set grants on role: %v.", err)
}
out, pr, roleGrants, err := repo.LookupRole(ctx, roleId)
if err != nil {
return nil, errors.Wrap(err, op, errors.WithMsg("unable to look up role after setting grants"))
return nil, nil, nil, errors.Wrap(err, op, errors.WithMsg("unable to look up role after setting grants"))
}
if out == nil {
return nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "Unable to lookup role after setting grants on it.")
return nil, nil, nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "Unable to lookup role after setting grants on it.")
}
return toProto(out, pr, roleGrants), nil
return out, pr, roleGrants, nil
}
func (s Service) removeGrantsInRepo(ctx context.Context, roleId string, grants []string, version uint32) (*pb.Role, error) {
func (s Service) removeGrantsInRepo(ctx context.Context, roleId string, grants []string, version uint32) (*iam.Role, []iam.PrincipalRole, []*iam.RoleGrant, error) {
const op = "roles.(Service).removeGrantsInRepo"
repo, err := s.repoFn()
if err != nil {
return nil, err
return nil, nil, nil, err
}
_, err = repo.DeleteRoleGrants(ctx, roleId, version, strutil.RemoveDuplicates(grants, false))
if err != nil {
// TODO: Figure out a way to surface more helpful error info beyond the Internal error.
return nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "Unable to remove grants from role: %v", err)
return nil, nil, nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "Unable to remove grants from role: %v", err)
}
out, pr, roleGrants, err := repo.LookupRole(ctx, roleId)
if err != nil {
return nil, errors.Wrap(err, op, errors.WithMsg("uable to look up role after removing grant"))
return nil, nil, nil, errors.Wrap(err, op, errors.WithMsg("uable to look up role after removing grant"))
}
if out == nil {
return nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "Unable to lookup role after removing grants from it.")
return nil, nil, nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "Unable to lookup role after removing grants from it.")
}
return toProto(out, pr, roleGrants), nil
return out, pr, roleGrants, nil
}
func (s Service) authResult(ctx context.Context, id string, a action.Type) auth.VerifyResults {
@@ -593,57 +789,91 @@ func (s Service) authResult(ctx context.Context, id string, a action.Type) auth.
return auth.Verify(ctx, opts...)
}
func toProto(in *iam.Role, principals []iam.PrincipalRole, grants []*iam.RoleGrant) *pb.Role {
out := pb.Role{
Id: in.GetPublicId(),
ScopeId: in.GetScopeId(),
CreatedTime: in.GetCreateTime().GetTimestamp(),
UpdatedTime: in.GetUpdateTime().GetTimestamp(),
Version: in.GetVersion(),
func toProto(ctx context.Context, in *iam.Role, principals []iam.PrincipalRole, grants []*iam.RoleGrant, opt ...handlers.Option) (*pb.Role, error) {
opts := handlers.GetOpts(opt...)
if opts.WithOutputFields == nil {
return nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "output fields not found when building role proto")
}
if in.GetDescription() != "" {
out.Description = &wrapperspb.StringValue{Value: in.GetDescription()}
outputFields := *opts.WithOutputFields
out := pb.Role{}
if outputFields.Has(globals.IdField) {
out.Id = in.GetPublicId()
}
if in.GetName() != "" {
out.Name = &wrapperspb.StringValue{Value: in.GetName()}
if outputFields.Has(globals.ScopeIdField) {
out.ScopeId = in.GetScopeId()
}
for _, p := range principals {
principal := &pb.Principal{
Id: p.GetPrincipalId(),
Type: p.GetType(),
ScopeId: p.GetPrincipalScopeId(),
}
out.Principals = append(out.Principals, principal)
out.PrincipalIds = append(out.PrincipalIds, p.GetPrincipalId())
if outputFields.Has(globals.DescriptionField) && in.GetDescription() != "" {
out.Description = wrapperspb.String(in.GetDescription())
}
for _, g := range grants {
out.GrantStrings = append(out.GrantStrings, g.GetRawGrant())
parsed, err := perms.Parse(in.GetGrantScopeId(), g.GetRawGrant())
if err != nil {
// This should never happen as we validate on the way in, but let's
// return what we can since we are still returning the raw grant
out.Grants = append(out.Grants, &pb.Grant{
Raw: g.GetRawGrant(),
Canonical: "<parse_error>",
Json: nil,
})
} else {
_, actions := parsed.Actions()
out.Grants = append(out.Grants, &pb.Grant{
Raw: g.GetRawGrant(),
Canonical: g.GetCanonicalGrant(),
Json: &pb.GrantJson{
Id: parsed.Id(),
Type: parsed.Type().String(),
Actions: actions,
},
})
}
if outputFields.Has(globals.NameField) && in.GetName() != "" {
out.Name = wrapperspb.String(in.GetName())
}
if in.GetGrantScopeId() != "" {
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.GrantScopeIdField) && in.GetGrantScopeId() != "" {
out.GrantScopeId = &wrapperspb.StringValue{Value: in.GetGrantScopeId()}
}
return &out
if outputFields.Has(globals.PrincipalIdsField) {
for _, p := range principals {
out.PrincipalIds = append(out.PrincipalIds, p.GetPrincipalId())
}
}
if outputFields.Has(globals.PrincipalsField) {
for _, p := range principals {
principal := &pb.Principal{
Id: p.GetPrincipalId(),
Type: p.GetType(),
ScopeId: p.GetPrincipalScopeId(),
}
out.Principals = append(out.Principals, principal)
}
}
if outputFields.Has(globals.GrantStringsField) {
for _, g := range grants {
out.GrantStrings = append(out.GrantStrings, g.GetRawGrant())
}
}
if outputFields.Has(globals.GrantsField) {
for _, g := range grants {
parsed, err := perms.Parse(in.GetGrantScopeId(), g.GetRawGrant())
if err != nil {
// This should never happen as we validate on the way in, but let's
// return what we can since we are still returning the raw grant
out.Grants = append(out.Grants, &pb.Grant{
Raw: g.GetRawGrant(),
Canonical: "<parse_error>",
Json: nil,
})
} else {
_, actions := parsed.Actions()
out.Grants = append(out.Grants, &pb.Grant{
Raw: g.GetRawGrant(),
Canonical: g.GetCanonicalGrant(),
Json: &pb.GrantJson{
Id: parsed.Id(),
Type: parsed.Type().String(),
Actions: actions,
},
})
}
}
}
return &out, nil
}
// A validateX method should exist for each method above. These methods do not make calls to any backing service but enforce
@@ -294,14 +294,29 @@ func TestList(t *testing.T) {
s, err := roles.NewService(repoFn)
require.NoError(err, "Couldn't create new role service.")
// Test the non-anon case
got, gErr := s.ListRoles(auth.DisabledAuthTestContext(repoFn, tc.req.GetScopeId()), tc.req)
if tc.err != nil {
require.Error(gErr)
assert.True(errors.Is(gErr, tc.err))
} else {
require.NoError(gErr)
return
}
require.NoError(gErr)
assert.Empty(cmp.Diff(got, tc.res, protocmp.Transform()), "ListRoles(%q) got response %q, wanted %q", tc.req, got, tc.res)
// Test the anon case
got, gErr = s.ListRoles(auth.DisabledAuthTestContext(repoFn, tc.req.GetScopeId(), auth.WithUserId(auth.AnonymousUserId)), tc.req)
require.NoError(gErr)
assert.Len(got.Items, len(tc.res.Items))
for _, item := range got.GetItems() {
require.Nil(item.CreatedTime)
require.Nil(item.PrincipalIds)
require.Nil(item.Principals)
require.Nil(item.Grants)
require.Nil(item.GrantStrings)
require.Nil(item.UpdatedTime)
require.Zero(item.Version)
}
})
}
}
@@ -6,6 +6,7 @@ import (
"sort"
"strings"
"github.com/hashicorp/boundary/globals"
"github.com/hashicorp/boundary/internal/auth"
"github.com/hashicorp/boundary/internal/auth/oidc"
"github.com/hashicorp/boundary/internal/auth/password"
@@ -15,6 +16,7 @@ import (
"github.com/hashicorp/boundary/internal/iam"
"github.com/hashicorp/boundary/internal/iam/store"
"github.com/hashicorp/boundary/internal/perms"
"github.com/hashicorp/boundary/internal/requests"
"github.com/hashicorp/boundary/internal/servers/controller/common"
"github.com/hashicorp/boundary/internal/servers/controller/common/scopeids"
"github.com/hashicorp/boundary/internal/servers/controller/handlers"
@@ -29,9 +31,7 @@ import (
"github.com/hashicorp/boundary/internal/types/action"
"github.com/hashicorp/boundary/internal/types/resource"
"github.com/hashicorp/boundary/internal/types/scope"
"github.com/hashicorp/boundary/sdk/strutil"
"google.golang.org/grpc/codes"
"google.golang.org/protobuf/types/known/structpb"
"google.golang.org/protobuf/types/known/wrapperspb"
)
@@ -54,21 +54,33 @@ var (
action.List,
}
orgCollectionTypeMap = map[resource.Type]action.ActionSet{
resource.AuthMethod: authmethods.CollectionActions,
resource.AuthToken: authtokens.CollectionActions,
resource.Group: groups.CollectionActions,
resource.Role: roles.CollectionActions,
resource.Scope: CollectionActions,
resource.Session: sessions.CollectionActions,
resource.User: users.CollectionActions,
}
scopeCollectionTypeMapMap = map[string]map[resource.Type]action.ActionSet{
scope.Global.String(): {
resource.AuthMethod: authmethods.CollectionActions,
resource.AuthToken: authtokens.CollectionActions,
resource.Group: groups.CollectionActions,
resource.Role: roles.CollectionActions,
resource.Scope: CollectionActions,
resource.Session: sessions.CollectionActions,
resource.User: users.CollectionActions,
},
projectCollectionTypeMap = map[resource.Type]action.ActionSet{
resource.Group: groups.CollectionActions,
resource.HostCatalog: host_catalogs.CollectionActions,
resource.Role: roles.CollectionActions,
resource.Target: targets.CollectionActions,
scope.Org.String(): {
resource.AuthMethod: authmethods.CollectionActions,
resource.AuthToken: authtokens.CollectionActions,
resource.Group: groups.CollectionActions,
resource.Role: roles.CollectionActions,
resource.Scope: CollectionActions,
resource.Session: sessions.CollectionActions,
resource.User: users.CollectionActions,
},
scope.Project.String(): {
resource.Group: groups.CollectionActions,
resource.HostCatalog: host_catalogs.CollectionActions,
resource.Role: roles.CollectionActions,
resource.Target: targets.CollectionActions,
},
}
)
@@ -97,36 +109,6 @@ func NewService(repo common.IamRepoFactory) (Service, error) {
var _ pbs.ScopeServiceServer = Service{}
func populateCollectionAuthorizedActions(ctx context.Context,
authResults auth.VerifyResults,
item *pb.Scope) error {
res := &perms.Resource{
ScopeId: item.Id,
}
mapToRange := orgCollectionTypeMap
if item.Type == "project" {
mapToRange = projectCollectionTypeMap
}
// Range over the defined collections and check permissions against those
// collections. We use the ID of this scope being returned, not its parent,
// hence passing in a resource here.
for k, v := range mapToRange {
res.Type = k
acts := authResults.FetchActionSetForType(ctx, k, v, auth.WithResource(res)).Strings()
if len(acts) > 0 {
if item.AuthorizedCollectionActions == nil {
item.AuthorizedCollectionActions = make(map[string]*structpb.ListValue)
}
lv, err := structpb.NewList(strutil.StringListToInterfaceList(acts))
if err != nil {
return err
}
item.AuthorizedCollectionActions[k.String()+"s"] = lv
}
}
return nil
}
// ListScopes implements the interface pbs.ScopeServiceServer.
func (s Service) ListScopes(ctx context.Context, req *pbs.ListScopesRequest) (*pbs.ListScopesResponse, error) {
if req.GetScopeId() == "" {
@@ -158,39 +140,65 @@ func (s Service) ListScopes(ctx context.Context, req *pbs.ListScopesRequest) (*p
return &pbs.ListScopesResponse{}, nil
}
pl, err := s.listFromRepo(ctx, scopeIds, authResults.UserId == auth.AnonymousUserId)
pl, err := s.listFromRepo(ctx, scopeIds)
if err != nil {
return nil, err
}
if len(pl) == 0 {
return &pbs.ListScopesResponse{}, nil
}
filter, err := handlers.NewFilter(req.GetFilter())
if err != nil {
return nil, err
}
finalItems := make([]*pb.Scope, 0, len(pl))
res := &perms.Resource{
res := perms.Resource{
Type: resource.Scope,
}
for _, item := range pl {
item.Scope = scopeInfoMap[item.GetScopeId()]
res.ScopeId = item.Scope.Id
item.AuthorizedActions = authResults.FetchActionSetForId(ctx, item.Id, IdActions, auth.WithResource(res)).Strings()
if len(item.AuthorizedActions) == 0 {
res.Id = item.GetPublicId()
res.ScopeId = item.GetParentId()
authorizedActions := authResults.FetchActionSetForId(ctx, item.GetPublicId(), IdActions, auth.WithResource(&res)).Strings()
if len(authorizedActions) == 0 {
continue
}
if filter.Match(item) {
finalItems = append(finalItems, item)
if err := populateCollectionAuthorizedActions(ctx, authResults, item); err != nil {
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) {
collectionActions, err := auth.CalculateAuthorizedCollectionActions(ctx, authResults, scopeCollectionTypeMapMap[item.Type], item.GetPublicId(), "")
if err != nil {
return nil, err
}
outputOpts = append(outputOpts, handlers.WithAuthorizedCollectionActions(collectionActions))
}
item, err := ToProto(ctx, item, outputOpts...)
if err != nil {
return nil, err
}
if filter.Match(item) {
finalItems = append(finalItems, item)
}
}
SortScopes(finalItems)
return &pbs.ListScopesResponse{Items: finalItems}, nil
}
// GetScopes implements the interface pbs.ScopeServiceServer.
func (s Service) GetScope(ctx context.Context, req *pbs.GetScopeRequest) (*pbs.GetScopeResponse, error) {
const op = "scopes.(Service).GetScope"
if err := validateGetRequest(req); err != nil {
return nil, err
}
@@ -202,21 +210,45 @@ func (s Service) GetScope(ctx context.Context, req *pbs.GetScopeRequest) (*pbs.G
if err != nil {
return nil, err
}
p.Scope = authResults.Scope
outputFields, ok := requests.OutputFields(ctx)
if !ok {
return nil, errors.New(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))
}
act := IdActions
// Can't delete global so elide it
if p.Id == "global" {
act = act[0:2]
if p.GetPublicId() == "global" {
act = act[0:3]
}
p.AuthorizedActions = authResults.FetchActionSetForId(ctx, p.Id, act).Strings()
if err := populateCollectionAuthorizedActions(ctx, authResults, p); err != nil {
if outputFields.Has(globals.AuthorizedActionsField) {
outputOpts = append(outputOpts, handlers.WithAuthorizedActions(authResults.FetchActionSetForId(ctx, p.GetPublicId(), act).Strings()))
}
if outputFields.Has(globals.AuthorizedCollectionActionsField) {
collectionActions, err := auth.CalculateAuthorizedCollectionActions(ctx, authResults, scopeCollectionTypeMapMap[p.Type], p.GetPublicId(), "")
if err != nil {
return nil, err
}
outputOpts = append(outputOpts, handlers.WithAuthorizedCollectionActions(collectionActions))
}
item, err := ToProto(ctx, p, outputOpts...)
if err != nil {
return nil, err
}
return &pbs.GetScopeResponse{Item: p}, nil
return &pbs.GetScopeResponse{Item: item}, nil
}
// CreateScope implements the interface pbs.ScopeServiceServer.
func (s Service) CreateScope(ctx context.Context, req *pbs.CreateScopeRequest) (*pbs.CreateScopeResponse, error) {
const op = "scopes.(Service).CreateScope"
if err := validateCreateRequest(req); err != nil {
return nil, err
}
@@ -228,16 +260,40 @@ func (s Service) CreateScope(ctx context.Context, req *pbs.CreateScopeRequest) (
if err != nil {
return nil, err
}
p.Scope = authResults.Scope
p.AuthorizedActions = authResults.FetchActionSetForId(ctx, p.Id, IdActions).Strings()
if err := populateCollectionAuthorizedActions(ctx, authResults, p); err != nil {
outputFields, ok := requests.OutputFields(ctx)
if !ok {
return nil, errors.New(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, p.GetPublicId(), IdActions).Strings()))
}
if outputFields.Has(globals.AuthorizedCollectionActionsField) {
collectionActions, err := auth.CalculateAuthorizedCollectionActions(ctx, authResults, scopeCollectionTypeMapMap[p.Type], p.GetPublicId(), "")
if err != nil {
return nil, err
}
outputOpts = append(outputOpts, handlers.WithAuthorizedCollectionActions(collectionActions))
}
item, err := ToProto(ctx, p, outputOpts...)
if err != nil {
return nil, err
}
return &pbs.CreateScopeResponse{Item: p, Uri: fmt.Sprintf("scopes/%s", p.GetId())}, nil
return &pbs.CreateScopeResponse{Item: item, Uri: fmt.Sprintf("scopes/%s", item.GetId())}, nil
}
// UpdateScope implements the interface pbs.ScopeServiceServer.
func (s Service) UpdateScope(ctx context.Context, req *pbs.UpdateScopeRequest) (*pbs.UpdateScopeResponse, error) {
const op = "scopes.(Service).UpdateScope"
if err := validateUpdateRequest(req); err != nil {
return nil, err
}
@@ -249,12 +305,34 @@ func (s Service) UpdateScope(ctx context.Context, req *pbs.UpdateScopeRequest) (
if err != nil {
return nil, err
}
p.Scope = authResults.Scope
p.AuthorizedActions = authResults.FetchActionSetForId(ctx, p.Id, IdActions).Strings()
if err := populateCollectionAuthorizedActions(ctx, authResults, p); err != nil {
outputFields, ok := requests.OutputFields(ctx)
if !ok {
return nil, errors.New(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, p.GetPublicId(), IdActions).Strings()))
}
if outputFields.Has(globals.AuthorizedCollectionActionsField) {
collectionActions, err := auth.CalculateAuthorizedCollectionActions(ctx, authResults, scopeCollectionTypeMapMap[p.Type], p.GetPublicId(), "")
if err != nil {
return nil, err
}
outputOpts = append(outputOpts, handlers.WithAuthorizedCollectionActions(collectionActions))
}
item, err := ToProto(ctx, p, outputOpts...)
if err != nil {
return nil, err
}
return &pbs.UpdateScopeResponse{Item: p}, nil
return &pbs.UpdateScopeResponse{Item: item}, nil
}
// DeleteScope implements the interface pbs.ScopeServiceServer.
@@ -273,22 +351,22 @@ func (s Service) DeleteScope(ctx context.Context, req *pbs.DeleteScopeRequest) (
return nil, nil
}
func (s Service) getFromRepo(ctx context.Context, id string) (*pb.Scope, error) {
func (s Service) getFromRepo(ctx context.Context, id string) (*iam.Scope, error) {
repo, err := s.repoFn()
if err != nil {
return nil, err
}
p, err := repo.LookupScope(ctx, id)
out, err := repo.LookupScope(ctx, id)
if err != nil {
return nil, err
}
if p == nil {
if out == nil {
return nil, handlers.NotFoundErrorf("Scope %q doesn't exist.", id)
}
return ToProto(p), nil
return out, nil
}
func (s Service) createInRepo(ctx context.Context, authResults auth.VerifyResults, req *pbs.CreateScopeRequest) (*pb.Scope, error) {
func (s Service) createInRepo(ctx context.Context, authResults auth.VerifyResults, req *pbs.CreateScopeRequest) (*iam.Scope, error) {
const op = "scopes.(Service).createInRepo"
item := req.GetItem()
var opts []iam.Option
@@ -324,10 +402,10 @@ func (s Service) createInRepo(ctx context.Context, authResults auth.VerifyResult
if out == nil {
return nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "Unable to create scope but no error returned from repository.")
}
return ToProto(out), nil
return out, nil
}
func (s Service) updateInRepo(ctx context.Context, parentScope *pb.ScopeInfo, scopeId string, mask []string, item *pb.Scope) (*pb.Scope, error) {
func (s Service) updateInRepo(ctx context.Context, parentScope *pb.ScopeInfo, scopeId string, mask []string, item *pb.Scope) (*iam.Scope, error) {
const op = "scope.(Service).updateInRepo"
var opts []iam.Option
var scopeDesc, scopeName, scopePrimaryAuthMethodId string
@@ -384,7 +462,7 @@ func (s Service) updateInRepo(ctx context.Context, parentScope *pb.ScopeInfo, sc
if rowsUpdated == 0 {
return nil, handlers.NotFoundErrorf("Scope %q doesn't exist or incorrect version provided.", scopeId)
}
return ToProto(out), nil
return out, nil
}
func (s Service) deleteFromRepo(ctx context.Context, scopeId string) (bool, error) {
@@ -408,7 +486,7 @@ func SortScopes(scps []*pb.Scope) {
})
}
func (s Service) listFromRepo(ctx context.Context, scopeIds []string, anonUser bool) ([]*pb.Scope, error) {
func (s Service) listFromRepo(ctx context.Context, scopeIds []string) ([]*iam.Scope, error) {
repo, err := s.repoFn()
if err != nil {
return nil, err
@@ -417,13 +495,7 @@ func (s Service) listFromRepo(ctx context.Context, scopeIds []string, anonUser b
if err != nil {
return nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "Unable to list scopes: %v", err)
}
var outPl []*pb.Scope
for _, scp := range scps {
outPl = append(outPl, ToProto(scp, handlers.WithAnonymousListing(anonUser)))
}
SortScopes(outPl)
return outPl, nil
return scps, nil
}
func (s Service) authResult(ctx context.Context, id string, a action.Type) auth.VerifyResults {
@@ -465,29 +537,52 @@ func (s Service) authResult(ctx context.Context, id string, a action.Type) auth.
return auth.Verify(ctx, opts...)
}
func ToProto(in *iam.Scope, opt ...handlers.Option) *pb.Scope {
anonListing := handlers.GetOpts(opt...).WithAnonymousListing
out := pb.Scope{
Id: in.GetPublicId(),
ScopeId: in.GetParentId(),
Type: in.GetType(),
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")
}
if in.GetDescription() != "" {
out.Description = &wrapperspb.StringValue{Value: in.GetDescription()}
outputFields := *opts.WithOutputFields
out := pb.Scope{}
if outputFields.Has(globals.IdField) {
out.Id = in.GetPublicId()
}
if in.GetName() != "" {
out.Name = &wrapperspb.StringValue{Value: in.GetName()}
if outputFields.Has(globals.ScopeIdField) {
out.ScopeId = in.GetParentId()
}
if in.GetPrimaryAuthMethodId() != "" {
out.PrimaryAuthMethodId = &wrapperspb.StringValue{Value: in.GetPrimaryAuthMethodId()}
if outputFields.Has(globals.TypeField) {
out.Type = in.GetType()
}
if !anonListing {
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()
}
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.AuthorizedCollectionActionsField) {
out.AuthorizedCollectionActions = opts.WithAuthorizedCollectionActions
}
if outputFields.Has(globals.PrimaryAuthMethodId) && in.GetPrimaryAuthMethodId() != "" {
out.PrimaryAuthMethodId = &wrapperspb.StringValue{Value: in.GetPrimaryAuthMethodId()}
}
return &out
return &out, nil
}
// A validateX method should exist for each method above. These methods do not make calls to any backing service but enforce
@@ -14,6 +14,7 @@ import (
pb "github.com/hashicorp/boundary/internal/gen/controller/api/resources/scopes"
pbs "github.com/hashicorp/boundary/internal/gen/controller/api/services"
"github.com/hashicorp/boundary/internal/iam"
"github.com/hashicorp/boundary/internal/perms"
"github.com/hashicorp/boundary/internal/servers/controller/handlers"
"github.com/hashicorp/boundary/internal/servers/controller/handlers/scopes"
"github.com/hashicorp/boundary/internal/types/scope"
@@ -290,13 +291,16 @@ func TestList(t *testing.T) {
_, err = repo.DeleteScope(context.Background(), p2.GetPublicId())
require.NoError(t, err)
outputFields := perms.OutputFieldsMap(nil).SelfOrDefaults("u_auth")
var initialOrgs []*pb.Scope
globalScope := &pb.ScopeInfo{Id: "global", Type: scope.Global.String(), Name: scope.Global.String(), Description: "Global Scope"}
oNoProjectsProto := scopes.ToProto(oNoProjects)
oNoProjectsProto, err := scopes.ToProto(context.Background(), oNoProjects, handlers.WithOutputFields(&outputFields))
require.NoError(t, err)
oNoProjectsProto.Scope = globalScope
oNoProjectsProto.AuthorizedActions = testAuthorizedActions
oNoProjectsProto.AuthorizedCollectionActions = orgAuthorizedCollectionActions
oWithProjectsProto := scopes.ToProto(oWithProjects)
oWithProjectsProto, err := scopes.ToProto(context.Background(), oWithProjects, handlers.WithOutputFields(&outputFields))
require.NoError(t, err)
oWithProjectsProto.Scope = globalScope
oWithProjectsProto.AuthorizedActions = testAuthorizedActions
oWithProjectsProto.AuthorizedCollectionActions = orgAuthorizedCollectionActions
@@ -342,7 +346,7 @@ func TestList(t *testing.T) {
require.NoError(err, "Couldn't create new role service.")
// Test with non-anonymous listing first
got, gErr := s.ListScopes(auth.DisabledAuthTestContext(repoFn, tc.scopeId, auth.WithUserId("u_auth")), tc.req)
got, gErr := s.ListScopes(auth.DisabledAuthTestContext(repoFn, tc.scopeId), tc.req)
if tc.err != nil {
require.Error(gErr)
assert.True(errors.Is(gErr, tc.err), "ListScopes(%+v) got error\n%v, wanted\n%v", tc.req, gErr, tc.err)
@@ -351,8 +355,9 @@ func TestList(t *testing.T) {
assert.Empty(cmp.Diff(got, tc.res, protocmp.Transform()), "ListScopes(%q) got response\n%q\nwanted\n%q", tc.req, got, tc.res)
// Now test with anonymous listing
got, gErr = s.ListScopes(auth.DisabledAuthTestContext(repoFn, tc.scopeId), tc.req)
got, gErr = s.ListScopes(auth.DisabledAuthTestContext(repoFn, tc.scopeId, auth.WithUserId(auth.AnonymousUserId)), tc.req)
require.NoError(gErr)
assert.Len(got.Items, len(tc.res.Items))
for _, item := range got.GetItems() {
assert.Nil(item.CreatedTime)
assert.Nil(item.UpdatedTime)
@@ -455,7 +460,7 @@ func TestList(t *testing.T) {
require.NoError(err, "Couldn't create new role service.")
// Test with non-anonymous listing first
got, gErr := s.ListScopes(auth.DisabledAuthTestContext(repoFn, tc.scopeId, auth.WithUserId("u_auth")), tc.req)
got, gErr := s.ListScopes(auth.DisabledAuthTestContext(repoFn, tc.scopeId), tc.req)
if tc.err != nil {
require.Error(gErr)
assert.True(errors.Is(gErr, tc.err), "ListScopes(%+v) got error\n%v, wanted\n%v", tc.req, gErr, tc.err)
@@ -465,8 +470,9 @@ func TestList(t *testing.T) {
assert.Empty(cmp.Diff(got, tc.res, protocmp.Transform()), "ListScopes(%q) got response\n%q, wanted\n%q", tc.req, got, tc.res)
// Now test with anonymous listing
got, gErr = s.ListScopes(auth.DisabledAuthTestContext(repoFn, tc.scopeId), tc.req)
got, gErr = s.ListScopes(auth.DisabledAuthTestContext(repoFn, tc.scopeId, auth.WithUserId(auth.AnonymousUserId)), tc.req)
require.NoError(gErr)
assert.Len(got.Items, len(tc.res.Items))
for _, item := range got.GetItems() {
assert.Nil(item.CreatedTime)
assert.Nil(item.UpdatedTime)
@@ -5,11 +5,13 @@ import (
stderrors "errors"
"fmt"
"github.com/hashicorp/boundary/globals"
"github.com/hashicorp/boundary/internal/auth"
"github.com/hashicorp/boundary/internal/errors"
pb "github.com/hashicorp/boundary/internal/gen/controller/api/resources/sessions"
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/servers/controller/common"
"github.com/hashicorp/boundary/internal/servers/controller/common/scopeids"
"github.com/hashicorp/boundary/internal/servers/controller/handlers"
@@ -18,6 +20,7 @@ import (
"github.com/hashicorp/boundary/internal/types/action"
"github.com/hashicorp/boundary/internal/types/resource"
"github.com/hashicorp/boundary/internal/types/scope"
"google.golang.org/grpc/codes"
)
var (
@@ -62,6 +65,8 @@ var _ pbs.SessionServiceServer = Service{}
// GetSessions implements the interface pbs.SessionServiceServer.
func (s Service) GetSession(ctx context.Context, req *pbs.GetSessionRequest) (*pbs.GetSessionResponse, error) {
const op = "sessions.(Service).GetSession"
if err := validateGetRequest(req); err != nil {
return nil, err
}
@@ -74,17 +79,42 @@ func (s Service) GetSession(ctx context.Context, req *pbs.GetSessionRequest) (*p
return nil, err
}
authzdActions := authResults.FetchActionSetForId(ctx, ses.Id, IdActions)
var outputFields perms.OutputFieldsMap
authorizedActions := authResults.FetchActionSetForId(ctx, ses.GetPublicId(), IdActions)
// Check to see if we need to verify Read vs. just ReadSelf
if ses.GetUserId() != authResults.UserId {
if !authzdActions.HasAction(action.Read) {
if ses.UserId != authResults.UserId {
if !authorizedActions.HasAction(action.Read) {
return nil, handlers.ForbiddenError()
}
outputFields = authResults.FetchOutputFields(perms.Resource{
Id: ses.GetPublicId(),
ScopeId: ses.ScopeId,
Type: resource.Session,
}, action.Read).SelfOrDefaults(authResults.UserId)
} else {
var ok bool
outputFields, ok = requests.OutputFields(ctx)
if !ok {
return nil, errors.New(errors.Internal, op, "no request context found")
}
}
ses.Scope = authResults.Scope
ses.AuthorizedActions = authzdActions.Strings()
return &pbs.GetSessionResponse{Item: ses}, nil
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.Strings()))
}
item, err := toProto(ctx, ses, outputOpts...)
if err != nil {
return nil, err
}
return &pbs.GetSessionResponse{Item: item}, nil
}
// ListSessions implements the interface pbs.SessionServiceServer.
@@ -116,32 +146,48 @@ func (s Service) ListSessions(ctx context.Context, req *pbs.ListSessionsRequest)
return &pbs.ListSessionsResponse{}, nil
}
seslist, err := s.listFromRepo(ctx, session.WithScopeIds(scopeIds))
sesList, err := s.listFromRepo(ctx, scopeIds)
if err != nil {
return nil, err
}
if len(sesList) == 0 {
return &pbs.ListSessionsResponse{}, nil
}
filter, err := handlers.NewFilter(req.GetFilter())
if err != nil {
return nil, err
}
finalItems := make([]*pb.Session, 0, len(seslist))
res := &perms.Resource{
finalItems := make([]*pb.Session, 0, len(sesList))
res := perms.Resource{
Type: resource.Session,
}
for _, item := range seslist {
item.Scope = scopeInfoMap[item.GetScopeId()]
res.ScopeId = item.Scope.Id
authorizedActions := authResults.FetchActionSetForId(ctx, item.Id, IdActions, auth.WithResource(res))
for _, item := range sesList {
res.Id = item.GetPublicId()
res.ScopeId = item.ScopeId
authorizedActions := authResults.FetchActionSetForId(ctx, item.GetPublicId(), IdActions, auth.WithResource(&res))
if len(authorizedActions) == 0 {
continue
}
if authorizedActions.OnlySelf() && item.GetUserId() != authResults.UserId {
if authorizedActions.OnlySelf() && item.UserId != authResults.UserId {
continue
}
item.AuthorizedActions = authorizedActions.Strings()
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.ScopeId]))
}
if outputFields.Has(globals.AuthorizedActionsField) {
outputOpts = append(outputOpts, handlers.WithAuthorizedActions(authorizedActions.Strings()))
}
item, err := toProto(ctx, item, outputOpts...)
if err != nil {
return nil, err
}
if filter.Match(item) {
finalItems = append(finalItems, item)
@@ -153,6 +199,8 @@ func (s Service) ListSessions(ctx context.Context, req *pbs.ListSessionsRequest)
// CancelSession implements the interface pbs.SessionServiceServer.
func (s Service) CancelSession(ctx context.Context, req *pbs.CancelSessionRequest) (*pbs.CancelSessionResponse, error) {
const op = "sessions.(Service).CancelSession"
if err := validateCancelRequest(req); err != nil {
return nil, err
}
@@ -165,24 +213,50 @@ func (s Service) CancelSession(ctx context.Context, req *pbs.CancelSessionReques
if err != nil {
return nil, err
}
authzdActions := authResults.FetchActionSetForId(ctx, ses.Id, IdActions)
// Check to see if we need to verify Cancel vs. just CancelSelf
if ses.GetUserId() != authResults.UserId {
if !authzdActions.HasAction(action.Cancel) {
var outputFields perms.OutputFieldsMap
authorizedActions := authResults.FetchActionSetForId(ctx, ses.GetPublicId(), IdActions)
// Check to see if we need to verify Read vs. just ReadSelf
if ses.UserId != authResults.UserId {
if !authorizedActions.HasAction(action.Cancel) {
return nil, handlers.ForbiddenError()
}
outputFields = authResults.FetchOutputFields(perms.Resource{
Id: ses.GetPublicId(),
ScopeId: ses.ScopeId,
Type: resource.Session,
}, action.Cancel).SelfOrDefaults(authResults.UserId)
} else {
var ok bool
outputFields, ok = requests.OutputFields(ctx)
if !ok {
return nil, errors.New(errors.Internal, op, "no request context found")
}
}
ses, err = s.cancelInRepo(ctx, req.GetId(), req.GetVersion())
if err != nil {
return nil, err
}
ses.Scope = authResults.Scope
ses.AuthorizedActions = authzdActions.Strings()
return &pbs.CancelSessionResponse{Item: ses}, nil
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.Strings()))
}
item, err := toProto(ctx, ses, outputOpts...)
if err != nil {
return nil, err
}
return &pbs.CancelSessionResponse{Item: item}, nil
}
func (s Service) getFromRepo(ctx context.Context, id string) (*pb.Session, error) {
func (s Service) getFromRepo(ctx context.Context, id string) (*session.Session, error) {
repo, err := s.repoFn()
if err != nil {
return nil, err
@@ -197,26 +271,22 @@ func (s Service) getFromRepo(ctx context.Context, id string) (*pb.Session, error
if sess == nil {
return nil, handlers.NotFoundErrorf("Session %q doesn't exist.", id)
}
return toProto(sess), nil
return sess, nil
}
func (s Service) listFromRepo(ctx context.Context, opts ...session.Option) ([]*pb.Session, error) {
func (s Service) listFromRepo(ctx context.Context, scopeIds []string) ([]*session.Session, error) {
repo, err := s.repoFn()
if err != nil {
return nil, err
}
seslist, err := repo.ListSessions(ctx, opts...)
sesList, err := repo.ListSessions(ctx, session.WithScopeIds(scopeIds))
if err != nil {
return nil, err
}
var outSl []*pb.Session
for _, ses := range seslist {
outSl = append(outSl, toProto(ses))
}
return outSl, nil
return sesList, nil
}
func (s Service) cancelInRepo(ctx context.Context, id string, version uint32) (*pb.Session, error) {
func (s Service) cancelInRepo(ctx context.Context, id string, version uint32) (*session.Session, error) {
const op = "sessions.(Service).cancelInRepo"
repo, err := s.repoFn()
if err != nil {
@@ -226,7 +296,7 @@ func (s Service) cancelInRepo(ctx context.Context, id string, version uint32) (*
if err != nil {
return nil, errors.Wrap(err, op, errors.WithMsg("unable to update session"))
}
return toProto(out), nil
return out, nil
}
func (s Service) authResult(ctx context.Context, id string, a action.Type) auth.VerifyResults {
@@ -276,42 +346,89 @@ func (s Service) authResult(ctx context.Context, id string, a action.Type) auth.
return auth.Verify(ctx, opts...)
}
func toProto(in *session.Session) *pb.Session {
out := pb.Session{
Id: in.GetPublicId(),
ScopeId: in.ScopeId,
TargetId: in.TargetId,
Version: in.Version,
UserId: in.UserId,
HostId: in.HostId,
HostSetId: in.HostSetId,
AuthTokenId: in.AuthTokenId,
Endpoint: in.Endpoint,
Type: target.SubtypeFromId(in.TargetId).String(),
// TODO: Provide the ServerType and the ServerId when that information becomes relevant in the API.
func toProto(ctx context.Context, in *session.Session, opt ...handlers.Option) (*pb.Session, error) {
opts := handlers.GetOpts(opt...)
if opts.WithOutputFields == nil {
return nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "output fields not found when building auth token proto")
}
outputFields := *opts.WithOutputFields
CreatedTime: in.CreateTime.GetTimestamp(),
UpdatedTime: in.UpdateTime.GetTimestamp(),
ExpirationTime: in.ExpirationTime.GetTimestamp(),
Certificate: in.Certificate,
TerminationReason: in.TerminationReason,
out := pb.Session{}
if outputFields.Has(globals.IdField) {
out.Id = in.GetPublicId()
}
if outputFields.Has(globals.ScopeIdField) {
out.ScopeId = in.ScopeId
}
if outputFields.Has(globals.TargetIdField) {
out.TargetId = in.TargetId
}
if outputFields.Has(globals.TypeField) {
out.Type = target.SubtypeFromId(in.TargetId).String()
}
if outputFields.Has(globals.CreatedTimeField) {
out.CreatedTime = in.CreateTime.GetTimestamp()
}
if outputFields.Has(globals.UpdatedTimeField) {
out.UpdatedTime = in.UpdateTime.GetTimestamp()
}
if outputFields.Has(globals.VersionField) {
out.Version = in.Version
}
if outputFields.Has(globals.UserIdField) {
out.UserId = in.UserId
}
if outputFields.Has(globals.HostIdField) {
out.HostId = in.HostId
}
if outputFields.Has(globals.HostSetIdField) {
out.HostSetId = in.HostSetId
}
if outputFields.Has(globals.AuthTokenIdField) {
out.AuthTokenId = in.AuthTokenId
}
if outputFields.Has(globals.EndpointField) {
out.Endpoint = in.Endpoint
}
if outputFields.Has(globals.HostIdField) {
out.HostId = in.HostId
}
if outputFields.Has(globals.ExpirationTimeField) {
out.ExpirationTime = in.ExpirationTime.GetTimestamp()
}
if outputFields.Has(globals.CertificateField) {
out.Certificate = in.Certificate
}
if outputFields.Has(globals.TerminationReasonField) {
out.TerminationReason = in.TerminationReason
}
if outputFields.Has(globals.ScopeField) {
out.Scope = opts.WithScope
}
if outputFields.Has(globals.AuthorizedActionsField) {
out.AuthorizedActions = opts.WithAuthorizedActions
}
// TODO: Provide the ServerType and the ServerId when that information becomes relevant in the API.
if len(in.States) > 0 {
out.Status = in.States[0].Status.String()
if outputFields.Has(globals.StatusField) {
out.Status = in.States[0].Status.String()
}
if outputFields.Has(globals.StatesField) {
for _, s := range in.States {
sessState := &pb.SessionState{
Status: s.Status.String(),
}
if s.StartTime != nil {
sessState.StartTime = s.StartTime.GetTimestamp()
}
if s.EndTime != nil {
sessState.EndTime = s.EndTime.GetTimestamp()
}
out.States = append(out.States, sessState)
}
}
}
for _, s := range in.States {
sessState := &pb.SessionState{
Status: s.Status.String(),
}
if s.StartTime != nil {
sessState.StartTime = s.StartTime.GetTimestamp()
}
if s.EndTime != nil {
sessState.EndTime = s.EndTime.GetTimestamp()
}
out.States = append(out.States, sessState)
}
return &out
return &out, nil
}
// A validateX method should exist for each method above. These methods do not make calls to any backing service but enforce
@@ -387,24 +387,44 @@ func TestList(t *testing.T) {
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
require, assert := require.New(t), assert.New(t)
s, err := sessions.NewService(sessRepoFn, iamRepoFn)
require.NoError(t, err, "Couldn't create new session service.")
require.NoError(err, "Couldn't create new session service.")
// Test without anon user
got, gErr := s.ListSessions(auth.DisabledAuthTestContext(iamRepoFn, tc.req.GetScopeId()), tc.req)
if tc.err != nil {
require.Error(t, gErr)
assert.True(t, errors.Is(gErr, tc.err), "ListSessions(%+v) got error %v, wanted %v", tc.req, gErr, tc.err)
} else {
require.NoError(t, gErr)
require.Error(gErr)
assert.True(errors.Is(gErr, tc.err), "ListSessions(%+v) got error %v, wanted %v", tc.req, gErr, tc.err)
return
}
require.NoError(gErr)
if tc.res != nil {
require.Equal(t, len(tc.res.GetItems()), len(got.GetItems()), "Didn't get expected number of sessions: %v", got.GetItems())
require.Equal(len(tc.res.GetItems()), len(got.GetItems()), "Didn't get expected number of sessions: %v", got.GetItems())
for i, wantSess := range tc.res.GetItems() {
assert.True(t, got.GetItems()[i].GetExpirationTime().AsTime().Sub(wantSess.GetExpirationTime().AsTime()) < 10*time.Millisecond)
assert.True(got.GetItems()[i].GetExpirationTime().AsTime().Sub(wantSess.GetExpirationTime().AsTime()) < 10*time.Millisecond)
wantSess.ExpirationTime = got.GetItems()[i].GetExpirationTime()
}
}
assert.Empty(t, cmp.Diff(got, tc.res, protocmp.Transform()), "ListSessions(%q) got response %q, wanted %q", tc.req, got, tc.res)
assert.Empty(cmp.Diff(got, tc.res, protocmp.Transform()), "ListSessions(%q) got response %q, wanted %q", tc.req, got, tc.res)
// Test with anon user
got, gErr = s.ListSessions(auth.DisabledAuthTestContext(iamRepoFn, tc.req.GetScopeId(), auth.WithUserId(auth.AnonymousUserId)), tc.req)
require.NoError(gErr)
assert.Len(got.Items, len(tc.res.Items))
for _, item := range got.GetItems() {
require.Empty(item.Version)
require.Empty(item.UserId)
require.Empty(item.HostId)
require.Empty(item.HostSetId)
require.Empty(item.AuthTokenId)
require.Empty(item.Endpoint)
require.Nil(item.CreatedTime)
require.Nil(item.ExpirationTime)
require.Nil(item.UpdatedTime)
require.Empty(item.Certificate)
require.Empty(item.TerminationReason)
}
})
}
}
@@ -9,6 +9,7 @@ import (
"strings"
"github.com/golang/protobuf/ptypes/wrappers"
"github.com/hashicorp/boundary/globals"
"github.com/hashicorp/boundary/internal/auth"
"github.com/hashicorp/boundary/internal/db/timestamp"
"github.com/hashicorp/boundary/internal/errors"
@@ -18,6 +19,7 @@ import (
"github.com/hashicorp/boundary/internal/host/static"
"github.com/hashicorp/boundary/internal/kms"
"github.com/hashicorp/boundary/internal/perms"
"github.com/hashicorp/boundary/internal/requests"
"github.com/hashicorp/boundary/internal/servers"
"github.com/hashicorp/boundary/internal/servers/controller/common"
"github.com/hashicorp/boundary/internal/servers/controller/common/scopeids"
@@ -145,26 +147,45 @@ func (s Service) ListTargets(ctx context.Context, req *pbs.ListTargetsRequest) (
return &pbs.ListTargetsResponse{}, nil
}
ul, err := s.listFromRepo(ctx, scopeIds)
tl, err := s.listFromRepo(ctx, scopeIds)
if err != nil {
return nil, err
}
if len(tl) == 0 {
return &pbs.ListTargetsResponse{}, nil
}
filter, err := handlers.NewFilter(req.GetFilter())
if err != nil {
return nil, err
}
finalItems := make([]*pb.Target, 0, len(ul))
res := &perms.Resource{
finalItems := make([]*pb.Target, 0, len(tl))
res := perms.Resource{
Type: resource.Target,
}
for _, item := range ul {
item.Scope = scopeInfoMap[item.GetScopeId()]
res.ScopeId = item.Scope.Id
item.AuthorizedActions = authResults.FetchActionSetForId(ctx, item.Id, IdActions, auth.WithResource(res)).Strings()
if len(item.AuthorizedActions) == 0 {
for _, item := range tl {
res.Id = item.GetPublicId()
res.ScopeId = item.GetScopeId()
authorizedActions := authResults.FetchActionSetForId(ctx, item.GetPublicId(), IdActions, auth.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(scopeInfoMap[item.GetScopeId()]))
}
if outputFields.Has(globals.AuthorizedActionsField) {
outputOpts = append(outputOpts, handlers.WithAuthorizedActions(authorizedActions))
}
item, err := toProto(ctx, item, nil, outputOpts...)
if err != nil {
return nil, err
}
if filter.Match(item) {
finalItems = append(finalItems, item)
}
@@ -174,6 +195,8 @@ func (s Service) ListTargets(ctx context.Context, req *pbs.ListTargetsRequest) (
// GetTargets implements the interface pbs.TargetServiceServer.
func (s Service) GetTarget(ctx context.Context, req *pbs.GetTargetRequest) (*pbs.GetTargetResponse, error) {
const op = "targets.(Service).GetTarget"
if err := validateGetRequest(req); err != nil {
return nil, err
}
@@ -181,17 +204,37 @@ func (s Service) GetTarget(ctx context.Context, req *pbs.GetTargetRequest) (*pbs
if authResults.Error != nil {
return nil, authResults.Error
}
u, err := s.getFromRepo(ctx, req.GetId())
t, ts, err := s.getFromRepo(ctx, req.GetId())
if err != nil {
return nil, err
}
u.Scope = authResults.Scope
u.AuthorizedActions = authResults.FetchActionSetForId(ctx, u.Id, IdActions).Strings()
return &pbs.GetTargetResponse{Item: u}, nil
outputFields, ok := requests.OutputFields(ctx)
if !ok {
return nil, errors.New(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, t.GetPublicId(), IdActions).Strings()))
}
item, err := toProto(ctx, t, ts, outputOpts...)
if err != nil {
return nil, err
}
return &pbs.GetTargetResponse{Item: item}, nil
}
// CreateTarget implements the interface pbs.TargetServiceServer.
func (s Service) CreateTarget(ctx context.Context, req *pbs.CreateTargetRequest) (*pbs.CreateTargetResponse, error) {
const op = "targets.(Service).CreateTarget"
if err := validateCreateRequest(req); err != nil {
return nil, err
}
@@ -199,17 +242,37 @@ func (s Service) CreateTarget(ctx context.Context, req *pbs.CreateTargetRequest)
if authResults.Error != nil {
return nil, authResults.Error
}
u, err := s.createInRepo(ctx, req.GetItem())
t, ts, err := s.createInRepo(ctx, req.GetItem())
if err != nil {
return nil, err
}
u.Scope = authResults.Scope
u.AuthorizedActions = authResults.FetchActionSetForId(ctx, u.Id, IdActions).Strings()
return &pbs.CreateTargetResponse{Item: u, Uri: fmt.Sprintf("targets/%s", u.GetId())}, nil
outputFields, ok := requests.OutputFields(ctx)
if !ok {
return nil, errors.New(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, t.GetPublicId(), IdActions).Strings()))
}
item, err := toProto(ctx, t, ts, outputOpts...)
if err != nil {
return nil, err
}
return &pbs.CreateTargetResponse{Item: item, Uri: fmt.Sprintf("targets/%s", item.GetId())}, nil
}
// UpdateTarget implements the interface pbs.TargetServiceServer.
func (s Service) UpdateTarget(ctx context.Context, req *pbs.UpdateTargetRequest) (*pbs.UpdateTargetResponse, error) {
const op = "targets.(Service).UpdateTarget"
if err := validateUpdateRequest(req); err != nil {
return nil, err
}
@@ -217,13 +280,31 @@ func (s Service) UpdateTarget(ctx context.Context, req *pbs.UpdateTargetRequest)
if authResults.Error != nil {
return nil, authResults.Error
}
u, err := s.updateInRepo(ctx, authResults.Scope.GetId(), req.GetId(), req.GetUpdateMask().GetPaths(), req.GetItem())
t, ts, err := s.updateInRepo(ctx, authResults.Scope.GetId(), req.GetId(), req.GetUpdateMask().GetPaths(), req.GetItem())
if err != nil {
return nil, err
}
u.Scope = authResults.Scope
u.AuthorizedActions = authResults.FetchActionSetForId(ctx, u.Id, IdActions).Strings()
return &pbs.UpdateTargetResponse{Item: u}, nil
outputFields, ok := requests.OutputFields(ctx)
if !ok {
return nil, errors.New(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, t.GetPublicId(), IdActions).Strings()))
}
item, err := toProto(ctx, t, ts, outputOpts...)
if err != nil {
return nil, err
}
return &pbs.UpdateTargetResponse{Item: item}, nil
}
// DeleteTarget implements the interface pbs.TargetServiceServer.
@@ -244,6 +325,8 @@ func (s Service) DeleteTarget(ctx context.Context, req *pbs.DeleteTargetRequest)
// AddTargetHostSets implements the interface pbs.TargetServiceServer.
func (s Service) AddTargetHostSets(ctx context.Context, req *pbs.AddTargetHostSetsRequest) (*pbs.AddTargetHostSetsResponse, error) {
const op = "targets.(Service).AddTargetHostSets"
if err := validateAddRequest(req); err != nil {
return nil, err
}
@@ -251,17 +334,37 @@ func (s Service) AddTargetHostSets(ctx context.Context, req *pbs.AddTargetHostSe
if authResults.Error != nil {
return nil, authResults.Error
}
u, err := s.addInRepo(ctx, req.GetId(), req.GetHostSetIds(), req.GetVersion())
t, ts, err := s.addInRepo(ctx, req.GetId(), req.GetHostSetIds(), req.GetVersion())
if err != nil {
return nil, err
}
u.Scope = authResults.Scope
u.AuthorizedActions = authResults.FetchActionSetForId(ctx, u.Id, IdActions).Strings()
return &pbs.AddTargetHostSetsResponse{Item: u}, nil
outputFields, ok := requests.OutputFields(ctx)
if !ok {
return nil, errors.New(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, t.GetPublicId(), IdActions).Strings()))
}
item, err := toProto(ctx, t, ts, outputOpts...)
if err != nil {
return nil, err
}
return &pbs.AddTargetHostSetsResponse{Item: item}, nil
}
// SetTargetHostSets implements the interface pbs.TargetServiceServer.
func (s Service) SetTargetHostSets(ctx context.Context, req *pbs.SetTargetHostSetsRequest) (*pbs.SetTargetHostSetsResponse, error) {
const op = "targets.(Service).SetTargetHostSets"
if err := validateSetRequest(req); err != nil {
return nil, err
}
@@ -269,17 +372,37 @@ func (s Service) SetTargetHostSets(ctx context.Context, req *pbs.SetTargetHostSe
if authResults.Error != nil {
return nil, authResults.Error
}
u, err := s.setInRepo(ctx, req.GetId(), req.GetHostSetIds(), req.GetVersion())
t, ts, err := s.setInRepo(ctx, req.GetId(), req.GetHostSetIds(), req.GetVersion())
if err != nil {
return nil, err
}
u.Scope = authResults.Scope
u.AuthorizedActions = authResults.FetchActionSetForId(ctx, u.Id, IdActions).Strings()
return &pbs.SetTargetHostSetsResponse{Item: u}, nil
outputFields, ok := requests.OutputFields(ctx)
if !ok {
return nil, errors.New(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, t.GetPublicId(), IdActions).Strings()))
}
item, err := toProto(ctx, t, ts, outputOpts...)
if err != nil {
return nil, err
}
return &pbs.SetTargetHostSetsResponse{Item: item}, nil
}
// RemoveTargetHostSets implements the interface pbs.TargetServiceServer.
func (s Service) RemoveTargetHostSets(ctx context.Context, req *pbs.RemoveTargetHostSetsRequest) (*pbs.RemoveTargetHostSetsResponse, error) {
const op = "targets.(Service).RemoveTargetHostSets"
if err := validateRemoveRequest(req); err != nil {
return nil, err
}
@@ -287,13 +410,31 @@ func (s Service) RemoveTargetHostSets(ctx context.Context, req *pbs.RemoveTarget
if authResults.Error != nil {
return nil, authResults.Error
}
u, err := s.removeInRepo(ctx, req.GetId(), req.GetHostSetIds(), req.GetVersion())
t, ts, err := s.removeInRepo(ctx, req.GetId(), req.GetHostSetIds(), req.GetVersion())
if err != nil {
return nil, err
}
u.Scope = authResults.Scope
u.AuthorizedActions = authResults.FetchActionSetForId(ctx, u.Id, IdActions).Strings()
return &pbs.RemoveTargetHostSetsResponse{Item: u}, nil
outputFields, ok := requests.OutputFields(ctx)
if !ok {
return nil, errors.New(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, t.GetPublicId(), IdActions).Strings()))
}
item, err := toProto(ctx, t, ts, outputOpts...)
if err != nil {
return nil, err
}
return &pbs.RemoveTargetHostSetsResponse{Item: item}, nil
}
func (s Service) AuthorizeSession(ctx context.Context, req *pbs.AuthorizeSessionRequest) (*pbs.AuthorizeSessionResponse, error) {
@@ -574,25 +715,25 @@ HostSetIterationLoop:
return &pbs.AuthorizeSessionResponse{Item: ret}, nil
}
func (s Service) getFromRepo(ctx context.Context, id string) (*pb.Target, error) {
func (s Service) getFromRepo(ctx context.Context, id string) (target.Target, []*target.TargetSet, error) {
repo, err := s.repoFn()
if err != nil {
return nil, err
return nil, nil, err
}
u, m, err := repo.LookupTarget(ctx, id)
if err != nil {
if errors.IsNotFoundError(err) {
return nil, handlers.NotFoundErrorf("Target %q doesn't exist.", id)
return nil, nil, handlers.NotFoundErrorf("Target %q doesn't exist.", id)
}
return nil, err
return nil, nil, err
}
if u == nil {
return nil, handlers.NotFoundErrorf("Target %q doesn't exist.", id)
return nil, nil, handlers.NotFoundErrorf("Target %q doesn't exist.", id)
}
return toProto(u, m)
return u, m, nil
}
func (s Service) createInRepo(ctx context.Context, item *pb.Target) (*pb.Target, error) {
func (s Service) createInRepo(ctx context.Context, item *pb.Target) (target.Target, []*target.TargetSet, error) {
const op = "targets.(Service).createInRepo"
opts := []target.Option{target.WithName(item.GetName().GetValue())}
if item.GetDescription() != nil {
@@ -609,30 +750,30 @@ func (s Service) createInRepo(ctx context.Context, item *pb.Target) (*pb.Target,
}
tcpAttrs := &pb.TcpTargetAttributes{}
if err := handlers.StructToProto(item.GetAttributes(), tcpAttrs); err != nil {
return nil, handlers.ApiErrorWithCodeAndMessage(codes.InvalidArgument, "Provided attributes don't match expected format.")
return nil, nil, handlers.ApiErrorWithCodeAndMessage(codes.InvalidArgument, "Provided attributes don't match expected format.")
}
if tcpAttrs.GetDefaultPort().GetValue() != 0 {
opts = append(opts, target.WithDefaultPort(tcpAttrs.GetDefaultPort().GetValue()))
}
u, err := target.NewTcpTarget(item.GetScopeId(), opts...)
if err != nil {
return nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "Unable to build target for creation: %v.", err)
return nil, nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "Unable to build target for creation: %v.", err)
}
repo, err := s.repoFn()
if err != nil {
return nil, err
return nil, nil, err
}
out, m, err := repo.CreateTcpTarget(ctx, u)
if err != nil {
return nil, errors.Wrap(err, op, errors.WithMsg("unable to create target"))
return nil, nil, errors.Wrap(err, op, errors.WithMsg("unable to create target"))
}
if out == nil {
return nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "Unable to create target but no error returned from repository.")
return nil, nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "Unable to create target but no error returned from repository.")
}
return toProto(out, m)
return out, m, nil
}
func (s Service) updateInRepo(ctx context.Context, scopeId, id string, mask []string, item *pb.Target) (*pb.Target, error) {
func (s Service) updateInRepo(ctx context.Context, scopeId, id string, mask []string, item *pb.Target) (target.Target, []*target.TargetSet, error) {
const op = "targets.(Service).updateInRepo"
var opts []target.Option
if desc := item.GetDescription(); desc != nil {
@@ -652,7 +793,7 @@ func (s Service) updateInRepo(ctx context.Context, scopeId, id string, mask []st
}
tcpAttrs := &pb.TcpTargetAttributes{}
if err := handlers.StructToProto(item.GetAttributes(), tcpAttrs); err != nil {
return nil, handlers.ApiErrorWithCodeAndMessage(codes.InvalidArgument, "Provided attributes don't match expected format.")
return nil, nil, handlers.ApiErrorWithCodeAndMessage(codes.InvalidArgument, "Provided attributes don't match expected format.")
}
if tcpAttrs.GetDefaultPort().GetValue() != 0 {
opts = append(opts, target.WithDefaultPort(tcpAttrs.GetDefaultPort().GetValue()))
@@ -660,25 +801,25 @@ func (s Service) updateInRepo(ctx context.Context, scopeId, id string, mask []st
version := item.GetVersion()
u, err := target.NewTcpTarget(scopeId, opts...)
if err != nil {
return nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "Unable to build target for update: %v.", err)
return nil, nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "Unable to build target for update: %v.", err)
}
u.PublicId = id
dbMask := maskManager.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 paths provided in the update mask."})
return nil, nil, handlers.InvalidArgumentErrorf("No valid fields included in the update mask.", map[string]string{"update_mask": "No valid paths provided in the update mask."})
}
repo, err := s.repoFn()
if err != nil {
return nil, err
return nil, nil, err
}
out, m, rowsUpdated, err := repo.UpdateTcpTarget(ctx, u, version, dbMask)
if err != nil {
return nil, errors.Wrap(err, op, errors.WithMsg("unable to update target"))
return nil, nil, errors.Wrap(err, op, errors.WithMsg("unable to update target"))
}
if rowsUpdated == 0 {
return nil, handlers.NotFoundErrorf("Target %q not found or incorrect version provided.", id)
return nil, nil, handlers.NotFoundErrorf("Target %q not found or incorrect version provided.", id)
}
return toProto(out, m)
return out, m, nil
}
func (s Service) deleteFromRepo(ctx context.Context, id string) (bool, error) {
@@ -697,7 +838,7 @@ func (s Service) deleteFromRepo(ctx context.Context, id string) (bool, error) {
return rows > 0, nil
}
func (s Service) listFromRepo(ctx context.Context, scopeIds []string) ([]*pb.Target, error) {
func (s Service) listFromRepo(ctx context.Context, scopeIds []string) ([]target.Target, error) {
repo, err := s.repoFn()
if err != nil {
return nil, err
@@ -706,74 +847,66 @@ func (s Service) listFromRepo(ctx context.Context, scopeIds []string) ([]*pb.Tar
if err != nil {
return nil, err
}
var outUl []*pb.Target
for _, u := range ul {
o, err := toProto(u, nil)
if err != nil {
return nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "Unable to convert value to proto: %v.", err)
}
outUl = append(outUl, o)
}
return outUl, nil
return ul, nil
}
func (s Service) addInRepo(ctx context.Context, targetId string, hostSetId []string, version uint32) (*pb.Target, error) {
func (s Service) addInRepo(ctx context.Context, targetId string, hostSetId []string, version uint32) (target.Target, []*target.TargetSet, error) {
repo, err := s.repoFn()
if err != nil {
return nil, err
return nil, nil, err
}
out, m, err := repo.AddTargetHostSets(ctx, targetId, version, strutil.RemoveDuplicates(hostSetId, false))
if err != nil {
// TODO: Figure out a way to surface more helpful error info beyond the Internal error.
return nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "Unable to add host sets to target: %v.", err)
return nil, nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "Unable to add host sets to target: %v.", err)
}
if out == nil {
return nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "Unable to lookup target after adding host sets to it.")
return nil, nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "Unable to lookup target after adding host sets to it.")
}
return toProto(out, m)
return out, m, nil
}
func (s Service) setInRepo(ctx context.Context, targetId string, hostSetIds []string, version uint32) (*pb.Target, error) {
func (s Service) setInRepo(ctx context.Context, targetId string, hostSetIds []string, version uint32) (target.Target, []*target.TargetSet, error) {
const op = "targets.(Service).setInRepo"
repo, err := s.repoFn()
if err != nil {
return nil, err
return nil, nil, err
}
_, _, err = repo.SetTargetHostSets(ctx, targetId, version, strutil.RemoveDuplicates(hostSetIds, false))
if err != nil {
// TODO: Figure out a way to surface more helpful error info beyond the Internal error.
return nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "Unable to set host sets in target: %v.", err)
return nil, nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "Unable to set host sets in target: %v.", err)
}
out, m, err := repo.LookupTarget(ctx, targetId)
if err != nil {
return nil, errors.Wrap(err, op, errors.WithMsg("unable to look up target after setting host sets"))
return nil, nil, errors.Wrap(err, op, errors.WithMsg("unable to look up target after setting host sets"))
}
if out == nil {
return nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "Unable to lookup target after setting host sets for it.")
return nil, nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "Unable to lookup target after setting host sets for it.")
}
return toProto(out, m)
return out, m, nil
}
func (s Service) removeInRepo(ctx context.Context, targetId string, hostSetIds []string, version uint32) (*pb.Target, error) {
func (s Service) removeInRepo(ctx context.Context, targetId string, hostSetIds []string, version uint32) (target.Target, []*target.TargetSet, error) {
const op = "targets.(Service).removeInRepo"
repo, err := s.repoFn()
if err != nil {
return nil, err
return nil, nil, err
}
_, err = repo.DeleteTargeHostSets(ctx, targetId, version, strutil.RemoveDuplicates(hostSetIds, false))
if err != nil {
// TODO: Figure out a way to surface more helpful error info beyond the Internal error.
return nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "Unable to remove host sets from target: %v.", err)
return nil, nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "Unable to remove host sets from target: %v.", err)
}
out, m, err := repo.LookupTarget(ctx, targetId)
if err != nil {
return nil, errors.Wrap(err, op, errors.WithMsg("unable to look up target after removing host sets"))
return nil, nil, errors.Wrap(err, op, errors.WithMsg("unable to look up target after removing host sets"))
}
if out == nil {
return nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "Unable to lookup target after removing host sets from it.")
return nil, nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "Unable to lookup target after removing host sets from it.")
}
return toProto(out, m)
return out, m, nil
}
func (s Service) authResult(ctx context.Context, id string, a action.Type, lookupOpt ...target.Option) auth.VerifyResults {
@@ -829,41 +962,76 @@ func (s Service) authResult(ctx context.Context, id string, a action.Type, looku
return ret
}
func toProto(in target.Target, m []*target.TargetSet) (*pb.Target, error) {
out := pb.Target{
Id: in.GetPublicId(),
ScopeId: in.GetScopeId(),
CreatedTime: in.GetCreateTime().GetTimestamp(),
UpdatedTime: in.GetUpdateTime().GetTimestamp(),
Version: in.GetVersion(),
Type: target.TcpTargetType.String(),
SessionMaxSeconds: wrapperspb.UInt32(in.GetSessionMaxSeconds()),
SessionConnectionLimit: wrapperspb.Int32(in.GetSessionConnectionLimit()),
func toProto(ctx context.Context, in target.Target, m []*target.TargetSet, opt ...handlers.Option) (*pb.Target, error) {
opts := handlers.GetOpts(opt...)
if opts.WithOutputFields == nil {
return nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "output fields not found when building target proto")
}
if in.GetDescription() != "" {
outputFields := *opts.WithOutputFields
out := pb.Target{}
if outputFields.Has(globals.IdField) {
out.Id = in.GetPublicId()
}
if outputFields.Has(globals.ScopeIdField) {
out.ScopeId = in.GetScopeId()
}
if outputFields.Has(globals.TypeField) {
out.Type = target.TcpTargetType.String()
}
if outputFields.Has(globals.DescriptionField) && in.GetDescription() != "" {
out.Description = wrapperspb.String(in.GetDescription())
}
if in.GetName() != "" {
if outputFields.Has(globals.NameField) && in.GetName() != "" {
out.Name = wrapperspb.String(in.GetName())
}
if in.GetWorkerFilter() != "" {
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.SessionMaxSecondsField) {
out.SessionMaxSeconds = wrapperspb.UInt32(in.GetSessionMaxSeconds())
}
if outputFields.Has(globals.SessionConnectionLimitField) {
out.SessionConnectionLimit = wrapperspb.Int32(in.GetSessionConnectionLimit())
}
if outputFields.Has(globals.WorkerFilterField) && in.GetWorkerFilter() != "" {
out.WorkerFilter = wrapperspb.String(in.GetWorkerFilter())
}
attrs := &pb.TcpTargetAttributes{}
if in.GetDefaultPort() > 0 {
attrs.DefaultPort = &wrappers.UInt32Value{Value: in.GetDefaultPort()}
if outputFields.Has(globals.ScopeField) {
out.Scope = opts.WithScope
}
st, err := handlers.ProtoToStruct(attrs)
if err != nil {
return nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "failed building password attribute struct: %v", err)
if outputFields.Has(globals.AuthorizedActionsField) {
out.AuthorizedActions = opts.WithAuthorizedActions
}
out.Attributes = st
for _, hs := range m {
out.HostSetIds = append(out.HostSetIds, hs.GetPublicId())
out.HostSets = append(out.HostSets, &pb.HostSet{
Id: hs.GetPublicId(),
HostCatalogId: hs.GetCatalogId(),
})
if outputFields.Has(globals.HostSetIdsField) {
for _, hs := range m {
out.HostSetIds = append(out.HostSetIds, hs.GetPublicId())
}
}
if outputFields.Has(globals.HostSetsField) {
for _, hs := range m {
out.HostSets = append(out.HostSets, &pb.HostSet{
Id: hs.GetPublicId(),
HostCatalogId: hs.GetCatalogId(),
})
}
}
if outputFields.Has(globals.AttributesField) {
attrs := &pb.TcpTargetAttributes{}
if in.GetDefaultPort() > 0 {
attrs.DefaultPort = &wrappers.UInt32Value{Value: in.GetDefaultPort()}
}
st, err := handlers.ProtoToStruct(attrs)
if err != nil {
return nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "failed building password attribute struct: %v", err)
}
out.Attributes = st
}
return &out, nil
}
@@ -243,14 +243,29 @@ func TestList(t *testing.T) {
s, err := testService(t, conn, kms, wrapper)
require.NoError(err, "Couldn't create new host set service.")
// Test with non-anon user
got, gErr := s.ListTargets(auth.DisabledAuthTestContext(iamRepoFn, tc.req.GetScopeId()), tc.req)
if tc.err != nil {
require.Error(gErr)
assert.True(errors.Is(gErr, tc.err), "ListTargets(%q) got error %v, wanted %v", tc.req.GetScopeId(), gErr, tc.err)
} else {
require.NoError(gErr)
return
}
require.NoError(gErr)
assert.Empty(cmp.Diff(got, tc.res, protocmp.Transform()), "ListTargets(%q) scope %q, got response %q, wanted %q", tc.name, tc.req.GetScopeId(), got, tc.res)
// Test with anon user
got, gErr = s.ListTargets(auth.DisabledAuthTestContext(iamRepoFn, tc.req.GetScopeId(), auth.WithUserId(auth.AnonymousUserId)), tc.req)
require.NoError(gErr)
assert.Len(got.Items, len(tc.res.Items))
for _, item := range got.GetItems() {
require.Empty(item.Version)
require.Nil(item.CreatedTime)
require.Nil(item.UpdatedTime)
require.Nil(item.SessionMaxSeconds)
require.Nil(item.SessionConnectionLimit)
require.Empty(item.WorkerFilter)
require.Nil(item.Attributes)
}
})
}
}
@@ -4,6 +4,7 @@ import (
"context"
"fmt"
"github.com/hashicorp/boundary/globals"
"github.com/hashicorp/boundary/internal/auth"
"github.com/hashicorp/boundary/internal/auth/oidc"
"github.com/hashicorp/boundary/internal/auth/password"
@@ -13,6 +14,7 @@ import (
"github.com/hashicorp/boundary/internal/iam"
"github.com/hashicorp/boundary/internal/iam/store"
"github.com/hashicorp/boundary/internal/perms"
"github.com/hashicorp/boundary/internal/requests"
"github.com/hashicorp/boundary/internal/servers/controller/common"
"github.com/hashicorp/boundary/internal/servers/controller/common/scopeids"
"github.com/hashicorp/boundary/internal/servers/controller/handlers"
@@ -104,21 +106,41 @@ func (s Service) ListUsers(ctx context.Context, req *pbs.ListUsersRequest) (*pbs
if err != nil {
return nil, err
}
if len(ul) == 0 {
return &pbs.ListUsersResponse{}, nil
}
filter, err := handlers.NewFilter(req.GetFilter())
if err != nil {
return nil, err
}
finalItems := make([]*pb.User, 0, len(ul))
res := &perms.Resource{
res := perms.Resource{
Type: resource.User,
}
for _, item := range ul {
item.Scope = scopeInfoMap[item.GetScopeId()]
res.ScopeId = item.Scope.Id
item.AuthorizedActions = authResults.FetchActionSetForId(ctx, item.Id, IdActions, auth.WithResource(res)).Strings()
if len(item.AuthorizedActions) == 0 {
res.Id = item.GetPublicId()
res.ScopeId = item.GetScopeId()
authorizedActions := authResults.FetchActionSetForId(ctx, item.GetPublicId(), IdActions, auth.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(scopeInfoMap[item.GetScopeId()]))
}
if outputFields.Has(globals.AuthorizedActionsField) {
outputOpts = append(outputOpts, handlers.WithAuthorizedActions(authorizedActions))
}
item, err := toProto(ctx, item, nil, outputOpts...)
if err != nil {
return nil, err
}
if filter.Match(item) {
finalItems = append(finalItems, item)
}
@@ -128,6 +150,8 @@ func (s Service) ListUsers(ctx context.Context, req *pbs.ListUsersRequest) (*pbs
// GetUsers implements the interface pbs.UserServiceServer.
func (s Service) GetUser(ctx context.Context, req *pbs.GetUserRequest) (*pbs.GetUserResponse, error) {
const op = "users.(Service).GetUser"
if err := validateGetRequest(req); err != nil {
return nil, err
}
@@ -135,17 +159,37 @@ func (s Service) GetUser(ctx context.Context, req *pbs.GetUserRequest) (*pbs.Get
if authResults.Error != nil {
return nil, authResults.Error
}
u, err := s.getFromRepo(ctx, req.GetId())
u, accts, err := s.getFromRepo(ctx, req.GetId())
if err != nil {
return nil, err
}
u.Scope = authResults.Scope
u.AuthorizedActions = authResults.FetchActionSetForId(ctx, u.Id, IdActions).Strings()
return &pbs.GetUserResponse{Item: u}, nil
outputFields, ok := requests.OutputFields(ctx)
if !ok {
return nil, errors.New(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, u.GetPublicId(), IdActions).Strings()))
}
item, err := toProto(ctx, u, accts, outputOpts...)
if err != nil {
return nil, err
}
return &pbs.GetUserResponse{Item: item}, nil
}
// CreateUser implements the interface pbs.UserServiceServer.
func (s Service) CreateUser(ctx context.Context, req *pbs.CreateUserRequest) (*pbs.CreateUserResponse, error) {
const op = "users.(Service).CreateUser"
if err := validateCreateRequest(req); err != nil {
return nil, err
}
@@ -157,13 +201,32 @@ func (s Service) CreateUser(ctx context.Context, req *pbs.CreateUserRequest) (*p
if err != nil {
return nil, err
}
u.Scope = authResults.Scope
u.AuthorizedActions = authResults.FetchActionSetForId(ctx, u.Id, IdActions).Strings()
return &pbs.CreateUserResponse{Item: u, Uri: fmt.Sprintf("users/%s", u.GetId())}, nil
outputFields, ok := requests.OutputFields(ctx)
if !ok {
return nil, errors.New(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, u.GetPublicId(), IdActions).Strings()))
}
item, err := toProto(ctx, u, nil, outputOpts...)
if err != nil {
return nil, err
}
return &pbs.CreateUserResponse{Item: item, Uri: fmt.Sprintf("users/%s", item.GetId())}, nil
}
// UpdateUser implements the interface pbs.UserServiceServer.
func (s Service) UpdateUser(ctx context.Context, req *pbs.UpdateUserRequest) (*pbs.UpdateUserResponse, error) {
const op = "users.(Service).UpdateUser"
if err := validateUpdateRequest(req); err != nil {
return nil, err
}
@@ -171,13 +234,31 @@ func (s Service) UpdateUser(ctx context.Context, req *pbs.UpdateUserRequest) (*p
if authResults.Error != nil {
return nil, authResults.Error
}
u, err := s.updateInRepo(ctx, authResults.Scope.GetId(), req.GetId(), req.GetUpdateMask().GetPaths(), req.GetItem())
u, accts, err := s.updateInRepo(ctx, authResults.Scope.GetId(), req.GetId(), req.GetUpdateMask().GetPaths(), req.GetItem())
if err != nil {
return nil, err
}
u.Scope = authResults.Scope
u.AuthorizedActions = authResults.FetchActionSetForId(ctx, u.Id, IdActions).Strings()
return &pbs.UpdateUserResponse{Item: u}, nil
outputFields, ok := requests.OutputFields(ctx)
if !ok {
return nil, errors.New(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, u.GetPublicId(), IdActions).Strings()))
}
item, err := toProto(ctx, u, accts, outputOpts...)
if err != nil {
return nil, err
}
return &pbs.UpdateUserResponse{Item: item}, nil
}
// DeleteUser implements the interface pbs.UserServiceServer.
@@ -198,6 +279,8 @@ func (s Service) DeleteUser(ctx context.Context, req *pbs.DeleteUserRequest) (*p
// AddUserAccounts implements the interface pbs.GroupServiceServer.
func (s Service) AddUserAccounts(ctx context.Context, req *pbs.AddUserAccountsRequest) (*pbs.AddUserAccountsResponse, error) {
const op = "users.(Service).AddUserAccounts"
if err := validateAddUserAccountsRequest(req); err != nil {
return nil, err
}
@@ -205,17 +288,37 @@ func (s Service) AddUserAccounts(ctx context.Context, req *pbs.AddUserAccountsRe
if authResults.Error != nil {
return nil, authResults.Error
}
u, err := s.addInRepo(ctx, req.GetId(), req.GetAccountIds(), req.GetVersion())
u, accts, err := s.addInRepo(ctx, req.GetId(), req.GetAccountIds(), req.GetVersion())
if err != nil {
return nil, err
}
u.Scope = authResults.Scope
u.AuthorizedActions = authResults.FetchActionSetForId(ctx, u.Id, IdActions).Strings()
return &pbs.AddUserAccountsResponse{Item: u}, nil
outputFields, ok := requests.OutputFields(ctx)
if !ok {
return nil, errors.New(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, u.GetPublicId(), IdActions).Strings()))
}
item, err := toProto(ctx, u, accts, outputOpts...)
if err != nil {
return nil, err
}
return &pbs.AddUserAccountsResponse{Item: item}, nil
}
// SetUserAccounts implements the interface pbs.GroupServiceServer.
func (s Service) SetUserAccounts(ctx context.Context, req *pbs.SetUserAccountsRequest) (*pbs.SetUserAccountsResponse, error) {
const op = "users.(Service).SetUserAccounts"
if err := validateSetUserAccountsRequest(req); err != nil {
return nil, err
}
@@ -223,17 +326,37 @@ func (s Service) SetUserAccounts(ctx context.Context, req *pbs.SetUserAccountsRe
if authResults.Error != nil {
return nil, authResults.Error
}
u, err := s.setInRepo(ctx, req.GetId(), req.GetAccountIds(), req.GetVersion())
u, accts, err := s.setInRepo(ctx, req.GetId(), req.GetAccountIds(), req.GetVersion())
if err != nil {
return nil, err
}
u.Scope = authResults.Scope
u.AuthorizedActions = authResults.FetchActionSetForId(ctx, u.Id, IdActions).Strings()
return &pbs.SetUserAccountsResponse{Item: u}, nil
outputFields, ok := requests.OutputFields(ctx)
if !ok {
return nil, errors.New(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, u.GetPublicId(), IdActions).Strings()))
}
item, err := toProto(ctx, u, accts, outputOpts...)
if err != nil {
return nil, err
}
return &pbs.SetUserAccountsResponse{Item: item}, nil
}
// RemoveUserAccounts implements the interface pbs.GroupServiceServer.
func (s Service) RemoveUserAccounts(ctx context.Context, req *pbs.RemoveUserAccountsRequest) (*pbs.RemoveUserAccountsResponse, error) {
const op = "users.(Service).RemoveUserAccounts"
if err := validateRemoveUserAccountsRequest(req); err != nil {
return nil, err
}
@@ -241,34 +364,52 @@ func (s Service) RemoveUserAccounts(ctx context.Context, req *pbs.RemoveUserAcco
if authResults.Error != nil {
return nil, authResults.Error
}
u, err := s.removeInRepo(ctx, req.GetId(), req.GetAccountIds(), req.GetVersion())
u, accts, err := s.removeInRepo(ctx, req.GetId(), req.GetAccountIds(), req.GetVersion())
if err != nil {
return nil, err
}
u.Scope = authResults.Scope
u.AuthorizedActions = authResults.FetchActionSetForId(ctx, u.Id, IdActions).Strings()
return &pbs.RemoveUserAccountsResponse{Item: u}, nil
}
func (s Service) getFromRepo(ctx context.Context, id string) (*pb.User, error) {
repo, err := s.repoFn()
outputFields, ok := requests.OutputFields(ctx)
if !ok {
return nil, errors.New(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, u.GetPublicId(), IdActions).Strings()))
}
item, err := toProto(ctx, u, accts, outputOpts...)
if err != nil {
return nil, err
}
return &pbs.RemoveUserAccountsResponse{Item: item}, nil
}
func (s Service) getFromRepo(ctx context.Context, id string) (*iam.User, []string, error) {
repo, err := s.repoFn()
if err != nil {
return nil, nil, err
}
u, accts, err := repo.LookupUser(ctx, id)
if err != nil {
if errors.IsNotFoundError(err) {
return nil, handlers.NotFoundErrorf("User %q doesn't exist.", id)
return nil, nil, handlers.NotFoundErrorf("User %q doesn't exist.", id)
}
return nil, err
return nil, nil, err
}
if u == nil {
return nil, handlers.NotFoundErrorf("User %q doesn't exist.", id)
return nil, nil, handlers.NotFoundErrorf("User %q doesn't exist.", id)
}
return toProto(u, accts), nil
return u, accts, nil
}
func (s Service) createInRepo(ctx context.Context, orgId string, item *pb.User) (*pb.User, error) {
func (s Service) createInRepo(ctx context.Context, orgId string, item *pb.User) (*iam.User, error) {
const op = "users.(Service).createInRepo"
var opts []iam.Option
if item.GetName() != nil {
@@ -292,10 +433,10 @@ func (s Service) createInRepo(ctx context.Context, orgId string, item *pb.User)
if out == nil {
return nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "Unable to create user but no error returned from repository.")
}
return toProto(out, nil), nil
return out, nil
}
func (s Service) updateInRepo(ctx context.Context, orgId, id string, mask []string, item *pb.User) (*pb.User, error) {
func (s Service) updateInRepo(ctx context.Context, orgId, id string, mask []string, item *pb.User) (*iam.User, []string, error) {
const op = "users.(Service).updateInRepo"
var opts []iam.Option
if desc := item.GetDescription(); desc != nil {
@@ -307,25 +448,25 @@ func (s Service) updateInRepo(ctx context.Context, orgId, id string, mask []stri
version := item.GetVersion()
u, err := iam.NewUser(orgId, opts...)
if err != nil {
return nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "Unable to build user for update: %v.", err)
return nil, nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "Unable to build user for update: %v.", err)
}
u.PublicId = id
dbMask := maskManager.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."})
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."})
}
repo, err := s.repoFn()
if err != nil {
return nil, err
return nil, nil, err
}
out, accts, rowsUpdated, err := repo.UpdateUser(ctx, u, version, dbMask)
if err != nil {
return nil, errors.Wrap(err, op, errors.WithMsg("unable to update user"))
return nil, nil, errors.Wrap(err, op, errors.WithMsg("unable to update user"))
}
if rowsUpdated == 0 {
return nil, handlers.NotFoundErrorf("User %q doesn't exist or incorrect version provided.", id)
return nil, nil, handlers.NotFoundErrorf("User %q doesn't exist or incorrect version provided.", id)
}
return toProto(out, accts), nil
return out, accts, nil
}
func (s Service) deleteFromRepo(ctx context.Context, id string) (bool, error) {
@@ -344,7 +485,7 @@ func (s Service) deleteFromRepo(ctx context.Context, id string) (bool, error) {
return rows > 0, nil
}
func (s Service) listFromRepo(ctx context.Context, scopeIds []string) ([]*pb.User, error) {
func (s Service) listFromRepo(ctx context.Context, scopeIds []string) ([]*iam.User, error) {
repo, err := s.repoFn()
if err != nil {
return nil, err
@@ -353,71 +494,67 @@ func (s Service) listFromRepo(ctx context.Context, scopeIds []string) ([]*pb.Use
if err != nil {
return nil, err
}
var outUl []*pb.User
for _, u := range ul {
outUl = append(outUl, toProto(u, nil))
}
return outUl, nil
return ul, nil
}
func (s Service) addInRepo(ctx context.Context, userId string, accountIds []string, version uint32) (*pb.User, error) {
func (s Service) addInRepo(ctx context.Context, userId string, accountIds []string, version uint32) (*iam.User, []string, error) {
const op = "users.(Service).addInRepo"
repo, err := s.repoFn()
if err != nil {
return nil, err
return nil, nil, err
}
_, err = repo.AddUserAccounts(ctx, userId, version, strutil.RemoveDuplicates(accountIds, false))
if err != nil {
return nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "Unable to add accounts to user: %v.", err)
return nil, nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "Unable to add accounts to user: %v.", err)
}
out, accts, err := repo.LookupUser(ctx, userId)
if err != nil {
return nil, errors.Wrap(err, op, errors.WithMsg("unable to look up user after adding accounts"))
return nil, nil, errors.Wrap(err, op, errors.WithMsg("unable to look up user after adding accounts"))
}
if out == nil {
return nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "Unable to lookup user after adding accounts to it.")
return nil, nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "Unable to lookup user after adding accounts to it.")
}
return toProto(out, accts), nil
return out, accts, nil
}
func (s Service) setInRepo(ctx context.Context, userId string, accountIds []string, version uint32) (*pb.User, error) {
func (s Service) setInRepo(ctx context.Context, userId string, accountIds []string, version uint32) (*iam.User, []string, error) {
const op = "users.(Service).setInRepo"
repo, err := s.repoFn()
if err != nil {
return nil, err
return nil, nil, err
}
_, err = repo.SetUserAccounts(ctx, userId, version, strutil.RemoveDuplicates(accountIds, false))
if err != nil {
return nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "Unable to set accounts for the user: %v.", err)
return nil, nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "Unable to set accounts for the user: %v.", err)
}
out, accts, err := repo.LookupUser(ctx, userId)
if err != nil {
return nil, errors.Wrap(err, op, errors.WithMsg("unable to look up user after setting accounts"))
return nil, nil, errors.Wrap(err, op, errors.WithMsg("unable to look up user after setting accounts"))
}
if out == nil {
return nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "Unable to lookup user after setting accounts for it.")
return nil, nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "Unable to lookup user after setting accounts for it.")
}
return toProto(out, accts), nil
return out, accts, nil
}
func (s Service) removeInRepo(ctx context.Context, userId string, accountIds []string, version uint32) (*pb.User, error) {
func (s Service) removeInRepo(ctx context.Context, userId string, accountIds []string, version uint32) (*iam.User, []string, error) {
const op = "users.(Service).removeInRepo"
repo, err := s.repoFn()
if err != nil {
return nil, err
return nil, nil, err
}
_, err = repo.DeleteUserAccounts(ctx, userId, version, strutil.RemoveDuplicates(accountIds, false))
if err != nil {
return nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "Unable to remove accounts from user: %v.", err)
return nil, nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "Unable to remove accounts from user: %v.", err)
}
out, accts, err := repo.LookupUser(ctx, userId)
if err != nil {
return nil, errors.Wrap(err, op, errors.WithMsg("unable to look up user after removing accounts"))
return nil, nil, errors.Wrap(err, op, errors.WithMsg("unable to look up user after removing accounts"))
}
if out == nil {
return nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "Unable to lookup user after removing accounts from it.")
return nil, nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "Unable to lookup user after removing accounts from it.")
}
return toProto(out, accts), nil
return out, accts, nil
}
func (s Service) authResult(ctx context.Context, id string, a action.Type) auth.VerifyResults {
@@ -459,41 +596,66 @@ func (s Service) authResult(ctx context.Context, id string, a action.Type) auth.
return auth.Verify(ctx, opts...)
}
func toProto(in *iam.User, accts []string) *pb.User {
out := pb.User{
Id: in.GetPublicId(),
ScopeId: in.GetScopeId(),
CreatedTime: in.GetCreateTime().GetTimestamp(),
UpdatedTime: in.GetUpdateTime().GetTimestamp(),
Version: in.GetVersion(),
AccountIds: accts,
func toProto(ctx context.Context, in *iam.User, accts []string, opt ...handlers.Option) (*pb.User, error) {
opts := handlers.GetOpts(opt...)
if opts.WithOutputFields == nil {
return nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "output fields not found when building user proto")
}
if in.GetDescription() != "" {
out.Description = &wrapperspb.StringValue{Value: in.GetDescription()}
outputFields := *opts.WithOutputFields
out := pb.User{}
if outputFields.Has(globals.IdField) {
out.Id = in.GetPublicId()
}
if in.GetName() != "" {
out.Name = &wrapperspb.StringValue{Value: in.GetName()}
if outputFields.Has(globals.ScopeIdField) {
out.ScopeId = in.GetScopeId()
}
if in.GetPrimaryAccountId() != "" {
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()
}
if outputFields.Has(globals.VersionField) {
out.Version = in.GetVersion()
}
if outputFields.Has(globals.AccountIdsField) {
out.AccountIds = accts
}
if outputFields.Has(globals.PrimaryAccountIdField) {
out.PrimaryAccountId = in.GetPrimaryAccountId()
}
if in.GetLoginName() != "" {
if outputFields.Has(globals.LoginNameField) {
out.LoginName = in.GetLoginName()
}
if in.GetFullName() != "" {
if outputFields.Has(globals.FullNameField) {
out.FullName = in.GetFullName()
}
if in.GetEmail() != "" {
if outputFields.Has(globals.EmailField) {
out.Email = in.GetEmail()
}
for _, a := range accts {
out.Accounts = append(out.Accounts, &pb.Account{
Id: a,
// TODO: Update this when an account can be associated with a user from a different scope.
ScopeId: in.GetScopeId(),
})
if outputFields.Has(globals.ScopeField) {
out.Scope = opts.WithScope
}
return &out
if outputFields.Has(globals.AuthorizedActionsField) {
out.AuthorizedActions = opts.WithAuthorizedActions
}
if outputFields.Has(globals.AccountsField) {
for _, a := range accts {
out.Accounts = append(out.Accounts, &pb.Account{
Id: a,
// TODO: Update this when an account can be associated with a user from a different scope.
ScopeId: in.GetScopeId(),
})
}
}
return &out, nil
}
// A validateX method should exist for each method above. These methods do not make calls to any backing service but enforce
@@ -227,7 +227,7 @@ func TestList(t *testing.T) {
}
// Populate these users into the total
ctx = auth.DisabledAuthTestContext(repoFn, oWithUsers.GetPublicId())
ctx = auth.DisabledAuthTestContext(repoFn, oWithUsers.GetPublicId(), auth.WithUserId("u_auth"))
usersInOrg, err := s.ListUsers(ctx, &pbs.ListUsersRequest{ScopeId: oWithUsers.GetPublicId()})
require.NoError(t, err)
totalUsers = append(totalUsers, usersInOrg.GetItems()...)
@@ -278,10 +278,12 @@ func TestList(t *testing.T) {
assert, require := assert.New(t), require.New(t)
require.NoError(err, "Couldn't create new user service.")
// Test with non-anon user
got, gErr := s.ListUsers(auth.DisabledAuthTestContext(repoFn, tc.req.GetScopeId()), tc.req)
if tc.err != nil {
require.Error(gErr)
assert.True(errors.Is(gErr, tc.err), "ListUsers(%+v) got error %v, wanted %v", tc.req, gErr, tc.err)
return
}
gotUsers := sortableUsers{
users: got.GetItems(),
@@ -298,6 +300,18 @@ func TestList(t *testing.T) {
tc.res.Items = resUsers.users
}
assert.Empty(cmp.Diff(got, tc.res, protocmp.Transform()), "ListUsers(%q) got response %q, wanted %q", tc.req, got, tc.res)
// Test with anon user
got, gErr = s.ListUsers(auth.DisabledAuthTestContext(repoFn, tc.req.GetScopeId(), auth.WithUserId(auth.AnonymousUserId)), tc.req)
require.NoError(gErr)
assert.Len(got.Items, len(tc.res.Items))
for _, item := range got.GetItems() {
require.Empty(item.Version)
require.Nil(item.CreatedTime)
require.Nil(item.UpdatedTime)
require.Nil(item.Accounts)
require.Nil(item.AccountIds)
}
})
}
}