diff options
| author | James Elliott <james-d-elliott@users.noreply.github.com> | 2025-02-22 22:03:33 +1100 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2025-02-22 11:03:33 +0000 |
| commit | e7d387ed9169dcdb4e8171db8ed20ec6ef376e0a (patch) | |
| tree | 96bfca916ad1e25c7f960e98cd7e3d0af8f3fdd3 /internal/handlers/handler_oauth_device_authorization.go | |
| parent | 111344eaea4fd0c32ce58a181b94414ae639fe2b (diff) | |
feat(oidc): rfc8628 oauth 2.0 device code grant (#8082)
This implements RFC8628 OAuth 2.0 Device Authorization Grant and the accompanying OAuth 2.0 Device Code Flow.
Signed-off-by: James Elliott <james-d-elliott@users.noreply.github.com>
Diffstat (limited to 'internal/handlers/handler_oauth_device_authorization.go')
| -rw-r--r-- | internal/handlers/handler_oauth_device_authorization.go | 138 |
1 files changed, 138 insertions, 0 deletions
diff --git a/internal/handlers/handler_oauth_device_authorization.go b/internal/handlers/handler_oauth_device_authorization.go new file mode 100644 index 000000000..3e217b55f --- /dev/null +++ b/internal/handlers/handler_oauth_device_authorization.go @@ -0,0 +1,138 @@ +package handlers + +import ( + "errors" + "net/http" + "net/url" + + oauthelia2 "authelia.com/provider/oauth2" + "authelia.com/provider/oauth2/x/errorsx" + + "github.com/authelia/authelia/v4/internal/authentication" + "github.com/authelia/authelia/v4/internal/middlewares" + "github.com/authelia/authelia/v4/internal/model" + "github.com/authelia/authelia/v4/internal/oidc" + "github.com/authelia/authelia/v4/internal/session" +) + +func OAuthDeviceAuthorizationPOST(ctx *middlewares.AutheliaCtx, rw http.ResponseWriter, req *http.Request) { + var ( + request oauthelia2.DeviceAuthorizeRequester + response oauthelia2.DeviceAuthorizeResponder + + err error + ) + + if request, err = ctx.Providers.OpenIDConnect.NewRFC862DeviceAuthorizeRequest(ctx, req); err != nil { + ctx.Logger.Errorf("Device Authorization Request failed with error: %s", oauthelia2.ErrorToDebugRFC6749Error(err)) + + errorsx.WriteJSONError(rw, req, err) + + return + } + + if response, err = ctx.Providers.OpenIDConnect.NewRFC862DeviceAuthorizeResponse(ctx, request, oidc.NewSession()); err != nil { + ctx.Logger.Errorf("Device Authorization Request with id '%s' on client with id '%s' failed with error: %s", request.GetID(), request.GetClient().GetID(), oauthelia2.ErrorToDebugRFC6749Error(err)) + + errorsx.WriteJSONError(rw, req, err) + + return + } + + ctx.Providers.OpenIDConnect.WriteRFC862DeviceAuthorizeResponse(ctx, rw, request, response) +} + +func OAuthDeviceAuthorizationPUT(ctx *middlewares.AutheliaCtx, rw http.ResponseWriter, r *http.Request) { + var ( + requester oauthelia2.DeviceAuthorizeRequester + responder oauthelia2.DeviceUserAuthorizeResponder + client oidc.Client + + err error + ) + + if requester, err = ctx.Providers.OpenIDConnect.NewRFC8628UserAuthorizeRequest(ctx, r); err != nil { + ctx.Logger.Errorf("Device Authorization Request failed with error: %s", oauthelia2.ErrorToDebugRFC6749Error(err)) + + ctx.Providers.OpenIDConnect.WriteRFC8628UserAuthorizeError(ctx, rw, requester, err) + + return + } + + clientID := requester.GetClient().GetID() + + ctx.Logger.Debugf("Device Authorization Request with id '%s' on client with id '%s' is being processed", requester.GetID(), clientID) + + if client, err = ctx.Providers.OpenIDConnect.GetRegisteredClient(ctx, clientID); err != nil { + if errors.Is(err, oauthelia2.ErrNotFound) { + ctx.Logger.Errorf("Device Authorization Request with id '%s' on client with id '%s' could not be processed: client was not found", requester.GetID(), clientID) + } else { + ctx.Logger.Errorf("Device Authorization Request with id '%s' on client with id '%s' could not be processed: failed to find client: %s", requester.GetID(), clientID, oauthelia2.ErrorToDebugRFC6749Error(err)) + } + + ctx.Providers.OpenIDConnect.WriteRFC8628UserAuthorizeError(ctx, rw, requester, err) + + return + } + + var ( + userSession session.UserSession + consent *model.OAuth2ConsentSession + issuer *url.URL + handled bool + ) + + if userSession, err = ctx.GetSession(); err != nil { + ctx.Logger.Errorf("Device Authorization Request with id '%s' on client with id '%s' could not be processed: error occurred obtaining session information: %+v", requester.GetID(), client.GetID(), err) + + ctx.Providers.OpenIDConnect.WriteRFC8628UserAuthorizeError(ctx, rw, requester, oauthelia2.ErrServerError.WithHint("Could not obtain the user session.")) + + return + } + + issuer = ctx.RootURL() + + if consent, handled = handleOIDCAuthorizationConsent(ctx, issuer, client, userSession, rw, r, requester); handled { + return + } + + var details *authentication.UserDetailsExtended + + if details, err = ctx.Providers.UserProvider.GetDetailsExtended(userSession.Username); err != nil { + ctx.Logger.WithError(err).Errorf("Device Authorization Request with id '%s' on client with id '%s' could not be processed: error occurred retrieving user details for '%s' from the backend", requester.GetID(), client.GetID(), userSession.Username) + + ctx.Providers.OpenIDConnect.WriteRFC8628UserAuthorizeError(ctx, rw, requester, oauthelia2.ErrServerError.WithHint("Could not obtain the users details.")) + + return + } + + var requests *oidc.ClaimsRequests + + extra := map[string]any{} + + if requests, handled = handleOAuth2AuthorizationClaims(ctx, rw, r, "Device Authorization", userSession, details, client, requester, issuer, consent, extra); handled { + return + } + + ctx.Logger.Debugf("Device Authorization Request with id '%s' on client with id '%s' was successfully processed, proceeding to build Authorization Response", requester.GetID(), clientID) + + session := oidc.NewSessionWithRequester(ctx, issuer, ctx.Providers.OpenIDConnect.KeyManager.GetKeyID(ctx, client.GetIDTokenSignedResponseKeyID(), client.GetIDTokenSignedResponseAlg()), details.Username, userSession.AuthenticationMethodRefs.MarshalRFC8176(), extra, userSession.LastAuthenticatedTime(), consent, requester, requests) + + if responder, err = ctx.Providers.OpenIDConnect.NewRFC8628UserAuthorizeResponse(ctx, requester, session); err != nil { + ctx.Logger.Errorf("Device Authorization Request with id '%s' failed with error: %s", requester.GetID(), oauthelia2.ErrorToDebugRFC6749Error(err)) + + ctx.Providers.OpenIDConnect.WriteRFC8628UserAuthorizeError(ctx, rw, requester, err) + + return + } + + if err = ctx.Providers.StorageProvider.SaveOAuth2ConsentSessionGranted(ctx, consent.ID); err != nil { + ctx.Logger.Errorf("Device Authorization Request with id '%s' on client with id '%s' could not be processed: error occurred saving consent session: %+v", requester.GetID(), client.GetID(), err) + + ctx.Providers.OpenIDConnect.WriteRFC8628UserAuthorizeError(ctx, rw, requester, oidc.ErrConsentCouldNotSave) + + return + } + + ctx.Providers.OpenIDConnect.WriteRFC8628UserAuthorizeResponse(ctx, rw, requester, responder) +} |
