2021-05-05 05:06:05 +07:00
|
|
|
package handlers
|
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
|
|
|
"net/http"
|
|
|
|
"strings"
|
feat(oidc): add additional config options, accurate token times, and refactoring (#1991)
* This gives admins more control over their OIDC installation exposing options that had defaults before. Things like lifespans for authorize codes, access tokens, id tokens, refresh tokens, a option to enable the debug client messages, minimum parameter entropy. It also allows admins to configure the response modes.
* Additionally this records specific values about a users session indicating when they performed a specific authz factor so this is represented in the token accurately.
* Lastly we also implemented a OIDC key manager which calculates the kid for jwk's using the SHA1 digest instead of being static, or more specifically the first 7 chars. As per https://datatracker.ietf.org/doc/html/draft-ietf-jose-json-web-key#section-8.1.1 the kid should not exceed 8 chars. While it's allowed to exceed 8 chars, it must only be done so with a compelling reason, which we do not have.
2021-07-04 06:44:30 +07:00
|
|
|
"time"
|
2021-05-05 05:06:05 +07:00
|
|
|
|
|
|
|
"github.com/ory/fosite"
|
feat(oidc): add additional config options, accurate token times, and refactoring (#1991)
* This gives admins more control over their OIDC installation exposing options that had defaults before. Things like lifespans for authorize codes, access tokens, id tokens, refresh tokens, a option to enable the debug client messages, minimum parameter entropy. It also allows admins to configure the response modes.
* Additionally this records specific values about a users session indicating when they performed a specific authz factor so this is represented in the token accurately.
* Lastly we also implemented a OIDC key manager which calculates the kid for jwk's using the SHA1 digest instead of being static, or more specifically the first 7 chars. As per https://datatracker.ietf.org/doc/html/draft-ietf-jose-json-web-key#section-8.1.1 the kid should not exceed 8 chars. While it's allowed to exceed 8 chars, it must only be done so with a compelling reason, which we do not have.
2021-07-04 06:44:30 +07:00
|
|
|
"github.com/ory/fosite/handler/openid"
|
|
|
|
"github.com/ory/fosite/token/jwt"
|
2021-05-05 05:06:05 +07:00
|
|
|
|
|
|
|
"github.com/authelia/authelia/internal/logging"
|
|
|
|
"github.com/authelia/authelia/internal/middlewares"
|
|
|
|
"github.com/authelia/authelia/internal/oidc"
|
|
|
|
"github.com/authelia/authelia/internal/session"
|
2021-07-10 11:56:33 +07:00
|
|
|
"github.com/authelia/authelia/internal/utils"
|
2021-05-05 05:06:05 +07:00
|
|
|
)
|
|
|
|
|
|
|
|
func oidcAuthorize(ctx *middlewares.AutheliaCtx, rw http.ResponseWriter, r *http.Request) {
|
|
|
|
ar, err := ctx.Providers.OpenIDConnect.Fosite.NewAuthorizeRequest(ctx, r)
|
|
|
|
if err != nil {
|
|
|
|
logging.Logger().Errorf("Error occurred in NewAuthorizeRequest: %+v", err)
|
|
|
|
ctx.Providers.OpenIDConnect.Fosite.WriteAuthorizeError(rw, ar, err)
|
|
|
|
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
clientID := ar.GetClient().GetID()
|
|
|
|
client, err := ctx.Providers.OpenIDConnect.Store.GetInternalClient(clientID)
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
err := fmt.Errorf("Unable to find related client configuration with name '%s': %v", ar.GetID(), err)
|
|
|
|
ctx.Logger.Error(err)
|
|
|
|
ctx.Providers.OpenIDConnect.Fosite.WriteAuthorizeError(rw, ar, err)
|
|
|
|
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
userSession := ctx.GetSession()
|
|
|
|
|
|
|
|
requestedScopes := ar.GetRequestedScopes()
|
|
|
|
requestedAudience := ar.GetRequestedAudience()
|
|
|
|
|
|
|
|
isAuthInsufficient := !client.IsAuthenticationLevelSufficient(userSession.AuthenticationLevel)
|
|
|
|
|
|
|
|
if isAuthInsufficient || (isConsentMissing(userSession.OIDCWorkflowSession, requestedScopes, requestedAudience)) {
|
|
|
|
oidcAuthorizeHandleAuthorizationOrConsentInsufficient(ctx, userSession, client, isAuthInsufficient, rw, r, ar)
|
|
|
|
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2021-07-10 11:56:33 +07:00
|
|
|
extraClaims := oidcGrantRequests(ar, requestedScopes, requestedAudience, &userSession)
|
2021-05-05 05:06:05 +07:00
|
|
|
|
feat(oidc): add additional config options, accurate token times, and refactoring (#1991)
* This gives admins more control over their OIDC installation exposing options that had defaults before. Things like lifespans for authorize codes, access tokens, id tokens, refresh tokens, a option to enable the debug client messages, minimum parameter entropy. It also allows admins to configure the response modes.
* Additionally this records specific values about a users session indicating when they performed a specific authz factor so this is represented in the token accurately.
* Lastly we also implemented a OIDC key manager which calculates the kid for jwk's using the SHA1 digest instead of being static, or more specifically the first 7 chars. As per https://datatracker.ietf.org/doc/html/draft-ietf-jose-json-web-key#section-8.1.1 the kid should not exceed 8 chars. While it's allowed to exceed 8 chars, it must only be done so with a compelling reason, which we do not have.
2021-07-04 06:44:30 +07:00
|
|
|
workflowCreated := time.Unix(userSession.OIDCWorkflowSession.CreatedTimestamp, 0)
|
|
|
|
|
2021-05-05 05:06:05 +07:00
|
|
|
userSession.OIDCWorkflowSession = nil
|
|
|
|
if err := ctx.SaveSession(userSession); err != nil {
|
|
|
|
ctx.Logger.Errorf("%v", err)
|
|
|
|
http.Error(rw, err.Error(), http.StatusInternalServerError)
|
|
|
|
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
feat(oidc): add additional config options, accurate token times, and refactoring (#1991)
* This gives admins more control over their OIDC installation exposing options that had defaults before. Things like lifespans for authorize codes, access tokens, id tokens, refresh tokens, a option to enable the debug client messages, minimum parameter entropy. It also allows admins to configure the response modes.
* Additionally this records specific values about a users session indicating when they performed a specific authz factor so this is represented in the token accurately.
* Lastly we also implemented a OIDC key manager which calculates the kid for jwk's using the SHA1 digest instead of being static, or more specifically the first 7 chars. As per https://datatracker.ietf.org/doc/html/draft-ietf-jose-json-web-key#section-8.1.1 the kid should not exceed 8 chars. While it's allowed to exceed 8 chars, it must only be done so with a compelling reason, which we do not have.
2021-07-04 06:44:30 +07:00
|
|
|
issuer, err := ctx.ForwardedProtoHost()
|
|
|
|
if err != nil {
|
|
|
|
ctx.Logger.Errorf("Error occurred obtaining issuer: %+v", err)
|
|
|
|
ctx.Providers.OpenIDConnect.Fosite.WriteAuthorizeError(rw, ar, err)
|
|
|
|
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
authTime, err := userSession.AuthenticatedTime(client.Policy)
|
2021-05-05 05:06:05 +07:00
|
|
|
if err != nil {
|
feat(oidc): add additional config options, accurate token times, and refactoring (#1991)
* This gives admins more control over their OIDC installation exposing options that had defaults before. Things like lifespans for authorize codes, access tokens, id tokens, refresh tokens, a option to enable the debug client messages, minimum parameter entropy. It also allows admins to configure the response modes.
* Additionally this records specific values about a users session indicating when they performed a specific authz factor so this is represented in the token accurately.
* Lastly we also implemented a OIDC key manager which calculates the kid for jwk's using the SHA1 digest instead of being static, or more specifically the first 7 chars. As per https://datatracker.ietf.org/doc/html/draft-ietf-jose-json-web-key#section-8.1.1 the kid should not exceed 8 chars. While it's allowed to exceed 8 chars, it must only be done so with a compelling reason, which we do not have.
2021-07-04 06:44:30 +07:00
|
|
|
ctx.Logger.Errorf("Error occurred obtaining authentication timestamp: %+v", err)
|
2021-05-05 05:06:05 +07:00
|
|
|
ctx.Providers.OpenIDConnect.Fosite.WriteAuthorizeError(rw, ar, err)
|
|
|
|
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
feat(oidc): add additional config options, accurate token times, and refactoring (#1991)
* This gives admins more control over their OIDC installation exposing options that had defaults before. Things like lifespans for authorize codes, access tokens, id tokens, refresh tokens, a option to enable the debug client messages, minimum parameter entropy. It also allows admins to configure the response modes.
* Additionally this records specific values about a users session indicating when they performed a specific authz factor so this is represented in the token accurately.
* Lastly we also implemented a OIDC key manager which calculates the kid for jwk's using the SHA1 digest instead of being static, or more specifically the first 7 chars. As per https://datatracker.ietf.org/doc/html/draft-ietf-jose-json-web-key#section-8.1.1 the kid should not exceed 8 chars. While it's allowed to exceed 8 chars, it must only be done so with a compelling reason, which we do not have.
2021-07-04 06:44:30 +07:00
|
|
|
response, err := ctx.Providers.OpenIDConnect.Fosite.NewAuthorizeResponse(ctx, ar, &oidc.OpenIDSession{
|
|
|
|
DefaultSession: &openid.DefaultSession{
|
|
|
|
Claims: &jwt.IDTokenClaims{
|
|
|
|
Subject: userSession.Username,
|
|
|
|
Issuer: issuer,
|
|
|
|
AuthTime: authTime,
|
|
|
|
RequestedAt: workflowCreated,
|
|
|
|
IssuedAt: time.Now(),
|
|
|
|
Nonce: ar.GetRequestForm().Get("nonce"),
|
2021-07-10 11:56:33 +07:00
|
|
|
Audience: ar.GetGrantedAudience(),
|
feat(oidc): add additional config options, accurate token times, and refactoring (#1991)
* This gives admins more control over their OIDC installation exposing options that had defaults before. Things like lifespans for authorize codes, access tokens, id tokens, refresh tokens, a option to enable the debug client messages, minimum parameter entropy. It also allows admins to configure the response modes.
* Additionally this records specific values about a users session indicating when they performed a specific authz factor so this is represented in the token accurately.
* Lastly we also implemented a OIDC key manager which calculates the kid for jwk's using the SHA1 digest instead of being static, or more specifically the first 7 chars. As per https://datatracker.ietf.org/doc/html/draft-ietf-jose-json-web-key#section-8.1.1 the kid should not exceed 8 chars. While it's allowed to exceed 8 chars, it must only be done so with a compelling reason, which we do not have.
2021-07-04 06:44:30 +07:00
|
|
|
Extra: extraClaims,
|
|
|
|
},
|
|
|
|
Headers: &jwt.Headers{Extra: map[string]interface{}{
|
|
|
|
"kid": ctx.Providers.OpenIDConnect.KeyManager.GetActiveKeyID(),
|
|
|
|
}},
|
|
|
|
Subject: userSession.Username,
|
|
|
|
},
|
|
|
|
ClientID: clientID,
|
|
|
|
})
|
2021-05-05 05:06:05 +07:00
|
|
|
if err != nil {
|
|
|
|
ctx.Logger.Errorf("Error occurred in NewAuthorizeResponse: %+v", err)
|
|
|
|
ctx.Providers.OpenIDConnect.Fosite.WriteAuthorizeError(rw, ar, err)
|
|
|
|
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
ctx.Providers.OpenIDConnect.Fosite.WriteAuthorizeResponse(rw, ar, response)
|
|
|
|
}
|
|
|
|
|
2021-07-10 11:56:33 +07:00
|
|
|
func oidcGrantRequests(ar fosite.AuthorizeRequester, scopes, audiences []string, userSession *session.UserSession) (extraClaims map[string]interface{}) {
|
|
|
|
extraClaims = map[string]interface{}{}
|
|
|
|
|
|
|
|
for _, scope := range scopes {
|
|
|
|
ar.GrantScope(scope)
|
|
|
|
|
|
|
|
switch scope {
|
|
|
|
case "groups":
|
|
|
|
extraClaims["groups"] = userSession.Groups
|
|
|
|
case "profile":
|
|
|
|
extraClaims["name"] = userSession.DisplayName
|
|
|
|
case "email":
|
|
|
|
if len(userSession.Emails) != 0 {
|
|
|
|
extraClaims["email"] = userSession.Emails[0]
|
|
|
|
if len(userSession.Emails) > 1 {
|
|
|
|
extraClaims["alt_emails"] = userSession.Emails[1:]
|
|
|
|
}
|
|
|
|
// TODO (james-d-elliott): actually verify emails and record that information.
|
|
|
|
extraClaims["email_verified"] = true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, audience := range audiences {
|
|
|
|
ar.GrantAudience(audience)
|
|
|
|
}
|
|
|
|
|
|
|
|
if !utils.IsStringInSlice(ar.GetClient().GetID(), ar.GetGrantedAudience()) {
|
|
|
|
ar.GrantAudience(ar.GetClient().GetID())
|
|
|
|
}
|
|
|
|
|
|
|
|
return extraClaims
|
|
|
|
}
|
|
|
|
|
2021-05-05 05:06:05 +07:00
|
|
|
func oidcAuthorizeHandleAuthorizationOrConsentInsufficient(
|
|
|
|
ctx *middlewares.AutheliaCtx, userSession session.UserSession, client *oidc.InternalClient, isAuthInsufficient bool,
|
|
|
|
rw http.ResponseWriter, r *http.Request,
|
|
|
|
ar fosite.AuthorizeRequester) {
|
|
|
|
forwardedProtoHost, err := ctx.ForwardedProtoHost()
|
|
|
|
if err != nil {
|
|
|
|
ctx.Logger.Errorf("%v", err)
|
|
|
|
http.Error(rw, err.Error(), http.StatusBadRequest)
|
|
|
|
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
redirectURL := fmt.Sprintf("%s%s", forwardedProtoHost, string(ctx.Request.RequestURI()))
|
|
|
|
|
|
|
|
ctx.Logger.Debugf("User %s must consent with scopes %s",
|
|
|
|
userSession.Username, strings.Join(ar.GetRequestedScopes(), ", "))
|
|
|
|
|
feat(oidc): add additional config options, accurate token times, and refactoring (#1991)
* This gives admins more control over their OIDC installation exposing options that had defaults before. Things like lifespans for authorize codes, access tokens, id tokens, refresh tokens, a option to enable the debug client messages, minimum parameter entropy. It also allows admins to configure the response modes.
* Additionally this records specific values about a users session indicating when they performed a specific authz factor so this is represented in the token accurately.
* Lastly we also implemented a OIDC key manager which calculates the kid for jwk's using the SHA1 digest instead of being static, or more specifically the first 7 chars. As per https://datatracker.ietf.org/doc/html/draft-ietf-jose-json-web-key#section-8.1.1 the kid should not exceed 8 chars. While it's allowed to exceed 8 chars, it must only be done so with a compelling reason, which we do not have.
2021-07-04 06:44:30 +07:00
|
|
|
userSession.OIDCWorkflowSession = &session.OIDCWorkflowSession{
|
|
|
|
ClientID: client.ID,
|
|
|
|
RequestedScopes: ar.GetRequestedScopes(),
|
|
|
|
RequestedAudience: ar.GetRequestedAudience(),
|
|
|
|
AuthURI: redirectURL,
|
|
|
|
TargetURI: ar.GetRedirectURI().String(),
|
|
|
|
RequiredAuthorizationLevel: client.Policy,
|
|
|
|
CreatedTimestamp: time.Now().Unix(),
|
|
|
|
}
|
2021-05-05 05:06:05 +07:00
|
|
|
|
|
|
|
if err := ctx.SaveSession(userSession); err != nil {
|
|
|
|
ctx.Logger.Errorf("%v", err)
|
|
|
|
http.Error(rw, err.Error(), http.StatusInternalServerError)
|
|
|
|
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
uri, err := ctx.ForwardedProtoHost()
|
|
|
|
if err != nil {
|
|
|
|
ctx.Logger.Errorf("%v", err)
|
|
|
|
http.Error(rw, err.Error(), http.StatusBadRequest)
|
|
|
|
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
if isAuthInsufficient {
|
|
|
|
http.Redirect(rw, r, uri, http.StatusFound)
|
|
|
|
} else {
|
|
|
|
http.Redirect(rw, r, fmt.Sprintf("%s/consent", uri), http.StatusFound)
|
|
|
|
}
|
|
|
|
}
|