Skip to content

Commit

Permalink
feat: added error redirection logic to ui for failed saml and sso logons
Browse files Browse the repository at this point in the history
  • Loading branch information
iustinum committed Dec 8, 2024
1 parent da04364 commit a0c2fc3
Show file tree
Hide file tree
Showing 3 changed files with 76 additions and 40 deletions.
49 changes: 36 additions & 13 deletions cmd/api/src/api/v2/auth/oidc.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ package auth

import (
"fmt"
"github.com/specterops/bloodhound/log"
"net/http"
"time"

Expand Down Expand Up @@ -96,11 +97,17 @@ func getRedirectURL(request *http.Request, provider model.SSOProvider) string {

func (s ManagementResource) OIDCLoginHandler(response http.ResponseWriter, request *http.Request, ssoProvider model.SSOProvider) {
if ssoProvider.OIDCProvider == nil {
api.WriteErrorResponse(request.Context(), api.BuildErrorResponse(http.StatusNotFound, api.ErrorResponseDetailsResourceNotFound, request), response)
// SSO misconfiguration scenario
redirectToLoginPage(response, request, "Your SSO Connection failed, please contact your Administrator")
} else if state, err := config.GenerateRandomBase64String(77); err != nil {
api.WriteErrorResponse(request.Context(), api.BuildErrorResponse(http.StatusInternalServerError, api.ErrorResponseDetailsInternalServerError, request), response)
log.Errorf("[OIDC] Failed to generate state: %v", err)
// Technical issues scenario
redirectToLoginPage(response, request, "We’re having trouble connecting. Please check your internet and try again.")
} else if provider, err := oidc.NewProvider(request.Context(), ssoProvider.OIDCProvider.Issuer); err != nil {
api.WriteErrorResponse(request.Context(), api.BuildErrorResponse(http.StatusInternalServerError, err.Error(), request), response)
log.Errorf("[OIDC] Failed to create OIDC provider: %v", err)
// SSO misconfiguration or technical issue
// Treat this as a misconfiguration scenario
redirectToLoginPage(response, request, "Your SSO Connection failed, please contact your Administrator")
} else {
conf := &oauth2.Config{
ClientID: ssoProvider.OIDCProvider.ClientID,
Expand Down Expand Up @@ -135,17 +142,25 @@ func (s ManagementResource) OIDCCallbackHandler(response http.ResponseWriter, re
)

if ssoProvider.OIDCProvider == nil {
api.WriteErrorResponse(request.Context(), api.BuildErrorResponse(http.StatusNotFound, api.ErrorResponseDetailsResourceNotFound, request), response)
// SSO misconfiguration scenario
redirectToLoginPage(response, request, "Your SSO Connection failed, please contact your Administrator")
} else if len(code) == 0 {
api.WriteErrorResponse(request.Context(), api.BuildErrorResponse(http.StatusBadRequest, "missing code", request), response)
// Missing authorization code implies a credentials or form issue
// Not explicitly covered, treat as technical issue
redirectToLoginPage(response, request, "We’re having trouble connecting. Please check your internet and try again.")
} else if pkceVerifier, err := request.Cookie(api.AuthPKCECookieName); err != nil {
api.WriteErrorResponse(request.Context(), api.BuildErrorResponse(http.StatusBadRequest, "missing pkce verifier", request), response)
// Missing PKCE verifier - likely a technical or config issue
redirectToLoginPage(response, request, "We’re having trouble connecting. Please check your internet and try again.")
} else if len(state) == 0 {
api.WriteErrorResponse(request.Context(), api.BuildErrorResponse(http.StatusBadRequest, "missing state", request), response)
// Missing state parameter - treat as technical issue
redirectToLoginPage(response, request, "We’re having trouble connecting. Please check your internet and try again.")
} else if stateCookie, err := request.Cookie(api.AuthStateCookieName); err != nil || stateCookie.Value != state[0] {
api.WriteErrorResponse(request.Context(), api.BuildErrorResponse(http.StatusBadRequest, "bad state", request), response)
// Invalid state - treat as technical issue or misconfiguration
redirectToLoginPage(response, request, "We’re having trouble connecting. Please check your internet and try again.")
} else if provider, err := oidc.NewProvider(request.Context(), ssoProvider.OIDCProvider.Issuer); err != nil {
api.WriteErrorResponse(request.Context(), api.BuildErrorResponse(http.StatusInternalServerError, err.Error(), request), response)
log.Errorf("[OIDC] Failed to create OIDC provider: %v", err)
// SSO misconfiguration scenario
redirectToLoginPage(response, request, "Your SSO Connection failed, please contact your Administrator")
} else {
var (
oidcVerifier = provider.Verifier(&oidc.Config{ClientID: ssoProvider.OIDCProvider.ClientID})
Expand All @@ -157,11 +172,16 @@ func (s ManagementResource) OIDCCallbackHandler(response http.ResponseWriter, re
)

if token, err := oauth2Conf.Exchange(request.Context(), code[0], oauth2.VerifierOption(pkceVerifier.Value)); err != nil {
api.WriteErrorResponse(request.Context(), api.BuildErrorResponse(http.StatusForbidden, api.ErrorResponseDetailsForbidden, request), response)
log.Errorf("[OIDC] Token exchange failed: %v", err)
// SAML credentials issue equivalent for OIDC authentication
redirectToLoginPage(response, request, "Your SSO was unable to authenticate your user, please contact your Administrator")
} else if rawIDToken, ok := token.Extra("id_token").(string); !ok { // Extract the ID Token from OAuth2 token
api.WriteErrorResponse(request.Context(), api.BuildErrorResponse(http.StatusBadRequest, "missing id token", request), response)
// Missing ID token - credentials issue
redirectToLoginPage(response, request, "Your SSO was unable to authenticate your user, please contact your Administrator")
} else if idToken, err := oidcVerifier.Verify(request.Context(), rawIDToken); err != nil {
api.WriteErrorResponse(request.Context(), api.BuildErrorResponse(http.StatusBadRequest, "invalid id token", request), response)
log.Errorf("[OIDC] ID token verification failed: %v", err)
// Credentials issue scenario
redirectToLoginPage(response, request, "Your SSO was unable to authenticate your user, please contact your Administrator")
} else {
// Extract custom claims
var claims struct {
Expand All @@ -172,7 +192,10 @@ func (s ManagementResource) OIDCCallbackHandler(response http.ResponseWriter, re
Verified bool `json:"email_verified"`
}
if err := idToken.Claims(&claims); err != nil {
api.WriteErrorResponse(request.Context(), api.BuildErrorResponse(http.StatusInternalServerError, err.Error(), request), response)
log.Errorf("[OIDC] Failed to parse claims: %v", err)
// Technical or credentials issue
// Not explicitly covered; treat as a technical issue
redirectToLoginPage(response, request, "We’re having trouble connecting. Please check your internet and try again.")
} else {
s.authenticator.CreateSSOSession(request, response, claims.Email, ssoProvider)
}
Expand Down
39 changes: 26 additions & 13 deletions cmd/api/src/api/v2/auth/saml.go
Original file line number Diff line number Diff line change
Expand Up @@ -302,13 +302,13 @@ func (s ManagementResource) ServeSigningCertificate(response http.ResponseWriter
// HandleStartAuthFlow is called to start the SAML authentication process.
func (s ManagementResource) SAMLLoginHandler(response http.ResponseWriter, request *http.Request, ssoProvider model.SSOProvider) {
if ssoProvider.SAMLProvider == nil {
//api.WriteErrorResponse(request.Context(), api.BuildErrorResponse(http.StatusNotFound, api.ErrorResponseDetailsResourceNotFound, request), response)
//response.Header().Add(headers.Location.String(), "http://bhe.localhost/ui/login")
//response.WriteHeader(http.StatusFound)
redirectToLoginPage(response, request, "SSO Provider not found")
// SAML misconfiguration scenario
redirectToLoginPage(response, request, "Your SSO Connection failed, please contact your Administrator")

} else if serviceProvider, err := auth.NewServiceProvider(*ctx.Get(request.Context()).Host, s.config, *ssoProvider.SAMLProvider); err != nil {
api.WriteErrorResponse(request.Context(), api.BuildErrorResponse(http.StatusInternalServerError, err.Error(), request), response)
log.Errorf("[SAML] Service provider creation failed: %v", err)
// Technical issues scenario
redirectToLoginPage(response, request, "We’re having trouble connecting. Please check your internet and try again.")
} else {
var (
binding = saml.HTTPRedirectBinding
Expand All @@ -322,13 +322,16 @@ func (s ManagementResource) SAMLLoginHandler(response http.ResponseWriter, reque
// TODO: add actual relay state support - BED-5071
if authReq, err := serviceProvider.MakeAuthenticationRequest(bindingLocation, binding, saml.HTTPPostBinding); err != nil {
log.Errorf("[SAML] Failed creating SAML authentication request: %v", err)
api.WriteErrorResponse(request.Context(), api.BuildErrorResponse(http.StatusInternalServerError, api.ErrorResponseDetailsInternalServerError, request), response)
// SAML misconfiguration or technical issue
// Since this likely indicates a configuration problem, we treat it as a misconfiguration scenario
redirectToLoginPage(response, request, "Your SSO Connection failed, please contact your Administrator")
} else {
switch binding {
case saml.HTTPRedirectBinding:
if redirectURL, err := authReq.Redirect("", &serviceProvider); err != nil {
log.Errorf("[SAML] Failed to format a redirect for SAML provider %s: %v", serviceProvider.EntityID, err)
api.WriteErrorResponse(request.Context(), api.BuildErrorResponse(http.StatusInternalServerError, api.ErrorResponseDetailsInternalServerError, request), response)
// Likely a technical or configuration issue
redirectToLoginPage(response, request, "Your SSO Connection failed, please contact your Administrator")
} else {
response.Header().Add(headers.Location.String(), redirectURL.String())
response.WriteHeader(http.StatusFound)
Expand All @@ -341,11 +344,14 @@ func (s ManagementResource) SAMLLoginHandler(response http.ResponseWriter, reque

if _, err := response.Write([]byte(fmt.Sprintf(authInitiationContentBodyFormat, authReq.Post("")))); err != nil {
log.Errorf("[SAML] Failed to write response with HTTP POST binding: %v", err)
// Technical issues scenario
redirectToLoginPage(response, request, "We’re having trouble connecting. Please check your internet and try again.")
}

default:
log.Errorf("[SAML] Unhandled binding type %s", binding)
api.WriteErrorResponse(request.Context(), api.BuildErrorResponse(http.StatusInternalServerError, api.ErrorResponseDetailsInternalServerError, request), response)
// Treating unknown binding as a misconfiguration
redirectToLoginPage(response, request, "Your SSO Connection failed, please contact your Administrator")
}
}
}
Expand All @@ -354,13 +360,18 @@ func (s ManagementResource) SAMLLoginHandler(response http.ResponseWriter, reque
// HandleStartAuthFlow is called to start the SAML authentication process.
func (s ManagementResource) SAMLCallbackHandler(response http.ResponseWriter, request *http.Request, ssoProvider model.SSOProvider) {
if ssoProvider.SAMLProvider == nil {
api.WriteErrorResponse(request.Context(), api.BuildErrorResponse(http.StatusNotFound, api.ErrorResponseDetailsResourceNotFound, request), response)
// SAML misconfiguration scenario
redirectToLoginPage(response, request, "Your SSO Connection failed, please contact your Administrator")
} else if serviceProvider, err := auth.NewServiceProvider(*ctx.Get(request.Context()).Host, s.config, *ssoProvider.SAMLProvider); err != nil {
api.WriteErrorResponse(request.Context(), api.BuildErrorResponse(http.StatusInternalServerError, err.Error(), request), response)
log.Errorf("[SAML] Service provider creation failed: %v", err)
// Technical issues scenario
redirectToLoginPage(response, request, "We’re having trouble connecting. Please check your internet and try again.")
} else {
if err := request.ParseForm(); err != nil {
log.Errorf("[SAML] Failed to parse form POST: %v", err)
api.WriteErrorResponse(request.Context(), api.BuildErrorResponse(http.StatusBadRequest, "form POST is malformed", request), response)
// Technical issues or invalid form data
// This is not covered by acceptance criteria directly; treat as technical issue
redirectToLoginPage(response, request, "We’re having trouble connecting. Please check your internet and try again.")
} else {
if assertion, err := serviceProvider.ParseResponse(request, nil); err != nil {
var typedErr *saml.InvalidResponseError
Expand All @@ -370,10 +381,12 @@ func (s ManagementResource) SAMLCallbackHandler(response http.ResponseWriter, re
default:
log.Errorf("[SAML] Failed to parse ACS response for provider %s: %v", ssoProvider.SAMLProvider.IssuerURI, err)
}
api.WriteErrorResponse(request.Context(), api.BuildErrorResponse(http.StatusUnauthorized, api.ErrorResponseDetailsAuthenticationInvalid, request), response)
// SAML credentials issue scenario (authentication failed)
redirectToLoginPage(response, request, "Your SSO was unable to authenticate your user, please contact your Administrator")
} else if principalName, err := ssoProvider.SAMLProvider.GetSAMLUserPrincipalNameFromAssertion(assertion); err != nil {
log.Errorf("[SAML] Failed to lookup user for SAML provider %s: %v", ssoProvider.Name, err)
api.WriteErrorResponse(request.Context(), api.BuildErrorResponse(http.StatusBadRequest, "session assertion does not meet the requirements for user lookup", request), response)
// SAML credentials issue scenario again
redirectToLoginPage(response, request, "Your SSO was unable to authenticate your user, please contact your Administrator")
} else {
s.authenticator.CreateSSOSession(request, response, principalName, ssoProvider)
}
Expand Down
28 changes: 14 additions & 14 deletions cmd/api/src/api/v2/auth/sso.go
Original file line number Diff line number Diff line change
Expand Up @@ -196,20 +196,6 @@ func (s ManagementResource) UpdateSSOProvider(response http.ResponseWriter, requ
}
}

func redirectToLoginPage(response http.ResponseWriter, request *http.Request, errorMessage string) {
hostURL := *ctx.FromRequest(request).Host
redirectURL := api.URLJoinPath(hostURL, "ui/login")

// Optionally, include the error message as a query parameter or in session storage
query := redirectURL.Query()
query.Set("error", errorMessage)
redirectURL.RawQuery = query.Encode()

// Redirect to the login page
response.Header().Add(headers.Location.String(), redirectURL.String())
response.WriteHeader(http.StatusFound)
}

func (s ManagementResource) SSOLoginHandler(response http.ResponseWriter, request *http.Request) {
ssoProviderSlug := mux.Vars(request)[api.URIPathVariableSSOProviderSlug]

Expand Down Expand Up @@ -243,3 +229,17 @@ func (s ManagementResource) SSOCallbackHandler(response http.ResponseWriter, req
}
}
}

func redirectToLoginPage(response http.ResponseWriter, request *http.Request, errorMessage string) {
hostURL := *ctx.FromRequest(request).Host
redirectURL := api.URLJoinPath(hostURL, api.UserInterfacePath)

// Optionally, include the error message as a query parameter or in session storage
query := redirectURL.Query()
query.Set("error", errorMessage)
redirectURL.RawQuery = query.Encode()

// Redirect to the login page
response.Header().Add(headers.Location.String(), redirectURL.String())
response.WriteHeader(http.StatusFound)
}

0 comments on commit a0c2fc3

Please sign in to comment.