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

mendersoftware / useradm / 1290251377

14 May 2024 01:33PM UTC coverage: 85.226% (-1.6%) from 86.874%
1290251377

Pull #423

gitlab-ci

alfrunes
test(acceptance): Expose Mongo URL as pytest argument

Signed-off-by: Alf-Rune Siqveland <alf.rune@northern.tech>
Pull Request #423: Acceptance tests fixup

2792 of 3276 relevant lines covered (85.23%)

49.04 hits per line

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

82.19
/model/user.go
1
// Copyright 2022 Northern.tech AS
2
//
3
//    Licensed under the Apache License, Version 2.0 (the "License");
4
//    you may not use this file except in compliance with the License.
5
//    You may obtain a copy of the License at
6
//
7
//        http://www.apache.org/licenses/LICENSE-2.0
8
//
9
//    Unless required by applicable law or agreed to in writing, software
10
//    distributed under the License is distributed on an "AS IS" BASIS,
11
//    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
//    See the License for the specific language governing permissions and
13
//    limitations under the License.
14

15
package model
16

17
import (
18
        "encoding/binary"
19
        "encoding/hex"
20
        "encoding/json"
21
        "net/url"
22
        "strconv"
23
        "strings"
24
        "time"
25

26
        validation "github.com/go-ozzo/ozzo-validation/v4"
27
        "github.com/go-ozzo/ozzo-validation/v4/is"
28
        "github.com/pkg/errors"
29

30
        "github.com/mendersoftware/useradm/jwt"
31
)
32

33
const (
34
        MinPasswordLength = 8
35
)
36

37
var (
38
        ErrPasswordTooShort = errors.Errorf(
39
                "password: must be minimum %d characters long",
40
                MinPasswordLength,
41
        )
42
        ErrEmptyUpdate = errors.New("no update information provided")
43
)
44

45
type Email string
46

47
func (email *Email) UnmarshalJSON(b []byte) error {
13✔
48
        var raw string
13✔
49
        if err := json.Unmarshal(b, &raw); err != nil {
14✔
50
                return err
1✔
51
        }
1✔
52
        *email = Email(strings.ToLower(raw))
12✔
53
        return nil
12✔
54
}
55

56
func (email Email) Validate() error {
116✔
57
        return validation.Validate(string(email),
116✔
58
                lessThan4096,
116✔
59
                is.ASCII, is.EmailFormat,
116✔
60
        )
116✔
61
}
116✔
62

63
type ETag [12]byte
64

65
var (
66
        ETagNil ETag
67
)
68

69
func (t *ETag) Increment() {
1✔
70
        c := binary.BigEndian.Uint32((*t)[8:])
1✔
71
        c += 1
1✔
72
        binary.BigEndian.PutUint32((*t)[8:], c)
1✔
73
        if *t == ETagNil {
1✔
74
                t.Increment()
×
75
        }
×
76
}
77

78
func (t *ETag) UnmarshalText(b []byte) error {
4✔
79
        if t == nil {
5✔
80
                return errors.New("nil ETag")
1✔
81
        }
1✔
82
        if len(b) == 0 {
4✔
83
                // Treat an empty string as a special case
1✔
84
                *t = [12]byte{}
1✔
85
                return nil
1✔
86
        }
1✔
87
        if len(b) != 24 {
3✔
88
                return errors.New("invalid ETag length")
1✔
89
        }
1✔
90
        _, err := hex.Decode((*t)[:], b)
1✔
91
        return err
1✔
92
}
93

94
func (t ETag) MarshalText() (b []byte, err error) {
3✔
95
        b = make([]byte, 24)
3✔
96
        hex.Encode(b, t[:])
3✔
97
        return b, err
3✔
98
}
3✔
99

100
func (t ETag) String() string {
3✔
101
        b, _ := t.MarshalText()
3✔
102
        return string(b)
3✔
103
}
3✔
104

105
type User struct {
106
        // system-generated user ID
107
        ID string `json:"id" bson:"_id"`
108

109
        // ETag is the entity tag that together with ID uniquely identifies
110
        // the User document.
111
        // NOTE: The v1 API does not support ETags, so this is only used
112
        // internally for checking pre-conditions before performing updates.
113
        ETag *ETag `json:"-" bson:"etag,omitempty"`
114

115
        // user email address
116
        Email Email `json:"email" bson:"email"`
117

118
        // user password
119
        Password string `json:"password,omitempty" bson:"password"`
120

121
        // timestamp of the user creation
122
        CreatedTs *time.Time `json:"created_ts,omitempty" bson:"created_ts,omitempty"`
123

124
        // timestamp of the last user information update
125
        UpdatedTs *time.Time `json:"updated_ts,omitempty" bson:"updated_ts,omitempty"`
126

127
        // LoginTs is the timestamp of the last login for this user.
128
        LoginTs *time.Time `json:"login_ts,omitempty" bson:"login_ts,omitempty"`
129
}
130

131
func (u User) NextETag() (ret ETag) {
6✔
132
        if u.ETag == nil {
12✔
133
                u.ETag = new(ETag)
6✔
134
        }
6✔
135
        if u.CreatedTs != nil {
12✔
136
                // Weak part of the ETag
6✔
137
                lsb := uint64(u.CreatedTs.Unix())
6✔
138
                binary.BigEndian.PutUint64(ret[:8], lsb)
6✔
139
        }
6✔
140
        c := binary.BigEndian.Uint32(u.ETag[8:])
6✔
141
        c += 1
6✔
142
        binary.BigEndian.PutUint32(ret[8:], c)
6✔
143
        if ret == ETagNil {
6✔
144
                ret.Increment()
×
145
        }
×
146
        return ret
6✔
147
}
148

149
func (u User) Validate() error {
108✔
150
        if err := validation.ValidateStruct(&u,
108✔
151
                validation.Field(&u.Email, validation.Required),
108✔
152
                validation.Field(&u.Password, validation.Required, lessThan4096),
108✔
153
        ); err != nil {
115✔
154
                return err
7✔
155
        }
7✔
156
        if len(u.Password) < MinPasswordLength {
104✔
157
                return ErrPasswordTooShort
3✔
158
        }
3✔
159
        return nil
98✔
160
}
161

162
type UserInternal struct {
163
        User
164
        PasswordHash string `json:"password_hash,omitempty" bson:"-"`
165
        Propagate    *bool  `json:"propagate,omitempty" bson:"-"`
166
}
167

168
func (u UserInternal) Validate() error {
×
169
        if u.Password == "" && u.PasswordHash == "" ||
×
170
                u.Password != "" && u.PasswordHash != "" {
×
171
                return errors.New("password *or* password_hash must be provided")
×
172
        } else if u.PasswordHash != "" {
×
173
                if u.ShouldPropagate() {
×
174
                        return errors.New(
×
175
                                "password_hash is not supported with 'propagate'; use 'password' instead",
×
176
                        )
×
177
                }
×
178
                u.User.Password = u.PasswordHash
×
179
                defer func() { u.User.Password = "" }()
×
180
        }
181

182
        return validation.ValidateStruct(&u,
×
183
                validation.Field(&u.User),
×
184
        )
×
185
}
186

187
func (u UserInternal) ShouldPropagate() bool {
×
188
        return u.Propagate == nil || *u.Propagate
×
189
}
×
190

191
type UserUpdate struct {
192
        // ETag selects user ETag for the user to update
193
        // NOTE: This is the only parameter that goes into the query condition if set.
194
        // NOTE: If set to ETagNil, it will match users without an ETag.
195
        ETag *ETag `json:"-" bson:"-"`
196

197
        // ETagUpdate sets the updated ETag value. If not set, it is incremented from the
198
        // ETag field if that field is set.
199
        ETagUpdate *ETag `json:"-" bson:"etag,omitempty"`
200

201
        // user email address
202
        Email Email `json:"email,omitempty" bson:",omitempty" valid:"email"`
203

204
        // user password
205
        Password string `json:"password,omitempty" bson:"password,omitempty"`
206

207
        // user password
208
        CurrentPassword string `json:"current_password,omitempty" bson:"-"`
209

210
        // timestamp of the last user information update
211
        UpdatedTs *time.Time `json:"-" bson:"updated_ts,omitempty"`
212

213
        // token used to update the user, optional
214
        Token *jwt.Token `json:"-" bson:"-"`
215

216
        LoginTs *time.Time `json:"-" bson:"login_ts,omitempty"`
217
}
218

219
func (u UserUpdate) Validate() error {
10✔
220
        if u.Email == "" && u.Password == "" {
11✔
221
                return ErrEmptyUpdate
1✔
222
        }
1✔
223

224
        if err := validation.ValidateStruct(&u,
9✔
225
                validation.Field(&u.Email),
9✔
226
                validation.Field(&u.Password,
9✔
227
                        validation.When(len(u.Password) > 0, lessThan4096),
9✔
228
                ),
9✔
229
        ); err != nil {
9✔
230
                return err
×
231
        }
×
232

233
        if len(u.Password) > 0 && len(u.Password) < MinPasswordLength {
9✔
234
                return ErrPasswordTooShort
×
235
        }
×
236
        return nil
9✔
237
}
238

239
type UserFilter struct {
240
        ID    []string `json:"id,omitempty"`
241
        Email []Email  `json:"email,omitempty"`
242

243
        CreatedAfter  *time.Time `json:"created_after,omitempty"`
244
        CreatedBefore *time.Time `json:"created_before,omitempty"`
245

246
        UpdatedAfter  *time.Time `json:"updated_after,omitempty"`
247
        UpdatedBefore *time.Time `json:"updated_before,omitempty"`
248
}
249

250
func (fltr *UserFilter) ParseForm(form url.Values) error {
33✔
251
        if ids, ok := form["id"]; ok {
34✔
252
                fltr.ID = ids
1✔
253
        }
1✔
254
        if emails, ok := form["email"]; ok {
34✔
255
                fltr.Email = make([]Email, len(emails))
1✔
256
                for i := range emails {
4✔
257
                        fltr.Email[i] = Email(strings.ToLower(emails[i]))
3✔
258
                }
3✔
259
        }
260
        if ca := form.Get("created_after"); ca != "" {
35✔
261
                caInt, err := strconv.ParseInt(ca, 10, 64)
2✔
262
                if err != nil {
3✔
263
                        return errors.Wrap(err,
1✔
264
                                `invalid form parameter "created_after"`)
1✔
265
                }
1✔
266
                caUnix := time.Unix(caInt, 0)
1✔
267
                fltr.CreatedAfter = &caUnix
1✔
268
        }
269

270
        if cb := form.Get("created_before"); cb != "" {
34✔
271
                cbInt, err := strconv.ParseInt(cb, 10, 64)
2✔
272
                if err != nil {
3✔
273
                        return errors.Wrap(err,
1✔
274
                                `invalid form parameter "created_before"`)
1✔
275
                }
1✔
276
                cbUnix := time.Unix(cbInt, 0)
1✔
277
                fltr.CreatedBefore = &cbUnix
1✔
278
        }
279

280
        if ua := form.Get("updated_after"); ua != "" {
33✔
281
                uaInt, err := strconv.ParseInt(ua, 10, 64)
2✔
282
                if err != nil {
3✔
283
                        return errors.Wrap(err,
1✔
284
                                `invalid form parameter "updated_after"`)
1✔
285
                }
1✔
286
                uaUnix := time.Unix(uaInt, 0)
1✔
287
                fltr.UpdatedAfter = &uaUnix
1✔
288
        }
289

290
        if ub := form.Get("updated_before"); ub != "" {
32✔
291
                ubInt, err := strconv.ParseInt(ub, 10, 64)
2✔
292
                if err != nil {
3✔
293
                        return errors.Wrap(err,
1✔
294
                                `invalid form parameter "updated_before"`)
1✔
295
                }
1✔
296
                ubUnix := time.Unix(ubInt, 0)
1✔
297
                fltr.UpdatedBefore = &ubUnix
1✔
298
        }
299
        return nil
29✔
300
}
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