From 5df34caf3ab90ca8ba69c3add989ba1bc41528b4 Mon Sep 17 00:00:00 2001 From: Jim Lambert Date: Tue, 30 Jun 2020 10:12:32 -0400 Subject: [PATCH] intial implementation for AddPrincipalRoles and ListPrincipalRoles --- internal/iam/repository_principal_role.go | 126 +++++++++++++++------- 1 file changed, 85 insertions(+), 41 deletions(-) diff --git a/internal/iam/repository_principal_role.go b/internal/iam/repository_principal_role.go index 23d8436285..1639558af8 100644 --- a/internal/iam/repository_principal_role.go +++ b/internal/iam/repository_principal_role.go @@ -8,79 +8,123 @@ import ( "github.com/hashicorp/watchtower/internal/oplog" ) -func (r *Repository) AddPrincipalRoles(ctx context.Context, roleId string, userIds, groupIds []string, opt ...Option) ([]PrincipalRole, error) { +// 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 role, users and groups must +// all be in the same scope +func (r *Repository) AddPrincipalRoles(ctx context.Context, roleId string, roleVersion int, userIds, groupIds []string, opt ...Option) ([]PrincipalRole, error) { + // NOTE - we are intentionally not going to check that the scopes are + // correct for the userIds and groupIds, given the roleId. We are going to + // rely on the database constraints and triggers to maintain the integrity + // of these scope relationships. The users and role need to either be in + // the same organization or the role needs to be in a project of the user's + // org. The groups and role have to be in the same scope (org or project). + // There are constraints and triggers to enforce these relationships. if roleId == "" { - return nil, fmt.Errorf("add principal role: missing role id %w", db.ErrInvalidParameter) + return nil, fmt.Errorf("add principal roles: missing role id %w", db.ErrInvalidParameter) } if len(userIds) == 0 && len(groupIds) == 0 { return nil, fmt.Errorf("add principal roles: missing either user or groups to add %w", db.ErrInvalidParameter) } + role := allocRole() + role.PublicId = roleId + scope, err := role.GetScope(ctx, r.reader) + if err != nil { + return nil, fmt.Errorf("add principal roles: unable to get role %s scope to create metadata: %w", roleId, err) + } + 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}, + } newUserRoles := make([]interface{}, 0, len(userIds)) for _, id := range userIds { - userRoles, err := NewUserRole(roleId, id) + usrRole, err := NewUserRole(scope.PublicId, roleId, id) if err != nil { - panic(err.Error()) + return nil, fmt.Errorf("add principal roles: unable to create new in memory user role: %w", err) } - newUserRoles = append(newUserRoles, userRoles) + newUserRoles = append(newUserRoles, usrRole) } - newGrpRoles := make([]PrincipalRole, 0, len(groupIds)) + newGrpRoles := make([]interface{}, 0, len(groupIds)) for _, id := range groupIds { - grpRole, err := NewGroupRole(roleId, id) + grpRole, err := NewGroupRole(scope.PublicId, roleId, id) if err != nil { - panic(err.Error()) + return nil, fmt.Errorf("add principal roles: unable to create new in memory group role: %w", err) } newGrpRoles = append(newGrpRoles, grpRole) } - role := allocRole() - role.PublicId = roleId - scope, err := role.GetScope(ctx, r.reader) - if err != nil { - return nil, fmt.Errorf("add principal roles: unable to get role %s scope to create metadata: %w", roleId, err) - } - metadata := oplog.Metadata{ - "op-type": []string{oplog.OpType_OP_TYPE_CREATE.String()}, - "scope-id": []string{scope.PublicId}, - "scope-type": []string{scope.Type}, - "aggregate-resource-public-id": []string{roleId}, - } - resultPrincipalRoles := make([]PrincipalRole, 0, len(userIds)+len(groupIds)) + _, err = r.writer.DoTx( ctx, db.StdRetryCnt, db.ExpBackoff{}, func(reader db.Reader, w db.Writer) error { - w.CreateItems(ctx, newUserRoles) - // for _, principalRole := range newPrincipalRoles { - // returnedPrincipalRole := principalRole.Clone() - // err := w.Create( - // ctx, - // returnedPrincipalRole, - // db.WithOplog(r.wrapper, metadata), - // ) - // if err != nil { - // if db.IsUniqueError(err) { - // return fmt.Errorf("add principal role: unable to add principal %s to role %s : %w", principalRole.GetPrincipalId(), roleId, db.ErrNotUnique) - // } - // return fmt.Errorf("add principal role: %w when attempting to add principal %s to role %s", err, principalRole.GetPrincipalId(), roleId) - // } - // resultPrincipalRoles = append(resultPrincipalRoles, returnedPrincipalRole.(PrincipalRole)) - // } + msgs := make([]*oplog.Message, 0, 2) + roleTicket, err := w.GetTicket(&role) + if err != nil { + return fmt.Errorf("add principal roles: unable to get ticket: %w", err) + } + updatedRole := allocRole() + updatedRole.PublicId = roleId + updatedRole.Version = uint32(roleVersion) + var roleOplogMsg oplog.Message + rowsUpdated, err := w.Update(ctx, &role, []string{"Version"}, nil, db.NewOplogMsg(&roleOplogMsg)) + if err != nil { + return fmt.Errorf("add principal roles: unable to update role version: %w", err) + } + if rowsUpdated != 1 { + return fmt.Errorf("add principal roles: updated role and %d rows updated: %w", rowsUpdated, err) + } + msgs = append(msgs, &roleOplogMsg) + userOplogMsgs := make([]*oplog.Message, 0, len(newUserRoles)) + if err := w.CreateItems(ctx, newUserRoles, db.NewOplogMsgs(&userOplogMsgs)); err != nil { + return fmt.Errorf("add principal roles: unable to add users: %w", err) + } + msgs = append(msgs, userOplogMsgs...) + + grpOplogMsgs := make([]*oplog.Message, 0, len(newGrpRoles)) + if err := w.CreateItems(ctx, newGrpRoles, db.NewOplogMsgs(&grpOplogMsgs)); err != nil { + return fmt.Errorf("add principal roles: unable to add groups: %w", err) + } + msgs = append(msgs, grpOplogMsgs...) + + if err := w.WriteOplogEntryWith(ctx, r.wrapper, roleTicket, metadata, msgs); err != nil { + return fmt.Errorf("add principal roles: unable to write oplog: %w", err) + } return nil }, ) if err != nil { return nil, err } - return resultPrincipalRoles, nil + principalRoles := make([]PrincipalRole, 0, len(newUserRoles)+len(newUserRoles)) + for _, role := range newUserRoles { + principalRoles = append(principalRoles, role.(*UserRole)) + } + for _, role := range newGrpRoles { + principalRoles = append(principalRoles, role.(*GroupRole)) + } + return principalRoles, nil } func (r *Repository) SetPrincipalRoles(ctx context.Context, roleId string, userIds, groupIds []string, opt ...Option) ([]*PrincipalRole, int, error) { panic("not implemented") } -func (r *Repository) LookupPrincipalRoles(ctx context.Context, roleId string) ([]*PrincipalRole, error) { - // see role_assigned_role.go for query - panic("not implemented") +func (r *Repository) ListPrincipalRoles(ctx context.Context, roleId string) ([]PrincipalRole, error) { + if roleId == "" { + return nil, fmt.Errorf("lookup principal roles: missing role id %w", db.ErrInvalidParameter) + } + var roles []principalRoleView + if err := r.reader.SearchWhere(ctx, &roles, "role_id = ?", []interface{}{roleId}); 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 } func (r *Repository) DeletePrincipalRoles(ctx context.Context, roleId string, userIds, groupIds []string, opt ...Option) (int, error) { panic("not implemented")