2017-02-22 07:14:37 +00:00
// Copyright 2017 The Gitea Authors. All rights reserved.
2022-11-27 18:20:29 +00:00
// SPDX-License-Identifier: MIT
2017-02-22 07:14:37 +00:00
2021-11-28 14:11:58 +00:00
package user
2017-02-22 07:14:37 +00:00
2019-10-14 06:10:42 +00:00
import (
2021-11-28 14:11:58 +00:00
"context"
"fmt"
2019-10-14 06:10:42 +00:00
"time"
2021-09-19 11:49:59 +00:00
"code.gitea.io/gitea/models/db"
2022-10-18 05:50:37 +00:00
"code.gitea.io/gitea/modules/util"
2019-10-14 06:10:42 +00:00
"xorm.io/builder"
)
2017-02-22 07:14:37 +00:00
2021-11-28 14:11:58 +00:00
// ErrExternalLoginUserAlreadyExist represents a "ExternalLoginUserAlreadyExist" kind of error.
type ErrExternalLoginUserAlreadyExist struct {
ExternalID string
UserID int64
LoginSourceID int64
}
// IsErrExternalLoginUserAlreadyExist checks if an error is a ExternalLoginUserAlreadyExist.
func IsErrExternalLoginUserAlreadyExist ( err error ) bool {
_ , ok := err . ( ErrExternalLoginUserAlreadyExist )
return ok
}
func ( err ErrExternalLoginUserAlreadyExist ) Error ( ) string {
return fmt . Sprintf ( "external login user already exists [externalID: %s, userID: %d, loginSourceID: %d]" , err . ExternalID , err . UserID , err . LoginSourceID )
}
2022-10-18 05:50:37 +00:00
func ( err ErrExternalLoginUserAlreadyExist ) Unwrap ( ) error {
return util . ErrAlreadyExist
}
2021-11-28 14:11:58 +00:00
// ErrExternalLoginUserNotExist represents a "ExternalLoginUserNotExist" kind of error.
type ErrExternalLoginUserNotExist struct {
UserID int64
LoginSourceID int64
}
// IsErrExternalLoginUserNotExist checks if an error is a ExternalLoginUserNotExist.
func IsErrExternalLoginUserNotExist ( err error ) bool {
_ , ok := err . ( ErrExternalLoginUserNotExist )
return ok
}
func ( err ErrExternalLoginUserNotExist ) Error ( ) string {
return fmt . Sprintf ( "external login user link does not exists [userID: %d, loginSourceID: %d]" , err . UserID , err . LoginSourceID )
}
2022-10-18 05:50:37 +00:00
func ( err ErrExternalLoginUserNotExist ) Unwrap ( ) error {
return util . ErrNotExist
}
2017-02-22 07:14:37 +00:00
// ExternalLoginUser makes the connecting between some existing user and additional external login sources
type ExternalLoginUser struct {
2023-07-04 18:36:08 +00:00
ExternalID string ` xorm:"pk NOT NULL" `
UserID int64 ` xorm:"INDEX NOT NULL" `
LoginSourceID int64 ` xorm:"pk NOT NULL" `
RawData map [ string ] any ` xorm:"TEXT JSON" `
Provider string ` xorm:"index VARCHAR(25)" `
2019-10-14 06:10:42 +00:00
Email string
Name string
FirstName string
LastName string
NickName string
Description string
2022-02-01 05:40:23 +00:00
AvatarURL string ` xorm:"TEXT" `
2019-10-14 06:10:42 +00:00
Location string
2019-10-18 06:58:36 +00:00
AccessToken string ` xorm:"TEXT" `
AccessTokenSecret string ` xorm:"TEXT" `
RefreshToken string ` xorm:"TEXT" `
2019-10-14 06:10:42 +00:00
ExpiresAt time . Time
2017-02-22 07:14:37 +00:00
}
2022-02-01 18:20:28 +00:00
type ExternalUserMigrated interface {
GetExternalName ( ) string
GetExternalID ( ) int64
}
type ExternalUserRemappable interface {
GetUserID ( ) int64
RemapExternalUser ( externalName string , externalID , userID int64 ) error
ExternalUserMigrated
}
2021-09-19 11:49:59 +00:00
func init ( ) {
db . RegisterModel ( new ( ExternalLoginUser ) )
}
2017-02-22 07:14:37 +00:00
// GetExternalLogin checks if a externalID in loginSourceID scope already exists
func GetExternalLogin ( externalLoginUser * ExternalLoginUser ) ( bool , error ) {
2021-09-23 15:45:36 +00:00
return db . GetEngine ( db . DefaultContext ) . Get ( externalLoginUser )
2017-02-22 07:14:37 +00:00
}
// ListAccountLinks returns a map with the ExternalLoginUser and its LoginSource
2021-11-28 14:11:58 +00:00
func ListAccountLinks ( user * User ) ( [ ] * ExternalLoginUser , error ) {
2017-02-22 07:14:37 +00:00
externalAccounts := make ( [ ] * ExternalLoginUser , 0 , 5 )
2021-09-23 15:45:36 +00:00
err := db . GetEngine ( db . DefaultContext ) . Where ( "user_id=?" , user . ID ) .
2017-02-22 07:14:37 +00:00
Desc ( "login_source_id" ) .
Find ( & externalAccounts )
if err != nil {
return nil , err
}
return externalAccounts , nil
}
2019-10-14 06:10:42 +00:00
// LinkExternalToUser link the external user to the user
2021-11-28 14:11:58 +00:00
func LinkExternalToUser ( user * User , externalLoginUser * ExternalLoginUser ) error {
2021-09-23 15:45:36 +00:00
has , err := db . GetEngine ( db . DefaultContext ) . Where ( "external_id=? AND login_source_id=?" , externalLoginUser . ExternalID , externalLoginUser . LoginSourceID ) .
2019-10-14 06:10:42 +00:00
NoAutoCondition ( ) .
Exist ( externalLoginUser )
2017-02-22 07:14:37 +00:00
if err != nil {
return err
} else if has {
2019-10-14 06:10:42 +00:00
return ErrExternalLoginUserAlreadyExist { externalLoginUser . ExternalID , user . ID , externalLoginUser . LoginSourceID }
2017-02-22 07:14:37 +00:00
}
2021-09-23 15:45:36 +00:00
_ , err = db . GetEngine ( db . DefaultContext ) . Insert ( externalLoginUser )
2017-02-22 07:14:37 +00:00
return err
}
// RemoveAccountLink will remove all external login sources for the given user
2021-11-28 14:11:58 +00:00
func RemoveAccountLink ( user * User , loginSourceID int64 ) ( int64 , error ) {
2021-09-23 15:45:36 +00:00
deleted , err := db . GetEngine ( db . DefaultContext ) . Delete ( & ExternalLoginUser { UserID : user . ID , LoginSourceID : loginSourceID } )
2017-02-22 07:14:37 +00:00
if err != nil {
return deleted , err
}
if deleted < 1 {
return deleted , ErrExternalLoginUserNotExist { user . ID , loginSourceID }
}
return deleted , err
}
2021-11-28 14:11:58 +00:00
// RemoveAllAccountLinks will remove all external login sources for the given user
func RemoveAllAccountLinks ( ctx context . Context , user * User ) error {
_ , err := db . GetEngine ( ctx ) . Delete ( & ExternalLoginUser { UserID : user . ID } )
2017-02-22 07:14:37 +00:00
return err
}
2019-10-14 06:10:42 +00:00
// GetUserIDByExternalUserID get user id according to provider and userID
2021-03-14 18:52:12 +00:00
func GetUserIDByExternalUserID ( provider , userID string ) ( int64 , error ) {
2019-10-14 06:10:42 +00:00
var id int64
2021-09-23 15:45:36 +00:00
_ , err := db . GetEngine ( db . DefaultContext ) . Table ( "external_login_user" ) .
2019-10-14 06:10:42 +00:00
Select ( "user_id" ) .
Where ( "provider=?" , provider ) .
And ( "external_id=?" , userID ) .
Get ( & id )
if err != nil {
return 0 , err
}
return id , nil
}
2021-12-14 08:37:11 +00:00
// UpdateExternalUserByExternalID updates an external user's information
func UpdateExternalUserByExternalID ( external * ExternalLoginUser ) error {
has , err := db . GetEngine ( db . DefaultContext ) . Where ( "external_id=? AND login_source_id=?" , external . ExternalID , external . LoginSourceID ) .
2019-10-14 06:10:42 +00:00
NoAutoCondition ( ) .
2021-12-14 08:37:11 +00:00
Exist ( external )
2019-10-14 06:10:42 +00:00
if err != nil {
return err
} else if ! has {
2021-12-14 08:37:11 +00:00
return ErrExternalLoginUserNotExist { external . UserID , external . LoginSourceID }
2019-10-14 06:10:42 +00:00
}
2021-12-14 08:37:11 +00:00
_ , err = db . GetEngine ( db . DefaultContext ) . Where ( "external_id=? AND login_source_id=?" , external . ExternalID , external . LoginSourceID ) . AllCols ( ) . Update ( external )
2019-10-14 06:10:42 +00:00
return err
}
// FindExternalUserOptions represents an options to find external users
type FindExternalUserOptions struct {
Provider string
Limit int
Start int
}
func ( opts FindExternalUserOptions ) toConds ( ) builder . Cond {
2021-03-14 18:52:12 +00:00
cond := builder . NewCond ( )
2019-10-14 06:10:42 +00:00
if len ( opts . Provider ) > 0 {
cond = cond . And ( builder . Eq { "provider" : opts . Provider } )
}
return cond
}
// FindExternalUsersByProvider represents external users via provider
func FindExternalUsersByProvider ( opts FindExternalUserOptions ) ( [ ] ExternalLoginUser , error ) {
var users [ ] ExternalLoginUser
2021-09-23 15:45:36 +00:00
err := db . GetEngine ( db . DefaultContext ) . Where ( opts . toConds ( ) ) .
2019-10-14 06:10:42 +00:00
Limit ( opts . Limit , opts . Start ) .
2019-10-14 07:22:46 +00:00
OrderBy ( "login_source_id ASC, external_id ASC" ) .
2019-10-14 06:10:42 +00:00
Find ( & users )
if err != nil {
return nil , err
}
return users , nil
}