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

mendersoftware / mender-server / 1622978334

13 Jan 2025 03:51PM UTC coverage: 72.802% (-3.8%) from 76.608%
1622978334

Pull #300

gitlab-ci

alfrunes
fix: Deployment device count should not exceed max devices

Added a condition to skip deployments when the device count reaches max
devices.

Changelog: Title
Ticket: MEN-7847
Signed-off-by: Alf-Rune Siqveland <alf.rune@northern.tech>
Pull Request #300: fix: Deployment device count should not exceed max devices

4251 of 6164 branches covered (68.96%)

Branch coverage included in aggregate %.

0 of 18 new or added lines in 1 file covered. (0.0%)

2544 existing lines in 83 files now uncovered.

42741 of 58384 relevant lines covered (73.21%)

21.49 hits per line

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

73.97
/backend/services/useradm/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/mender-server/services/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 {
1✔
48
        var raw string
1✔
49
        if err := json.Unmarshal(b, &raw); err != nil {
2✔
50
                return err
1✔
51
        }
1✔
52
        *email = Email(strings.ToLower(raw))
1✔
53
        return nil
1✔
54
}
55

56
func (email Email) Validate() error {
3✔
57
        return validation.Validate(string(email),
3✔
58
                lessThan4096,
3✔
59
                is.ASCII, is.EmailFormat,
3✔
60
        )
3✔
61
}
3✔
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 {
1✔
79
        if t == nil {
2✔
80
                return errors.New("nil ETag")
1✔
81
        }
1✔
82
        if len(b) == 0 {
2✔
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 {
2✔
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) {
1✔
95
        b = make([]byte, 24)
1✔
96
        hex.Encode(b, t[:])
1✔
97
        return b, err
1✔
98
}
1✔
99

100
func (t ETag) String() string {
1✔
101
        b, _ := t.MarshalText()
1✔
102
        return string(b)
1✔
103
}
1✔
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) {
1✔
132
        if u.ETag == nil {
2✔
133
                u.ETag = new(ETag)
1✔
134
        }
1✔
135
        if u.CreatedTs != nil {
2✔
136
                // Weak part of the ETag
1✔
137
                lsb := uint64(u.CreatedTs.Unix())
1✔
138
                binary.BigEndian.PutUint64(ret[:8], lsb)
1✔
139
        }
1✔
140
        c := binary.BigEndian.Uint32(u.ETag[8:])
1✔
141
        c += 1
1✔
142
        binary.BigEndian.PutUint32(ret[8:], c)
1✔
143
        if ret == ETagNil {
1✔
144
                ret.Increment()
×
145
        }
×
146
        return ret
1✔
147
}
148

149
func (u User) Validate() error {
3✔
150
        if err := validation.ValidateStruct(&u,
3✔
151
                validation.Field(&u.Email, validation.Required),
3✔
152
                validation.Field(&u.Password, validation.Required, lessThan4096),
3✔
153
        ); err != nil {
4✔
154
                return err
1✔
155
        }
1✔
156
        if len(u.Password) < MinPasswordLength {
4✔
157
                return ErrPasswordTooShort
1✔
158
        }
1✔
159
        return nil
3✔
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

UNCOV
219
func (u UserUpdate) Validate() error {
×
UNCOV
220
        if u.Email == "" && u.Password == "" {
×
UNCOV
221
                return ErrEmptyUpdate
×
UNCOV
222
        }
×
223

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

UNCOV
233
        if len(u.Password) > 0 && len(u.Password) < MinPasswordLength {
×
234
                return ErrPasswordTooShort
×
235
        }
×
UNCOV
236
        return nil
×
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 {
1✔
251
        if ids, ok := form["id"]; ok {
2✔
252
                fltr.ID = ids
1✔
253
        }
1✔
254
        if emails, ok := form["email"]; ok {
2✔
255
                fltr.Email = make([]Email, len(emails))
1✔
256
                for i := range emails {
2✔
257
                        fltr.Email[i] = Email(strings.ToLower(emails[i]))
1✔
258
                }
1✔
259
        }
260
        if ca := form.Get("created_after"); ca != "" {
2✔
261
                caInt, err := strconv.ParseInt(ca, 10, 64)
1✔
262
                if err != nil {
2✔
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 != "" {
2✔
271
                cbInt, err := strconv.ParseInt(cb, 10, 64)
1✔
272
                if err != nil {
2✔
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 != "" {
2✔
281
                uaInt, err := strconv.ParseInt(ua, 10, 64)
1✔
282
                if err != nil {
2✔
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 != "" {
2✔
291
                ubInt, err := strconv.ParseInt(ub, 10, 64)
1✔
292
                if err != nil {
2✔
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
1✔
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