2021-05-04 22:06:05 +00:00
package handlers
import (
2022-10-20 02:16:36 +00:00
"database/sql"
2021-05-04 22:06:05 +00:00
"encoding/json"
"fmt"
2022-07-26 05:43:39 +00:00
"net/url"
"path"
2022-04-08 05:35:21 +00:00
"time"
2021-05-04 22:06:05 +00:00
2022-07-26 05:43:39 +00:00
"github.com/google/uuid"
2021-08-11 01:04:35 +00:00
"github.com/authelia/authelia/v4/internal/middlewares"
2022-04-07 05:33:53 +00:00
"github.com/authelia/authelia/v4/internal/model"
"github.com/authelia/authelia/v4/internal/oidc"
"github.com/authelia/authelia/v4/internal/session"
2021-05-04 22:06:05 +00:00
)
2022-04-07 00:58:51 +00:00
// OpenIDConnectConsentGET handles requests to provide consent for OpenID Connect.
func OpenIDConnectConsentGET ( ctx * middlewares . AutheliaCtx ) {
2022-07-26 05:43:39 +00:00
var (
consentID uuid . UUID
err error
)
2022-10-20 02:16:36 +00:00
if consentID , err = uuid . Parse ( string ( ctx . RequestCtx . QueryArgs ( ) . PeekBytes ( qryArgID ) ) ) ; err != nil {
ctx . Logger . Errorf ( "Unable to convert '%s' into a UUID: %+v" , ctx . RequestCtx . QueryArgs ( ) . PeekBytes ( qryArgID ) , err )
2022-07-26 05:43:39 +00:00
ctx . ReplyForbidden ( )
return
}
2022-10-20 02:16:36 +00:00
var (
consent * model . OAuth2ConsentSession
client * oidc . Client
handled bool
)
2021-05-04 22:06:05 +00:00
2022-10-20 02:16:36 +00:00
if _ , consent , client , handled = oidcConsentGetSessionsAndClient ( ctx , consentID ) ; handled {
2021-05-04 22:06:05 +00:00
return
}
2022-07-26 05:43:39 +00:00
if err = ctx . SetJSONBody ( client . GetConsentResponseBody ( consent ) ) ; err != nil {
2021-09-17 05:53:40 +00:00
ctx . Error ( fmt . Errorf ( "unable to set JSON body: %v" , err ) , "Operation failed" )
2021-05-04 22:06:05 +00:00
}
}
2022-04-07 00:58:51 +00:00
// OpenIDConnectConsentPOST handles consent responses for OpenID Connect.
func OpenIDConnectConsentPOST ( ctx * middlewares . AutheliaCtx ) {
2022-04-07 05:33:53 +00:00
var (
2022-07-26 05:43:39 +00:00
consentID uuid . UUID
bodyJSON oidc . ConsentPostRequestBody
err error
2022-04-07 05:33:53 +00:00
)
2021-05-04 22:06:05 +00:00
2022-07-26 05:43:39 +00:00
if err = json . Unmarshal ( ctx . Request . Body ( ) , & bodyJSON ) ; err != nil {
ctx . Logger . Errorf ( "Failed to parse JSON bodyJSON in consent POST: %+v" , err )
2022-04-07 05:33:53 +00:00
ctx . SetJSONError ( messageOperationFailed )
2021-05-04 22:06:05 +00:00
return
}
2022-07-26 05:43:39 +00:00
if consentID , err = uuid . Parse ( bodyJSON . ConsentID ) ; err != nil {
2022-10-20 02:16:36 +00:00
ctx . Logger . Errorf ( "Unable to convert '%s' into a UUID: %+v" , bodyJSON . ConsentID , err )
2022-07-26 05:43:39 +00:00
ctx . ReplyForbidden ( )
return
}
2022-10-20 02:16:36 +00:00
var (
userSession session . UserSession
consent * model . OAuth2ConsentSession
client * oidc . Client
handled bool
)
2021-05-04 22:06:05 +00:00
2022-10-20 02:16:36 +00:00
if userSession , consent , client , handled = oidcConsentGetSessionsAndClient ( ctx , consentID ) ; handled {
2021-05-04 22:06:05 +00:00
return
}
2022-07-26 05:43:39 +00:00
if consent . ClientID != bodyJSON . ClientID {
2022-04-07 05:33:53 +00:00
ctx . Logger . Errorf ( "User '%s' consented to scopes of another client (%s) than expected (%s). Beware this can be a sign of attack" ,
2022-07-26 05:43:39 +00:00
userSession . Username , bodyJSON . ClientID , consent . ClientID )
2022-04-07 05:33:53 +00:00
ctx . SetJSONError ( messageOperationFailed )
2021-05-04 22:06:05 +00:00
return
}
2022-07-26 05:43:39 +00:00
if bodyJSON . Consent {
2022-10-20 02:16:36 +00:00
consent . Grant ( )
2022-04-08 05:35:21 +00:00
2022-10-20 02:16:36 +00:00
if bodyJSON . PreConfigure {
if client . Consent . Mode == oidc . ClientConsentModePreConfigured {
config := model . OAuth2ConsentPreConfig {
ClientID : consent . ClientID ,
Subject : consent . Subject . UUID ,
CreatedAt : time . Now ( ) ,
ExpiresAt : sql . NullTime { Time : time . Now ( ) . Add ( client . Consent . Duration ) , Valid : true } ,
Scopes : consent . GrantedScopes ,
Audience : consent . GrantedAudience ,
}
2022-04-08 05:35:21 +00:00
2022-10-20 02:16:36 +00:00
var id int64
2022-04-07 05:33:53 +00:00
2022-10-20 02:16:36 +00:00
if id , err = ctx . Providers . StorageProvider . SaveOAuth2ConsentPreConfiguration ( ctx , config ) ; err != nil {
ctx . Logger . Errorf ( "Failed to save the consent pre-configuration to the database: %+v" , err )
ctx . SetJSONError ( messageOperationFailed )
2022-07-26 05:43:39 +00:00
2022-10-20 02:16:36 +00:00
return
}
2022-07-26 05:43:39 +00:00
2022-10-20 02:16:36 +00:00
consent . PreConfiguration = sql . NullInt64 { Int64 : id , Valid : true }
2021-05-04 22:06:05 +00:00
2022-10-20 02:16:36 +00:00
ctx . Logger . Debugf ( "Consent session with id '%s' for user '%s': pre-configured and set to expire at %v" , consent . ChallengeID , userSession . Username , config . ExpiresAt . Time )
} else {
ctx . Logger . Warnf ( "Consent session with id '%s' for user '%s': consent pre-configuration was requested and was ignored because it is not permitted on this client" , consent . ChallengeID , userSession . Username )
}
}
2021-05-04 22:06:05 +00:00
}
2022-07-26 05:43:39 +00:00
if err = ctx . Providers . StorageProvider . SaveOAuth2ConsentSessionResponse ( ctx , * consent , bodyJSON . Consent ) ; err != nil {
2022-04-07 05:33:53 +00:00
ctx . Logger . Errorf ( "Failed to save the consent session response to the database: %+v" , err )
ctx . SetJSONError ( messageOperationFailed )
2021-05-04 22:06:05 +00:00
return
}
2022-07-26 05:43:39 +00:00
var (
redirectURI * url . URL
query url . Values
)
2022-10-20 02:16:36 +00:00
if redirectURI , err = ctx . IssuerURL ( ) ; err != nil {
2022-07-26 05:43:39 +00:00
ctx . Logger . Errorf ( "Failed to parse the consent redirect URL: %+v" , err )
ctx . SetJSONError ( messageOperationFailed )
return
}
if query , err = url . ParseQuery ( consent . Form ) ; err != nil {
ctx . Logger . Errorf ( "Failed to parse the consent form values: %+v" , err )
ctx . SetJSONError ( messageOperationFailed )
return
}
2022-10-20 02:16:36 +00:00
query . Set ( queryArgConsentID , consent . ChallengeID . String ( ) )
2022-07-26 05:43:39 +00:00
2022-10-20 02:16:36 +00:00
redirectURI . Path = path . Join ( redirectURI . Path , oidc . EndpointPathAuthorization )
2022-07-26 05:43:39 +00:00
redirectURI . RawQuery = query . Encode ( )
response := oidc . ConsentPostResponseBody { RedirectURI : redirectURI . String ( ) }
2022-04-07 05:33:53 +00:00
if err = ctx . SetJSONBody ( response ) ; err != nil {
2022-07-26 05:43:39 +00:00
ctx . Error ( fmt . Errorf ( "unable to set JSON bodyJSON in response" ) , "Operation failed" )
2022-04-07 05:33:53 +00:00
}
}
2021-05-04 22:06:05 +00:00
2022-07-26 05:43:39 +00:00
func oidcConsentGetSessionsAndClient ( ctx * middlewares . AutheliaCtx , consentID uuid . UUID ) ( userSession session . UserSession , consent * model . OAuth2ConsentSession , client * oidc . Client , handled bool ) {
2022-04-07 05:33:53 +00:00
var (
err error
)
2021-05-04 22:06:05 +00:00
2022-04-07 05:33:53 +00:00
userSession = ctx . GetSession ( )
2021-05-04 22:06:05 +00:00
2022-07-26 05:43:39 +00:00
if consent , err = ctx . Providers . StorageProvider . LoadOAuth2ConsentSessionByChallengeID ( ctx , consentID ) ; err != nil {
ctx . Logger . Errorf ( "Unable to load consent session with challenge id '%s': %v" , consentID , err )
2022-04-07 05:33:53 +00:00
ctx . ReplyForbidden ( )
return userSession , nil , nil , true
2021-05-04 22:06:05 +00:00
}
2022-10-20 02:16:36 +00:00
if client , err = ctx . Providers . OpenIDConnect . GetFullClient ( consent . ClientID ) ; err != nil {
2022-07-26 05:43:39 +00:00
ctx . Logger . Errorf ( "Unable to find related client configuration with name '%s': %v" , consent . ClientID , err )
2022-04-07 05:33:53 +00:00
ctx . ReplyForbidden ( )
2021-05-04 22:06:05 +00:00
2022-04-07 05:33:53 +00:00
return userSession , nil , nil , true
2021-05-04 22:06:05 +00:00
}
2022-04-07 05:33:53 +00:00
2022-07-26 05:43:39 +00:00
if err = verifyOIDCUserAuthorizedForConsent ( ctx , client , userSession , consent , uuid . UUID { } ) ; err != nil {
ctx . Logger . Errorf ( "Could not authorize the user user '%s' for the consent session with challenge id '%s' on client with id '%s': %v" , userSession . Username , consent . ChallengeID , client . GetID ( ) , err )
2022-04-07 05:33:53 +00:00
ctx . ReplyForbidden ( )
return userSession , nil , nil , true
}
2022-10-20 02:16:36 +00:00
switch client . Consent . Mode {
case oidc . ClientConsentModeImplicit :
ctx . Logger . Errorf ( "Unable to perform OpenID Connect Consent for user '%s' and client id '%s': the client is using the implicit consent mode" , userSession . Username , consent . ClientID )
ctx . ReplyForbidden ( )
return
default :
switch {
case consent . Responded ( ) :
ctx . Logger . Errorf ( "Unable to perform OpenID Connect Consent for user '%s' and client id '%s': the client is using the explicit consent mode and this consent session has already been responded to" , userSession . Username , consent . ClientID )
ctx . ReplyForbidden ( )
return userSession , nil , nil , true
case ! consent . CanGrant ( ) :
ctx . Logger . Errorf ( "Unable to perform OpenID Connect Consent for user '%s' and client id '%s': the specified consent session cannot be granted" , userSession . Username , consent . ClientID )
ctx . ReplyForbidden ( )
return userSession , nil , nil , true
}
}
if ! client . IsAuthenticationLevelSufficient ( userSession . AuthenticationLevel ) {
ctx . Logger . Errorf ( "Unable to perform OpenID Connect Consent for user '%s' and client id '%s': the user is not sufficiently authenticated" , userSession . Username , consent . ClientID )
ctx . ReplyForbidden ( )
return userSession , nil , nil , true
}
2022-04-07 05:33:53 +00:00
return userSession , consent , client , false
2021-05-04 22:06:05 +00:00
}