2019-03-08 17:42:50 +01:00
// Copyright 2019 The Gitea Authors. All rights reserved.
2022-11-27 13:20:29 -05:00
// SPDX-License-Identifier: MIT
2019-03-08 17:42:50 +01:00
2022-01-02 21:12:35 +08:00
package auth
2019-03-08 17:42:50 +01:00
import (
2025-07-20 09:49:36 +08:00
"encoding/gob"
2022-01-02 21:12:35 +08:00
"errors"
2019-03-08 17:42:50 +01:00
"fmt"
2020-08-28 05:37:05 +01:00
"html"
2022-01-02 21:12:35 +08:00
"io"
2021-04-05 17:30:52 +02:00
"net/http"
2026-03-01 07:28:26 +01:00
"net/url"
2023-06-15 09:12:50 +08:00
"sort"
2019-03-11 03:54:59 +01:00
"strings"
2019-03-08 17:42:50 +01:00
2022-01-02 21:12:35 +08:00
"code.gitea.io/gitea/models/auth"
2021-11-24 17:49:20 +08:00
user_model "code.gitea.io/gitea/models/user"
2023-02-08 07:44:42 +01:00
auth_module "code.gitea.io/gitea/modules/auth"
"code.gitea.io/gitea/modules/container"
2026-03-01 07:28:26 +01:00
"code.gitea.io/gitea/modules/httplib"
2019-03-08 17:42:50 +01:00
"code.gitea.io/gitea/modules/log"
2024-02-04 14:29:09 +01:00
"code.gitea.io/gitea/modules/optional"
2025-04-29 06:31:59 +08:00
"code.gitea.io/gitea/modules/session"
2019-03-08 17:42:50 +01:00
"code.gitea.io/gitea/modules/setting"
2023-02-08 07:44:42 +01:00
source_service "code.gitea.io/gitea/services/auth/source"
2021-07-24 11:16:34 +01:00
"code.gitea.io/gitea/services/auth/source/oauth2"
2024-02-27 15:12:22 +08:00
"code.gitea.io/gitea/services/context"
2022-01-02 21:12:35 +08:00
"code.gitea.io/gitea/services/externalaccount"
user_service "code.gitea.io/gitea/services/user"
2019-06-12 21:41:28 +02:00
2022-01-02 21:12:35 +08:00
"github.com/markbates/goth"
2022-06-20 18:37:54 +03:00
"github.com/markbates/goth/gothic"
2023-06-15 09:12:50 +08:00
go_oauth2 "golang.org/x/oauth2"
2019-03-08 17:42:50 +01:00
)
2022-01-02 21:12:35 +08:00
// SignInOAuth handles the OAuth2 login buttons
func SignInOAuth ( ctx * context . Context ) {
2025-07-11 02:35:59 +08:00
authName := ctx . PathParam ( "provider" )
authSource , err := auth . GetActiveOAuth2SourceByAuthName ( ctx , authName )
2022-01-02 21:12:35 +08:00
if err != nil {
ctx . ServerError ( "SignIn" , err )
return
}
2026-01-03 11:43:04 +08:00
rememberAuthRedirectLink ( ctx )
2023-01-24 17:41:38 +01:00
2022-01-02 21:12:35 +08:00
// try to do a direct callback flow, so we don't authenticate the user again but use the valid accesstoken to get the user
2023-09-14 19:09:32 +02:00
user , gothUser , err := oAuth2UserLoginCallback ( ctx , authSource , ctx . Req , ctx . Resp )
2022-01-02 21:12:35 +08:00
if err == nil && user != nil {
// we got the user without going through the whole OAuth2 authentication flow again
handleOAuth2SignIn ( ctx , authSource , user , gothUser )
return
}
if err = authSource . Cfg . ( * oauth2 . Source ) . Callout ( ctx . Req , ctx . Resp ) ; err != nil {
if strings . Contains ( err . Error ( ) , "no provider for " ) {
2023-10-14 10:37:24 +02:00
if err = oauth2 . ResetOAuth2 ( ctx ) ; err != nil {
2022-01-02 21:12:35 +08:00
ctx . ServerError ( "SignIn" , err )
return
}
if err = authSource . Cfg . ( * oauth2 . Source ) . Callout ( ctx . Req , ctx . Resp ) ; err != nil {
ctx . ServerError ( "SignIn" , err )
}
return
}
ctx . ServerError ( "SignIn" , err )
}
// redirect is done in oauth2.Auth
}
// SignInOAuthCallback handles the callback from the given provider
func SignInOAuthCallback ( ctx * context . Context ) {
2023-06-15 09:12:50 +08:00
if ctx . Req . FormValue ( "error" ) != "" {
var errorKeyValues [ ] string
for k , vv := range ctx . Req . Form {
for _ , v := range vv {
errorKeyValues = append ( errorKeyValues , fmt . Sprintf ( "%s = %s" , html . EscapeString ( k ) , html . EscapeString ( v ) ) )
}
}
sort . Strings ( errorKeyValues )
ctx . Flash . Error ( strings . Join ( errorKeyValues , "<br>" ) , true )
}
2022-01-02 21:12:35 +08:00
// first look if the provider is still active
2025-07-11 02:35:59 +08:00
authName := ctx . PathParam ( "provider" )
authSource , err := auth . GetActiveOAuth2SourceByAuthName ( ctx , authName )
2022-01-02 21:12:35 +08:00
if err != nil {
ctx . ServerError ( "SignIn" , err )
return
}
if authSource == nil {
2023-06-15 09:12:50 +08:00
ctx . ServerError ( "SignIn" , errors . New ( "no valid provider found, check configured callback url in provider" ) )
2022-01-02 21:12:35 +08:00
return
}
2023-09-14 19:09:32 +02:00
u , gothUser , err := oAuth2UserLoginCallback ( ctx , authSource , ctx . Req , ctx . Resp )
2022-01-02 21:12:35 +08:00
if err != nil {
if user_model . IsErrUserProhibitLogin ( err ) {
2022-02-03 10:44:18 +00:00
uplerr := err . ( user_model . ErrUserProhibitLogin )
2022-01-02 21:12:35 +08:00
log . Info ( "Failed authentication attempt for %s from %s: %v" , uplerr . Name , ctx . RemoteAddr ( ) , err )
ctx . Data [ "Title" ] = ctx . Tr ( "auth.prohibit_login" )
ctx . HTML ( http . StatusOK , "user/auth/prohibit_login" )
return
}
2022-01-07 22:02:09 +01:00
if callbackErr , ok := err . ( errCallback ) ; ok {
log . Info ( "Failed OAuth callback: (%v) %v" , callbackErr . Code , callbackErr . Description )
switch callbackErr . Code {
case "access_denied" :
ctx . Flash . Error ( ctx . Tr ( "auth.oauth.signin.error.access_denied" ) )
case "temporarily_unavailable" :
ctx . Flash . Error ( ctx . Tr ( "auth.oauth.signin.error.temporarily_unavailable" ) )
default :
2025-03-22 10:15:45 +08:00
ctx . Flash . Error ( ctx . Tr ( "auth.oauth.signin.error.general" , callbackErr . Description ) )
2022-01-07 22:02:09 +01:00
}
ctx . Redirect ( setting . AppSubURL + "/user/login" )
return
}
2023-06-15 09:12:50 +08:00
if err , ok := err . ( * go_oauth2 . RetrieveError ) ; ok {
2026-03-19 07:13:55 +08:00
ctx . Flash . Error ( "OAuth2 RetrieveError: " + err . Error ( ) )
2024-11-14 18:13:01 -08:00
ctx . Redirect ( setting . AppSubURL + "/user/login" )
return
2023-06-15 09:12:50 +08:00
}
2022-01-02 21:12:35 +08:00
ctx . ServerError ( "UserSignIn" , err )
return
}
if u == nil {
2022-05-29 02:03:17 +02:00
if ctx . Doer != nil {
2024-04-25 19:22:32 +08:00
// attach user to the current signed-in user
2025-07-11 02:35:59 +08:00
err = externalaccount . LinkAccountToUser ( ctx , authSource . ID , ctx . Doer , gothUser )
2022-05-29 02:03:17 +02:00
if err != nil {
ctx . ServerError ( "UserLinkAccount" , err )
return
}
ctx . Redirect ( setting . AppSubURL + "/user/settings/security" )
return
} else if ! setting . Service . AllowOnlyInternalRegistration && setting . OAuth2Client . EnableAutoRegistration {
2022-01-02 21:12:35 +08:00
// create new user with details from oauth2 provider
var missingFields [ ] string
if gothUser . UserID == "" {
missingFields = append ( missingFields , "sub" )
}
if gothUser . Email == "" {
missingFields = append ( missingFields , "email" )
}
2024-04-25 19:22:32 +08:00
uname , err := extractUserNameFromOAuth2 ( & gothUser )
if err != nil {
ctx . ServerError ( "UserSignIn" , err )
return
}
if uname == "" {
2025-03-29 22:32:28 +01:00
switch setting . OAuth2Client . Username {
case setting . OAuth2UsernameNickname :
2024-04-25 19:22:32 +08:00
missingFields = append ( missingFields , "nickname" )
2025-03-29 22:32:28 +01:00
case setting . OAuth2UsernamePreferredUsername :
2024-04-25 19:22:32 +08:00
missingFields = append ( missingFields , "preferred_username" )
} // else: "UserID" and "Email" have been handled above separately
2022-01-02 21:12:35 +08:00
}
if len ( missingFields ) > 0 {
2024-04-25 19:22:32 +08:00
log . Error ( ` OAuth2 auto registration (ENABLE_AUTO_REGISTRATION) is enabled but OAuth2 provider %q doesn't return required fields: %s. ` +
` Suggest to: disable auto registration, or make OPENID_CONNECT_SCOPES (for OpenIDConnect) / Authentication Source Scopes (for Admin panel) to request all required fields, and the fields shouldn't be empty. ` ,
authSource . Name , strings . Join ( missingFields , "," ) )
// The RawData is the only way to pass the missing fields to the another page at the moment, other ways all have various problems:
// by session or cookie: difficult to clean or reset; by URL: could be injected with uncontrollable content; by ctx.Flash: the link_account page is a mess ...
// Since the RawData is for the provider's data, so we need to use our own prefix here to avoid conflict.
if gothUser . RawData == nil {
gothUser . RawData = make ( map [ string ] any )
2022-01-02 21:12:35 +08:00
}
2024-04-25 19:22:32 +08:00
gothUser . RawData [ "__giteaAutoRegMissingFields" ] = missingFields
2025-07-20 09:49:36 +08:00
showLinkingLogin ( ctx , authSource . ID , gothUser )
2024-01-03 16:48:20 -08:00
return
}
2022-01-02 21:12:35 +08:00
u = & user_model . User {
2024-01-03 16:48:20 -08:00
Name : uname ,
2022-04-29 21:38:11 +02:00
Email : gothUser . Email ,
LoginType : auth . OAuth2 ,
LoginSource : authSource . ID ,
LoginName : gothUser . UserID ,
}
overwriteDefault := & user_model . CreateUserOverwriteOptions {
2024-02-23 03:18:33 +01:00
IsActive : optional . Some ( ! setting . OAuth2Client . RegisterEmailConfirm && ! setting . Service . RegisterManualConfirm ) ,
2022-01-02 21:12:35 +08:00
}
2023-02-08 07:44:42 +01:00
source := authSource . Cfg . ( * oauth2 . Source )
2024-02-04 14:29:09 +01:00
isAdmin , isRestricted := getUserAdminAndRestrictedFromGroupClaims ( source , & gothUser )
2025-06-10 04:57:45 +08:00
u . IsAdmin = isAdmin . ValueOrDefault ( user_service . UpdateOptionField [ bool ] { FieldValue : false } ) . FieldValue
u . IsRestricted = isRestricted . ValueOrDefault ( setting . Service . DefaultUserIsRestricted )
2022-01-02 21:12:35 +08:00
2025-07-20 09:49:36 +08:00
linkAccountData := & LinkAccountData { authSource . ID , gothUser }
2025-07-11 02:35:59 +08:00
if setting . OAuth2Client . AccountLinking == setting . OAuth2AccountLinkingDisabled {
linkAccountData = nil
}
if ! createAndHandleCreatedUser ( ctx , "" , nil , u , overwriteDefault , linkAccountData ) {
2022-01-02 21:12:35 +08:00
// error already handled
return
}
2023-02-08 07:44:42 +01:00
if err := syncGroupsToTeams ( ctx , source , & gothUser , u ) ; err != nil {
ctx . ServerError ( "SyncGroupsToTeams" , err )
return
}
2022-01-02 21:12:35 +08:00
} else {
// no existing user is found, request attach or new account
2025-07-20 09:49:36 +08:00
showLinkingLogin ( ctx , authSource . ID , gothUser )
2022-01-02 21:12:35 +08:00
return
}
}
handleOAuth2SignIn ( ctx , authSource , u , gothUser )
}
2023-07-04 20:36:08 +02:00
func claimValueToStringSet ( claimValue any ) container . Set [ string ] {
2022-01-02 21:12:35 +08:00
var groups [ ] string
switch rawGroup := claimValue . ( type ) {
case [ ] string :
groups = rawGroup
2023-07-04 20:36:08 +02:00
case [ ] any :
2022-01-31 20:41:11 +00:00
for _ , group := range rawGroup {
groups = append ( groups , fmt . Sprintf ( "%s" , group ) )
}
2022-01-02 21:12:35 +08:00
default :
str := fmt . Sprintf ( "%s" , rawGroup )
groups = strings . Split ( str , "," )
}
2023-02-08 07:44:42 +01:00
return container . SetOf ( groups ... )
2022-01-02 21:12:35 +08:00
}
2023-02-08 07:44:42 +01:00
func syncGroupsToTeams ( ctx * context . Context , source * oauth2 . Source , gothUser * goth . User , u * user_model . User ) error {
if source . GroupTeamMap != "" || source . GroupTeamMapRemoval {
groupTeamMapping , err := auth_module . UnmarshalGroupTeamMapping ( source . GroupTeamMap )
if err != nil {
return err
}
groups := getClaimedGroups ( source , gothUser )
if err := source_service . SyncGroupsToTeams ( ctx , u , groups , groupTeamMapping , source . GroupTeamMapRemoval ) ; err != nil {
return err
}
2022-01-02 21:12:35 +08:00
}
2023-02-08 07:44:42 +01:00
return nil
}
func getClaimedGroups ( source * oauth2 . Source , gothUser * goth . User ) container . Set [ string ] {
2022-01-02 21:12:35 +08:00
groupClaims , has := gothUser . RawData [ source . GroupClaimName ]
if ! has {
2023-02-08 07:44:42 +01:00
return nil
2022-01-02 21:12:35 +08:00
}
2023-02-08 07:44:42 +01:00
return claimValueToStringSet ( groupClaims )
}
2025-06-10 04:57:45 +08:00
func getUserAdminAndRestrictedFromGroupClaims ( source * oauth2 . Source , gothUser * goth . User ) ( isAdmin optional . Option [ user_service . UpdateOptionField [ bool ] ] , isRestricted optional . Option [ bool ] ) {
2023-02-08 07:44:42 +01:00
groups := getClaimedGroups ( source , gothUser )
2022-01-02 21:12:35 +08:00
if source . AdminGroup != "" {
2025-06-10 04:57:45 +08:00
isAdmin = user_service . UpdateOptionFieldFromSync ( groups . Contains ( source . AdminGroup ) )
2022-01-02 21:12:35 +08:00
}
if source . RestrictedGroup != "" {
2024-02-04 14:29:09 +01:00
isRestricted = optional . Some ( groups . Contains ( source . RestrictedGroup ) )
2022-01-02 21:12:35 +08:00
}
2024-02-04 14:29:09 +01:00
return isAdmin , isRestricted
2022-01-02 21:12:35 +08:00
}
2025-07-11 02:35:59 +08:00
type LinkAccountData struct {
2025-07-20 09:49:36 +08:00
AuthSourceID int64
GothUser goth . User
2025-07-11 02:35:59 +08:00
}
2025-11-26 23:25:34 +08:00
func init ( ) {
gob . Register ( LinkAccountData { } ) // TODO: CHI-SESSION-GOB-REGISTER
}
2025-07-11 02:35:59 +08:00
func oauth2GetLinkAccountData ( ctx * context . Context ) * LinkAccountData {
v , ok := ctx . Session . Get ( "linkAccountData" ) . ( LinkAccountData )
if ! ok {
return nil
}
return & v
}
2025-07-20 09:49:36 +08:00
func Oauth2SetLinkAccountData ( ctx * context . Context , linkAccountData LinkAccountData ) error {
return updateSession ( ctx , nil , map [ string ] any {
"linkAccountData" : linkAccountData ,
} )
}
func showLinkingLogin ( ctx * context . Context , authSourceID int64 , gothUser goth . User ) {
if err := Oauth2SetLinkAccountData ( ctx , LinkAccountData { authSourceID , gothUser } ) ; err != nil {
ctx . ServerError ( "Oauth2SetLinkAccountData" , err )
2022-01-02 21:12:35 +08:00
return
}
ctx . Redirect ( setting . AppSubURL + "/user/link_account" )
}
2025-07-11 02:35:59 +08:00
func oauth2UpdateAvatarIfNeed ( ctx * context . Context , url string , u * user_model . User ) {
2022-01-02 21:12:35 +08:00
if setting . OAuth2Client . UpdateAvatar && len ( url ) > 0 {
resp , err := http . Get ( url )
if err == nil {
defer func ( ) {
_ = resp . Body . Close ( )
} ( )
}
// ignore any error
if err == nil && resp . StatusCode == http . StatusOK {
data , err := io . ReadAll ( io . LimitReader ( resp . Body , setting . Avatar . MaxFileSize + 1 ) )
if err == nil && int64 ( len ( data ) ) <= setting . Avatar . MaxFileSize {
2023-10-11 06:24:07 +02:00
_ = user_service . UploadAvatar ( ctx , u , data )
2022-01-02 21:12:35 +08:00
}
}
}
}
2025-07-11 02:35:59 +08:00
func handleOAuth2SignIn ( ctx * context . Context , authSource * auth . Source , u * user_model . User , gothUser goth . User ) {
2025-07-20 09:49:36 +08:00
oauth2SignInSync ( ctx , authSource . ID , u , gothUser )
2025-07-11 02:35:59 +08:00
if ctx . Written ( ) {
return
}
2022-01-02 21:12:35 +08:00
needs2FA := false
2025-07-11 02:35:59 +08:00
if ! authSource . TwoFactorShouldSkip ( ) {
2023-09-15 08:13:19 +02:00
_ , err := auth . GetTwoFactorByUID ( ctx , u . ID )
2022-01-02 21:12:35 +08:00
if err != nil && ! auth . IsErrTwoFactorNotEnrolled ( err ) {
ctx . ServerError ( "UserSignIn" , err )
return
}
needs2FA = err == nil
}
2025-07-11 02:35:59 +08:00
oauth2Source := authSource . Cfg . ( * oauth2 . Source )
2023-02-08 07:44:42 +01:00
groupTeamMapping , err := auth_module . UnmarshalGroupTeamMapping ( oauth2Source . GroupTeamMap )
if err != nil {
ctx . ServerError ( "UnmarshalGroupTeamMapping" , err )
return
}
groups := getClaimedGroups ( oauth2Source , & gothUser )
2024-07-16 13:33:16 -05:00
opts := & user_service . UpdateOptions { }
// Reactivate user if they are deactivated
if ! u . IsActive {
opts . IsActive = optional . Some ( true )
}
// Update GroupClaims
opts . IsAdmin , opts . IsRestricted = getUserAdminAndRestrictedFromGroupClaims ( oauth2Source , & gothUser )
if oauth2Source . GroupTeamMap != "" || oauth2Source . GroupTeamMapRemoval {
if err := source_service . SyncGroupsToTeams ( ctx , u , groups , groupTeamMapping , oauth2Source . GroupTeamMapRemoval ) ; err != nil {
ctx . ServerError ( "SyncGroupsToTeams" , err )
return
}
}
2025-07-11 02:35:59 +08:00
if err := externalaccount . EnsureLinkExternalToUser ( ctx , authSource . ID , u , gothUser ) ; err != nil {
2024-07-16 13:33:16 -05:00
ctx . ServerError ( "EnsureLinkExternalToUser" , err )
return
}
2022-01-02 21:12:35 +08:00
// If this user is enrolled in 2FA and this source doesn't override it,
// we can't sign the user in just yet. Instead, redirect them to the 2FA authentication page.
if ! needs2FA {
2024-07-16 13:33:16 -05:00
// Register last login
opts . SetLastLogin = true
if err := user_service . UpdateUser ( ctx , u , opts ) ; err != nil {
ctx . ServerError ( "UpdateUser" , err )
return
}
2025-04-29 06:31:59 +08:00
userHasTwoFactorAuth , err := auth . HasTwoFactorOrWebAuthn ( ctx , u . ID )
if err != nil {
ctx . ServerError ( "UpdateUser" , err )
return
}
2024-07-16 13:33:16 -05:00
2023-07-04 20:36:08 +02:00
if err := updateSession ( ctx , nil , map [ string ] any {
2025-04-29 06:31:59 +08:00
session . KeyUID : u . ID ,
session . KeyUname : u . Name ,
session . KeyUserHasTwoFactorAuth : userHasTwoFactorAuth ,
2022-11-10 19:43:06 +08:00
} ) ; err != nil {
ctx . ServerError ( "updateSession" , err )
2022-01-02 21:12:35 +08:00
return
}
if err := resetLocale ( ctx , u ) ; err != nil {
ctx . ServerError ( "resetLocale" , err )
return
}
2026-01-03 11:43:04 +08:00
redirectAfterAuth ( ctx )
2022-01-02 21:12:35 +08:00
return
}
2024-07-16 13:33:16 -05:00
if opts . IsActive . Has ( ) || opts . IsAdmin . Has ( ) || opts . IsRestricted . Has ( ) {
2024-02-04 14:29:09 +01:00
if err := user_service . UpdateUser ( ctx , u , opts ) ; err != nil {
ctx . ServerError ( "UpdateUser" , err )
2022-01-02 21:12:35 +08:00
return
}
}
2023-07-04 20:36:08 +02:00
if err := updateSession ( ctx , nil , map [ string ] any {
2022-11-10 19:43:06 +08:00
// User needs to use 2FA, save data and redirect to 2FA page.
"twofaUid" : u . ID ,
"twofaRemember" : false ,
} ) ; err != nil {
ctx . ServerError ( "updateSession" , err )
2022-01-02 21:12:35 +08:00
return
}
2022-01-14 23:03:31 +08:00
// If WebAuthn is enrolled -> Redirect to WebAuthn instead
2023-09-16 16:39:12 +02:00
regs , err := auth . GetWebAuthnCredentialsByUID ( ctx , u . ID )
2022-01-02 21:12:35 +08:00
if err == nil && len ( regs ) > 0 {
2022-01-14 23:03:31 +08:00
ctx . Redirect ( setting . AppSubURL + "/user/webauthn" )
2022-01-02 21:12:35 +08:00
return
}
ctx . Redirect ( setting . AppSubURL + "/user/two_factor" )
}
// OAuth2UserLoginCallback attempts to handle the callback from the OAuth2 provider and if successful
// login the user
2023-09-14 19:09:32 +02:00
func oAuth2UserLoginCallback ( ctx * context . Context , authSource * auth . Source , request * http . Request , response http . ResponseWriter ) ( * user_model . User , goth . User , error ) {
2022-01-02 21:12:35 +08:00
oauth2Source := authSource . Cfg . ( * oauth2 . Source )
2022-06-20 18:37:54 +03:00
// Make sure that the response is not an error response.
errorName := request . FormValue ( "error" )
if len ( errorName ) > 0 {
errorDescription := request . FormValue ( "error_description" )
// Delete the goth session
err := gothic . Logout ( response , request )
if err != nil {
return nil , goth . User { } , err
}
return nil , goth . User { } , errCallback {
Code : errorName ,
Description : errorDescription ,
}
}
// Proceed to authenticate through goth.
2022-01-02 21:12:35 +08:00
gothUser , err := oauth2Source . Callback ( request , response )
if err != nil {
if err . Error ( ) == "securecookie: the value is too long" || strings . Contains ( err . Error ( ) , "Data too long" ) {
err = fmt . Errorf ( "OAuth2 Provider %s returned too long a token. Current max: %d. Either increase the [OAuth2] MAX_TOKEN_LENGTH or reduce the information returned from the OAuth2 provider" , authSource . Name , setting . OAuth2 . MaxTokenLength )
2025-03-22 10:15:45 +08:00
log . Error ( "oauth2Source.Callback failed: %v" , err )
} else {
err = errCallback { Code : "internal" , Description : err . Error ( ) }
2022-01-02 21:12:35 +08:00
}
return nil , goth . User { } , err
}
if oauth2Source . RequiredClaimName != "" {
claimInterface , has := gothUser . RawData [ oauth2Source . RequiredClaimName ]
if ! has {
return nil , goth . User { } , user_model . ErrUserProhibitLogin { Name : gothUser . UserID }
}
if oauth2Source . RequiredClaimValue != "" {
2023-02-08 07:44:42 +01:00
groups := claimValueToStringSet ( claimInterface )
if ! groups . Contains ( oauth2Source . RequiredClaimValue ) {
2022-01-02 21:12:35 +08:00
return nil , goth . User { } , user_model . ErrUserProhibitLogin { Name : gothUser . UserID }
}
}
}
user := & user_model . User {
LoginName : gothUser . UserID ,
LoginType : auth . OAuth2 ,
LoginSource : authSource . ID ,
}
2026-04-03 20:19:04 +08:00
hasUser , err := user_model . GetIndividualUser ( ctx , user )
2022-01-02 21:12:35 +08:00
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 ,
}
2023-10-14 10:37:24 +02:00
hasUser , err = user_model . GetExternalLogin ( request . Context ( ) , externalLoginUser )
2022-01-02 21:12:35 +08:00
if err != nil {
return nil , goth . User { } , err
}
if hasUser {
2022-12-03 10:48:26 +08:00
user , err = user_model . GetUserByID ( request . Context ( ) , externalLoginUser . UserID )
2022-01-02 21:12:35 +08:00
return user , gothUser , err
}
// no user found to login
return nil , gothUser , nil
}
2026-03-01 07:28:26 +01:00
// buildOIDCEndSessionURL constructs an OIDC RP-Initiated Logout URL for the
// given user. Returns "" if the user's auth source is not OIDC or doesn't
// advertise an end_session_endpoint.
func buildOIDCEndSessionURL ( ctx * context . Context , doer * user_model . User ) string {
authSource , err := auth . GetSourceByID ( ctx , doer . LoginSource )
if err != nil {
log . Error ( "Failed to get auth source for OIDC logout (source=%d): %v" , doer . LoginSource , err )
return ""
}
oauth2Cfg , ok := authSource . Cfg . ( * oauth2 . Source )
if ! ok {
return ""
}
endSessionEndpoint := oauth2 . GetOIDCEndSessionEndpoint ( authSource . Name )
if endSessionEndpoint == "" {
return ""
}
endSessionURL , err := url . Parse ( endSessionEndpoint )
if err != nil {
log . Error ( "Failed to parse end_session_endpoint %q: %v" , endSessionEndpoint , err )
return ""
}
// RP-Initiated Logout 1.0: use client_id to identify the client to the IdP.
// https://openid.net/specs/openid-connect-rpinitiated-1_0.html#RPLogout
params := endSessionURL . Query ( )
params . Set ( "client_id" , oauth2Cfg . ClientID )
params . Set ( "post_logout_redirect_uri" , httplib . GuessCurrentAppURL ( ctx ) )
endSessionURL . RawQuery = params . Encode ( )
return endSessionURL . String ( )
}