2021-07-10 04:56:33 +00:00
package handlers
import (
"fmt"
"net/http"
"time"
"github.com/google/uuid"
"github.com/ory/fosite"
"github.com/ory/fosite/token/jwt"
"github.com/pkg/errors"
2022-10-02 02:07:40 +00:00
"github.com/valyala/fasthttp"
2021-07-10 04:56:33 +00:00
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"
2021-08-11 01:04:35 +00:00
"github.com/authelia/authelia/v4/internal/oidc"
2021-07-10 04:56:33 +00:00
)
2022-04-07 00:58:51 +00:00
// OpenIDConnectUserinfo handles GET/POST requests to the OpenID Connect 1.0 UserInfo endpoint.
//
// https://openid.net/specs/openid-connect-core-1_0.html#UserInfo
func OpenIDConnectUserinfo ( ctx * middlewares . AutheliaCtx , rw http . ResponseWriter , req * http . Request ) {
2022-03-15 22:55:38 +00:00
var (
tokenType fosite . TokenType
requester fosite . AccessRequester
2023-04-13 10:58:18 +00:00
client oidc . Client
2022-03-15 22:55:38 +00:00
err error
)
2021-07-10 04:56:33 +00:00
2022-03-15 22:55:38 +00:00
oidcSession := oidc . NewSession ( )
2022-10-20 02:16:36 +00:00
if tokenType , requester , err = ctx . Providers . OpenIDConnect . IntrospectToken (
2022-03-15 22:55:38 +00:00
req . Context ( ) , fosite . AccessTokenFromRequest ( req ) , fosite . AccessToken , oidcSession ) ; err != nil {
2021-07-10 04:56:33 +00:00
rfc := fosite . ErrorToRFC6749Error ( err )
2022-03-15 22:55:38 +00:00
2023-04-08 04:48:55 +00:00
ctx . Logger . Errorf ( "UserInfo Request failed with error: %s" , rfc . WithExposeDebug ( true ) . GetDescription ( ) )
2022-03-15 22:55:38 +00:00
2021-07-10 04:56:33 +00:00
if rfc . StatusCode ( ) == http . StatusUnauthorized {
2022-10-02 02:07:40 +00:00
rw . Header ( ) . Set ( fasthttp . HeaderWWWAuthenticate , fmt . Sprintf ( ` Bearer error="%s",error_description="%s" ` , rfc . ErrorField , rfc . GetDescription ( ) ) )
2021-07-10 04:56:33 +00:00
}
ctx . Providers . OpenIDConnect . WriteError ( rw , req , err )
return
}
2022-03-15 22:55:38 +00:00
clientID := requester . GetClient ( ) . GetID ( )
2021-07-10 04:56:33 +00:00
if tokenType != fosite . AccessToken {
2023-04-08 04:48:55 +00:00
ctx . Logger . Errorf ( "UserInfo Request with id '%s' on client with id '%s' failed with error: bearer authorization failed as the token is not an access token" , requester . GetID ( ) , client . GetID ( ) )
2022-03-15 22:55:38 +00:00
errStr := "Only access tokens are allowed in the authorization header."
2022-10-20 02:16:36 +00:00
rw . Header ( ) . Set ( fasthttp . HeaderWWWAuthenticate , fmt . Sprintf ( ` Bearer error="invalid_token",error_description="%s" ` , errStr ) )
2021-07-10 04:56:33 +00:00
ctx . Providers . OpenIDConnect . WriteErrorCode ( rw , req , http . StatusUnauthorized , errors . New ( errStr ) )
return
}
2022-10-20 02:16:36 +00:00
if client , err = ctx . Providers . OpenIDConnect . GetFullClient ( clientID ) ; err != nil {
2023-04-08 04:48:55 +00:00
rfc := fosite . ErrorToRFC6749Error ( err )
ctx . Logger . Errorf ( "UserInfo Request with id '%s' on client with id '%s' failed to retrieve client configuration with error: %s" , requester . GetID ( ) , client . GetID ( ) , rfc . WithExposeDebug ( true ) . GetDescription ( ) )
ctx . Providers . OpenIDConnect . WriteError ( rw , req , errors . WithStack ( rfc ) )
2021-07-10 04:56:33 +00:00
return
}
2022-04-07 05:33:53 +00:00
claims := requester . GetSession ( ) . ( * model . OpenIDSession ) . IDTokenClaims ( ) . ToMap ( )
2022-10-20 02:16:36 +00:00
delete ( claims , oidc . ClaimJWTID )
delete ( claims , oidc . ClaimSessionID )
delete ( claims , oidc . ClaimAccessTokenHash )
delete ( claims , oidc . ClaimCodeHash )
delete ( claims , oidc . ClaimExpirationTime )
delete ( claims , oidc . ClaimNonce )
2021-07-10 04:56:33 +00:00
2022-10-20 02:16:36 +00:00
audience , ok := claims [ oidc . ClaimAudience ] . ( [ ] string )
2022-03-15 22:55:38 +00:00
if ! ok || len ( audience ) == 0 {
audience = [ ] string { client . GetID ( ) }
} else {
found := false
for _ , aud := range audience {
if aud == clientID {
found = true
break
}
}
if found {
audience = append ( audience , clientID )
}
2021-07-10 04:56:33 +00:00
}
2022-10-20 02:16:36 +00:00
claims [ oidc . ClaimAudience ] = audience
2022-03-15 22:55:38 +00:00
2022-10-02 02:07:40 +00:00
var token string
2022-03-15 22:55:38 +00:00
ctx . Logger . Tracef ( "UserInfo Response with id '%s' on client with id '%s' is being sent with the following claims: %+v" , requester . GetID ( ) , clientID , claims )
2023-04-19 04:24:05 +00:00
switch alg := client . GetUserinfoSigningAlg ( ) ; alg {
case oidc . SigningAlgNone , "" :
ctx . Providers . OpenIDConnect . Write ( rw , req , claims )
default :
var jwk * oidc . JWK
if jwk = ctx . Providers . OpenIDConnect . KeyManager . GetByAlg ( ctx , alg ) ; jwk == nil {
ctx . Providers . OpenIDConnect . WriteError ( rw , req , errors . WithStack ( fosite . ErrServerError . WithHintf ( "Unsupported UserInfo signing algorithm '%s'." , alg ) ) )
return
}
2022-03-16 00:29:46 +00:00
var jti uuid . UUID
if jti , err = uuid . NewRandom ( ) ; err != nil {
2023-04-08 04:48:55 +00:00
ctx . Providers . OpenIDConnect . WriteError ( rw , req , fosite . ErrServerError . WithHint ( "Could not generate JTI." ) )
2022-03-16 00:29:46 +00:00
return
}
2022-10-20 02:16:36 +00:00
claims [ oidc . ClaimJWTID ] = jti . String ( )
claims [ oidc . ClaimIssuedAt ] = time . Now ( ) . Unix ( )
2021-07-10 04:56:33 +00:00
2022-03-15 22:55:38 +00:00
headers := & jwt . Headers {
2022-10-20 02:16:36 +00:00
Extra : map [ string ] any {
2023-04-19 04:24:05 +00:00
oidc . JWTHeaderKeyIdentifier : jwk . KeyID ( ) ,
2022-10-20 02:16:36 +00:00
} ,
2022-03-15 22:55:38 +00:00
}
2023-04-19 04:24:05 +00:00
if token , _ , err = jwk . Strategy ( ) . Generate ( req . Context ( ) , claims , headers ) ; err != nil {
2021-07-10 04:56:33 +00:00
ctx . Providers . OpenIDConnect . WriteError ( rw , req , err )
return
}
2023-04-08 04:48:55 +00:00
rw . Header ( ) . Set ( fasthttp . HeaderContentType , "application/jwt" )
2021-07-10 04:56:33 +00:00
_ , _ = rw . Write ( [ ] byte ( token ) )
}
}