Files
upstream-corteza/server/auth/handlers/handle_external.go
upstream-mirror 7e385fc126
Some checks failed
SonarQube / SonarQube (push) Failing after 5m28s
E2e test on main branch push / server and client test (map[name:admin port:8080]) (push) Failing after 3m33s
Server and lib tests / Library test (js) (push) Failing after 1m36s
Server and lib tests / Library test (vue) (push) Successful in 2m24s
Server and lib tests / Web client test (admin) (push) Successful in 2m52s
Server and lib tests / Web client test (compose) (push) Successful in 3m23s
Server and lib tests / Web client test (discovery) (push) Successful in 2m49s
Server and lib tests / Web client test (one) (push) Successful in 2m52s
Server and lib tests / Web client test (privacy) (push) Successful in 2m44s
Server and lib tests / Web client test (reporter) (push) Successful in 2m56s
Server and lib tests / Web client test (workflow) (push) Successful in 3m0s
Server and lib tests / Server tests (push) Failing after 4m55s
E2e test on main branch push / notify-failure (push) Failing after 9s
Server and lib tests / Send matrix testing notification (push) Successful in 2s
Mark stale issues and pull requests / stale (push) Successful in 40s
snapshot of https://github.com/cortezaproject/corteza.git (2024.9.x, 2026-09-18; blobs > 131072B omitted)
2026-09-18 01:22:23 +05:00

233 lines
6.5 KiB
Go

package handlers
import (
"context"
"fmt"
"net/http"
"strings"
"github.com/cortezaproject/corteza/server/auth/external"
"github.com/cortezaproject/corteza/server/auth/request"
"github.com/cortezaproject/corteza/server/auth/settings"
"github.com/cortezaproject/corteza/server/pkg/api"
"github.com/cortezaproject/corteza/server/pkg/auth"
"github.com/cortezaproject/corteza/server/system/types"
"github.com/go-chi/chi/v5"
"go.uber.org/zap"
)
func copyProviderToContext(r *http.Request) *http.Request {
return r.WithContext(context.WithValue(r.Context(), "provider", chi.URLParam(r, "provider")))
}
func (h *AuthHandlers) externalInit(w http.ResponseWriter, r *http.Request) {
r = copyProviderToContext(r)
h.Log.Info("starting external authentication flow")
beginUserAuth(w, r, external.NewDefaultExternalHandler())
}
func (h *AuthHandlers) externalCallback(w http.ResponseWriter, r *http.Request) {
r = copyProviderToContext(r)
h.Log.Info("external authentication callback")
if user, err := completeUserAuth(w, r, external.NewDefaultExternalHandler()); err != nil {
h.Log.Error("failed to complete user auth", zap.Error(err))
h.handleFailedExternalAuth(w, r, err)
} else {
h.handleSuccessfulExternalAuth(w, r, *user)
}
}
// Handles authentication via external auth providers of
// unknown an user + appending authentication on external providers
// to a current user
func (h *AuthHandlers) handleSuccessfulExternalAuth(w http.ResponseWriter, r *http.Request, cred types.ExternalAuthUser) {
h.Log.Info("login successful", zap.String("provider", cred.Provider))
switch cred.Provider {
case "saml":
h.handleSuccessfulExternalSAMLAuth(w, r, cred)
default:
h.handleSuccessfulExternalGenericAuth(w, r, cred)
}
}
// handleSuccessfulExternalSAMLAuth is a special handler just for the SAML authentication
func (h *AuthHandlers) handleSuccessfulExternalSAMLAuth(w http.ResponseWriter, r *http.Request, cred types.ExternalAuthUser) {
var (
user *types.User
err error
ctx = r.Context()
)
// Try to login/sign-up external user
if user, err = h.AuthService.External(ctx, cred); err != nil {
api.Send(w, r, err)
return
}
h.handle(func(req *request.AuthReq) error {
req.AuthUser = request.NewAuthUser(
h.Settings,
user,
// external logins are never permanent!
false,
)
// auto-complete EmailOTP and TOTP when authenticating via external identity provider
req.AuthUser.CompleteEmailOTP()
req.AuthUser.CompleteTOTP()
req.AuthUser.Save(req.Session)
handleSuccessfulAuth(req)
return nil
})(w, r)
}
// handleSuccessfulExternalGenericAuth is a generic external auth handler
// which can be used with generic auth providers to also support external API integrations
func (h *AuthHandlers) handleSuccessfulExternalGenericAuth(w http.ResponseWriter, r *http.Request, cred types.ExternalAuthUser) {
var (
user *types.User
err error
ctx = r.Context()
)
handleErr := func(err error) {
h.handleFailedExternalAuth(w, r, err)
}
// Get the provider config so we can correctly handle the provided values
p := h.getProviderConfig(cred.Provider, h.Settings.Providers)
if p == nil {
handleErr(fmt.Errorf("credentials for provider %s are not registered in the system", cred.Provider))
return
}
// For later, the request's auth user (no big deal if there isn't one)
au := request.GetAuthUser(h.SessionManager.Get(r))
// Check if we're using it as an identity provider; if so, use it to authenticate
// If no providers are specified, assume identity (legacy support)
if len(p.Usage) == 0 || p.HasUsage(types.ExternalProviderUsageIdentity) {
// Try to login/sign-up external user
if user, err = h.AuthService.External(ctx, cred); err != nil {
handleErr(err)
return
}
au = request.NewAuthUser(
h.Settings,
user,
// external logins are never permanent!
false,
)
// If that's that, cut the flow here
if !p.HasUsage(types.ExternalProviderUsageAPI) {
h.handle(func(req *request.AuthReq) error {
req.AuthUser = au
// auto-complete EmailOTP and TOTP when authenticating via external identity provider
req.AuthUser.CompleteEmailOTP()
req.AuthUser.CompleteTOTP()
req.AuthUser.Save(req.Session)
handleSuccessfulAuth(req)
return nil
})(w, r)
return
}
}
// Check if we're using it for an API integration; if so, note the access tokens
if p.HasUsage(types.ExternalProviderUsageAPI) {
if au.User == nil {
handleErr(fmt.Errorf("could not add credentials for user: not authenticated"))
return
}
ctx = auth.SetIdentityToContext(ctx, au.User)
// Look for existing
cc, err := h.CredentialsService.List(ctx, au.User.ID)
if err != nil {
handleErr(fmt.Errorf("couldn't fetch user credentials: %w", err))
return
}
// Find the existing one
kind := fmt.Sprintf("access-%s", cred.Provider)
var current *types.Credential
for _, c := range cc {
if c.Kind == kind && c.OwnerID == au.User.ID {
current = c
break
}
}
// Update
if current != nil {
current.Credentials = cred.AccessToken
_, err = h.CredentialsService.Update(ctx, current)
if err != nil {
handleErr(fmt.Errorf("couldn't update user credentials: %w", err))
return
}
} else {
_, err = h.CredentialsService.Create(ctx, &types.Credential{
Label: fmt.Sprintf("access token %s %s", cred.Provider, au.User.Email),
Kind: kind,
OwnerID: au.User.ID,
Credentials: cred.AccessToken,
})
if err != nil {
handleErr(fmt.Errorf("couldn't create user credentials: %w", err))
return
}
}
}
h.handle(func(req *request.AuthReq) error {
req.AuthUser = au
handleSuccessfulAuth(req)
return nil
})(w, r)
}
func (h *AuthHandlers) getProviderConfig(handle string, set []settings.Provider) *settings.Provider {
for _, s := range set {
if s.Handle == handle {
return &s
}
}
return nil
}
func (h *AuthHandlers) handleFailedExternalAuth(w http.ResponseWriter, _ *http.Request, err error) {
if strings.Contains(err.Error(), "Error processing your OAuth request: Invalid oauth_verifier parameter") {
// Just take user through the same loop again
w.Header().Set("Location", GetLinks().Profile)
w.WriteHeader(http.StatusSeeOther)
return
}
w.WriteHeader(http.StatusOK)
_, _ = fmt.Fprintf(w, "SSO Error: %v", err.Error())
}
func beginUserAuth(w http.ResponseWriter, r *http.Request, eh external.ExternalAuthHandler) {
eh.BeginUserAuth(w, r)
}
func completeUserAuth(w http.ResponseWriter, r *http.Request, eh external.ExternalAuthHandler) (u *types.ExternalAuthUser, err error) {
return eh.CompleteUserAuth(w, r)
}