|
|
// Copyright IBM Corp. 2020, 2025
|
|
|
// SPDX-License-Identifier: BUSL-1.1
|
|
|
|
|
|
package iam
|
|
|
|
|
|
import (
|
|
|
"context"
|
|
|
"fmt"
|
|
|
"strings"
|
|
|
|
|
|
"github.com/hashicorp/boundary/globals"
|
|
|
"github.com/hashicorp/boundary/internal/db"
|
|
|
"github.com/hashicorp/boundary/internal/errors"
|
|
|
"github.com/hashicorp/boundary/internal/kms"
|
|
|
"github.com/hashicorp/boundary/internal/oplog"
|
|
|
"github.com/hashicorp/boundary/internal/types/scope"
|
|
|
)
|
|
|
|
|
|
// AddPrincipalRoles provides the ability to add principals (userIds and
|
|
|
// groupIds) to a role (roleId). The role's current db version must match the
|
|
|
// roleVersion or an error will be returned. The list of current PrincipalRoles
|
|
|
// after the adds will be returned on success. Zero is not a valid value for
|
|
|
// the WithVersion option and will return an error.
|
|
|
func (r *Repository) AddPrincipalRoles(ctx context.Context, roleId string, roleVersion uint32, principalIds []string, _ ...Option) ([]*PrincipalRole, error) {
|
|
|
const op = "iam.(Repository).AddPrincipalRoles"
|
|
|
if roleId == "" {
|
|
|
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing role id")
|
|
|
}
|
|
|
if roleVersion == 0 {
|
|
|
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing version")
|
|
|
}
|
|
|
userIds, groupIds, managedGroupIds, err := splitPrincipals(ctx, principalIds)
|
|
|
if err != nil {
|
|
|
return nil, errors.Wrap(ctx, err, op)
|
|
|
}
|
|
|
if len(userIds) == 0 && len(groupIds) == 0 && len(managedGroupIds) == 0 {
|
|
|
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing any of users, groups, or managed groups to add")
|
|
|
}
|
|
|
newUserRoles := make([]*UserRole, 0, len(userIds))
|
|
|
for _, id := range userIds {
|
|
|
usrRole, err := NewUserRole(ctx, roleId, id)
|
|
|
if err != nil {
|
|
|
return nil, errors.Wrap(ctx, err, op, errors.WithMsg("unable to create in memory user role"))
|
|
|
}
|
|
|
newUserRoles = append(newUserRoles, usrRole)
|
|
|
}
|
|
|
newGrpRoles := make([]*GroupRole, 0, len(groupIds))
|
|
|
for _, id := range groupIds {
|
|
|
grpRole, err := NewGroupRole(ctx, roleId, id)
|
|
|
if err != nil {
|
|
|
return nil, errors.Wrap(ctx, err, op, errors.WithMsg("unable to create in memory group role"))
|
|
|
}
|
|
|
newGrpRoles = append(newGrpRoles, grpRole)
|
|
|
}
|
|
|
newManagedGrpRoles := make([]*ManagedGroupRole, 0, len(managedGroupIds))
|
|
|
for _, id := range managedGroupIds {
|
|
|
managedGrpRole, err := NewManagedGroupRole(ctx, roleId, id)
|
|
|
if err != nil {
|
|
|
return nil, errors.Wrap(ctx, err, op, errors.WithMsg("unable to create in memory managed group role"))
|
|
|
}
|
|
|
newManagedGrpRoles = append(newManagedGrpRoles, managedGrpRole)
|
|
|
}
|
|
|
scp, err := getRoleScope(ctx, r.reader, roleId)
|
|
|
if err != nil {
|
|
|
return nil, errors.Wrap(ctx, err, op, errors.WithMsg(fmt.Sprintf("unable to get role %s scope", roleId)))
|
|
|
}
|
|
|
|
|
|
oplogWrapper, err := r.kms.GetWrapper(ctx, scp.GetPublicId(), kms.KeyPurposeOplog)
|
|
|
if err != nil {
|
|
|
return nil, errors.Wrap(ctx, err, op, errors.WithMsg("unable to get oplog wrapper"))
|
|
|
}
|
|
|
|
|
|
var currentPrincipals []*PrincipalRole
|
|
|
_, err = r.writer.DoTx(
|
|
|
ctx,
|
|
|
db.StdRetryCnt,
|
|
|
db.ExpBackoff{},
|
|
|
func(reader db.Reader, w db.Writer) error {
|
|
|
msgs := make([]*oplog.Message, 0, 2)
|
|
|
var updatedRole Resource
|
|
|
switch scp.GetType() {
|
|
|
case scope.Global.String():
|
|
|
g := allocGlobalRole()
|
|
|
g.PublicId = roleId
|
|
|
g.Version = roleVersion + 1
|
|
|
updatedRole = &g
|
|
|
case scope.Org.String():
|
|
|
o := allocOrgRole()
|
|
|
o.PublicId = roleId
|
|
|
o.Version = roleVersion + 1
|
|
|
updatedRole = &o
|
|
|
case scope.Project.String():
|
|
|
p := allocProjectRole()
|
|
|
p.PublicId = roleId
|
|
|
p.Version = roleVersion + 1
|
|
|
updatedRole = &p
|
|
|
default:
|
|
|
return errors.New(ctx, errors.InvalidParameter, op, fmt.Sprintf("unknown scope type %s for scope %s", scp.GetType(), scp.GetPublicId()))
|
|
|
}
|
|
|
roleTicket, err := w.GetTicket(ctx, updatedRole)
|
|
|
if err != nil {
|
|
|
return errors.Wrap(ctx, err, op, errors.WithMsg("unable to get ticket"))
|
|
|
}
|
|
|
|
|
|
var roleOplogMsg oplog.Message
|
|
|
rowsUpdated, err := w.Update(ctx, updatedRole, []string{"Version"}, nil, db.NewOplogMsg(&roleOplogMsg), db.WithVersion(&roleVersion))
|
|
|
if err != nil {
|
|
|
return errors.Wrap(ctx, err, op, errors.WithMsg("unable to update role version"))
|
|
|
}
|
|
|
if rowsUpdated != 1 {
|
|
|
return errors.New(ctx, errors.MultipleRecords, op, fmt.Sprintf("updated role and %d rows updated", rowsUpdated))
|
|
|
}
|
|
|
msgs = append(msgs, &roleOplogMsg)
|
|
|
if len(newUserRoles) > 0 {
|
|
|
userOplogMsgs := make([]*oplog.Message, 0, len(newUserRoles))
|
|
|
if err := w.CreateItems(ctx, newUserRoles, db.NewOplogMsgs(&userOplogMsgs)); err != nil {
|
|
|
return errors.Wrap(ctx, err, op, errors.WithMsg("unable to add users"))
|
|
|
}
|
|
|
msgs = append(msgs, userOplogMsgs...)
|
|
|
}
|
|
|
if len(newGrpRoles) > 0 {
|
|
|
grpOplogMsgs := make([]*oplog.Message, 0, len(newGrpRoles))
|
|
|
if err := w.CreateItems(ctx, newGrpRoles, db.NewOplogMsgs(&grpOplogMsgs)); err != nil {
|
|
|
return errors.Wrap(ctx, err, op, errors.WithMsg("unable to add groups"))
|
|
|
}
|
|
|
msgs = append(msgs, grpOplogMsgs...)
|
|
|
}
|
|
|
if len(newManagedGrpRoles) > 0 {
|
|
|
managedGrpOplogMsgs := make([]*oplog.Message, 0, len(newManagedGrpRoles))
|
|
|
if err := w.CreateItems(ctx, newManagedGrpRoles, db.NewOplogMsgs(&managedGrpOplogMsgs)); err != nil {
|
|
|
return errors.Wrap(ctx, err, op, errors.WithMsg("unable to add managed groups"))
|
|
|
}
|
|
|
msgs = append(msgs, managedGrpOplogMsgs...)
|
|
|
}
|
|
|
metadata := oplog.Metadata{
|
|
|
"op-type": []string{oplog.OpType_OP_TYPE_CREATE.String()},
|
|
|
"scope-id": []string{scp.PublicId},
|
|
|
"scope-type": []string{scp.Type},
|
|
|
"resource-public-id": []string{roleId},
|
|
|
}
|
|
|
if err := w.WriteOplogEntryWith(ctx, oplogWrapper, roleTicket, metadata, msgs); err != nil {
|
|
|
return errors.Wrap(ctx, err, op, errors.WithMsg("unable to write oplog"))
|
|
|
}
|
|
|
// we need a new repo, that's using the same reader/writer as this TxHandler
|
|
|
txRepo := &Repository{
|
|
|
reader: reader,
|
|
|
writer: w,
|
|
|
kms: r.kms,
|
|
|
// intentionally not setting the defaultLimit, so we'll get all
|
|
|
// the principal roles without a limit
|
|
|
}
|
|
|
currentPrincipals, err = txRepo.ListPrincipalRoles(ctx, roleId)
|
|
|
if err != nil {
|
|
|
return errors.Wrap(ctx, err, op, errors.WithMsg("unable to retrieve current principal roles after adds"))
|
|
|
}
|
|
|
return nil
|
|
|
},
|
|
|
)
|
|
|
if err != nil {
|
|
|
return nil, errors.Wrap(ctx, err, op)
|
|
|
}
|
|
|
return currentPrincipals, nil
|
|
|
}
|
|
|
|
|
|
// SetPrincipalRoles will set the role's principals. Set add and/or delete
|
|
|
// principals as need to reconcile the existing principals with the principals
|
|
|
// requested. If both userIds and groupIds are empty, the principal roles will
|
|
|
// be cleared. Zero is not a valid value for the WithVersion option and will
|
|
|
// return an error.
|
|
|
func (r *Repository) SetPrincipalRoles(ctx context.Context, roleId string, roleVersion uint32, principalIds []string, _ ...Option) ([]*PrincipalRole, int, error) {
|
|
|
const op = "iam.(Repository).SetPrincipalRoles"
|
|
|
if roleId == "" {
|
|
|
return nil, db.NoRowsAffected, errors.New(ctx, errors.InvalidParameter, op, "missing role id")
|
|
|
}
|
|
|
if roleVersion == 0 {
|
|
|
return nil, db.NoRowsAffected, errors.New(ctx, errors.InvalidParameter, op, "missing version")
|
|
|
}
|
|
|
// it's "safe" to do this lookup outside the DoTx transaction because we
|
|
|
// have a roleVersion so the principals can’t change without the version
|
|
|
// changing.
|
|
|
userIds, groupIds, managedGroupIds, err := splitPrincipals(ctx, principalIds)
|
|
|
if err != nil {
|
|
|
return nil, db.NoRowsAffected, errors.Wrap(ctx, err, op)
|
|
|
}
|
|
|
toSet, err := r.PrincipalsToSet(ctx, &Role{PublicId: roleId}, userIds, groupIds, managedGroupIds)
|
|
|
if err != nil {
|
|
|
return nil, db.NoRowsAffected, errors.Wrap(ctx, err, op)
|
|
|
}
|
|
|
|
|
|
// handle no change to existing principal roles
|
|
|
if len(toSet.UnchangedPrincipalRoles) > 0 {
|
|
|
return toSet.UnchangedPrincipalRoles, db.NoRowsAffected, nil
|
|
|
}
|
|
|
|
|
|
scp, err := getRoleScope(ctx, r.reader, roleId)
|
|
|
if err != nil {
|
|
|
return nil, db.NoRowsAffected, errors.Wrap(ctx, err, op, errors.WithMsg(fmt.Sprintf("unable to get role %s scope", roleId)))
|
|
|
}
|
|
|
oplogWrapper, err := r.kms.GetWrapper(ctx, scp.GetPublicId(), kms.KeyPurposeOplog)
|
|
|
if err != nil {
|
|
|
return nil, db.NoRowsAffected, errors.Wrap(ctx, err, op, errors.WithMsg("unable to get oplog wrapper"))
|
|
|
}
|
|
|
|
|
|
var currentPrincipals []*PrincipalRole
|
|
|
var totalRowsAffected int
|
|
|
_, err = r.writer.DoTx(
|
|
|
ctx,
|
|
|
db.StdRetryCnt,
|
|
|
db.ExpBackoff{},
|
|
|
func(reader db.Reader, w db.Writer) error {
|
|
|
// we need a roleTicket, which won't be redeemed until all the other
|
|
|
// writes are successful. We can't just use a single ticket because
|
|
|
// we need to write oplog entries for deletes and adds
|
|
|
var updatedRole Resource
|
|
|
switch scp.GetType() {
|
|
|
case scope.Global.String():
|
|
|
g := allocGlobalRole()
|
|
|
g.PublicId = roleId
|
|
|
g.Version = roleVersion + 1
|
|
|
updatedRole = &g
|
|
|
case scope.Org.String():
|
|
|
o := allocOrgRole()
|
|
|
o.PublicId = roleId
|
|
|
o.Version = roleVersion + 1
|
|
|
updatedRole = &o
|
|
|
case scope.Project.String():
|
|
|
p := allocProjectRole()
|
|
|
p.PublicId = roleId
|
|
|
p.Version = roleVersion + 1
|
|
|
updatedRole = &p
|
|
|
default:
|
|
|
return errors.New(ctx, errors.InvalidParameter, op, fmt.Sprintf("unknown scope type %s for scope %s", scp.GetType(), scp.GetPublicId()))
|
|
|
}
|
|
|
roleTicket, err := w.GetTicket(ctx, updatedRole)
|
|
|
if err != nil {
|
|
|
return errors.Wrap(ctx, err, op, errors.WithMsg("unable to get ticket for role"))
|
|
|
}
|
|
|
var roleOplogMsg oplog.Message
|
|
|
rowsUpdated, err := w.Update(ctx, updatedRole, []string{"Version"}, nil, db.NewOplogMsg(&roleOplogMsg), db.WithVersion(&roleVersion))
|
|
|
if err != nil {
|
|
|
return errors.Wrap(ctx, err, op)
|
|
|
}
|
|
|
if rowsUpdated != 1 {
|
|
|
return errors.New(ctx, errors.MultipleRecords, op, fmt.Sprintf("updated role and %d rows updated", rowsUpdated))
|
|
|
}
|
|
|
msgs := make([]*oplog.Message, 0, 5)
|
|
|
metadata := oplog.Metadata{
|
|
|
"op-type": []string{oplog.OpType_OP_TYPE_UPDATE.String()},
|
|
|
"scope-id": []string{scp.PublicId},
|
|
|
"scope-type": []string{scp.Type},
|
|
|
"resource-public-id": []string{roleId},
|
|
|
}
|
|
|
msgs = append(msgs, &roleOplogMsg)
|
|
|
|
|
|
if len(toSet.DeleteUserRoles) > 0 ||
|
|
|
len(toSet.DeleteGroupRoles) > 0 ||
|
|
|
len(toSet.DeleteManagedGroupRoles) > 0 {
|
|
|
metadata["op-type"] = append(metadata["op-type"], oplog.OpType_OP_TYPE_DELETE.String())
|
|
|
if len(toSet.DeleteUserRoles) > 0 {
|
|
|
userOplogMsgs := make([]*oplog.Message, 0, len(toSet.DeleteUserRoles))
|
|
|
rowsDeleted, err := w.DeleteItems(ctx, toSet.DeleteUserRoles, db.NewOplogMsgs(&userOplogMsgs))
|
|
|
if err != nil {
|
|
|
return errors.Wrap(ctx, err, op, errors.WithMsg("unable to delete user roles"))
|
|
|
}
|
|
|
if rowsDeleted != len(toSet.DeleteUserRoles) {
|
|
|
return errors.New(ctx, errors.MultipleRecords, op, fmt.Sprintf("user roles deleted %d did not match request for %d", rowsDeleted, len(toSet.DeleteUserRoles)))
|
|
|
}
|
|
|
totalRowsAffected += rowsDeleted
|
|
|
msgs = append(msgs, userOplogMsgs...)
|
|
|
}
|
|
|
if len(toSet.DeleteGroupRoles) > 0 {
|
|
|
grpOplogMsgs := make([]*oplog.Message, 0, len(toSet.DeleteGroupRoles))
|
|
|
rowsDeleted, err := w.DeleteItems(ctx, toSet.DeleteGroupRoles, db.NewOplogMsgs(&grpOplogMsgs))
|
|
|
if err != nil {
|
|
|
return errors.Wrap(ctx, err, op, errors.WithMsg("unable to delete groups"))
|
|
|
}
|
|
|
if rowsDeleted != len(toSet.DeleteGroupRoles) {
|
|
|
return errors.New(ctx, errors.MultipleRecords, op, fmt.Sprintf("group roles deleted %d did not match request for %d", rowsDeleted, len(toSet.DeleteGroupRoles)))
|
|
|
}
|
|
|
totalRowsAffected += rowsDeleted
|
|
|
msgs = append(msgs, grpOplogMsgs...)
|
|
|
}
|
|
|
if len(toSet.DeleteManagedGroupRoles) > 0 {
|
|
|
managedGrpOplogMsgs := make([]*oplog.Message, 0, len(toSet.DeleteManagedGroupRoles))
|
|
|
rowsDeleted, err := w.DeleteItems(ctx, toSet.DeleteManagedGroupRoles, db.NewOplogMsgs(&managedGrpOplogMsgs))
|
|
|
if err != nil {
|
|
|
return errors.Wrap(ctx, err, op, errors.WithMsg("unable to delete managed groups"))
|
|
|
}
|
|
|
if rowsDeleted != len(toSet.DeleteManagedGroupRoles) {
|
|
|
return errors.New(ctx, errors.MultipleRecords, op, fmt.Sprintf("managed group roles deleted %d did not match request for %d", rowsDeleted, len(toSet.DeleteManagedGroupRoles)))
|
|
|
}
|
|
|
totalRowsAffected += rowsDeleted
|
|
|
msgs = append(msgs, managedGrpOplogMsgs...)
|
|
|
}
|
|
|
}
|
|
|
if len(toSet.AddUserRoles) > 0 ||
|
|
|
len(toSet.AddGroupRoles) > 0 ||
|
|
|
len(toSet.AddManagedGroupRoles) > 0 {
|
|
|
metadata["op-type"] = append(metadata["op-type"], oplog.OpType_OP_TYPE_CREATE.String())
|
|
|
if len(toSet.AddUserRoles) > 0 {
|
|
|
userOplogMsgs := make([]*oplog.Message, 0, len(toSet.AddUserRoles))
|
|
|
if err := w.CreateItems(ctx, toSet.AddUserRoles, db.NewOplogMsgs(&userOplogMsgs)); err != nil {
|
|
|
return errors.Wrap(ctx, err, op, errors.WithMsg("unable to add users"))
|
|
|
}
|
|
|
totalRowsAffected += len(toSet.AddUserRoles)
|
|
|
msgs = append(msgs, userOplogMsgs...)
|
|
|
}
|
|
|
if len(toSet.AddGroupRoles) > 0 {
|
|
|
grpOplogMsgs := make([]*oplog.Message, 0, len(toSet.AddGroupRoles))
|
|
|
if err := w.CreateItems(ctx, toSet.AddGroupRoles, db.NewOplogMsgs(&grpOplogMsgs)); err != nil {
|
|
|
return errors.Wrap(ctx, err, op, errors.WithMsg("unable to add groups"))
|
|
|
}
|
|
|
totalRowsAffected += len(toSet.AddGroupRoles)
|
|
|
msgs = append(msgs, grpOplogMsgs...)
|
|
|
}
|
|
|
if len(toSet.AddManagedGroupRoles) > 0 {
|
|
|
managedGrpOplogMsgs := make([]*oplog.Message, 0, len(toSet.AddManagedGroupRoles))
|
|
|
if err := w.CreateItems(ctx, toSet.AddManagedGroupRoles, db.NewOplogMsgs(&managedGrpOplogMsgs)); err != nil {
|
|
|
return errors.Wrap(ctx, err, op, errors.WithMsg("unable to add managed groups"))
|
|
|
}
|
|
|
totalRowsAffected += len(toSet.AddManagedGroupRoles)
|
|
|
msgs = append(msgs, managedGrpOplogMsgs...)
|
|
|
}
|
|
|
}
|
|
|
if err := w.WriteOplogEntryWith(ctx, oplogWrapper, roleTicket, metadata, msgs); err != nil {
|
|
|
return errors.Wrap(ctx, err, op, errors.WithMsg("unable to write oplog"))
|
|
|
}
|
|
|
// we need a new repo, that's using the same reader/writer as this TxHandler
|
|
|
txRepo := &Repository{
|
|
|
reader: reader,
|
|
|
writer: w,
|
|
|
kms: r.kms,
|
|
|
// intentionally not setting the defaultLimit, so we'll get all
|
|
|
// the principal roles without a limit
|
|
|
}
|
|
|
currentPrincipals, err = txRepo.ListPrincipalRoles(ctx, roleId)
|
|
|
if err != nil {
|
|
|
return errors.Wrap(ctx, err, op, errors.WithMsg("unable to retrieve current principal roles after sets"))
|
|
|
}
|
|
|
return nil
|
|
|
})
|
|
|
if err != nil {
|
|
|
return nil, db.NoRowsAffected, errors.Wrap(ctx, err, op)
|
|
|
}
|
|
|
return currentPrincipals, totalRowsAffected, nil
|
|
|
}
|
|
|
|
|
|
// DeletePrincipalRoles principals (userIds and/or groupIds) from a role
|
|
|
// (roleId). The role's current db version must match the roleVersion or an
|
|
|
// error will be returned. Zero is not a valid value for the WithVersion option
|
|
|
// and will return an error.
|
|
|
func (r *Repository) DeletePrincipalRoles(ctx context.Context, roleId string, roleVersion uint32, principalIds []string, _ ...Option) (int, error) {
|
|
|
const op = "iam.(Repository).DeletePrincipalRoles"
|
|
|
if roleId == "" {
|
|
|
return db.NoRowsAffected, errors.New(ctx, errors.InvalidParameter, op, "missing role id")
|
|
|
}
|
|
|
userIds, groupIds, managedGroupIds, err := splitPrincipals(ctx, principalIds)
|
|
|
if err != nil {
|
|
|
return db.NoRowsAffected, errors.Wrap(ctx, err, op)
|
|
|
}
|
|
|
if len(userIds) == 0 && len(groupIds) == 0 && len(managedGroupIds) == 0 {
|
|
|
return db.NoRowsAffected, errors.New(ctx, errors.InvalidParameter, op, "missing any of users, groups, or managed groups to delete")
|
|
|
}
|
|
|
if roleVersion == 0 {
|
|
|
return db.NoRowsAffected, errors.New(ctx, errors.InvalidParameter, op, "missing version")
|
|
|
}
|
|
|
|
|
|
var roleResource Resource
|
|
|
scp, err := getRoleScope(ctx, r.reader, roleId)
|
|
|
if err != nil {
|
|
|
return db.NoRowsAffected, errors.Wrap(ctx, err, op, errors.WithMsg(fmt.Sprintf("unable to get role %s scope", roleId)))
|
|
|
}
|
|
|
deleteUserRoles := make([]*UserRole, 0, len(userIds))
|
|
|
for _, id := range userIds {
|
|
|
usrRole, err := NewUserRole(ctx, roleId, id)
|
|
|
if err != nil {
|
|
|
return db.NoRowsAffected, errors.Wrap(ctx, err, op, errors.WithMsg("unable to create in memory user role"))
|
|
|
}
|
|
|
deleteUserRoles = append(deleteUserRoles, usrRole)
|
|
|
}
|
|
|
deleteGrpRoles := make([]*GroupRole, 0, len(groupIds))
|
|
|
for _, id := range groupIds {
|
|
|
grpRole, err := NewGroupRole(ctx, roleId, id)
|
|
|
if err != nil {
|
|
|
return db.NoRowsAffected, errors.Wrap(ctx, err, op, errors.WithMsg("unable to create in memory group role"))
|
|
|
}
|
|
|
deleteGrpRoles = append(deleteGrpRoles, grpRole)
|
|
|
}
|
|
|
deleteManagedGrpRoles := make([]*ManagedGroupRole, 0, len(managedGroupIds))
|
|
|
for _, id := range managedGroupIds {
|
|
|
managedGrpRole, err := NewManagedGroupRole(ctx, roleId, id)
|
|
|
if err != nil {
|
|
|
return db.NoRowsAffected, errors.Wrap(ctx, err, op, errors.WithMsg("unable to create in memory managed group role"))
|
|
|
}
|
|
|
deleteManagedGrpRoles = append(deleteManagedGrpRoles, managedGrpRole)
|
|
|
}
|
|
|
|
|
|
oplogWrapper, err := r.kms.GetWrapper(ctx, scp.GetPublicId(), kms.KeyPurposeOplog)
|
|
|
if err != nil {
|
|
|
return db.NoRowsAffected, errors.Wrap(ctx, err, op, errors.WithMsg("unable to get oplog wrapper"))
|
|
|
}
|
|
|
|
|
|
var totalRowsDeleted int
|
|
|
_, err = r.writer.DoTx(
|
|
|
ctx,
|
|
|
db.StdRetryCnt,
|
|
|
db.ExpBackoff{},
|
|
|
func(reader db.Reader, w db.Writer) error {
|
|
|
msgs := make([]*oplog.Message, 0, 2)
|
|
|
var updatedRole Resource
|
|
|
switch scp.Type {
|
|
|
case scope.Global.String():
|
|
|
g := allocGlobalRole()
|
|
|
g.PublicId = roleId
|
|
|
g.Version = roleVersion + 1
|
|
|
updatedRole = &g
|
|
|
case scope.Org.String():
|
|
|
o := allocOrgRole()
|
|
|
o.PublicId = roleId
|
|
|
o.Version = roleVersion + 1
|
|
|
updatedRole = &o
|
|
|
case scope.Project.String():
|
|
|
p := allocProjectRole()
|
|
|
p.PublicId = roleId
|
|
|
p.Version = roleVersion + 1
|
|
|
updatedRole = &p
|
|
|
default:
|
|
|
return errors.New(ctx, errors.InvalidParameter, op, fmt.Sprintf("unknown role resource type %T", roleResource))
|
|
|
}
|
|
|
roleTicket, err := w.GetTicket(ctx, updatedRole)
|
|
|
if err != nil {
|
|
|
return errors.Wrap(ctx, err, op, errors.WithMsg("unable to get ticket"))
|
|
|
}
|
|
|
var roleOplogMsg oplog.Message
|
|
|
rowsUpdated, err := w.Update(ctx, updatedRole, []string{"Version"}, nil, db.NewOplogMsg(&roleOplogMsg), db.WithVersion(&roleVersion))
|
|
|
if err != nil {
|
|
|
return errors.Wrap(ctx, err, op, errors.WithMsg("unable to update role version"))
|
|
|
}
|
|
|
if rowsUpdated != 1 {
|
|
|
return errors.New(ctx, errors.MultipleRecords, op, fmt.Sprintf("updated role and %d rows updated", rowsUpdated))
|
|
|
}
|
|
|
msgs = append(msgs, &roleOplogMsg)
|
|
|
if len(deleteUserRoles) > 0 {
|
|
|
userOplogMsgs := make([]*oplog.Message, 0, len(deleteUserRoles))
|
|
|
rowsDeleted, err := w.DeleteItems(ctx, deleteUserRoles, db.NewOplogMsgs(&userOplogMsgs))
|
|
|
if err != nil {
|
|
|
return errors.Wrap(ctx, err, op, errors.WithMsg("unable to delete user roles"))
|
|
|
}
|
|
|
if rowsDeleted != len(deleteUserRoles) {
|
|
|
return errors.New(ctx, errors.MultipleRecords, op, fmt.Sprintf("user roles deleted %d did not match request for %d", rowsDeleted, len(deleteUserRoles)))
|
|
|
}
|
|
|
totalRowsDeleted += rowsDeleted
|
|
|
msgs = append(msgs, userOplogMsgs...)
|
|
|
}
|
|
|
if len(deleteGrpRoles) > 0 {
|
|
|
grpOplogMsgs := make([]*oplog.Message, 0, len(deleteGrpRoles))
|
|
|
rowsDeleted, err := w.DeleteItems(ctx, deleteGrpRoles, db.NewOplogMsgs(&grpOplogMsgs))
|
|
|
if err != nil {
|
|
|
return errors.Wrap(ctx, err, op, errors.WithMsg("unable to delete groups"))
|
|
|
}
|
|
|
if rowsDeleted != len(deleteGrpRoles) {
|
|
|
return errors.New(ctx, errors.MultipleRecords, op, fmt.Sprintf("group roles deleted %d did not match request for %d", rowsDeleted, len(deleteGrpRoles)))
|
|
|
}
|
|
|
totalRowsDeleted += rowsDeleted
|
|
|
msgs = append(msgs, grpOplogMsgs...)
|
|
|
}
|
|
|
if len(deleteManagedGrpRoles) > 0 {
|
|
|
managedGrpOplogMsgs := make([]*oplog.Message, 0, len(deleteManagedGrpRoles))
|
|
|
rowsDeleted, err := w.DeleteItems(ctx, deleteManagedGrpRoles, db.NewOplogMsgs(&managedGrpOplogMsgs))
|
|
|
if err != nil {
|
|
|
return errors.Wrap(ctx, err, op, errors.WithMsg("unable to delete managed groups"))
|
|
|
}
|
|
|
if rowsDeleted != len(deleteManagedGrpRoles) {
|
|
|
return errors.New(ctx, errors.MultipleRecords, op, fmt.Sprintf("managed group roles deleted %d did not match request for %d", rowsDeleted, len(deleteManagedGrpRoles)))
|
|
|
}
|
|
|
totalRowsDeleted += rowsDeleted
|
|
|
msgs = append(msgs, managedGrpOplogMsgs...)
|
|
|
}
|
|
|
metadata := oplog.Metadata{
|
|
|
"op-type": []string{oplog.OpType_OP_TYPE_DELETE.String()},
|
|
|
"scope-id": []string{scp.PublicId},
|
|
|
"scope-type": []string{scp.Type},
|
|
|
"resource-public-id": []string{roleId},
|
|
|
}
|
|
|
if err := w.WriteOplogEntryWith(ctx, oplogWrapper, roleTicket, metadata, msgs); err != nil {
|
|
|
return errors.Wrap(ctx, err, op, errors.WithMsg("unable to write oplog"))
|
|
|
}
|
|
|
return nil
|
|
|
},
|
|
|
)
|
|
|
if err != nil {
|
|
|
return db.NoRowsAffected, errors.Wrap(ctx, err, op)
|
|
|
}
|
|
|
return totalRowsDeleted, nil
|
|
|
}
|
|
|
|
|
|
// ListPrincipalRoles returns the principal roles for the roleId and supports the WithLimit option.
|
|
|
func (r *Repository) ListPrincipalRoles(ctx context.Context, roleId string, opt ...Option) ([]*PrincipalRole, error) {
|
|
|
const op = "iam.(Repository).ListPrincipalRoles"
|
|
|
if roleId == "" {
|
|
|
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing role id")
|
|
|
}
|
|
|
var roles []*PrincipalRole
|
|
|
if err := r.list(ctx, &roles, "role_id = ?", []any{roleId}, opt...); err != nil {
|
|
|
return nil, errors.Wrap(ctx, err, op, errors.WithMsg("unable to lookup roles"))
|
|
|
}
|
|
|
principals := make([]*PrincipalRole, 0, len(roles))
|
|
|
principals = append(principals, roles...)
|
|
|
return principals, nil
|
|
|
}
|
|
|
|
|
|
type PrincipalSet struct {
|
|
|
AddUserRoles []*UserRole
|
|
|
AddGroupRoles []*GroupRole
|
|
|
AddManagedGroupRoles []*ManagedGroupRole
|
|
|
DeleteUserRoles []*UserRole
|
|
|
DeleteGroupRoles []*GroupRole
|
|
|
DeleteManagedGroupRoles []*ManagedGroupRole
|
|
|
// unchangedPrincipalRoles is set iff there are no changes, that is, the
|
|
|
// length of all other members is zero
|
|
|
UnchangedPrincipalRoles []*PrincipalRole
|
|
|
}
|
|
|
|
|
|
// TODO: Should this be moved inside the transaction, at this point?
|
|
|
// PrincipalsToSet sets principals on a role from the given lists.
|
|
|
func (r *Repository) PrincipalsToSet(ctx context.Context, role *Role, userIds, groupIds, managedGroupIds []string) (*PrincipalSet, error) {
|
|
|
const op = "iam.(Repository).PrincipalsToSet"
|
|
|
// TODO(mgaffney) 08/2020: Use SQL to calculate changes.
|
|
|
if role == nil {
|
|
|
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing role")
|
|
|
}
|
|
|
existing, err := r.ListPrincipalRoles(ctx, role.PublicId)
|
|
|
if err != nil {
|
|
|
return nil, errors.Wrap(ctx, err, op, errors.WithMsg(fmt.Sprintf("unable to list existing principal role %s", role.PublicId)))
|
|
|
}
|
|
|
existingUsers := map[string]*PrincipalRole{}
|
|
|
existingGroups := map[string]*PrincipalRole{}
|
|
|
existingManagedGroups := map[string]*PrincipalRole{}
|
|
|
for _, p := range existing {
|
|
|
switch p.GetType() {
|
|
|
case UserRoleType.String():
|
|
|
existingUsers[p.PrincipalId] = p
|
|
|
case GroupRoleType.String():
|
|
|
existingGroups[p.PrincipalId] = p
|
|
|
case ManagedGroupRoleType.String():
|
|
|
existingManagedGroups[p.PrincipalId] = p
|
|
|
default:
|
|
|
return nil, errors.New(ctx, errors.InvalidParameter, op, fmt.Sprintf("%s is unknown principal type %s", p.PrincipalId, p.GetType()))
|
|
|
}
|
|
|
}
|
|
|
var newUserRoles []*UserRole
|
|
|
userIdsMap := map[string]struct{}{}
|
|
|
for _, id := range userIds {
|
|
|
userIdsMap[id] = struct{}{}
|
|
|
if _, ok := existingUsers[id]; !ok {
|
|
|
usrRole, err := NewUserRole(ctx, role.PublicId, id)
|
|
|
if err != nil {
|
|
|
return nil, errors.Wrap(ctx, err, op, errors.WithMsg("unable to create in memory user role for add"))
|
|
|
}
|
|
|
newUserRoles = append(newUserRoles, usrRole)
|
|
|
}
|
|
|
}
|
|
|
var newGrpRoles []*GroupRole
|
|
|
groupIdsMap := map[string]struct{}{}
|
|
|
for _, id := range groupIds {
|
|
|
groupIdsMap[id] = struct{}{}
|
|
|
if _, ok := existingGroups[id]; !ok {
|
|
|
grpRole, err := NewGroupRole(ctx, role.PublicId, id)
|
|
|
if err != nil {
|
|
|
return nil, errors.Wrap(ctx, err, op, errors.WithMsg("unable to create in memory group role for add"))
|
|
|
}
|
|
|
newGrpRoles = append(newGrpRoles, grpRole)
|
|
|
}
|
|
|
}
|
|
|
var newManagedGrpRoles []*ManagedGroupRole
|
|
|
managedGroupIdsMap := map[string]struct{}{}
|
|
|
for _, id := range managedGroupIds {
|
|
|
managedGroupIdsMap[id] = struct{}{}
|
|
|
if _, ok := existingManagedGroups[id]; !ok {
|
|
|
managedGrpRole, err := NewManagedGroupRole(ctx, role.PublicId, id)
|
|
|
if err != nil {
|
|
|
return nil, errors.Wrap(ctx, err, op, errors.WithMsg("unable to create in memory managed group role for add"))
|
|
|
}
|
|
|
newManagedGrpRoles = append(newManagedGrpRoles, managedGrpRole)
|
|
|
}
|
|
|
}
|
|
|
var deleteUserRoles []*UserRole
|
|
|
for _, p := range existingUsers {
|
|
|
if _, ok := userIdsMap[p.PrincipalId]; !ok {
|
|
|
usrRole, err := NewUserRole(ctx, p.GetRoleId(), p.GetPrincipalId())
|
|
|
if err != nil {
|
|
|
return nil, errors.Wrap(ctx, err, op, errors.WithMsg("unable to create in memory user role for delete"))
|
|
|
}
|
|
|
deleteUserRoles = append(deleteUserRoles, usrRole)
|
|
|
}
|
|
|
}
|
|
|
var deleteGrpRoles []*GroupRole
|
|
|
for _, p := range existingGroups {
|
|
|
if _, ok := groupIdsMap[p.PrincipalId]; !ok {
|
|
|
grpRole, err := NewGroupRole(ctx, p.GetRoleId(), p.GetPrincipalId())
|
|
|
if err != nil {
|
|
|
return nil, errors.Wrap(ctx, err, op, errors.WithMsg("unable to create in memory group role for delete"))
|
|
|
}
|
|
|
deleteGrpRoles = append(deleteGrpRoles, grpRole)
|
|
|
}
|
|
|
}
|
|
|
var deleteManagedGrpRoles []*ManagedGroupRole
|
|
|
for _, p := range existingManagedGroups {
|
|
|
if _, ok := managedGroupIdsMap[p.PrincipalId]; !ok {
|
|
|
managedGrpRole, err := NewManagedGroupRole(ctx, p.GetRoleId(), p.GetPrincipalId())
|
|
|
if err != nil {
|
|
|
return nil, errors.Wrap(ctx, err, op, errors.WithMsg("unable to create in memory managed group role for delete"))
|
|
|
}
|
|
|
deleteManagedGrpRoles = append(deleteManagedGrpRoles, managedGrpRole)
|
|
|
}
|
|
|
}
|
|
|
|
|
|
toSet := &PrincipalSet{
|
|
|
AddUserRoles: newUserRoles,
|
|
|
AddGroupRoles: newGrpRoles,
|
|
|
AddManagedGroupRoles: newManagedGrpRoles,
|
|
|
DeleteUserRoles: deleteUserRoles,
|
|
|
DeleteGroupRoles: deleteGrpRoles,
|
|
|
DeleteManagedGroupRoles: deleteManagedGrpRoles,
|
|
|
}
|
|
|
|
|
|
if len(toSet.AddUserRoles) == 0 &&
|
|
|
len(toSet.AddGroupRoles) == 0 &&
|
|
|
len(toSet.AddManagedGroupRoles) == 0 &&
|
|
|
len(toSet.DeleteUserRoles) == 0 &&
|
|
|
len(toSet.DeleteGroupRoles) == 0 &&
|
|
|
len(toSet.DeleteManagedGroupRoles) == 0 {
|
|
|
toSet.UnchangedPrincipalRoles = existing
|
|
|
}
|
|
|
|
|
|
return toSet, nil
|
|
|
}
|
|
|
|
|
|
func splitPrincipals(ctx context.Context, principals []string) (users, groups, managedGroups []string, retErr error) {
|
|
|
const op = "iam.splitPrincipals"
|
|
|
for _, principal := range principals {
|
|
|
switch {
|
|
|
case strings.HasPrefix(principal, globals.UserPrefix):
|
|
|
if users == nil {
|
|
|
users = make([]string, 0, len(principals))
|
|
|
}
|
|
|
users = append(users, principal)
|
|
|
case strings.HasPrefix(principal, globals.GroupPrefix):
|
|
|
if groups == nil {
|
|
|
groups = make([]string, 0, len(principals))
|
|
|
}
|
|
|
groups = append(groups, principal)
|
|
|
case strings.HasPrefix(principal, globals.OidcManagedGroupPrefix):
|
|
|
if managedGroups == nil {
|
|
|
managedGroups = make([]string, 0, len(principals))
|
|
|
}
|
|
|
managedGroups = append(managedGroups, principal)
|
|
|
case strings.HasPrefix(principal, globals.LdapManagedGroupPrefix):
|
|
|
if managedGroups == nil {
|
|
|
managedGroups = make([]string, 0, len(principals))
|
|
|
}
|
|
|
managedGroups = append(managedGroups, principal)
|
|
|
default:
|
|
|
return nil, nil, nil, errors.New(ctx, errors.InvalidParameter, op, fmt.Sprintf("invalid principal ID %q", principal))
|
|
|
}
|
|
|
}
|
|
|
|
|
|
return users, groups, managedGroups, retErr
|
|
|
}
|