diff options
Diffstat (limited to 'routers/web/auth/saml.go')
-rw-r--r-- | routers/web/auth/saml.go | 172 |
1 files changed, 172 insertions, 0 deletions
diff --git a/routers/web/auth/saml.go b/routers/web/auth/saml.go new file mode 100644 index 0000000000..29d689d2e9 --- /dev/null +++ b/routers/web/auth/saml.go @@ -0,0 +1,172 @@ +// Copyright 2024 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package auth + +import ( + "errors" + "fmt" + "net/http" + "strings" + + "code.gitea.io/gitea/models/auth" + user_model "code.gitea.io/gitea/models/user" + "code.gitea.io/gitea/modules/context" + "code.gitea.io/gitea/modules/log" + "code.gitea.io/gitea/modules/setting" + "code.gitea.io/gitea/modules/util" + "code.gitea.io/gitea/modules/web/middleware" + "code.gitea.io/gitea/services/auth/source/saml" + "code.gitea.io/gitea/services/externalaccount" + + "github.com/markbates/goth" +) + +func SignInSAML(ctx *context.Context) { + provider := ctx.Params(":provider") + + loginSource, err := auth.GetActiveAuthSourceByName(ctx, provider, auth.SAML) + if err != nil || loginSource == nil { + ctx.NotFound("SAMLMetadata", err) + return + } + + if err = loginSource.Cfg.(*saml.Source).Callout(ctx.Req, ctx.Resp); err != nil { + if strings.Contains(err.Error(), "no provider for ") { + ctx.Error(http.StatusNotFound) + return + } + ctx.ServerError("SignIn", err) + } +} + +func SignInSAMLCallback(ctx *context.Context) { + provider := ctx.Params(":provider") + loginSource, err := auth.GetActiveAuthSourceByName(ctx, provider, auth.SAML) + if err != nil || loginSource == nil { + ctx.NotFound("SignInSAMLCallback", err) + return + } + + if loginSource == nil { + ctx.ServerError("SignIn", fmt.Errorf("no valid provider found, check configured callback url in provider")) + return + } + + u, gothUser, err := samlUserLoginCallback(*ctx, loginSource, ctx.Req, ctx.Resp) + if err != nil { + ctx.ServerError("SignInSAMLCallback", err) + return + } + + if u == nil { + if ctx.Doer != nil { + // attach user to already logged in user + err = externalaccount.LinkAccountToUser(ctx, ctx.Doer, gothUser, auth.SAML) + if err != nil { + ctx.ServerError("LinkAccountToUser", err) + return + } + + ctx.Redirect(setting.AppSubURL + "/user/settings/security") + return + } else if !setting.Service.AllowOnlyInternalRegistration && false { + // TODO: allow auto registration from saml users (OAuth2 uses the following setting.OAuth2Client.EnableAutoRegistration) + } else { + // no existing user is found, request attach or new account + showLinkingLogin(ctx, gothUser, auth.SAML) + return + } + } + + handleSamlSignIn(ctx, loginSource, u, gothUser) +} + +func handleSamlSignIn(ctx *context.Context, source *auth.Source, u *user_model.User, gothUser goth.User) { + if err := updateSession(ctx, nil, map[string]any{ + "uid": u.ID, + "uname": u.Name, + }); err != nil { + ctx.ServerError("updateSession", err) + return + } + + // Clear whatever CSRF cookie has right now, force to generate a new one + ctx.Csrf.DeleteCookie(ctx) + + // Register last login + u.SetLastLogin() + + // update external user information + if err := externalaccount.UpdateExternalUser(ctx, u, gothUser, auth.SAML); err != nil { + if !errors.Is(err, util.ErrNotExist) { + log.Error("UpdateExternalUser failed: %v", err) + } + } + + if err := resetLocale(ctx, u); err != nil { + ctx.ServerError("resetLocale", err) + return + } + + if redirectTo := ctx.GetSiteCookie("redirect_to"); len(redirectTo) > 0 { + middleware.DeleteRedirectToCookie(ctx.Resp) + ctx.RedirectToFirst(redirectTo) + return + } + + ctx.Redirect(setting.AppSubURL + "/") +} + +func samlUserLoginCallback(ctx context.Context, authSource *auth.Source, request *http.Request, response http.ResponseWriter) (*user_model.User, goth.User, error) { + samlSource := authSource.Cfg.(*saml.Source) + + gothUser, err := samlSource.Callback(request, response) + if err != nil { + return nil, gothUser, err + } + + user := &user_model.User{ + LoginName: gothUser.UserID, + LoginType: auth.SAML, + LoginSource: authSource.ID, + } + + hasUser, err := user_model.GetUser(ctx, user) + if err != nil { + return nil, goth.User{}, err + } + + if hasUser { + return user, gothUser, nil + } + + // search in external linked users + externalLoginUser := &user_model.ExternalLoginUser{ + ExternalID: gothUser.UserID, + LoginSourceID: authSource.ID, + } + hasUser, err = user_model.GetExternalLogin(ctx, externalLoginUser) + if err != nil { + return nil, goth.User{}, err + } + if hasUser { + user, err = user_model.GetUserByID(request.Context(), externalLoginUser.UserID) + return user, gothUser, err + } + + // no user found to login + return nil, gothUser, nil +} + +func SAMLMetadata(ctx *context.Context) { + provider := ctx.Params(":provider") + loginSource, err := auth.GetActiveAuthSourceByName(ctx, provider, auth.SAML) + if err != nil || loginSource == nil { + ctx.NotFound("SAMLMetadata", err) + return + } + if err = loginSource.Cfg.(*saml.Source).Metadata(ctx.Req, ctx.Resp); err != nil { + ctx.ServerError("SAMLMetadata", err) + } +} |