2019-03-09 00:42:50 +08:00
// Copyright 2019 The Gitea Authors. All rights reserved.
2022-11-28 02:20:29 +08:00
// SPDX-License-Identifier: MIT
2019-03-09 00:42:50 +08:00
2022-01-02 21:12:35 +08:00
package auth
2019-03-09 00:42:50 +08:00
import (
2022-01-02 21:12:35 +08:00
"errors"
2019-03-09 00:42:50 +08:00
"fmt"
2020-08-28 12:37:05 +08:00
"html"
2022-01-02 21:12:35 +08:00
"io"
2021-04-05 23:30:52 +08:00
"net/http"
2023-06-15 09:12:50 +08:00
"sort"
2019-03-11 10:54:59 +08:00
"strings"
2019-03-09 00:42:50 +08: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 14:44:42 +08:00
auth_module "code.gitea.io/gitea/modules/auth"
2019-03-09 00:42:50 +08:00
"code.gitea.io/gitea/modules/base"
2023-02-08 14:44:42 +08:00
"code.gitea.io/gitea/modules/container"
2019-03-09 00:42:50 +08:00
"code.gitea.io/gitea/modules/log"
2024-02-04 21:29:09 +08:00
"code.gitea.io/gitea/modules/optional"
2019-03-09 00:42:50 +08:00
"code.gitea.io/gitea/modules/setting"
2022-01-02 21:12:35 +08:00
"code.gitea.io/gitea/modules/web/middleware"
2023-02-08 14:44:42 +08:00
source_service "code.gitea.io/gitea/services/auth/source"
2021-07-24 18:16:34 +08: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-13 03:41:28 +08:00
2022-01-02 21:12:35 +08:00
"github.com/markbates/goth"
2022-06-20 23:37:54 +08:00
"github.com/markbates/goth/gothic"
2023-06-15 09:12:50 +08:00
go_oauth2 "golang.org/x/oauth2"
2019-03-09 00:42:50 +08:00
)
2022-01-02 21:12:35 +08:00
// SignInOAuth handles the OAuth2 login buttons
func SignInOAuth ( ctx * context . Context ) {
2024-06-19 06:32:45 +08:00
provider := ctx . PathParam ( ":provider" )
2022-01-02 21:12:35 +08:00
2024-02-24 12:18:49 +08:00
authSource , err := auth . GetActiveOAuth2SourceByName ( ctx , provider )
2022-01-02 21:12:35 +08:00
if err != nil {
ctx . ServerError ( "SignIn" , err )
return
}
2023-01-25 00:41:38 +08:00
redirectTo := ctx . FormString ( "redirect_to" )
if len ( redirectTo ) > 0 {
middleware . SetRedirectToCookie ( ctx . Resp , redirectTo )
}
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-15 01:09:32 +08: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 16:37:24 +08: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 ) {
2024-06-19 06:32:45 +08:00
provider := ctx . PathParam ( ":provider" )
2022-01-02 21:12:35 +08:00
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
2024-02-24 12:18:49 +08:00
authSource , err := auth . GetActiveOAuth2SourceByName ( ctx , provider )
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-15 01:09:32 +08: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 18:44:18 +08: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-08 05:02:09 +08: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 :
ctx . Flash . Error ( ctx . Tr ( "auth.oauth.signin.error" ) )
}
ctx . Redirect ( setting . AppSubURL + "/user/login" )
return
}
2023-06-15 09:12:50 +08:00
if err , ok := err . ( * go_oauth2 . RetrieveError ) ; ok {
ctx . Flash . Error ( "OAuth2 RetrieveError: " + err . Error ( ) , true )
2024-11-15 10: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 08:03:17 +08:00
if ctx . Doer != nil {
2024-04-25 19:22:32 +08:00
// attach user to the current signed-in user
2024-02-24 12:18:49 +08:00
err = externalaccount . LinkAccountToUser ( ctx , ctx . Doer , gothUser )
2022-05-29 08:03:17 +08: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 == "" {
if setting . OAuth2Client . Username == setting . OAuth2UsernameNickname {
missingFields = append ( missingFields , "nickname" )
} else if setting . OAuth2Client . Username == setting . OAuth2UsernamePreferredUsername {
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
showLinkingLogin ( ctx , gothUser )
2024-01-04 08:48:20 +08:00
return
}
2022-01-02 21:12:35 +08:00
u = & user_model . User {
2024-01-04 08:48:20 +08:00
Name : uname ,
2022-04-30 03:38:11 +08:00
FullName : gothUser . Name ,
Email : gothUser . Email ,
LoginType : auth . OAuth2 ,
LoginSource : authSource . ID ,
LoginName : gothUser . UserID ,
}
overwriteDefault := & user_model . CreateUserOverwriteOptions {
2024-02-23 10:18:33 +08:00
IsActive : optional . Some ( ! setting . OAuth2Client . RegisterEmailConfirm && ! setting . Service . RegisterManualConfirm ) ,
2022-01-02 21:12:35 +08:00
}
2023-02-08 14:44:42 +08:00
source := authSource . Cfg . ( * oauth2 . Source )
2024-02-04 21:29:09 +08:00
isAdmin , isRestricted := getUserAdminAndRestrictedFromGroupClaims ( source , & gothUser )
u . IsAdmin = isAdmin . ValueOrDefault ( false )
u . IsRestricted = isRestricted . ValueOrDefault ( false )
2022-01-02 21:12:35 +08:00
2024-02-24 12:18:49 +08:00
if ! createAndHandleCreatedUser ( ctx , base . TplName ( "" ) , nil , u , overwriteDefault , & gothUser , setting . OAuth2Client . AccountLinking != setting . OAuth2AccountLinkingDisabled ) {
2022-01-02 21:12:35 +08:00
// error already handled
return
}
2023-02-08 14:44:42 +08: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
2024-02-24 12:18:49 +08:00
showLinkingLogin ( ctx , gothUser )
2022-01-02 21:12:35 +08:00
return
}
}
handleOAuth2SignIn ( ctx , authSource , u , gothUser )
}
2023-07-05 02:36:08 +08: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-05 02:36:08 +08:00
case [ ] any :
2022-02-01 04:41:11 +08: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 14:44:42 +08:00
return container . SetOf ( groups ... )
2022-01-02 21:12:35 +08:00
}
2023-02-08 14:44:42 +08: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 14:44:42 +08: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 14:44:42 +08:00
return nil
2022-01-02 21:12:35 +08:00
}
2023-02-08 14:44:42 +08:00
return claimValueToStringSet ( groupClaims )
}
2024-02-04 21:29:09 +08:00
func getUserAdminAndRestrictedFromGroupClaims ( source * oauth2 . Source , gothUser * goth . User ) ( isAdmin , isRestricted optional . Option [ bool ] ) {
2023-02-08 14:44:42 +08:00
groups := getClaimedGroups ( source , gothUser )
2022-01-02 21:12:35 +08:00
if source . AdminGroup != "" {
2024-02-04 21:29:09 +08:00
isAdmin = optional . Some ( groups . Contains ( source . AdminGroup ) )
2022-01-02 21:12:35 +08:00
}
if source . RestrictedGroup != "" {
2024-02-04 21:29:09 +08:00
isRestricted = optional . Some ( groups . Contains ( source . RestrictedGroup ) )
2022-01-02 21:12:35 +08:00
}
2024-02-04 21:29:09 +08:00
return isAdmin , isRestricted
2022-01-02 21:12:35 +08:00
}
2024-02-24 12:18:49 +08:00
func showLinkingLogin ( ctx * context . Context , gothUser goth . User ) {
2023-07-05 02:36:08 +08:00
if err := updateSession ( ctx , nil , map [ string ] any {
2024-02-24 12:18:49 +08:00
"linkAccountGothUser" : gothUser ,
2022-11-10 19:43:06 +08:00
} ) ; err != nil {
ctx . ServerError ( "updateSession" , err )
2022-01-02 21:12:35 +08:00
return
}
ctx . Redirect ( setting . AppSubURL + "/user/link_account" )
}
2023-10-11 12:24:07 +08:00
func updateAvatarIfNeed ( 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 12:24:07 +08:00
_ = user_service . UploadAvatar ( ctx , u , data )
2022-01-02 21:12:35 +08:00
}
}
}
}
func handleOAuth2SignIn ( ctx * context . Context , source * auth . Source , u * user_model . User , gothUser goth . User ) {
2023-10-11 12:24:07 +08:00
updateAvatarIfNeed ( ctx , gothUser . AvatarURL , u )
2022-01-02 21:12:35 +08:00
needs2FA := false
if ! source . Cfg . ( * oauth2 . Source ) . SkipLocalTwoFA {
2023-09-15 14:13:19 +08: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
}
2023-02-08 14:44:42 +08:00
oauth2Source := source . Cfg . ( * oauth2 . Source )
groupTeamMapping , err := auth_module . UnmarshalGroupTeamMapping ( oauth2Source . GroupTeamMap )
if err != nil {
ctx . ServerError ( "UnmarshalGroupTeamMapping" , err )
return
}
groups := getClaimedGroups ( oauth2Source , & gothUser )
allow synchronizing user status from OAuth2 login providers (#31572)
This leverages the existing `sync_external_users` cron job to
synchronize the `IsActive` flag on users who use an OAuth2 provider set
to synchronize. This synchronization is done by checking for expired
access tokens, and using the stored refresh token to request a new
access token. If the response back from the OAuth2 provider is the
`invalid_grant` error code, the user is marked as inactive. However, the
user is able to reactivate their account by logging in the web browser
through their OAuth2 flow.
Also changed to support this is that a linked `ExternalLoginUser` is
always created upon a login or signup via OAuth2.
### Notes on updating permissions
Ideally, we would also refresh permissions from the configured OAuth
provider (e.g., admin, restricted and group mappings) to match the
implementation of LDAP. However, the OAuth library used for this `goth`,
doesn't seem to support issuing a session via refresh tokens. The
interface provides a [`RefreshToken`
method](https://github.com/markbates/goth/blob/master/provider.go#L20),
but the returned `oauth.Token` doesn't implement the `goth.Session` we
would need to call `FetchUser`. Due to specific implementations, we
would need to build a compatibility function for every provider, since
they cast to concrete types (e.g.
[Azure](https://github.com/markbates/goth/blob/master/providers/azureadv2/azureadv2.go#L132))
---------
Co-authored-by: Kyle D <kdumontnu@gmail.com>
2024-07-17 02:33:16 +08: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
}
}
if err := externalaccount . EnsureLinkExternalToUser ( ctx , u , gothUser ) ; err != nil {
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 {
allow synchronizing user status from OAuth2 login providers (#31572)
This leverages the existing `sync_external_users` cron job to
synchronize the `IsActive` flag on users who use an OAuth2 provider set
to synchronize. This synchronization is done by checking for expired
access tokens, and using the stored refresh token to request a new
access token. If the response back from the OAuth2 provider is the
`invalid_grant` error code, the user is marked as inactive. However, the
user is able to reactivate their account by logging in the web browser
through their OAuth2 flow.
Also changed to support this is that a linked `ExternalLoginUser` is
always created upon a login or signup via OAuth2.
### Notes on updating permissions
Ideally, we would also refresh permissions from the configured OAuth
provider (e.g., admin, restricted and group mappings) to match the
implementation of LDAP. However, the OAuth library used for this `goth`,
doesn't seem to support issuing a session via refresh tokens. The
interface provides a [`RefreshToken`
method](https://github.com/markbates/goth/blob/master/provider.go#L20),
but the returned `oauth.Token` doesn't implement the `goth.Session` we
would need to call `FetchUser`. Due to specific implementations, we
would need to build a compatibility function for every provider, since
they cast to concrete types (e.g.
[Azure](https://github.com/markbates/goth/blob/master/providers/azureadv2/azureadv2.go#L132))
---------
Co-authored-by: Kyle D <kdumontnu@gmail.com>
2024-07-17 02:33:16 +08:00
// Register last login
opts . SetLastLogin = true
if err := user_service . UpdateUser ( ctx , u , opts ) ; err != nil {
ctx . ServerError ( "UpdateUser" , err )
return
}
2023-07-05 02:36:08 +08:00
if err := updateSession ( ctx , nil , map [ string ] any {
2022-11-10 19:43:06 +08:00
"uid" : u . ID ,
"uname" : u . Name ,
} ) ; err != nil {
ctx . ServerError ( "updateSession" , err )
2022-01-02 21:12:35 +08:00
return
}
2024-10-10 11:48:21 +08:00
// force to generate a new CSRF token
ctx . Csrf . PrepareForSessionUser ( ctx )
2022-01-02 21:12:35 +08:00
if err := resetLocale ( ctx , u ) ; err != nil {
ctx . ServerError ( "resetLocale" , err )
return
}
2023-04-14 03:45:33 +08:00
if redirectTo := ctx . GetSiteCookie ( "redirect_to" ) ; len ( redirectTo ) > 0 {
2022-01-02 21:12:35 +08:00
middleware . DeleteRedirectToCookie ( ctx . Resp )
2024-03-21 20:02:34 +08:00
ctx . RedirectToCurrentSite ( redirectTo )
2022-01-02 21:12:35 +08:00
return
}
ctx . Redirect ( setting . AppSubURL + "/" )
return
}
allow synchronizing user status from OAuth2 login providers (#31572)
This leverages the existing `sync_external_users` cron job to
synchronize the `IsActive` flag on users who use an OAuth2 provider set
to synchronize. This synchronization is done by checking for expired
access tokens, and using the stored refresh token to request a new
access token. If the response back from the OAuth2 provider is the
`invalid_grant` error code, the user is marked as inactive. However, the
user is able to reactivate their account by logging in the web browser
through their OAuth2 flow.
Also changed to support this is that a linked `ExternalLoginUser` is
always created upon a login or signup via OAuth2.
### Notes on updating permissions
Ideally, we would also refresh permissions from the configured OAuth
provider (e.g., admin, restricted and group mappings) to match the
implementation of LDAP. However, the OAuth library used for this `goth`,
doesn't seem to support issuing a session via refresh tokens. The
interface provides a [`RefreshToken`
method](https://github.com/markbates/goth/blob/master/provider.go#L20),
but the returned `oauth.Token` doesn't implement the `goth.Session` we
would need to call `FetchUser`. Due to specific implementations, we
would need to build a compatibility function for every provider, since
they cast to concrete types (e.g.
[Azure](https://github.com/markbates/goth/blob/master/providers/azureadv2/azureadv2.go#L132))
---------
Co-authored-by: Kyle D <kdumontnu@gmail.com>
2024-07-17 02:33:16 +08:00
if opts . IsActive . Has ( ) || opts . IsAdmin . Has ( ) || opts . IsRestricted . Has ( ) {
2024-02-04 21:29:09 +08: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-05 02:36:08 +08: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 22:39:12 +08: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-15 01:09:32 +08: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 23:37:54 +08: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" ) {
log . Error ( "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 )
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 )
}
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 14:44:42 +08: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 ,
}
2023-09-15 01:09:32 +08:00
hasUser , err := user_model . GetUser ( 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 16:37:24 +08: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
}