-
Notifications
You must be signed in to change notification settings - Fork 414
/
user.go
282 lines (242 loc) · 7.65 KB
/
user.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
package api
import (
"context"
"net/http"
"github.com/gorilla/mux"
"github.com/lib/pq"
"github.com/rockbears/log"
"github.com/ovh/cds/engine/api/authentication"
"github.com/ovh/cds/engine/api/group"
"github.com/ovh/cds/engine/api/organization"
"github.com/ovh/cds/engine/api/user"
"github.com/ovh/cds/engine/gorpmapper"
"github.com/ovh/cds/engine/service"
"github.com/ovh/cds/sdk"
)
// GetUsers fetches all users from databases
func (api *API) getUsersHandler() service.Handler {
return func(ctx context.Context, w http.ResponseWriter, r *http.Request) error {
users, err := user.LoadAll(ctx, api.mustDB(), user.LoadOptions.WithOrganization)
if err != nil {
return sdk.WrapError(err, "cannot load user from db")
}
return service.WriteJSON(w, users, http.StatusOK)
}
}
// GetUserHandler returns a specific user's information
func (api *API) getUserHandler() service.Handler {
return func(ctx context.Context, w http.ResponseWriter, r *http.Request) error {
vars := mux.Vars(r)
username := vars["permUsernamePublic"]
consumer := getUserConsumer(ctx)
var u *sdk.AuthentifiedUser
var err error
if username == "me" {
u, err = user.LoadByID(ctx, api.mustDB(), consumer.AuthConsumerUser.AuthentifiedUserID, user.LoadOptions.WithOrganization)
} else {
u, err = user.LoadByUsername(ctx, api.mustDB(), username, user.LoadOptions.WithOrganization)
}
if err != nil {
return err
}
return service.WriteJSON(w, u, http.StatusOK)
}
}
func (api *API) putUserHandler() service.Handler {
return func(ctx context.Context, w http.ResponseWriter, r *http.Request) error {
vars := mux.Vars(r)
username := vars["permUsernamePublic"]
var data sdk.AuthentifiedUser
if err := service.UnmarshalBody(r, &data); err != nil {
return err
}
if err := data.IsValid(); err != nil {
return err
}
consumer := getUserConsumer(ctx)
tx, err := api.mustDB().Begin()
if err != nil {
return sdk.WrapError(err, "cannot start transaction")
}
defer tx.Rollback() // nolint
var oldUser *sdk.AuthentifiedUser
if username == "me" {
oldUser, err = user.LoadByID(ctx, tx, consumer.AuthConsumerUser.AuthentifiedUserID)
} else {
oldUser, err = user.LoadByUsername(ctx, tx, username)
}
if err != nil {
return err
}
newUser := *oldUser
if oldUser.Username != data.Username {
// Only an admin can change the username
if isAdmin(ctx) {
trackSudo(ctx, w)
log.Info(ctx, "putUserHandler> %s change username of user %s from %s to %s", consumer.AuthConsumerUser.AuthentifiedUserID, oldUser.ID, oldUser.Username, data.Username)
newUser.Username = data.Username
} else {
return sdk.WithStack(sdk.ErrForbidden)
}
}
newUser.Fullname = data.Fullname
// Only an admin can change the ring of a user
if isAdmin(ctx) && oldUser.Ring != data.Ring {
trackSudo(ctx, w)
// If previous ring was admin, check that the user is not the last admin
if oldUser.Ring == sdk.UserRingAdmin {
count, err := user.CountAdmin(tx)
if err != nil {
return err
}
if count < 2 {
return sdk.NewErrorFrom(sdk.ErrForbidden, "can't remove the last admin")
}
// Invalidate consumer's group if user is not part of it
gs, err := group.LoadAllByUserID(ctx, tx, oldUser.ID)
if err != nil {
return err
}
if err := authentication.ConsumerInvalidateGroupsForUser(ctx, tx, oldUser.ID, gs.ToIDs()); err != nil {
return err
}
}
// If new ring is admin we need to restore invalid consumer group for user
if data.Ring == sdk.UserRingAdmin {
if err := authentication.ConsumerRestoreInvalidatedGroupsForUser(ctx, tx, oldUser.ID); err != nil {
return err
}
}
newUser.Ring = data.Ring
log.Info(ctx, "putUserHandler> %s change ring of user %s from %s to %s", consumer.AuthConsumerUser.AuthentifiedUserID, oldUser.ID, oldUser.Ring, newUser.Ring)
}
if err := user.Update(ctx, tx, &newUser); err != nil {
if e, ok := sdk.Cause(err).(*pq.Error); ok && e.Code == gorpmapper.ViolateUniqueKeyPGCode {
return sdk.NewErrorWithStack(e, sdk.ErrUsernamePresent)
}
return sdk.WrapError(err, "cannot update user")
}
if isAdmin(ctx) && data.Organization != "" && oldUser.Organization != data.Organization {
trackSudo(ctx, w)
if err := api.userSetOrganization(ctx, tx, &newUser, data.Organization); err != nil {
return err
}
}
if err := tx.Commit(); err != nil {
return sdk.WithStack(err)
}
if err := user.LoadOptions.WithOrganization(ctx, api.mustDBWithCtx(ctx), &newUser); err != nil {
return err
}
return service.WriteJSON(w, newUser, http.StatusOK)
}
}
func (api *API) userSetOrganization(ctx context.Context, db gorpmapper.SqlExecutorWithTx, u *sdk.AuthentifiedUser, org string) error {
if org == "" {
return nil
}
isAllowed := api.Config.Auth.AllowedOrganizations.Contains(org)
if !isAllowed {
return sdk.NewErrorFrom(sdk.ErrForbidden, "user organization %q is not allowed", org)
}
existingOrg, err := organization.LoadOrganizationByName(ctx, db, org)
if err != nil {
return err
}
if err := user.LoadOptions.WithOrganization(ctx, db, u); err != nil {
return err
}
if u.Organization != "" {
if u.Organization == org {
return nil
}
return sdk.NewErrorFrom(sdk.ErrForbidden, "cannot change user organization to %q, value already set to %q", org, u.Organization)
}
u.Organization = org
if err := user.InsertUserOrganization(ctx, db, &user.UserOrganization{
AuthentifiedUserID: u.ID,
OrganizationID: existingOrg.ID,
}); err != nil {
return err
}
gs, err := group.LoadAllByUserID(ctx, db, u.ID)
if err != nil {
return err
}
for i := range gs {
if err := group.EnsureOrganization(ctx, db, &gs[i]); err != nil {
return err
}
}
return nil
}
// DeleteUserHandler removes a user.
func (api *API) deleteUserHandler() service.Handler {
return func(ctx context.Context, w http.ResponseWriter, r *http.Request) error {
vars := mux.Vars(r)
username := vars["permUsernamePublic"]
consumer := getUserConsumer(ctx)
tx, err := api.mustDB().Begin()
if err != nil {
return sdk.WrapError(err, "cannot start transaction")
}
defer tx.Rollback() // nolint
var u *sdk.AuthentifiedUser
if username == "me" {
u, err = user.LoadByID(ctx, tx, consumer.AuthConsumerUser.AuthentifiedUserID)
} else {
u, err = user.LoadByUsername(ctx, tx, username)
}
if err != nil {
return err
}
// We can't delete the last admin
if u.Ring == sdk.UserRingAdmin {
count, err := user.CountAdmin(tx)
if err != nil {
return err
}
if count < 2 {
return sdk.NewErrorFrom(sdk.ErrForbidden, "can't remove the last admin")
}
}
// We can't delete a user if it's the last admin in a group
var adminGroupIDs []int64
gus, err := group.LoadLinksGroupUserForUserIDs(ctx, tx, []string{u.ID})
if err != nil {
return err
}
for i := range gus {
if gus[i].Admin {
adminGroupIDs = append(adminGroupIDs, gus[i].GroupID)
}
}
if len(adminGroupIDs) > 0 {
gus, err := group.LoadLinksGroupUserForGroupIDs(ctx, tx, adminGroupIDs)
if err != nil {
return err
}
adminLeftCount := make(map[int64]int)
for _, id := range adminGroupIDs {
adminLeftCount[id] = 0
}
for i := range gus {
if gus[i].AuthentifiedUserID != u.ID && gus[i].Admin {
adminLeftCount[gus[i].GroupID] += 1
}
}
for _, count := range adminLeftCount {
if count < 1 {
return sdk.NewErrorFrom(sdk.ErrForbidden, "cannot remove user because it is the last admin of a group")
}
}
}
if err := user.DeleteByID(tx, u.ID); err != nil {
return sdk.WrapError(err, "cannot delete user")
}
if err := tx.Commit(); err != nil {
return sdk.WithStack(err)
}
return service.WriteJSON(w, nil, http.StatusOK)
}
}