summaryrefslogtreecommitdiff
path: root/internal/handlers/handler_oauth_device_authorization.go
diff options
context:
space:
mode:
authorJames Elliott <james-d-elliott@users.noreply.github.com>2025-02-22 22:03:33 +1100
committerGitHub <noreply@github.com>2025-02-22 11:03:33 +0000
commite7d387ed9169dcdb4e8171db8ed20ec6ef376e0a (patch)
tree96bfca916ad1e25c7f960e98cd7e3d0af8f3fdd3 /internal/handlers/handler_oauth_device_authorization.go
parent111344eaea4fd0c32ce58a181b94414ae639fe2b (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.go138
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)
+}