• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

mindersec / minder / 13283006810

12 Feb 2025 10:14AM UTC coverage: 57.515% (+0.03%) from 57.481%
13283006810

Pull #5418

github

web-flow
Merge 7e7f02f59 into 8aba270eb
Pull Request #5418: build(deps): bump google.golang.org/protobuf from 1.36.4 to 1.36.5 in /tools

18170 of 31592 relevant lines covered (57.51%)

37.64 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

76.85
/internal/roles/service.go
1
// SPDX-FileCopyrightText: Copyright 2024 The Minder Authors
2
// SPDX-License-Identifier: Apache-2.0
3

4
// Package roles contains the logic for managing user roles within a Minder project
5
package roles
6

7
import (
8
        "context"
9
        "database/sql"
10
        "errors"
11

12
        "github.com/google/uuid"
13
        "github.com/rs/zerolog"
14
        "google.golang.org/grpc/codes"
15
        "google.golang.org/grpc/status"
16

17
        "github.com/mindersec/minder/internal/auth"
18
        "github.com/mindersec/minder/internal/authz"
19
        "github.com/mindersec/minder/internal/db"
20
        "github.com/mindersec/minder/internal/util"
21
        pb "github.com/mindersec/minder/pkg/api/protobuf/go/minder/v1"
22
)
23

24
//go:generate go run go.uber.org/mock/mockgen -package mock_$GOPACKAGE -destination=./mock/$GOFILE -source=./$GOFILE
25

26
// RoleService encapsulates the methods to manage user role assignments
27
type RoleService interface {
28
        // CreateRoleAssignment assigns a user a role on a project
29
        CreateRoleAssignment(ctx context.Context, qtx db.Querier, authzClient authz.Client,
30
                targetProject uuid.UUID, subject auth.Identity, authzRole authz.Role) (*pb.RoleAssignment, error)
31

32
        // UpdateRoleAssignment updates the users role on a project
33
        UpdateRoleAssignment(ctx context.Context, qtx db.Querier, authzClient authz.Client, idClient auth.Resolver,
34
                targetProject uuid.UUID, subject string, authzRole authz.Role) (*pb.RoleAssignment, error)
35

36
        // RemoveRoleAssignment removes the role assignment for the user on a project
37
        RemoveRoleAssignment(ctx context.Context, qtx db.Querier, authzClient authz.Client, idClient auth.Resolver,
38
                targetProject uuid.UUID, subject string, roleToRemove authz.Role) (*pb.RoleAssignment, error)
39
}
40

41
type roleService struct {
42
}
43

44
// NewRoleService creates a new instance of RoleService
45
func NewRoleService() RoleService {
13✔
46
        return &roleService{}
13✔
47
}
13✔
48

49
func (_ *roleService) CreateRoleAssignment(ctx context.Context, qtx db.Querier, authzClient authz.Client,
50
        targetProject uuid.UUID, identity auth.Identity, authzRole authz.Role) (*pb.RoleAssignment, error) {
9✔
51

9✔
52
        // For users in the primary (human) identity store, verify if user exists in our database.
9✔
53
        // TODO: this assumes that we store human users in the Minder database, in addition to the
9✔
54
        // identity provider and the auth store.  We should revisit this assumption.
9✔
55
        if identity.Provider.String() == "" {
18✔
56
                if _, err := qtx.GetUserBySubject(ctx, identity.String()); err != nil {
10✔
57
                        if errors.Is(err, sql.ErrNoRows) {
2✔
58
                                return nil, util.UserVisibleError(codes.NotFound, "User not found")
1✔
59
                        }
1✔
60
                        return nil, status.Errorf(codes.Internal, "error getting user: %v", err)
×
61
                }
62
        }
63

64
        // Check in case there's an existing role assignment for the user
65
        as, err := authzClient.AssignmentsToProject(ctx, targetProject)
8✔
66
        if err != nil {
8✔
67
                return nil, status.Errorf(codes.Internal, "error getting role assignments: %v", err)
×
68
        }
×
69

70
        for _, a := range as {
12✔
71
                if a.Subject == identity.String() {
5✔
72
                        return nil, util.UserVisibleError(codes.AlreadyExists, "role assignment for this user already exists, use update instead")
1✔
73
                }
1✔
74
        }
75

76
        // Assign the role to the user
77
        if err := authzClient.Write(ctx, identity.String(), authzRole, targetProject); err != nil {
7✔
78
                return nil, status.Errorf(codes.Internal, "error writing role assignment: %v", err)
×
79
        }
×
80

81
        respProj := targetProject.String()
7✔
82
        return &pb.RoleAssignment{
7✔
83
                Role:    authzRole.String(),
7✔
84
                Subject: identity.Human(),
7✔
85
                Project: &respProj,
7✔
86
        }, nil
7✔
87
}
88

89
func (_ *roleService) UpdateRoleAssignment(ctx context.Context, qtx db.Querier, authzClient authz.Client,
90
        idClient auth.Resolver, targetProject uuid.UUID, sub string, authzRole authz.Role) (*pb.RoleAssignment, error) {
2✔
91
        // Resolve the subject to an identity
2✔
92
        identity, err := idClient.Resolve(ctx, sub)
2✔
93
        if err != nil {
2✔
94
                zerolog.Ctx(ctx).Error().Err(err).Msg("error resolving identity")
×
95
                return nil, util.UserVisibleError(codes.NotFound, "could not find identity %q", sub)
×
96
        }
×
97

98
        // Verify if user exists
99
        if identity.Provider.String() == "" {
4✔
100
                if _, err := qtx.GetUserBySubject(ctx, identity.String()); err != nil {
3✔
101
                        if errors.Is(err, sql.ErrNoRows) {
2✔
102
                                return nil, util.UserVisibleError(codes.NotFound, "User not found")
1✔
103
                        }
1✔
104
                        return nil, status.Errorf(codes.Internal, "error getting user: %v", err)
×
105
                }
106
        }
107

108
        // Remove the existing role assignment for the user
109
        as, err := authzClient.AssignmentsToProject(ctx, targetProject)
1✔
110
        if err != nil {
1✔
111
                return nil, status.Errorf(codes.Internal, "error getting role assignments: %v", err)
×
112
        }
×
113

114
        for _, a := range as {
2✔
115
                if a.Subject == identity.String() {
2✔
116
                        roleToDelete, err := authz.ParseRole(a.Role)
1✔
117
                        if err != nil {
1✔
118
                                return nil, util.UserVisibleError(codes.Internal, "%s", err.Error())
×
119
                        }
×
120
                        if err := authzClient.Delete(ctx, identity.String(), roleToDelete, targetProject); err != nil {
1✔
121
                                return nil, status.Errorf(codes.Internal, "error deleting previous role assignment: %v", err)
×
122
                        }
×
123
                }
124
        }
125

126
        // Update the role assignment for the user
127
        if err := authzClient.Write(ctx, identity.String(), authzRole, targetProject); err != nil {
1✔
128
                return nil, status.Errorf(codes.Internal, "error writing role assignment: %v", err)
×
129
        }
×
130

131
        respProj := targetProject.String()
1✔
132
        return &pb.RoleAssignment{
1✔
133
                Role:    authzRole.String(),
1✔
134
                Subject: identity.UserID,
1✔
135
                Project: &respProj,
1✔
136
        }, nil
1✔
137
}
138

139
func (_ *roleService) RemoveRoleAssignment(ctx context.Context, qtx db.Querier, authzClient authz.Client,
140
        idClient auth.Resolver, targetProject uuid.UUID, subject string, roleToRemove authz.Role) (*pb.RoleAssignment, error) {
5✔
141

5✔
142
        // Resolve the subject to an identity
5✔
143
        identity, err := idClient.Resolve(ctx, subject)
5✔
144
        if err != nil {
5✔
145
                zerolog.Ctx(ctx).Error().Err(err).Msg("error resolving identity")
×
146
                return nil, util.UserVisibleError(codes.NotFound, "could not find identity %q", subject)
×
147
        }
×
148

149
        // Verify if user exists
150
        if identity.Provider.String() == "" {
10✔
151
                if _, err := qtx.GetUserBySubject(ctx, identity.String()); err != nil {
6✔
152
                        if errors.Is(err, sql.ErrNoRows) {
2✔
153
                                return nil, util.UserVisibleError(codes.NotFound, "User not found")
1✔
154
                        }
1✔
155
                        return nil, status.Errorf(codes.Internal, "error getting user: %v", err)
×
156
                }
157
        }
158

159
        // Get all role assignments for the project
160
        as, err := authzClient.AssignmentsToProject(ctx, targetProject)
4✔
161
        if err != nil {
4✔
162
                return nil, status.Errorf(codes.Internal, "error getting role assignments: %v", err)
×
163
        }
×
164

165
        // Check if there is such role assignment for the user or the user is the last admin
166
        found := false
4✔
167
        adminRolesCnt := 0
4✔
168
        for _, a := range as {
8✔
169
                if a.Subject == identity.String() && a.Role == roleToRemove.String() {
7✔
170
                        found = true
3✔
171
                }
3✔
172
                if a.Role == authz.RoleAdmin.String() {
7✔
173
                        adminRolesCnt++
3✔
174
                }
3✔
175
        }
176

177
        // If there's no role assignment for the user, return an error
178
        if !found {
5✔
179
                return nil, util.UserVisibleError(codes.NotFound, "role assignment for this user does not exist")
1✔
180
        }
1✔
181

182
        // If there's only one admin role, return an error
183
        if roleToRemove == authz.RoleAdmin && adminRolesCnt <= 1 {
4✔
184
                return nil, util.UserVisibleError(codes.FailedPrecondition, "cannot remove the last admin from the project")
1✔
185
        }
1✔
186

187
        // Delete the role assignment
188
        if err := authzClient.Delete(ctx, identity.String(), roleToRemove, targetProject); err != nil {
2✔
189
                return nil, status.Errorf(codes.Internal, "error writing role assignment: %v", err)
×
190
        }
×
191
        prj := targetProject.String()
2✔
192
        return &pb.RoleAssignment{
2✔
193
                Role:    roleToRemove.String(),
2✔
194
                Subject: identity.Human(),
2✔
195
                Project: &prj,
2✔
196
        }, nil
2✔
197
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc