SetPrincipalRoles builds.. needs tests

pull/165/head
Jim Lambert 6 years ago
parent a0fd978a1b
commit 9c2b65f772

@ -110,25 +110,250 @@ func (r *Repository) AddPrincipalRoles(ctx context.Context, roleId string, roleV
return principalRoles, nil
}
func (r *Repository) SetPrincipalRoles(ctx context.Context, roleId string, userIds, groupIds []string, opt ...Option) ([]PrincipalRole, int, error) {
panic("not implemented")
}
// tests: no change, just delete, just add
// 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) {
func (r *Repository) SetPrincipalRoles(ctx context.Context, roleId string, roleVersion int, userIds, groupIds []string, opt ...Option) ([]PrincipalRole, int, error) {
if roleId == "" {
return nil, fmt.Errorf("lookup principal roles: missing role id %w", db.ErrInvalidParameter)
return nil, db.NoRowsAffected, fmt.Errorf("set principal roles: missing role id: %w", db.ErrInvalidParameter)
}
var roles []principalRoleView
if err := r.list(ctx, &roles, "role_id = ?", []interface{}{roleId}, opt...); err != nil {
return nil, fmt.Errorf("lookup principal role: unable to lookup roles: %w", err)
if len(userIds) == 0 && len(groupIds) == 0 {
return nil, db.NoRowsAffected, fmt.Errorf("set principal roles: missing either user or groups to delete %w", db.ErrInvalidParameter)
}
principals := make([]PrincipalRole, 0, len(roles))
for _, r := range roles {
principals = append(principals, r)
toSet, err := r.principalsToSet(ctx, roleId, userIds, groupIds)
if err != nil {
return nil, db.NoRowsAffected, fmt.Errorf("set principal roles: unable to determine set: %w", err)
}
return principals, nil
role := allocRole()
role.PublicId = roleId
scope, err := role.GetScope(ctx, r.reader)
if err != nil {
return nil, db.NoRowsAffected, fmt.Errorf("set principal roles: unable to get role %s scope: %w", roleId, err)
}
// handle no change to existing principal roles
if len(toSet.addUserRoles) == 0 && len(toSet.addGroupRoles) == 0 && len(toSet.deleteUserRoles) == 0 && len(toSet.deleteGroupRoles) == 0 {
results := make([]PrincipalRole, 0, len(userIds)+len(groupIds))
for _, id := range userIds {
role, err := NewUserRole(scope.PublicId, roleId, id)
if err != nil {
return nil, db.NoRowsAffected, fmt.Errorf("add principal roles: unable to create in memory user role: %w", err)
}
results = append(results, role)
}
for _, id := range groupIds {
role, err := NewGroupRole(scope.PublicId, roleId, id)
if err != nil {
return nil, db.NoRowsAffected, fmt.Errorf("add principal roles: unable to create in memory group role: %w", err)
}
results = append(results, role)
}
return results, db.NoRowsAffected, nil
}
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
roleTicket, err := w.GetTicket(role)
if err != nil {
return fmt.Errorf("set principal roles: unable to get ticket for role: %w", err)
}
updatedRole := allocRole()
updatedRole.PublicId = roleId
updatedRole.Version = uint32(roleVersion) + 1
var roleOplogMsg oplog.Message
rowsUpdated, err := w.Update(ctx, &updatedRole, []string{"Version"}, nil, db.NewOplogMsg(&roleOplogMsg), db.WithVersion(roleVersion))
if err != nil {
return fmt.Errorf("set principal roles: unable to update role version: %w", err)
}
if rowsUpdated != 1 {
return fmt.Errorf("set principal roles: updated role and %d rows updated", rowsUpdated)
}
if len(toSet.deleteUserRoles) > 0 || len(toSet.deleteGroupRoles) > 0 {
msgs := make([]*oplog.Message, 0, 2)
// deleteTicket which will be redeemed when we write the oplog
// for these deletes
deleteTicket, err := w.GetTicket(&principalRoleView{})
if err != nil {
return fmt.Errorf("set principal roles: unable to get ticket for principal role deletes: %w", err)
}
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 fmt.Errorf("set principal roles: unable to delete user roles: %w", err)
}
if rowsDeleted != len(toSet.deleteUserRoles) {
return fmt.Errorf("set principal roles: 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 fmt.Errorf("set principal roles: unable to delete groups: %w", err)
}
if rowsDeleted != len(toSet.deleteGroupRoles) {
return fmt.Errorf("set principal roles: group roles deleted %d did not match request for %d", rowsDeleted, len(toSet.deleteGroupRoles))
}
totalRowsAffected += rowsDeleted
msgs = append(msgs, grpOplogMsgs...)
}
metadata := oplog.Metadata{
"op-type": []string{oplog.OpType_OP_TYPE_DELETE.String()},
"scope-id": []string{scope.PublicId},
"scope-type": []string{scope.Type},
"resource-public-id": []string{roleId},
}
// write the oplog msgs for the deletes
if err := w.WriteOplogEntryWith(ctx, r.wrapper, deleteTicket, metadata, msgs); err != nil {
return fmt.Errorf("set principal roles: unable to write oplog for deletes: %w", err)
}
}
if len(toSet.addUserRoles) > 0 || len(toSet.addGroupRoles) > 0 {
msgs := make([]*oplog.Message, 0, 2)
// addTicket which will be redeemed when we write the oplog
// entry for these writes.
addTicket, err := w.GetTicket(&principalRoleView{})
if err != nil {
return fmt.Errorf("set principal roles: unable to get ticket for principal role additions: %w", err)
}
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 fmt.Errorf("set principal roles: unable to add users: %w", err)
}
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 fmt.Errorf("set principal roles: unable to add groups: %w", err)
}
msgs = append(msgs, grpOplogMsgs...)
}
metadata := oplog.Metadata{
"op-type": []string{oplog.OpType_OP_TYPE_CREATE.String()},
"scope-id": []string{scope.PublicId},
"scope-type": []string{scope.Type},
"resource-public-id": []string{roleId},
}
// write the oplog msgs for the additions
if err := w.WriteOplogEntryWith(ctx, r.wrapper, addTicket, metadata, msgs); err != nil {
return fmt.Errorf("set principal roles: unable to write oplog for additions: %w", err)
}
}
// we're done with all the principal writes, so let's write the
// role's update oplog message
metadata := oplog.Metadata{
"op-type": []string{oplog.OpType_OP_TYPE_UPDATE.String()},
"scope-id": []string{scope.PublicId},
"scope-type": []string{scope.Type},
"resource-public-id": []string{roleId},
}
if err := w.WriteOplogEntryWith(ctx, r.wrapper, roleTicket, metadata, []*oplog.Message{&roleOplogMsg}); err != nil {
return fmt.Errorf("set principal roles: unable to write oplog for additions: %w", err)
}
currentPrincipals, err = r.ListPrincipalRoles(ctx, roleId)
if err != nil {
return fmt.Errorf("set principal roles: unable to retrieve current principal roles after sets: %w", err)
}
return nil
})
if err != nil {
return nil, db.NoRowsAffected, fmt.Errorf("set principal roles: unable to set principals: %w", err)
}
return currentPrincipals, totalRowsAffected, nil
}
type principalSet struct {
addUserRoles []interface{}
addGroupRoles []interface{}
deleteUserRoles []interface{}
deleteGroupRoles []interface{}
}
func (r *Repository) principalsToSet(ctx context.Context, roleId string, userIds, groupIds []string) (*principalSet, error) {
existing, err := r.ListPrincipalRoles(ctx, roleId)
if err != nil {
return nil, fmt.Errorf("unable to list existing principal role %s: %w", roleId, err)
}
existingUsers := map[string]PrincipalRole{}
existingGroups := map[string]PrincipalRole{}
for _, p := range existing {
switch p.GetType() {
case UserRoleType.String():
existingUsers[p.GetPrincipalId()] = p
case GroupRoleType.String():
existingGroups[p.GetPrincipalId()] = p
default:
return nil, fmt.Errorf("%s is unknown principal type %s", p.GetPrincipalId(), p.GetType())
}
}
var newUserRoles []interface{}
userIdsMap := map[string]struct{}{}
for _, id := range userIds {
userIdsMap[id] = struct{}{}
if p, ok := existingUsers[id]; !ok {
usrRole, err := NewUserRole(p.GetScopeId(), p.GetRoleId(), id)
if err != nil {
return nil, fmt.Errorf("unable to create in memory user role for add: %w", err)
}
newUserRoles = append(newUserRoles, usrRole)
}
}
var newGrpRoles []interface{}
groupIdsMap := map[string]struct{}{}
for _, id := range groupIds {
groupIdsMap[id] = struct{}{}
if p, ok := existingGroups[id]; !ok {
grpRole, err := NewGroupRole(p.GetScopeId(), p.GetRoleId(), id)
if err != nil {
return nil, fmt.Errorf("unable to create in memory group role for add: %w", err)
}
newGrpRoles = append(newGrpRoles, grpRole)
}
}
var deleteUserRoles []interface{}
for _, p := range existingUsers {
if _, ok := userIdsMap[p.GetPrincipalId()]; !ok {
usrRole, err := NewUserRole(p.GetScopeId(), p.GetRoleId(), p.GetPrincipalId())
if err != nil {
return nil, fmt.Errorf("unable to create in memory user role for delete: %w", err)
}
deleteUserRoles = append(deleteUserRoles, usrRole)
}
}
var deleteGrpRoles []interface{}
for _, p := range existingGroups {
if _, ok := userIdsMap[p.GetPrincipalId()]; !ok {
grpRole, err := NewGroupRole(p.GetScopeId(), p.GetRoleId(), p.GetPrincipalId())
if err != nil {
return nil, fmt.Errorf("unable to create in memory group role for delete: %w", err)
}
deleteGrpRoles = append(deleteGrpRoles, grpRole)
}
}
return &principalSet{
addUserRoles: newUserRoles,
addGroupRoles: newGrpRoles,
deleteUserRoles: deleteUserRoles,
deleteGroupRoles: deleteGrpRoles,
}, 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.
func (r *Repository) DeletePrincipalRoles(ctx context.Context, roleId string, roleVersion int, userIds, groupIds []string, opt ...Option) (int, error) {
if roleId == "" {
return db.NoRowsAffected, fmt.Errorf("delete principal roles: missing role id: %w", db.ErrInvalidParameter)
@ -219,7 +444,23 @@ func (r *Repository) DeletePrincipalRoles(ctx context.Context, roleId string, ro
},
)
if err != nil {
return db.NoRowsAffected, fmt.Errorf("delete principal roles: error deleting roles: %w", err)
return db.NoRowsAffected, fmt.Errorf("delete principal roles: error deleting principal roles: %w", err)
}
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) {
if roleId == "" {
return nil, fmt.Errorf("lookup principal roles: missing role id %w", db.ErrInvalidParameter)
}
var roles []principalRoleView
if err := r.list(ctx, &roles, "role_id = ?", []interface{}{roleId}, opt...); err != nil {
return nil, fmt.Errorf("lookup principal role: unable to lookup roles: %w", err)
}
principals := make([]PrincipalRole, 0, len(roles))
for _, r := range roles {
principals = append(principals, r)
}
return principals, nil
}

Loading…
Cancel
Save