-
Notifications
You must be signed in to change notification settings - Fork 4.6k
/
manager.go
246 lines (220 loc) · 8.14 KB
/
manager.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
// Copyright Project Harbor Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package user
import (
"context"
"fmt"
"hash/crc32"
"strings"
commonmodels "github.com/goharbor/harbor/src/common/models"
"github.com/goharbor/harbor/src/common/utils"
"github.com/goharbor/harbor/src/lib"
"github.com/goharbor/harbor/src/lib/errors"
"github.com/goharbor/harbor/src/lib/q"
"github.com/goharbor/harbor/src/pkg/user/dao"
"github.com/goharbor/harbor/src/pkg/user/models"
)
var (
// Mgr is the global project manager
Mgr = New()
)
// Manager is used for user management
type Manager interface {
// Get get user by user id
Get(ctx context.Context, id int) (*commonmodels.User, error)
// GetByName get user by username, it will return an error if the user does not exist
GetByName(ctx context.Context, username string) (*commonmodels.User, error)
// List users according to the query
List(ctx context.Context, query *q.Query, options ...models.Option) (commonmodels.Users, error)
// Count counts the number of users according to the query
Count(ctx context.Context, query *q.Query, options ...models.Option) (int64, error)
// Create creates the user, the password of input should be plaintext
Create(ctx context.Context, user *commonmodels.User) (int, error)
// Delete deletes the user by updating user's delete flag and update the name and Email
Delete(ctx context.Context, id int) error
// DeleteGDPR deletes the user by updating user's delete flag and replace identifiable data with its crc
DeleteGDPR(ctx context.Context, id int) error
// SetSysAdminFlag sets the system admin flag of the user in local DB
SetSysAdminFlag(ctx context.Context, id int, admin bool) error
// UpdateProfile updates the user's profile
UpdateProfile(ctx context.Context, user *commonmodels.User, col ...string) error
// UpdatePassword updates user's password
UpdatePassword(ctx context.Context, id int, newPassword string) error
// MatchLocalPassword tries to match the record in DB based on the input, the first return value is
// the user model corresponding to the entry in DB
MatchLocalPassword(ctx context.Context, username, password string) (*commonmodels.User, error)
// Onboard will check if a user exists in user table, if not insert the user and
// put the id in the pointer of user model, if it does exist, return the user's profile.
// This is used for ldap and uaa authentication, such the user can have an ID in Harbor.
Onboard(ctx context.Context, user *commonmodels.User) error
// GenerateCheckSum generates truncated crc32 checksum from a given string
GenerateCheckSum(in string) string
}
// New returns a default implementation of Manager
func New() Manager {
return &manager{dao: dao.New()}
}
type manager struct {
dao dao.DAO
}
func (m *manager) Onboard(ctx context.Context, user *commonmodels.User) error {
u, err := m.GetByName(ctx, user.Username)
if err == nil {
user.Email = u.Email
user.SysAdminFlag = u.SysAdminFlag
user.Realname = u.Realname
user.UserID = u.UserID
return nil
} else if !errors.IsNotFoundErr(err) {
return err
}
// User does not exists, insert the user record.
// Given this func is ALWAYS called in a tx, the conflict error can rollback the tx to ensure the consistency
id, err2 := m.Create(ctx, user)
if err2 != nil {
return err2
}
user.UserID = id
return nil
}
func (m *manager) Delete(ctx context.Context, id int) error {
u, err := m.Get(ctx, id)
if err != nil {
return err
}
u.Username = lib.Truncate(u.Username, fmt.Sprintf("#%d", u.UserID), 255)
u.Email = lib.Truncate(u.Email, fmt.Sprintf("#%d", u.UserID), 255)
u.Deleted = true
return m.dao.Update(ctx, u, "username", "email", "deleted")
}
func (m *manager) DeleteGDPR(ctx context.Context, id int) error {
u, err := m.Get(ctx, id)
if err != nil {
return err
}
u.Username = fmt.Sprintf("%s#%d", m.GenerateCheckSum(u.Username), u.UserID)
u.Email = fmt.Sprintf("%s#%d", m.GenerateCheckSum(u.Email), u.UserID)
u.Realname = fmt.Sprintf("%s#%d", m.GenerateCheckSum(u.Realname), u.UserID)
u.Deleted = true
return m.dao.Update(ctx, u, "username", "email", "realname", "deleted")
}
func (m *manager) MatchLocalPassword(ctx context.Context, usernameOrEmail, password string) (*commonmodels.User, error) {
l, err := m.dao.List(ctx, q.New(q.KeyWords{"username_or_email": usernameOrEmail}))
if err != nil {
return nil, err
}
for _, entry := range l {
if utils.Encrypt(password, entry.Salt, entry.PasswordVersion) == entry.Password {
entry.Password = ""
return entry, nil
}
}
return nil, nil
}
func (m *manager) Count(ctx context.Context, query *q.Query, options ...models.Option) (int64, error) {
opts := models.NewOptions(options...)
if !opts.IncludeDefaultAdmin {
query = excludeDefaultAdmin(query)
}
return m.dao.Count(ctx, query)
}
func (m *manager) UpdateProfile(ctx context.Context, user *commonmodels.User, cols ...string) error {
if len(cols) == 0 {
cols = []string{"Email", "Realname", "Comment"}
}
return m.dao.Update(ctx, user, cols...)
}
func (m *manager) UpdatePassword(ctx context.Context, id int, newPassword string) error {
user := &commonmodels.User{
UserID: id,
}
injectPasswd(user, newPassword)
return m.dao.Update(ctx, user, "salt", "password", "password_version")
}
func (m *manager) SetSysAdminFlag(ctx context.Context, id int, admin bool) error {
u := &commonmodels.User{
UserID: id,
SysAdminFlag: admin,
}
return m.dao.Update(ctx, u, "sysadmin_flag")
}
func (m *manager) Create(ctx context.Context, user *commonmodels.User) (int, error) {
injectPasswd(user, user.Password)
// replace comma in username with underscore to avoid #19356
// if the username conflict with existing username,
// it will return error and have to update to a new username manually with sql
user.Username = strings.Replace(user.Username, ",", "_", -1)
return m.dao.Create(ctx, user)
}
// Get get user by user id
func (m *manager) Get(ctx context.Context, id int) (*commonmodels.User, error) {
users, err := m.dao.List(ctx, q.New(q.KeyWords{"user_id": id}))
if err != nil {
return nil, err
}
if len(users) == 0 {
return nil, errors.NotFoundError(nil).WithMessage("user %d not found", id)
}
return users[0], nil
}
// GetByName get user by username
func (m *manager) GetByName(ctx context.Context, username string) (*commonmodels.User, error) {
users, err := m.dao.List(ctx, q.New(q.KeyWords{"username": username}))
if err != nil {
return nil, err
}
if len(users) == 0 {
return nil, errors.NotFoundError(nil).WithMessage("user %s not found", username)
}
return users[0], nil
}
// List users according to the query
func (m *manager) List(ctx context.Context, query *q.Query, options ...models.Option) (commonmodels.Users, error) {
query = q.MustClone(query)
for key := range query.Keywords {
str := strings.ToLower(key)
if str == "user_id__in" {
options = append(options, models.WithDefaultAdmin())
break
} else if str == "user_id" {
options = append(options, models.WithDefaultAdmin())
break
}
}
opts := models.NewOptions(options...)
if !opts.IncludeDefaultAdmin {
query = excludeDefaultAdmin(query)
}
return m.dao.List(ctx, query)
}
func excludeDefaultAdmin(query *q.Query) (qu *q.Query) {
if query == nil {
query = q.New(q.KeyWords{})
}
if query.Keywords == nil {
query.Keywords = q.KeyWords{}
}
query.Keywords["user_id__gt"] = 1
return query
}
// GenerateCheckSum generates checksum for a given string
func (m *manager) GenerateCheckSum(str string) string {
return fmt.Sprintf("%08x", crc32.Checksum([]byte(str), crc32.IEEETable))
}
func injectPasswd(u *commonmodels.User, password string) {
salt := utils.GenerateRandomString()
u.Password = utils.Encrypt(password, salt, utils.SHA256)
u.Salt = salt
u.PasswordVersion = utils.SHA256
}