Skip to content

Commit

Permalink
Make role grants add users to projects
Browse files Browse the repository at this point in the history
This adds the necessary code to ensure that a user belongs to a project
once it gets a role in it.
  • Loading branch information
JAORMX committed Feb 1, 2024
1 parent 0889f84 commit fab4105
Show file tree
Hide file tree
Showing 9 changed files with 142 additions and 16 deletions.
15 changes: 15 additions & 0 deletions database/mock/store.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 4 additions & 1 deletion database/query/user_projects.sql
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,10 @@ INSERT INTO user_projects (
project_id
) VALUES (
$1, $2
) RETURNING *;
) ON CONFLICT DO NOTHING RETURNING *;

-- name: RemoveUserProject :one
DELETE FROM user_projects WHERE user_id = $1 AND project_id = $2 RETURNING *;

-- name: GetUserProjects :many
SELECT * FROM projects INNER JOIN user_projects ON projects.id = user_projects.project_id WHERE user_projects.user_id = $1;
71 changes: 59 additions & 12 deletions internal/authz/authz.go
Original file line number Diff line number Diff line change
Expand Up @@ -353,20 +353,48 @@ func (a *ClientWrapper) AssignmentsToProject(ctx context.Context, project uuid.U
return nil, fmt.Errorf("unable to read authorization tuples: %w", err)
}

for _, t := range resp.GetTuples() {
k := t.GetKey()
r, err := ParseRole(k.GetRelation())
if err != nil {
a.l.Err(err).Msg("Found invalid role in authz store")
continue
}
assignments = append(assignments, &minderv1.RoleAssignment{
Subject: getUserFromTuple(k.GetUser()),
Role: r.String(),
Project: &prjStr,
})
assignments = append(assignments, getRoleAssignmentsFromReadResones(resp, &prjStr)...)

if resp.GetContinuationToken() == "" {
break
}

contTok = &resp.ContinuationToken
}

return assignments, nil
}

// AssignmentsToProjectForUser lists the current role assignments that are scoped to a project
// for a given user
func (a *ClientWrapper) AssignmentsToProjectForUser(
ctx context.Context,
project uuid.UUID,
sub string,
) ([]*minderv1.RoleAssignment, error) {
o := getProjectForTuple(project)
u := getUserForTuple(sub)
prjStr := project.String()

var pagesize int32 = 50
var contTok *string = nil

assignments := []*minderv1.RoleAssignment{}

for {
resp, err := a.cli.Read(ctx).Options(fgaclient.ClientReadOptions{
PageSize: &pagesize,
ContinuationToken: contTok,
}).Body(fgaclient.ClientReadRequest{
User: &u,
Object: &o,
}).Execute()
if err != nil {
return nil, fmt.Errorf("unable to read authorization tuples: %w", err)
}

assignments = append(assignments, getRoleAssignmentsFromReadResones(resp, &prjStr)...)

if resp.GetContinuationToken() == "" {
break
}
Expand All @@ -388,3 +416,22 @@ func getProjectForTuple(project uuid.UUID) string {
func getUserFromTuple(user string) string {
return strings.TrimPrefix(user, "user:")
}

func getRoleAssignmentsFromReadResones(resp *fgaclient.ClientReadResponse, proj *string) []*minderv1.RoleAssignment {
assignments := []*minderv1.RoleAssignment{}

for _, t := range resp.GetTuples() {
k := t.GetKey()
r, err := ParseRole(k.GetRelation())
if err != nil {
continue
}
assignments = append(assignments, &minderv1.RoleAssignment{
Subject: getUserFromTuple(k.GetUser()),
Role: r.String(),
Project: proj,
})
}

return assignments
}
4 changes: 4 additions & 0 deletions internal/authz/interface.go
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,10 @@ type Client interface {
// AssignmentsToProject outputs the existing role assignments for a given project.
AssignmentsToProject(ctx context.Context, project uuid.UUID) ([]*minderv1.RoleAssignment, error)

// AssingmentsToProjectForUser outputs the existing role assignments for a given project and user.
//nolint:lll
AssignmentsToProjectForUser(ctx context.Context, project uuid.UUID, user string) ([]*minderv1.RoleAssignment, error)

// PrepareForRun allows for any preflight configurations to be done before
// the server is started.
PrepareForRun(ctx context.Context) error
Expand Down
6 changes: 6 additions & 0 deletions internal/authz/mock/noop_authz.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,12 @@ func (_ *NoopClient) AssignmentsToProject(_ context.Context, _ uuid.UUID) ([]*mi
return nil, nil
}

// AssignmentsToProjectForUser implements authz.Client
func (_ *NoopClient) AssignmentsToProjectForUser(
_ context.Context, _ uuid.UUID, _ string) ([]*minderv1.RoleAssignment, error) {
return nil, nil
}

// PrepareForRun implements authz.Client
func (_ *NoopClient) PrepareForRun(_ context.Context) error {
return nil
Expand Down
6 changes: 6 additions & 0 deletions internal/authz/mock/simple_authz.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,12 @@ func (_ *SimpleClient) AssignmentsToProject(_ context.Context, _ uuid.UUID) ([]*
return nil, nil
}

// AssignmentsToProjectForUser implements authz.Client
func (_ *SimpleClient) AssignmentsToProjectForUser(
_ context.Context, _ uuid.UUID, _ string) ([]*minderv1.RoleAssignment, error) {
return nil, nil
}

// PrepareForRun implements authz.Client
func (_ *SimpleClient) PrepareForRun(_ context.Context) error {
return nil
Expand Down
32 changes: 30 additions & 2 deletions internal/controlplane/handlers_authz.go
Original file line number Diff line number Diff line change
Expand Up @@ -221,7 +221,8 @@ func (s *Server) AssignRole(ctx context.Context, req *minder.AssignRoleRequest)
}

// Verify if user exists
if _, err := s.store.GetUserBySubject(ctx, sub); err != nil {
usr, err := s.store.GetUserBySubject(ctx, sub)
if err != nil {
if errors.Is(err, sql.ErrNoRows) {
return nil, util.UserVisibleError(codes.NotFound, "User not found")
}
Expand All @@ -236,6 +237,15 @@ func (s *Server) AssignRole(ctx context.Context, req *minder.AssignRoleRequest)
return nil, status.Errorf(codes.Internal, "error writing role assignment: %v", err)
}

// Add user to project
_, err = s.store.AddUserProject(ctx, db.AddUserProjectParams{
UserID: usr.ID,
ProjectID: projectID,
})
if err != nil {
return nil, status.Errorf(codes.Internal, "error adding user to project: %v", err)
}

respProj := projectID.String()
return &minder.AssignRoleResponse{
RoleAssignment: &minder.RoleAssignment{
Expand Down Expand Up @@ -264,7 +274,8 @@ func (s *Server) RemoveRole(ctx context.Context, req *minder.RemoveRoleRequest)
}

// Verify if user exists
if _, err := s.store.GetUserBySubject(ctx, sub); err != nil {
usr, err := s.store.GetUserBySubject(ctx, sub)
if err != nil {
if errors.Is(err, sql.ErrNoRows) {
return nil, util.UserVisibleError(codes.NotFound, "User not found")
}
Expand All @@ -279,6 +290,23 @@ func (s *Server) RemoveRole(ctx context.Context, req *minder.RemoveRoleRequest)
return nil, status.Errorf(codes.Internal, "error writing role assignment: %v", err)
}

// Verify if user still has roles on project
assignments, err := s.authzClient.AssignmentsToProjectForUser(ctx, projectID, sub)
if err != nil {
return nil, status.Errorf(codes.Internal, "error getting role assignments: %v", err)
}

if len(assignments) == 0 {
// Remove user from project
_, err := s.store.RemoveUserProject(ctx, db.RemoveUserProjectParams{
UserID: usr.ID,
ProjectID: projectID,
})
if err != nil {
return nil, status.Errorf(codes.Internal, "error removing user from project: %v", err)
}
}

respProj := projectID.String()
return &minder.RemoveRoleResponse{
RoleAssignment: &minder.RoleAssignment{
Expand Down
1 change: 1 addition & 0 deletions internal/db/querier.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

18 changes: 17 additions & 1 deletion internal/db/user_projects.sql.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit fab4105

Please sign in to comment.