authelia/internal/handlers/handler_oidc_userinfo.go

136 lines
4.0 KiB
Go

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"
"github.com/authelia/authelia/v4/internal/middlewares"
"github.com/authelia/authelia/v4/internal/model"
"github.com/authelia/authelia/v4/internal/oidc"
)
// 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) {
var (
tokenType fosite.TokenType
requester fosite.AccessRequester
client *oidc.Client
err error
)
oidcSession := oidc.NewSession()
if tokenType, requester, err = ctx.Providers.OpenIDConnect.Fosite.IntrospectToken(
req.Context(), fosite.AccessTokenFromRequest(req), fosite.AccessToken, oidcSession); err != nil {
rfc := fosite.ErrorToRFC6749Error(err)
ctx.Logger.Errorf("UserInfo Request failed with error: %+v", rfc)
if rfc.StatusCode() == http.StatusUnauthorized {
rw.Header().Set("WWW-Authenticate", fmt.Sprintf(`Bearer error="%s",error_description="%s"`, rfc.ErrorField, rfc.GetDescription()))
}
ctx.Providers.OpenIDConnect.WriteError(rw, req, err)
return
}
clientID := requester.GetClient().GetID()
if tokenType != fosite.AccessToken {
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())
errStr := "Only access tokens are allowed in the authorization header."
rw.Header().Set("WWW-Authenticate", fmt.Sprintf(`Bearer error="invalid_token",error_description="%s"`, errStr))
ctx.Providers.OpenIDConnect.WriteErrorCode(rw, req, http.StatusUnauthorized, errors.New(errStr))
return
}
if client, err = ctx.Providers.OpenIDConnect.Store.GetFullClient(clientID); err != nil {
ctx.Providers.OpenIDConnect.WriteError(rw, req, errors.WithStack(fosite.ErrServerError.WithHint("Unable to assert type of client")))
return
}
claims := requester.GetSession().(*model.OpenIDSession).IDTokenClaims().ToMap()
delete(claims, "jti")
delete(claims, "sid")
delete(claims, "at_hash")
delete(claims, "c_hash")
delete(claims, "exp")
delete(claims, "nonce")
audience, ok := claims["aud"].([]string)
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)
}
}
claims["aud"] = audience
var (
keyID, token string
)
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)
switch client.UserinfoSigningAlgorithm {
case "RS256":
var jti uuid.UUID
if jti, err = uuid.NewRandom(); err != nil {
ctx.Providers.OpenIDConnect.WriteError(rw, req, fosite.ErrServerError.WithHintf("Could not generate JTI."))
return
}
claims["jti"] = jti.String()
claims["iat"] = time.Now().Unix()
if keyID, err = ctx.Providers.OpenIDConnect.KeyManager.Strategy().GetPublicKeyID(req.Context()); err != nil {
ctx.Providers.OpenIDConnect.WriteError(rw, req, fosite.ErrServerError.WithHintf("Could not find the active JWK."))
return
}
headers := &jwt.Headers{
Extra: map[string]interface{}{"kid": keyID},
}
if token, _, err = ctx.Providers.OpenIDConnect.KeyManager.Strategy().Generate(req.Context(), claims, headers); err != nil {
ctx.Providers.OpenIDConnect.WriteError(rw, req, err)
return
}
rw.Header().Set("Content-Type", "application/jwt")
_, _ = rw.Write([]byte(token))
case "none", "":
ctx.Providers.OpenIDConnect.Write(rw, req, claims)
default:
ctx.Providers.OpenIDConnect.WriteError(rw, req, errors.WithStack(fosite.ErrServerError.WithHintf("Unsupported UserInfo signing algorithm '%s'.", client.UserinfoSigningAlgorithm)))
}
}