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:
parent
eb1b225d9a
commit
7b4d2f7a2a
174 changed files with 6363 additions and 1306 deletions
|
@ -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)
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue