mirror of
				https://gitee.com/gitea/gitea
				synced 2025-11-04 16:40:24 +08:00 
			
		
		
		
	Add single sign-on support via SSPI on Windows (#8463)
* Add single sign-on support via SSPI on Windows * Ensure plugins implement interface * Ensure plugins implement interface * Move functions used only by the SSPI auth method to sspi_windows.go * Field SSPISeparatorReplacement of AuthenticationForm should not be required via binding, as binding will insist the field is non-empty even if another login type is selected * Fix breaking of oauth authentication on download links. Do not create new session with SSPI authentication on download links. * Update documentation for the new 'SPNEGO with SSPI' login source * Mention in documentation that ROOT_URL should contain the FQDN of the server * Make sure that Contexter is not checking for active login sources when the ORM engine is not initialized (eg. when installing) * Always initialize and free SSO methods, even if they are not enabled, as a method can be activated while the app is running (from Authentication sources) * Add option in SSPIConfig for removing of domains from logon names * Update helper text for StripDomainNames option * Make sure handleSignIn() is called after a new user object is created by SSPI auth method * Remove default value from text of form field helper Co-Authored-By: Lauris BH <lauris@nix.lv> * Remove default value from text of form field helper Co-Authored-By: Lauris BH <lauris@nix.lv> * Remove default value from text of form field helper Co-Authored-By: Lauris BH <lauris@nix.lv> * Only make a query to the DB to check if SSPI is enabled on handlers that need that information for templates * Remove code duplication * Log errors in ActiveLoginSources Co-Authored-By: Lauris BH <lauris@nix.lv> * Revert suffix of randomly generated E-mails for Reverse proxy authentication Co-Authored-By: Lauris BH <lauris@nix.lv> * Revert unneeded white-space change in template Co-Authored-By: Lauris BH <lauris@nix.lv> * Add copyright comments at the top of new files * Use loopback name for randomly generated emails * Add locale tag for the SSPISeparatorReplacement field with proper casing * Revert casing of SSPISeparatorReplacement field in locale file, moving it up, next to other form fields * Update docs/content/doc/features/authentication.en-us.md Co-Authored-By: guillep2k <18600385+guillep2k@users.noreply.github.com> * Remove Priority() method and define the order in which SSO auth methods should be executed in one place * Log authenticated username only if it's not empty * Rephrase helper text for automatic creation of users * Return error if more than one active SSPI auth source is found * Change newUser() function to return error, letting caller log/handle the error * Move isPublicResource, isPublicPage and handleSignIn functions outside SSPI auth method to allow other SSO methods to reuse them if needed * Refactor initialization of the list containing SSO auth methods * Validate SSPI settings on POST * Change SSPI to only perform authentication on its own login page, API paths and download links. Leave Toggle middleware to redirect non authenticated users to login page * Make 'Default language' in SSPI config empty, unless changed by admin * Show error if admin tries to add a second authentication source of type SSPI * Simplify declaration of global variable * Rebuild gitgraph.js on Linux * Make sure config values containing only whitespace are not accepted
This commit is contained in:
		@@ -39,6 +39,7 @@ const (
 | 
			
		||||
	LoginPAM              // 4
 | 
			
		||||
	LoginDLDAP            // 5
 | 
			
		||||
	LoginOAuth2           // 6
 | 
			
		||||
	LoginSSPI             // 7
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// LoginNames contains the name of LoginType values.
 | 
			
		||||
@@ -48,6 +49,7 @@ var LoginNames = map[LoginType]string{
 | 
			
		||||
	LoginSMTP:   "SMTP",
 | 
			
		||||
	LoginPAM:    "PAM",
 | 
			
		||||
	LoginOAuth2: "OAuth2",
 | 
			
		||||
	LoginSSPI:   "SPNEGO with SSPI",
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// SecurityProtocolNames contains the name of SecurityProtocol values.
 | 
			
		||||
@@ -63,6 +65,7 @@ var (
 | 
			
		||||
	_ core.Conversion = &SMTPConfig{}
 | 
			
		||||
	_ core.Conversion = &PAMConfig{}
 | 
			
		||||
	_ core.Conversion = &OAuth2Config{}
 | 
			
		||||
	_ core.Conversion = &SSPIConfig{}
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// LDAPConfig holds configuration for LDAP login source.
 | 
			
		||||
@@ -140,6 +143,25 @@ func (cfg *OAuth2Config) ToDB() ([]byte, error) {
 | 
			
		||||
	return json.Marshal(cfg)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// SSPIConfig holds configuration for SSPI single sign-on.
 | 
			
		||||
type SSPIConfig struct {
 | 
			
		||||
	AutoCreateUsers      bool
 | 
			
		||||
	AutoActivateUsers    bool
 | 
			
		||||
	StripDomainNames     bool
 | 
			
		||||
	SeparatorReplacement string
 | 
			
		||||
	DefaultLanguage      string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// FromDB fills up an SSPIConfig from serialized format.
 | 
			
		||||
func (cfg *SSPIConfig) FromDB(bs []byte) error {
 | 
			
		||||
	return json.Unmarshal(bs, cfg)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ToDB exports an SSPIConfig to a serialized format.
 | 
			
		||||
func (cfg *SSPIConfig) ToDB() ([]byte, error) {
 | 
			
		||||
	return json.Marshal(cfg)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// LoginSource represents an external way for authorizing users.
 | 
			
		||||
type LoginSource struct {
 | 
			
		||||
	ID            int64 `xorm:"pk autoincr"`
 | 
			
		||||
@@ -176,6 +198,8 @@ func (source *LoginSource) BeforeSet(colName string, val xorm.Cell) {
 | 
			
		||||
			source.Cfg = new(PAMConfig)
 | 
			
		||||
		case LoginOAuth2:
 | 
			
		||||
			source.Cfg = new(OAuth2Config)
 | 
			
		||||
		case LoginSSPI:
 | 
			
		||||
			source.Cfg = new(SSPIConfig)
 | 
			
		||||
		default:
 | 
			
		||||
			panic("unrecognized login source type: " + com.ToStr(*val))
 | 
			
		||||
		}
 | 
			
		||||
@@ -212,6 +236,11 @@ func (source *LoginSource) IsOAuth2() bool {
 | 
			
		||||
	return source.Type == LoginOAuth2
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// IsSSPI returns true of this source is of the SSPI type.
 | 
			
		||||
func (source *LoginSource) IsSSPI() bool {
 | 
			
		||||
	return source.Type == LoginSSPI
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// HasTLS returns true of this source supports TLS.
 | 
			
		||||
func (source *LoginSource) HasTLS() bool {
 | 
			
		||||
	return ((source.IsLDAP() || source.IsDLDAP()) &&
 | 
			
		||||
@@ -264,6 +293,11 @@ func (source *LoginSource) OAuth2() *OAuth2Config {
 | 
			
		||||
	return source.Cfg.(*OAuth2Config)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// SSPI returns SSPIConfig for this source, if of SSPI type.
 | 
			
		||||
func (source *LoginSource) SSPI() *SSPIConfig {
 | 
			
		||||
	return source.Cfg.(*SSPIConfig)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// CreateLoginSource inserts a LoginSource in the DB if not already
 | 
			
		||||
// existing with the given name.
 | 
			
		||||
func CreateLoginSource(source *LoginSource) error {
 | 
			
		||||
@@ -300,6 +334,38 @@ func LoginSources() ([]*LoginSource, error) {
 | 
			
		||||
	return auths, x.Find(&auths)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// LoginSourcesByType returns all sources of the specified type
 | 
			
		||||
func LoginSourcesByType(loginType LoginType) ([]*LoginSource, error) {
 | 
			
		||||
	sources := make([]*LoginSource, 0, 1)
 | 
			
		||||
	if err := x.Where("type = ?", loginType).Find(&sources); err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
	return sources, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ActiveLoginSources returns all active sources of the specified type
 | 
			
		||||
func ActiveLoginSources(loginType LoginType) ([]*LoginSource, error) {
 | 
			
		||||
	sources := make([]*LoginSource, 0, 1)
 | 
			
		||||
	if err := x.Where("is_actived = ? and type = ?", true, loginType).Find(&sources); err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
	return sources, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// IsSSPIEnabled returns true if there is at least one activated login
 | 
			
		||||
// source of type LoginSSPI
 | 
			
		||||
func IsSSPIEnabled() bool {
 | 
			
		||||
	if !HasEngine {
 | 
			
		||||
		return false
 | 
			
		||||
	}
 | 
			
		||||
	sources, err := ActiveLoginSources(LoginSSPI)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		log.Error("ActiveLoginSources: %v", err)
 | 
			
		||||
		return false
 | 
			
		||||
	}
 | 
			
		||||
	return len(sources) > 0
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// GetLoginSourceByID returns login source by given ID.
 | 
			
		||||
func GetLoginSourceByID(id int64) (*LoginSource, error) {
 | 
			
		||||
	source := new(LoginSource)
 | 
			
		||||
@@ -719,8 +785,8 @@ func UserSignIn(username, password string) (*User, error) {
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for _, source := range sources {
 | 
			
		||||
		if source.IsOAuth2() {
 | 
			
		||||
			// don't try to authenticate against OAuth2 sources
 | 
			
		||||
		if source.IsOAuth2() || source.IsSSPI() {
 | 
			
		||||
			// don't try to authenticate against OAuth2 and SSPI sources here
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
		authUser, err := ExternalUserLogin(nil, username, password, source, true)
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user