mirror of
				https://codeberg.org/forgejo/forgejo.git
				synced 2025-11-04 00:11:04 +00:00 
			
		
		
		
	Enable [unparam](https://github.com/mvdan/unparam) linter. Often I could not tell the intention why param is unused, so I put `//nolint` for those cases like webhook request creation functions never using `ctx`. --------- Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com> Co-authored-by: delvh <dev.lh@web.de> (cherry picked from commit fc2d75f86d77b022ece848acf2581c14ef21d43b) Conflicts: modules/setting/config_env.go modules/storage/azureblob.go services/webhook/dingtalk.go services/webhook/discord.go services/webhook/feishu.go services/webhook/matrix.go services/webhook/msteams.go services/webhook/packagist.go services/webhook/slack.go services/webhook/telegram.go services/webhook/wechatwork.go run make lint-go and fix Forgejo specific warnings
		
			
				
	
	
		
			133 lines
		
	
	
	
		
			4.7 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			133 lines
		
	
	
	
		
			4.7 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
// Copyright Earl Warren <contact@earl-warren.org>
 | 
						|
// SPDX-License-Identifier: MIT
 | 
						|
 | 
						|
package remote
 | 
						|
 | 
						|
import (
 | 
						|
	"context"
 | 
						|
 | 
						|
	auth_model "code.gitea.io/gitea/models/auth"
 | 
						|
	"code.gitea.io/gitea/models/db"
 | 
						|
	user_model "code.gitea.io/gitea/models/user"
 | 
						|
	"code.gitea.io/gitea/modules/log"
 | 
						|
	"code.gitea.io/gitea/services/auth/source/oauth2"
 | 
						|
	remote_source "code.gitea.io/gitea/services/auth/source/remote"
 | 
						|
)
 | 
						|
 | 
						|
type Reason int
 | 
						|
 | 
						|
const (
 | 
						|
	ReasonNoMatch Reason = iota
 | 
						|
	ReasonNotAuth2
 | 
						|
	ReasonBadAuth2
 | 
						|
	ReasonLoginNameNotExists
 | 
						|
	ReasonNotRemote
 | 
						|
	ReasonEmailIsSet
 | 
						|
	ReasonNoSource
 | 
						|
	ReasonSourceWrongType
 | 
						|
	ReasonCanPromote
 | 
						|
	ReasonPromoted
 | 
						|
	ReasonUpdateFail
 | 
						|
	ReasonErrorLoginName
 | 
						|
	ReasonErrorGetSource
 | 
						|
)
 | 
						|
 | 
						|
func NewReason(level log.Level, reason Reason, message string, args ...any) Reason {
 | 
						|
	log.Log(1, level, message, args...)
 | 
						|
	return reason
 | 
						|
}
 | 
						|
 | 
						|
func getUsersByLoginName(ctx context.Context, name string) ([]*user_model.User, error) {
 | 
						|
	if len(name) == 0 {
 | 
						|
		return nil, user_model.ErrUserNotExist{Name: name}
 | 
						|
	}
 | 
						|
 | 
						|
	users := make([]*user_model.User, 0, 5)
 | 
						|
 | 
						|
	return users, db.GetEngine(ctx).
 | 
						|
		Table("user").
 | 
						|
		Where("login_name = ? AND login_type = ? AND type = ?", name, auth_model.Remote, user_model.UserTypeRemoteUser).
 | 
						|
		Find(&users)
 | 
						|
}
 | 
						|
 | 
						|
// The remote user has:
 | 
						|
//
 | 
						|
//	Type        UserTypeRemoteUser
 | 
						|
//	LogingType  Remote
 | 
						|
//	LoginName   set to the unique identifier of the originating authentication source
 | 
						|
//	LoginSource set to the Remote source that can be matched against an OAuth2 source
 | 
						|
//
 | 
						|
// If the source from which an authentication happens is OAuth2, an existing
 | 
						|
// remote user will be promoted to an OAuth2 user provided:
 | 
						|
//
 | 
						|
//	user.LoginName is the same as goth.UserID (argument loginName)
 | 
						|
//	user.LoginSource has a MatchingSource equals to the name of the OAuth2 provider
 | 
						|
//
 | 
						|
// Once promoted, the user will be logged in without further interaction from the
 | 
						|
// user and will own all repositories, issues, etc. associated with it.
 | 
						|
func MaybePromoteRemoteUser(ctx context.Context, source *auth_model.Source, loginName, email string) (promoted bool, reason Reason, err error) {
 | 
						|
	user, reason, err := getRemoteUserToPromote(ctx, source, loginName, email)
 | 
						|
	if err != nil || user == nil {
 | 
						|
		return false, reason, err
 | 
						|
	}
 | 
						|
	promote := &user_model.User{
 | 
						|
		ID:          user.ID,
 | 
						|
		Type:        user_model.UserTypeIndividual,
 | 
						|
		Email:       email,
 | 
						|
		LoginSource: source.ID,
 | 
						|
		LoginType:   source.Type,
 | 
						|
	}
 | 
						|
	reason = NewReason(log.DEBUG, ReasonPromoted, "promote user %v: LoginName %v => %v, LoginSource %v => %v, LoginType %v => %v, Email %v => %v", user.ID, user.LoginName, promote.LoginName, user.LoginSource, promote.LoginSource, user.LoginType, promote.LoginType, user.Email, promote.Email)
 | 
						|
	if err := user_model.UpdateUserCols(ctx, promote, "type", "email", "login_source", "login_type"); err != nil {
 | 
						|
		return false, ReasonUpdateFail, err
 | 
						|
	}
 | 
						|
	return true, reason, nil
 | 
						|
}
 | 
						|
 | 
						|
func getRemoteUserToPromote(ctx context.Context, source *auth_model.Source, loginName, email string) (*user_model.User, Reason, error) { //nolint:unparam
 | 
						|
	if !source.IsOAuth2() {
 | 
						|
		return nil, NewReason(log.DEBUG, ReasonNotAuth2, "source %v is not OAuth2", source), nil
 | 
						|
	}
 | 
						|
	oauth2Source, ok := source.Cfg.(*oauth2.Source)
 | 
						|
	if !ok {
 | 
						|
		return nil, NewReason(log.ERROR, ReasonBadAuth2, "source claims to be OAuth2 but is not"), nil
 | 
						|
	}
 | 
						|
 | 
						|
	users, err := getUsersByLoginName(ctx, loginName)
 | 
						|
	if err != nil {
 | 
						|
		return nil, NewReason(log.ERROR, ReasonErrorLoginName, "getUserByLoginName('%s') %v", loginName, err), err
 | 
						|
	}
 | 
						|
	if len(users) == 0 {
 | 
						|
		return nil, NewReason(log.ERROR, ReasonLoginNameNotExists, "no user with LoginType UserTypeRemoteUser and LoginName '%s'", loginName), nil
 | 
						|
	}
 | 
						|
 | 
						|
	reason := ReasonNoSource
 | 
						|
	for _, u := range users {
 | 
						|
		userSource, err := auth_model.GetSourceByID(ctx, u.LoginSource)
 | 
						|
		if err != nil {
 | 
						|
			if auth_model.IsErrSourceNotExist(err) {
 | 
						|
				reason = NewReason(log.DEBUG, ReasonNoSource, "source id = %v for user %v not found %v", u.LoginSource, u.ID, err)
 | 
						|
				continue
 | 
						|
			}
 | 
						|
			return nil, NewReason(log.ERROR, ReasonErrorGetSource, "GetSourceByID('%s') %v", u.LoginSource, err), err
 | 
						|
		}
 | 
						|
		if u.Email != "" {
 | 
						|
			reason = NewReason(log.DEBUG, ReasonEmailIsSet, "the user email is already set to '%s'", u.Email)
 | 
						|
			continue
 | 
						|
		}
 | 
						|
		remoteSource, ok := userSource.Cfg.(*remote_source.Source)
 | 
						|
		if !ok {
 | 
						|
			reason = NewReason(log.DEBUG, ReasonSourceWrongType, "expected a remote source but got %T %v", userSource, userSource)
 | 
						|
			continue
 | 
						|
		}
 | 
						|
 | 
						|
		if oauth2Source.Provider != remoteSource.MatchingSource {
 | 
						|
			reason = NewReason(log.DEBUG, ReasonNoMatch, "skip OAuth2 source %s because it is different from %s which is the expected match for the remote source %s", oauth2Source.Provider, remoteSource.MatchingSource, remoteSource.URL)
 | 
						|
			continue
 | 
						|
		}
 | 
						|
 | 
						|
		return u, ReasonCanPromote, nil
 | 
						|
	}
 | 
						|
 | 
						|
	return nil, reason, nil
 | 
						|
}
 |