Skip to content

Commit

Permalink
Refactor authorize handling
Browse files Browse the repository at this point in the history
  • Loading branch information
giftkugel committed Sep 30, 2024
1 parent c374b97 commit ce82be2
Showing 1 changed file with 158 additions and 124 deletions.
282 changes: 158 additions & 124 deletions internal/server/handler/authorize/authorize.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,19 @@ import (
"time"
)

type authorizeRequestValues struct {
clientIdParameter string
redirectParameter string
responseTypeParameter string
stateParameter string
codeChallengeParameter string
codeChallengeMethodParameter string
scopeParameter string
nonceParameter string
promptParameter string
maxAgeParameter string
}

type Handler struct {
validator *validation.RequestValidator
cookieManager *cookie.Manager
Expand Down Expand Up @@ -88,70 +101,151 @@ func (h *Handler) handleGetRequest(w http.ResponseWriter, r *http.Request) {
promptParameter := r.URL.Query().Get(oidc.ParameterPrompt)
maxAgeParameter := r.URL.Query().Get(oidc.ParameterMaxAge)

client, exists := h.validator.ValidateClientId(clientIdParameter)
authorizeRequest := &authorizeRequestValues{
clientIdParameter: clientIdParameter,
redirectParameter: redirectParameter,
responseTypeParameter: responseTypeParameter,
stateParameter: stateParameter,
codeChallengeParameter: codeChallengeParameter,
codeChallengeMethodParameter: codeChallengeMethodParameter,
scopeParameter: scopeParameter,
nonceParameter: nonceParameter,
promptParameter: promptParameter,
maxAgeParameter: maxAgeParameter,
}

h.handleAuthorizeRequest(w, r, authorizeRequest)
}

func (h *Handler) handlePostRequest(w http.ResponseWriter, r *http.Request, user *config.User) {
loginSession := &session.LoginSession{
Id: uuid.NewString(),
Username: user.Username,
}
h.loginSessionManager.StartSession(loginSession)
authCookie, authCookieError := h.cookieManager.CreateAuthCookie(user.Username, loginSession.Id)
if authCookieError != nil {
h.errorHandler.InternalServerErrorHandler(w, r, authCookieError)
return
}

authSessionForm := r.PostFormValue("stopnik_auth_session")
loginToken, loginTokenError := h.validator.GetLoginToken(authSessionForm)
if loginTokenError != nil {
h.errorHandler.BadRequestHandler(w, r)
return
}
authSessionId := loginToken.Subject()
authSession, authSessionExists := h.authSessionManager.GetSession(authSessionId)
if !authSessionExists {
h.sendRetryLocation(w, r, "")
return
}

authSession.Username = user.Username
authSession.AuthTime = loginSession.StartTime
redirectURL, urlParseError := url.Parse(authSession.Redirect)
if urlParseError != nil {
h.errorHandler.InternalServerErrorHandler(w, r, urlParseError)
return
}

http.SetCookie(w, &authCookie)

responseTypes := authSession.ResponseTypes

query := redirectURL.Query()
if slices.Contains(responseTypes, oauth2.RtToken) {
client, clientExists := h.validator.ValidateClientId(authSession.ClientId)
if !clientExists {
h.errorHandler.BadRequestHandler(w, r)
return
}
accessTokenResponse := h.tokenManager.CreateAccessTokenResponse(r, user.Username, client, &loginSession.StartTime, authSession.Scopes, authSession.Nonce, "")
setImplicitGrantParameter(query, accessTokenResponse)
} else if slices.Contains(responseTypes, oauth2.RtCode) {
setAuthorizationGrantParameter(query, authSession.Id)
} else {
oauth2.AuthorizationErrorResponseHandler(w, redirectURL, authSession.State, &oauth2.AuthorizationErrorResponseParameter{Error: oauth2.AuthorizationEtUnsupportedResponseType})
return
}

if authSession.State != "" {
query.Set(oauth2.ParameterState, authSession.State)
}

sendFound(w, redirectURL, query)
}

func (h *Handler) handleAuthorizeRequest(w http.ResponseWriter, r *http.Request, authorizeRequest *authorizeRequestValues) {
if authorizeRequest == nil {
h.errorHandler.InternalServerErrorHandler(w, r, errors.New("no authorization values provided"))
return
}
client, exists := h.validator.ValidateClientId(authorizeRequest.clientIdParameter)
if !exists {
log.Error("Invalid client id %s", clientIdParameter)
log.Error("Invalid client id %s", authorizeRequest.clientIdParameter)
h.errorHandler.BadRequestHandler(w, r)
return
}

redirectURL, urlParseError := url.Parse(redirectParameter)
redirectURL, urlParseError := url.Parse(authorizeRequest.redirectParameter)
if urlParseError != nil {
log.Error("Could not parse redirect URI %s for client %s", redirectParameter, client.Id)
log.Error("Could not parse redirect URI %s for client %s", authorizeRequest.redirectParameter, client.Id)
h.errorHandler.BadRequestHandler(w, r)
return
}

invalidRedirectErrorHandler := h.validateRedirect(client, redirectParameter)
invalidRedirectErrorHandler := h.validateRedirect(client, authorizeRequest.redirectParameter)
if invalidRedirectErrorHandler != nil {
invalidRedirectErrorHandler(w, r)
return
}

responseTypes, validResponseTypes := h.getResponseTypes(responseTypeParameter)
responseTypes, validResponseTypes := h.getResponseTypes(authorizeRequest.responseTypeParameter)
if !validResponseTypes {
log.Error("Invalid %s parameter with value %s for client %s", oauth2.ParameterResponseType, responseTypeParameter, client.Id)
log.Error("Invalid %s parameter with value %s for client %s", oauth2.ParameterResponseType, authorizeRequest.responseTypeParameter, client.Id)

errorMessage := fmt.Sprintf("Invalid %s parameter value", oauth2.ParameterResponseType)
authorizeError := &oauth2.AuthorizationErrorResponseParameter{Error: oauth2.AuthorizationEtInvalidRequest, Description: errorMessage}
oauth2.AuthorizationErrorResponseHandler(w, redirectURL, stateParameter, authorizeError)
oauth2.AuthorizationErrorResponseHandler(w, redirectURL, authorizeRequest.stateParameter, authorizeError)
return
}

if !slices.Contains(responseTypes, oauth2.RtCode) && codeChallengeParameter != "" && codeChallengeMethodParameter != "" {
if !slices.Contains(responseTypes, oauth2.RtCode) && authorizeRequest.codeChallengeParameter != "" && authorizeRequest.codeChallengeMethodParameter != "" {
errorMessage := fmt.Sprintf("Code challenge should only be used for response type %s", oauth2.RtCode)
authorizeError := &oauth2.AuthorizationErrorResponseParameter{Error: oauth2.AuthorizationEtInvalidRequest, Description: errorMessage}
oauth2.AuthorizationErrorResponseHandler(w, redirectURL, stateParameter, authorizeError)
oauth2.AuthorizationErrorResponseHandler(w, redirectURL, authorizeRequest.stateParameter, authorizeError)
return
}

if log.IsDebug() {
log.Debug("Response types: %v", responseTypes)
log.Debug("Redirect URI: %s", redirectParameter)
log.Debug("State: %s", stateParameter)
log.Debug("Scope: %s", scopeParameter)
log.Debug("Redirect URI: %s", authorizeRequest.redirectParameter)
log.Debug("State: %s", authorizeRequest.stateParameter)
log.Debug("Scope: %s", authorizeRequest.scopeParameter)
}

scopes := strings.Split(scopeParameter, " ")
scopes := strings.Split(authorizeRequest.scopeParameter, " ")

id := uuid.NewString()
authSession := &session.AuthSession{
Id: id,
Redirect: redirectParameter,
Redirect: authorizeRequest.redirectParameter,
AuthURI: r.URL.RequestURI(),
CodeChallenge: codeChallengeParameter,
CodeChallengeMethod: codeChallengeMethodParameter,
ClientId: clientIdParameter,
CodeChallenge: authorizeRequest.codeChallengeParameter,
CodeChallengeMethod: authorizeRequest.codeChallengeMethodParameter,
ClientId: authorizeRequest.clientIdParameter,
ResponseTypes: responseTypes,
Scopes: scopes,
State: stateParameter,
State: authorizeRequest.stateParameter,
}

if client.Oidc && oidc.HasOidcScope(scopes) && nonceParameter != "" {
authSession.Nonce = nonceParameter
} else if nonceParameter != "" {
if client.Oidc && oidc.HasOidcScope(scopes) && authorizeRequest.nonceParameter != "" {
authSession.Nonce = authorizeRequest.nonceParameter
} else if authorizeRequest.nonceParameter != "" {
log.Error("Nonce used without OpenID Connect setting for client with id %s", client.Id)
oauth2.AuthorizationErrorResponseHandler(w, redirectURL, stateParameter, &oauth2.AuthorizationErrorResponseParameter{Error: oauth2.AuthorizationEtInvalidRequest})
oauth2.AuthorizationErrorResponseHandler(w, redirectURL, authorizeRequest.stateParameter, &oauth2.AuthorizationErrorResponseParameter{Error: oauth2.AuthorizationEtInvalidRequest})
return
}

Expand All @@ -164,40 +258,40 @@ func (h *Handler) handleGetRequest(w http.ResponseWriter, r *http.Request) {
user, loginSession, validCookie := h.cookieManager.ValidateAuthCookie(r)

var promptType *oidc.PromptType
if client.Oidc && oidc.HasOidcScope(scopes) && promptParameter != "" {
if client.Oidc && oidc.HasOidcScope(scopes) && authorizeRequest.promptParameter != "" {
var authorizationErrorResponse *oauth2.AuthorizationErrorResponseParameter
promptType, authorizationErrorResponse = h.getPromptType(validCookie, promptParameter)
promptType, authorizationErrorResponse = h.getPromptType(validCookie, authorizeRequest.promptParameter)
if authorizationErrorResponse != nil {
oauth2.AuthorizationErrorResponseHandler(w, redirectURL, stateParameter, authorizationErrorResponse)
oauth2.AuthorizationErrorResponseHandler(w, redirectURL, authorizeRequest.stateParameter, authorizationErrorResponse)
return
}
} else if promptParameter != "" {
} else if authorizeRequest.promptParameter != "" {
log.Error("Prompt used without OpenID Connect setting for client with id %s", client.Id)
oauth2.AuthorizationErrorResponseHandler(w, redirectURL, stateParameter, &oauth2.AuthorizationErrorResponseParameter{Error: oauth2.AuthorizationEtInvalidRequest})
oauth2.AuthorizationErrorResponseHandler(w, redirectURL, authorizeRequest.stateParameter, &oauth2.AuthorizationErrorResponseParameter{Error: oauth2.AuthorizationEtInvalidRequest})
return
}

var maxAge *int
if client.Oidc && oidc.HasOidcScope(scopes) && maxAgeParameter != "" {
maxAgeResult, maxAgeError := strconv.Atoi(maxAgeParameter)
if client.Oidc && oidc.HasOidcScope(scopes) && authorizeRequest.maxAgeParameter != "" {
maxAgeResult, maxAgeError := strconv.Atoi(authorizeRequest.maxAgeParameter)
if maxAgeError != nil {
oauth2.AuthorizationErrorResponseHandler(w, redirectURL, stateParameter, &oauth2.AuthorizationErrorResponseParameter{Error: oauth2.AuthorizationEtInvalidRequest})
oauth2.AuthorizationErrorResponseHandler(w, redirectURL, authorizeRequest.stateParameter, &oauth2.AuthorizationErrorResponseParameter{Error: oauth2.AuthorizationEtInvalidRequest})
return
}
maxAge = &maxAgeResult
} else if maxAgeParameter != "" {
} else if authorizeRequest.maxAgeParameter != "" {
log.Error("Max age used without OpenID Connect setting for client with id %s", client.Id)
oauth2.AuthorizationErrorResponseHandler(w, redirectURL, stateParameter, &oauth2.AuthorizationErrorResponseParameter{Error: oauth2.AuthorizationEtInvalidRequest})
oauth2.AuthorizationErrorResponseHandler(w, redirectURL, authorizeRequest.stateParameter, &oauth2.AuthorizationErrorResponseParameter{Error: oauth2.AuthorizationEtInvalidRequest})
return
}

if validCookie && !h.forceLogin(loginSession, promptType, maxAge) {
authSession.Username = user.Username
authSession.AuthTime = loginSession.StartTime

query, authorizationErrorResponse := h.createQuery(r, redirectURL, user, client, scopes, authSession, loginSession, responseTypes, id, idTokenRequest, stateParameter)
query, authorizationErrorResponse := h.createLocationResponseQuery(r, redirectURL, user, client, scopes, authSession, loginSession, responseTypes, id, idTokenRequest, authorizeRequest.stateParameter)
if authorizationErrorResponse != nil {
oauth2.AuthorizationErrorResponseHandler(w, redirectURL, stateParameter, authorizationErrorResponse)
oauth2.AuthorizationErrorResponseHandler(w, redirectURL, authorizeRequest.stateParameter, authorizationErrorResponse)
return
}

Expand All @@ -208,95 +302,6 @@ func (h *Handler) handleGetRequest(w http.ResponseWriter, r *http.Request) {
}
}

func (h *Handler) createQuery(r *http.Request, redirectURL *url.URL, user *config.User, client *config.Client, scopes []string, authSession *session.AuthSession, loginSession *session.LoginSession, responseTypes []oauth2.ResponseType, id string, idTokenRequest bool, stateParameter string) (url.Values, *oauth2.AuthorizationErrorResponseParameter) {
query := redirectURL.Query()

var idToken string
accessTokenResponse := h.tokenManager.CreateAccessTokenResponse(r, user.Username, client, &loginSession.StartTime, scopes, authSession.Nonce, "")
if slices.Contains(responseTypes, oauth2.RtToken) {
setImplicitGrantParameter(query, accessTokenResponse)
} else if slices.Contains(responseTypes, oauth2.RtCode) {
setAuthorizationGrantParameter(query, id)
} else if idTokenRequest {
if accessTokenResponse.IdTokenValue == "" {
system.CriticalError(errors.New("no id_token found in response"))
}
idToken = accessTokenResponse.IdTokenValue
} else {
log.Error("Invalid response type %v", responseTypes)
return nil, &oauth2.AuthorizationErrorResponseParameter{Error: oauth2.AuthorizationEtUnsupportedResponseType}
}

if stateParameter != "" {
query.Set(oauth2.ParameterState, stateParameter)
}

if idToken != "" {
setIdTokenParameter(query, idToken)
}
return query, nil
}

func (h *Handler) handlePostRequest(w http.ResponseWriter, r *http.Request, user *config.User) {
loginSession := &session.LoginSession{
Id: uuid.NewString(),
Username: user.Username,
}
h.loginSessionManager.StartSession(loginSession)
authCookie, authCookieError := h.cookieManager.CreateAuthCookie(user.Username, loginSession.Id)
if authCookieError != nil {
h.errorHandler.InternalServerErrorHandler(w, r, authCookieError)
return
}

authSessionForm := r.PostFormValue("stopnik_auth_session")
loginToken, loginTokenError := h.validator.GetLoginToken(authSessionForm)
if loginTokenError != nil {
h.errorHandler.BadRequestHandler(w, r)
return
}
authSessionId := loginToken.Subject()
authSession, authSessionExists := h.authSessionManager.GetSession(authSessionId)
if !authSessionExists {
h.sendRetryLocation(w, r, "")
return
}

authSession.Username = user.Username
authSession.AuthTime = loginSession.StartTime
redirectURL, urlParseError := url.Parse(authSession.Redirect)
if urlParseError != nil {
h.errorHandler.InternalServerErrorHandler(w, r, urlParseError)
return
}

http.SetCookie(w, &authCookie)

responseTypes := authSession.ResponseTypes

query := redirectURL.Query()
if slices.Contains(responseTypes, oauth2.RtToken) {
client, clientExists := h.validator.ValidateClientId(authSession.ClientId)
if !clientExists {
h.errorHandler.BadRequestHandler(w, r)
return
}
accessTokenResponse := h.tokenManager.CreateAccessTokenResponse(r, user.Username, client, &loginSession.StartTime, authSession.Scopes, authSession.Nonce, "")
setImplicitGrantParameter(query, accessTokenResponse)
} else if slices.Contains(responseTypes, oauth2.RtCode) {
setAuthorizationGrantParameter(query, authSession.Id)
} else {
oauth2.AuthorizationErrorResponseHandler(w, redirectURL, authSession.State, &oauth2.AuthorizationErrorResponseParameter{Error: oauth2.AuthorizationEtUnsupportedResponseType})
return
}

if authSession.State != "" {
query.Set(oauth2.ParameterState, authSession.State)
}

sendFound(w, redirectURL, query)
}

func (h *Handler) validateLogin(w http.ResponseWriter, r *http.Request) (*config.User, bool) {
// Handle Post from Login
user, loginError := h.validator.ValidateFormLogin(r)
Expand Down Expand Up @@ -327,6 +332,35 @@ func (h *Handler) validateRedirect(client *config.Client, redirect string) func(
return nil
}

func (h *Handler) createLocationResponseQuery(r *http.Request, redirectURL *url.URL, user *config.User, client *config.Client, scopes []string, authSession *session.AuthSession, loginSession *session.LoginSession, responseTypes []oauth2.ResponseType, id string, idTokenRequest bool, stateParameter string) (url.Values, *oauth2.AuthorizationErrorResponseParameter) {
query := redirectURL.Query()

var idToken string
accessTokenResponse := h.tokenManager.CreateAccessTokenResponse(r, user.Username, client, &loginSession.StartTime, scopes, authSession.Nonce, "")
if slices.Contains(responseTypes, oauth2.RtToken) {
setImplicitGrantParameter(query, accessTokenResponse)
} else if slices.Contains(responseTypes, oauth2.RtCode) {
setAuthorizationGrantParameter(query, id)
} else if idTokenRequest {
if accessTokenResponse.IdTokenValue == "" {
system.CriticalError(errors.New("no id_token found in response"))
}
idToken = accessTokenResponse.IdTokenValue
} else {
log.Error("Invalid response type %v", responseTypes)
return nil, &oauth2.AuthorizationErrorResponseParameter{Error: oauth2.AuthorizationEtUnsupportedResponseType}
}

if stateParameter != "" {
query.Set(oauth2.ParameterState, stateParameter)
}

if idToken != "" {
setIdTokenParameter(query, idToken)
}
return query, nil
}

func (h *Handler) getPromptType(validCookie bool, promptQueryParameter string) (*oidc.PromptType, *oauth2.AuthorizationErrorResponseParameter) {
promptType, validPromptType := oidc.PromptTypeFromString(promptQueryParameter)
if !validPromptType {
Expand Down

0 comments on commit ce82be2

Please sign in to comment.