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

mendersoftware / mender-server / 1893336421

27 Jun 2025 07:09AM UTC coverage: 65.651% (-0.08%) from 65.731%
1893336421

Pull #764

gitlab-ci

bahaa-ghazal
fix(useradm): Use contenttype middleware for login endpoint

Signed-off-by: Bahaa Aldeen Ghazal <bahaa.ghazal@northern.tech>
Pull Request #764: refactor(useradm): Migrate from ant0nie/go-json-rest to gin-gonic/gin

265 of 312 new or added lines in 6 files covered. (84.94%)

5 existing lines in 2 files now uncovered.

32387 of 49332 relevant lines covered (65.65%)

1.39 hits per line

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

97.84
/backend/pkg/identity/middleware.go
1
// Copyright 2023 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 identity
16

17
import (
18
        "net/http"
19
        "regexp"
20

21
        "github.com/ant0ine/go-json-rest/rest"
22
        "github.com/gin-gonic/gin"
23

24
        "github.com/mendersoftware/mender-server/pkg/log"
25
        urest "github.com/mendersoftware/mender-server/pkg/rest.utils"
26
)
27

28
type MiddlewareOptions struct {
29
        // PathRegex sets the regex for the path for which this middleware
30
        // applies. Defaults to "^/api/management/v[0-9.]{1,6}/.+".
31
        PathRegex *string
32

33
        // UpdateLogger adds the decoded identity to the log context.
34
        UpdateLogger *bool
35
}
36

37
func NewMiddlewareOptions() *MiddlewareOptions {
8✔
38
        return new(MiddlewareOptions)
8✔
39
}
8✔
40

41
func (opts *MiddlewareOptions) SetPathRegex(regex string) *MiddlewareOptions {
3✔
42
        opts.PathRegex = &regex
3✔
43
        return opts
3✔
44
}
3✔
45

46
func (opts *MiddlewareOptions) SetUpdateLogger(updateLogger bool) *MiddlewareOptions {
8✔
47
        opts.UpdateLogger = &updateLogger
8✔
48
        return opts
8✔
49
}
8✔
50

51
func middlewareWithLogger(c *gin.Context) {
8✔
52
        var (
8✔
53
                err    error
8✔
54
                jwt    string
8✔
55
                idty   Identity
8✔
56
                logCtx = log.Ctx{}
8✔
57
                key    = "sub"
8✔
58
                ctx    = c.Request.Context()
8✔
59
                l      = log.FromContext(ctx)
8✔
60
        )
8✔
61
        if FromContext(ctx) != nil {
8✔
NEW
62
                c.Next()
×
NEW
63
                return
×
NEW
64
        }
×
65
        jwt, err = ExtractJWTFromHeader(c.Request)
8✔
66
        if err != nil {
12✔
67
                goto exitUnauthorized
4✔
68
        }
69
        idty, err = ExtractIdentity(jwt)
8✔
70
        if err != nil {
10✔
71
                goto exitUnauthorized
2✔
72
        }
73
        ctx = WithContext(ctx, &idty)
8✔
74
        if idty.IsDevice {
13✔
75
                key = "device_id"
5✔
76
        } else if idty.IsUser {
20✔
77
                key = "user_id"
7✔
78
        }
7✔
79
        logCtx[key] = idty.Subject
8✔
80
        if idty.Tenant != "" {
13✔
81
                logCtx["tenant_id"] = idty.Tenant
5✔
82
        }
5✔
83
        if idty.Plan != "" {
12✔
84
                logCtx["plan"] = idty.Plan
4✔
85
        }
4✔
86
        ctx = log.WithContext(ctx, l.F(logCtx))
8✔
87

8✔
88
        c.Request = c.Request.WithContext(ctx)
8✔
89
        return
8✔
90
exitUnauthorized:
8✔
91
        c.Header("WWW-Authenticate", `Bearer realm="ManagementJWT"`)
4✔
92
        urest.RenderError(c, http.StatusUnauthorized, err)
4✔
93
        c.Abort()
4✔
94
}
95

96
func middlewareBase(c *gin.Context) {
1✔
97
        var (
1✔
98
                err  error
1✔
99
                jwt  string
1✔
100
                idty Identity
1✔
101
                ctx  = c.Request.Context()
1✔
102
        )
1✔
103
        jwt, err = ExtractJWTFromHeader(c.Request)
1✔
104
        if err != nil {
2✔
105
                goto exitUnauthorized
1✔
106
        }
107
        idty, err = ExtractIdentity(jwt)
1✔
108
        if err != nil {
2✔
109
                goto exitUnauthorized
1✔
110
        }
111
        ctx = WithContext(ctx, &idty)
1✔
112
        c.Request = c.Request.WithContext(ctx)
1✔
113
        return
1✔
114
exitUnauthorized:
1✔
115
        c.Header("WWW-Authenticate", `Bearer realm="ManagementJWT"`)
1✔
116
        urest.RenderError(c, http.StatusUnauthorized, err)
1✔
117
        c.Abort()
1✔
118
}
119

120
func Middleware(opts ...*MiddlewareOptions) gin.HandlerFunc {
8✔
121

8✔
122
        var middleware gin.HandlerFunc
8✔
123

8✔
124
        // Initialize default options
8✔
125
        opt := NewMiddlewareOptions().
8✔
126
                SetUpdateLogger(true)
8✔
127
        for _, o := range opts {
11✔
128
                if o == nil {
4✔
129
                        continue
1✔
130
                }
131
                if o.PathRegex != nil {
6✔
132
                        opt.PathRegex = o.PathRegex
3✔
133
                }
3✔
134
                if o.UpdateLogger != nil {
4✔
135
                        opt.UpdateLogger = o.UpdateLogger
1✔
136
                }
1✔
137
        }
138

139
        if *opt.UpdateLogger {
16✔
140
                middleware = middlewareWithLogger
8✔
141
        } else {
9✔
142
                middleware = middlewareBase
1✔
143
        }
1✔
144

145
        if opt.PathRegex != nil {
11✔
146
                pathRegex := regexp.MustCompile(*opt.PathRegex)
3✔
147
                return func(c *gin.Context) {
6✔
148
                        if !pathRegex.MatchString(c.FullPath()) {
6✔
149
                                return
3✔
150
                        }
3✔
151
                        middleware(c)
3✔
152
                }
153
        }
154
        return middleware
7✔
155
}
156

157
// IdentityMiddleware adds the identity extracted from JWT token to the request's context.
158
// IdentityMiddleware does not perform any form of token signature verification.
159
// If it is not possible to extract identity from header error log will be generated.
160
// IdentityMiddleware will not stop control propagating through the chain in any case.
161
// It is recommended to use IdentityMiddleware with RequestLogMiddleware and
162
// RequestLogMiddleware should be placed before IdentityMiddleware.
163
// Otherwise, log generated by IdentityMiddleware will not contain "request_id" field.
164
type IdentityMiddleware struct {
165
        // If set to true, the middleware will update context logger setting
166
        // 'user_id' or 'device_id' to the value of subject field, if the token
167
        // is not a user or a device token, the middelware will add a 'sub'
168
        // field to the logger
169
        UpdateLogger bool
170
}
171

172
// MiddlewareFunc makes IdentityMiddleware implement the Middleware interface.
173
func (mw *IdentityMiddleware) MiddlewareFunc(h rest.HandlerFunc) rest.HandlerFunc {
3✔
174
        return func(w rest.ResponseWriter, r *rest.Request) {
6✔
175
                jwt, err := ExtractJWTFromHeader(r.Request)
3✔
176
                if err != nil {
6✔
177
                        h(w, r)
3✔
178
                        return
3✔
179
                }
3✔
180

181
                ctx := r.Context()
3✔
182
                l := log.FromContext(ctx)
3✔
183

3✔
184
                identity, err := ExtractIdentity(jwt)
3✔
185
                if err != nil {
5✔
186
                        l.Warnf("Failed to parse extracted JWT: %s",
2✔
187
                                err.Error(),
2✔
188
                        )
2✔
189
                } else {
5✔
190
                        if mw.UpdateLogger {
6✔
191
                                logCtx := log.Ctx{}
3✔
192

3✔
193
                                key := "sub"
3✔
194
                                if identity.IsDevice {
6✔
195
                                        key = "device_id"
3✔
196
                                } else if identity.IsUser {
9✔
197
                                        key = "user_id"
3✔
198
                                }
3✔
199

200
                                logCtx[key] = identity.Subject
3✔
201

3✔
202
                                if identity.Tenant != "" {
5✔
203
                                        logCtx["tenant_id"] = identity.Tenant
2✔
204
                                }
2✔
205

206
                                if identity.Plan != "" {
5✔
207
                                        logCtx["plan"] = identity.Plan
2✔
208
                                }
2✔
209

210
                                l = l.F(logCtx)
3✔
211
                                ctx = log.WithContext(ctx, l)
3✔
212
                        }
213
                        ctx = WithContext(ctx, &identity)
3✔
214
                        r.Request = r.WithContext(ctx)
3✔
215
                }
216

217
                h(w, r)
3✔
218
        }
219
}
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