1 package barcode
2
3 import (
4 "context"
5 "html"
6 "time"
7
8 "github.com/ory/fosite"
9 "github.com/ory/fosite/handler/oauth2"
10 "github.com/ory/fosite/handler/openid"
11 "github.com/ory/fosite/token/hmac"
12 "github.com/ory/x/errorsx"
13
14 "edge-infra.dev/pkg/edge/iam/config"
15 "edge-infra.dev/pkg/edge/iam/device"
16 "edge-infra.dev/pkg/edge/iam/profile"
17 "edge-infra.dev/pkg/edge/iam/prometheus"
18 "edge-infra.dev/pkg/edge/iam/session"
19 "edge-infra.dev/pkg/edge/iam/util"
20
21 iamErrors "edge-infra.dev/pkg/edge/iam/errors"
22 )
23
24
25 type GrantHandler struct {
26
27 BarcodeStrategy *OpaqueStrategy
28 SignedStrategy *SignedStrategy
29 LoginHintStrategy *hmac.HMACStrategy
30 BarcodeStorage Storage
31
32
33 AccessTokenStrategy oauth2.AccessTokenStrategy
34 ScopeStrategy fosite.ScopeStrategy
35 AccessTokenStorage oauth2.AccessTokenStorage
36 ProfileStorage profile.Storage
37 DeviceStorage device.Storage
38 LoginSessionStorage session.LoginSessionStorage
39 ProfileTTL time.Duration
40 BarcodeLength uint8
41
42
43 RefreshTokenStrategy oauth2.RefreshTokenStrategy
44 RefreshTokenStorage oauth2.RefreshTokenStorage
45 RefreshTokenScopes []string
46 RevocationStorage oauth2.TokenRevocationStorage
47
48
49 IDTokenHandleHelper *openid.IDTokenHandleHelper
50
51
52 Metrics *prometheus.Metrics
53 }
54
55
56 func (h *GrantHandler) CanHandleTokenEndpointRequest(requester fosite.AccessRequester) bool {
57 return requester.GetGrantTypes().ExactOne("barcode")
58 }
59
60
61 func (h *GrantHandler) CanSkipClientAuth(_ fosite.AccessRequester) bool {
62 return false
63 }
64
65
66 func (h *GrantHandler) HandleTokenEndpointRequest(ctx context.Context, requester fosite.AccessRequester) error {
67
68
69
70
71 if !requester.GetGrantTypes().ExactOne("barcode") {
72 return errorsx.WithStack(fosite.ErrUnknownRequest)
73 }
74 h.Metrics.IncHTTPRequestsTotal(signInBarcode)
75
76
77 client := requester.GetClient()
78 s := session.FromRequester(requester)
79
80
81 if client.IsPublic() {
82 hint := "The OAuth 2.0 Client is marked as public and is thus not allowed to use authorization grant 'barcode'."
83 return errorsx.WithStack(fosite.ErrInvalidGrant.WithHint(hint))
84 }
85
86
87 if !client.GetGrantTypes().Has("barcode") {
88 hint := "The OAuth 2.0 Client is not allowed to use authorization grant 'barcode'."
89 return errorsx.WithStack(fosite.ErrUnauthorizedClient.WithHint(hint))
90 }
91
92
93 for _, scope := range requester.GetRequestedScopes() {
94 if !h.ScopeStrategy(client.GetScopes(), scope) {
95 return errorsx.WithStack(fosite.ErrInvalidScope.WithHintf("The OAuth 2.0 Client is not allowed to request scope '%s'.", scope))
96 }
97 requester.GrantScope(scope)
98 }
99
100
101 barcode := requester.GetRequestForm().Get("barcode")
102 if barcode == "" {
103
104 h.Metrics.IncSignInRequestsTotal(signInBarcode, util.Failed)
105 hint := "The barcode parameter is missing"
106 return errorsx.WithStack(fosite.ErrUnauthorizedClient.WithHint(hint))
107 }
108
109 clientID := client.GetID()
110 var userProfile *profile.Profile
111 var err error
112
113
114 if barcode[:len(config.BarcodePrefix())] != config.BarcodePrefix() {
115 h.Metrics.IncSignInRequestsTotal(signInBarcode, util.Failed)
116 return errorsx.WithStack(fosite.ErrRequestUnauthorized.
117 WithWrap(h.invalidBarcodeScan(s, clientID, iamErrors.ErrUnrecognisedBarcode)))
118 }
119
120 if h.is128ABarcode(barcode) {
121 userProfile, err = h.handle128AScan(ctx, s, barcode, clientID)
122 if err != nil {
123 return err
124 }
125 } else {
126 userProfile, err = h.handleEBCScan(ctx, s, barcode, clientID)
127 if err != nil {
128 return err
129 }
130 }
131
132 for _, audience := range requester.GetGrantedAudience() {
133 requester.GrantAudience(audience)
134 }
135
136
137
138
139
140 s.SetOrg(userProfile.Organization)
141 s.SetRls(userProfile.Roles)
142
143
144 s.SetGivenName(userProfile.GivenName)
145 s.SetFamilyName(userProfile.FamilyName)
146 s.SetAge(userProfile.Age)
147 s.SetDeviceLogin(userProfile.DeviceLogin)
148 s.SetFullName(userProfile.FullName)
149 s.SetEmail(userProfile.Email)
150 if userProfile.Address != nil {
151 s.SetAddress(userProfile.Address.ToMap())
152 }
153
154
155 s.AsFosite().SetExpiresAt(fosite.AccessToken, accessExpiresAt())
156 s.AsFosite().SetExpiresAt(fosite.RefreshToken, refreshExpiresAt())
157 return nil
158 }
159
160 func hasValidRefreshToken(token string) error {
161
162 if util.IsDeviceLoginAvailable() {
163 return device.ExchangeRefreshToken(token)
164 }
165 return device.IsRefreshTokenValid(token)
166 }
167
168 func (h *GrantHandler) handle128AScan(ctx context.Context, s session.Session, barcode, clientID string) (*profile.Profile, error) {
169
170 if len(barcode) != int(h.BarcodeLength) {
171 h.Metrics.IncSignInRequestsTotal(signInBarcode, util.Failed)
172 return nil, errorsx.WithStack(fosite.ErrRequestUnauthorized.
173 WithWrap(h.invalidBarcodeScan(s, clientID, iamErrors.ErrUnrecognisedBarcode)))
174 }
175
176
177 key := h.BarcodeStrategy.GetKey(barcode)
178 barcodeData, err := h.BarcodeStorage.GetBarcode(ctx, key)
179 if err != nil {
180 return nil, errorsx.WithStack(fosite.ErrRequestUnauthorized.
181 WithWrap(h.invalidBarcodeScan(s, clientID, iamErrors.ErrUnrecognisedBarcode)))
182 }
183
184
185 userProfile, err := h.ProfileStorage.GetIdentityProfile(ctx, barcodeData.Subject)
186 if err != nil {
187 return nil, errorsx.WithStack(fosite.ErrServerError.
188 WithWrap(err).
189 WithDebug("storage error"))
190 }
191
192 if userProfile == nil {
193 return nil, errorsx.WithStack(fosite.ErrRequestUnauthorized.
194 WithWrap(h.invalidBarcodeScan(s, clientID, iamErrors.ErrUnrecognisedBarcode)))
195 }
196
197 s.SetSubject(userProfile.Subject)
198
199
200
201
202
203
204
205 if config.DeviceLoginEnabled() {
206 if err := h.verifyDeviceAuth(ctx, s, userProfile, clientID); err != nil {
207
208 return userProfile, err
209 }
210 } else {
211 if err := h.verifyProfile(s, userProfile, clientID); err != nil {
212 return userProfile, errorsx.WithStack(fosite.ErrRequestUnauthorized.
213 WithWrap(err).WithDebug("error verifying profile"))
214 }
215 }
216
217 currentBarcodeKey, err := h.BarcodeStorage.GetBarcodeUser(ctx, barcodeData.Subject)
218 if err != nil {
219
220
221 return nil, errorsx.WithStack(fosite.ErrServerError.WithHint(err.Error()))
222 }
223
224
225 if currentBarcodeKey != key {
226 h.Metrics.IncSignInRequestsTotal(signInBarcode, util.Failed)
227 return nil, errorsx.WithStack(fosite.ErrRequestUnauthorized.
228 WithWrap(h.invalidBarcodeScan(s, clientID, iamErrors.ErrExpiredBarcode)))
229 }
230
231
232 if err := h.BarcodeStrategy.Verify(barcode, barcodeData); err != nil {
233 h.Metrics.IncSignInRequestsTotal(signInBarcode, util.Failed)
234 if err == iamErrors.ErrExpiredBarcode {
235 return nil, errorsx.WithStack(fosite.ErrRequestUnauthorized.
236 WithWrap(h.invalidBarcodeScan(s, clientID, iamErrors.ErrExpiredBarcode)))
237 }
238 return nil, errorsx.WithStack(fosite.ErrRequestUnauthorized.
239 WithWrap(h.invalidBarcodeScan(s, clientID, iamErrors.ErrUnrecognisedBarcode)))
240 }
241 return userProfile, nil
242 }
243
244 func (h *GrantHandler) verifyDeviceAuth(ctx context.Context, s session.Session, userProfile *profile.Profile, clientID string) error {
245 deviceLogin := userProfile.DeviceLogin
246 acc, err := h.DeviceStorage.GetDeviceAccount(ctx, deviceLogin)
247 if err != nil {
248 return errorsx.WithStack(fosite.ErrServerError.
249 WithWrap(err).
250 WithDebug("error fetching device account from storage"))
251 }
252 if acc == nil {
253 return errorsx.WithStack(fosite.ErrRequestUnauthorized.
254 WithWrap(h.invalidBarcodeScan(s, clientID, iamErrors.ErrUnrecognisedBarcode)).
255 WithDebug("no such device account"))
256 }
257
258 err = hasValidRefreshToken(acc.RefreshToken)
259 if err != nil {
260 return errorsx.WithStack(fosite.ErrRequestUnauthorized.
261 WithWrap(h.invalidBarcodeScan(s, clientID, iamErrors.ErrExpiredBarcode)))
262 }
263 return nil
264 }
265
266 func (h *GrantHandler) verifyProfile(s session.Session, userProfile *profile.Profile, clientID string) error {
267 verify, err := userProfile.RequireVerification(h.BarcodeStorage.IsOffline())
268 if err != nil {
269 return errorsx.WithStack(fosite.ErrServerError.
270 WithWrap(err).
271 WithDebug("profile error"))
272 }
273 if verify {
274 challenge, signature, err := h.LoginHintStrategy.Generate()
275 if err != nil {
276 return errorsx.WithStack(fosite.ErrServerError.WithHint(err.Error()))
277 }
278 s.SetChallenge(challenge)
279
280 err = h.LoginSessionStorage.SetLoginSession(signature, &session.LoginSession{
281 Subject: userProfile.Subject,
282 Reason: 1,
283 Active: true,
284 ClientID: clientID,
285 LoginOptions: session.LoginOptions{},
286 })
287
288 if err != nil {
289 return errorsx.WithStack(fosite.ErrServerError.WithHint(err.Error()))
290 }
291 return iamErrors.ErrLoginRequired
292 }
293 return nil
294 }
295
296 func (h *GrantHandler) handleEBCScan(ctx context.Context, s session.Session, barcode, clientID string) (*profile.Profile, error) {
297 payload, err := h.SignedStrategy.Verify(barcode)
298
299 if err == iamErrors.ErrUnrecognisedBarcode {
300 return nil, errorsx.WithStack(fosite.ErrRequestUnauthorized.
301 WithWrap(h.invalidBarcodeScan(s, clientID, iamErrors.ErrUnrecognisedBarcode)))
302 } else if err == iamErrors.ErrExpiredEBC {
303 return nil, errorsx.WithStack(fosite.ErrRequestUnauthorized.
304 WithWrap(h.invalidBarcodeScan(s, clientID, iamErrors.ErrExpiredBarcode)))
305 } else if err != nil {
306 return nil, errorsx.WithStack(fosite.ErrRequestUnauthorized.
307 WithWrap(h.invalidBarcodeScan(s, clientID, iamErrors.ErrUnrecognisedBarcode)))
308 }
309
310
311
312
313
314 if !h.BarcodeStorage.IsOffline() && util.IsCloudLoginAvailable() {
315 return nil, errorsx.WithStack(fosite.ErrRequestUnauthorized.
316 WithWrap(h.invalidBarcodeScan(s, clientID, iamErrors.ErrInvalidEBCUsage)))
317 }
318
319 subject, err := h.ProfileStorage.GetSubjectFromAlias(ctx, payload.Subject)
320 if err != nil {
321 return nil, errorsx.WithStack(fosite.ErrServerError.
322 WithWrap(err).
323 WithDebug("storage error, missing subject for alias."))
324 }
325
326 userProfile, err := h.ProfileStorage.GetIdentityProfile(ctx, subject)
327 if err != nil {
328 return nil, errorsx.WithStack(fosite.ErrServerError.
329 WithWrap(err).
330 WithDebug("storage error"))
331 }
332
333 if userProfile == nil {
334 return nil, errorsx.WithStack(fosite.ErrRequestUnauthorized.
335 WithWrap(h.invalidBarcodeScan(s, clientID, iamErrors.ErrUnrecognisedBarcode)))
336 }
337
338 if time.Unix(userProfile.LastUpdated, 0).Sub(time.Unix(payload.IssuedAt, 0)) > 0 {
339 return nil, errorsx.WithStack(fosite.ErrRequestUnauthorized.
340 WithWrap(h.invalidBarcodeScan(s, clientID, iamErrors.ErrExpiredBarcode)))
341 }
342
343 s.SetSubject(userProfile.Subject)
344
345 return userProfile, nil
346 }
347
348
349 func (h *GrantHandler) invalidBarcodeScan(s session.Session, clientID string, barcodeErr error) error {
350 challenge, signature, err := h.LoginHintStrategy.Generate()
351 if err != nil {
352 return errorsx.WithStack(fosite.ErrServerError.WithHint(err.Error()))
353 }
354
355 s.SetChallenge(challenge)
356
357 loginSession := &session.LoginSession{
358 Subject: "",
359 Reason: 7,
360 Active: true,
361 ClientID: clientID,
362 LoginOptions: session.LoginOptions{
363 ErrorMessage: html.EscapeString(barcodeErr.Error()),
364 },
365 }
366
367 err = h.LoginSessionStorage.SetLoginSession(signature, loginSession)
368
369 if err != nil {
370 return errorsx.WithStack(fosite.ErrServerError.WithHint(err.Error()))
371 }
372
373 return barcodeErr
374 }
375
376
377 func (h *GrantHandler) PopulateTokenEndpointResponse(ctx context.Context, requester fosite.AccessRequester, responder fosite.AccessResponder) (err error) {
378
379 if !h.CanHandleTokenEndpointRequest(requester) {
380 return errorsx.WithStack(fosite.ErrUnknownRequest)
381 }
382
383
384 if err = h.PopulateAccessToken(ctx, requester, responder); err != nil {
385 return err
386 }
387
388
389 if h.CanIssueRefreshToken(requester) {
390 if err = h.PopulateRefreshToken(ctx, requester, responder); err != nil {
391 return err
392 }
393 }
394
395
396 if h.CanIssueIDToken(requester) {
397 if err = h.PopulateIDToken(ctx, requester, responder); err != nil {
398 return err
399 }
400 }
401 h.Metrics.IncSignInRequestsTotal(signInBarcode, util.Succeeded)
402 return nil
403 }
404
405 func (h *GrantHandler) PopulateIDToken(ctx context.Context, requester fosite.AccessRequester, responder fosite.AccessResponder) error {
406
407 s := session.FromRequester(requester)
408
409
410 claims := s.AsOpenID().Claims
411 if claims.Subject == "" {
412 return errorsx.WithStack(fosite.ErrServerError.WithDebug("Failed to generate id token because subject is an empty string."))
413 }
414
415
416 claims.AccessTokenHash = h.IDTokenHandleHelper.GetAccessTokenHash(ctx, requester, responder)
417
418
419 return h.IDTokenHandleHelper.IssueExplicitIDToken(ctx, requester, responder)
420 }
421
422
423 func (h *GrantHandler) PopulateAccessToken(ctx context.Context, requester fosite.AccessRequester, responder fosite.AccessResponder) error {
424
425 accessToken, accessSignature, err := h.AccessTokenStrategy.GenerateAccessToken(ctx, requester)
426 if err != nil {
427 return errorsx.WithStack(fosite.ErrServerError.WithWrap(err).WithDebug(err.Error()))
428 }
429
430
431 if err = h.AccessTokenStorage.CreateAccessTokenSession(ctx, accessSignature, requester.Sanitize([]string{})); err != nil {
432 return errorsx.WithStack(fosite.ErrServerError.WithWrap(err).WithDebug(err.Error()))
433 }
434
435
436 s := session.FromRequester(requester)
437 responder.SetAccessToken(accessToken)
438 responder.SetTokenType("bearer")
439 expiresAt := s.AsFosite().GetExpiresAt(fosite.AccessToken)
440 responder.SetExpiresIn(time.Until(expiresAt))
441
442 return nil
443 }
444
445 func (h *GrantHandler) CanIssueRefreshToken(requester fosite.Requester) bool {
446
447 if len(h.RefreshTokenScopes) > 0 && !requester.GetGrantedScopes().HasOneOf(h.RefreshTokenScopes...) {
448 return false
449 }
450
451 if !requester.GetClient().GetGrantTypes().Has("refresh_token") {
452 return false
453 }
454 return true
455 }
456
457
458 func (h *GrantHandler) PopulateRefreshToken(ctx context.Context, requester fosite.AccessRequester, responder fosite.AccessResponder) error {
459
460 refreshToken, refreshSignature, err := h.RefreshTokenStrategy.GenerateRefreshToken(ctx, requester)
461 if err != nil {
462 return errorsx.WithStack(fosite.ErrServerError.WithWrap(err).WithDebug(err.Error()))
463 }
464
465
466 if err = h.RefreshTokenStorage.CreateRefreshTokenSession(ctx, refreshSignature, requester.Sanitize([]string{})); err != nil {
467 return errorsx.WithStack(fosite.ErrServerError.WithWrap(err).WithDebug(err.Error()))
468 }
469
470
471 responder.SetExtra("refresh_token", refreshToken)
472
473 return nil
474 }
475
476 func (h *GrantHandler) CanIssueIDToken(requester fosite.Requester) bool {
477
478 if len(h.RefreshTokenScopes) > 0 && !requester.GetGrantedScopes().HasOneOf("openid") {
479 return false
480 }
481
482 if !requester.GetClient().GetGrantTypes().Has("authorization_code") {
483 return false
484 }
485 return true
486 }
487
488 func (h *GrantHandler) is128ABarcode(barcode string) bool {
489
490 return len(barcode) <= int(h.BarcodeLength)
491 }
492
View as plain text