2019-11-23 02:33:31 +03:00
// Copyright 2019 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
2021-06-09 20:53:16 +03:00
package auth
2019-11-23 02:33:31 +03:00
import (
2022-08-28 12:43:25 +03:00
"context"
2019-11-23 02:33:31 +03:00
"errors"
2021-01-05 16:05:40 +03:00
"net/http"
2019-11-23 02:33:31 +03:00
"strings"
2022-01-02 16:12:35 +03:00
"code.gitea.io/gitea/models/auth"
Avatar refactor, move avatar code from `models` to `models.avatars`, remove duplicated code (#17123)
Why this refactor
The goal is to move most files from `models` package to `models.xxx` package. Many models depend on avatar model, so just move this first.
And the existing logic is not clear, there are too many function like `AvatarLink`, `RelAvatarLink`, `SizedRelAvatarLink`, `SizedAvatarLink`, `MakeFinalAvatarURL`, `HashedAvatarLink`, etc. This refactor make everything clear:
* user.AvatarLink()
* user.AvatarLinkWithSize(size)
* avatars.GenerateEmailAvatarFastLink(email, size)
* avatars.GenerateEmailAvatarFinalLink(email, size)
And many duplicated code are deleted in route handler, the handler and the model share the same avatar logic now.
2021-10-06 02:25:46 +03:00
"code.gitea.io/gitea/models/avatars"
2021-11-24 12:49:20 +03:00
user_model "code.gitea.io/gitea/models/user"
2019-11-23 02:33:31 +03:00
"code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
2021-01-06 04:38:00 +03:00
"code.gitea.io/gitea/modules/templates"
2022-04-29 22:38:11 +03:00
"code.gitea.io/gitea/modules/util"
2021-01-30 11:55:53 +03:00
"code.gitea.io/gitea/modules/web/middleware"
2021-07-24 13:16:34 +03:00
"code.gitea.io/gitea/services/auth/source/sspi"
2021-08-12 10:26:33 +03:00
"code.gitea.io/gitea/services/mailer"
2019-11-23 02:33:31 +03:00
2020-06-18 12:18:44 +03:00
gouuid "github.com/google/uuid"
2019-11-23 02:33:31 +03:00
"github.com/quasoft/websspi"
2021-01-06 04:38:00 +03:00
"github.com/unrolled/render"
2019-11-23 02:33:31 +03:00
)
const (
tplSignIn base . TplName = "user/auth/signin"
)
var (
// sspiAuth is a global instance of the websspi authentication package,
// which is used to avoid acquiring the server credential handle on
// every request
sspiAuth * websspi . Authenticator
// Ensure the struct implements the interface.
2021-07-24 13:16:34 +03:00
_ Method = & SSPI { }
_ Named = & SSPI { }
_ Initializable = & SSPI { }
_ Freeable = & SSPI { }
2019-11-23 02:33:31 +03:00
)
// SSPI implements the SingleSignOn interface and authenticates requests
// via the built-in SSPI module in Windows for SPNEGO authentication.
// On successful authentication returns a valid user object.
// Returns nil if authentication fails.
type SSPI struct {
2021-01-06 04:38:00 +03:00
rnd * render . Render
2019-11-23 02:33:31 +03:00
}
// Init creates a new global websspi.Authenticator object
2022-08-28 12:43:25 +03:00
func ( s * SSPI ) Init ( ctx context . Context ) error {
2019-11-23 02:33:31 +03:00
config := websspi . NewConfig ( )
var err error
sspiAuth , err = websspi . New ( config )
2021-01-06 04:38:00 +03:00
if err != nil {
return err
}
2022-08-28 12:43:25 +03:00
_ , s . rnd = templates . HTMLRenderer ( ctx )
2021-01-06 04:38:00 +03:00
return nil
2019-11-23 02:33:31 +03:00
}
2021-06-09 20:53:16 +03:00
// Name represents the name of auth method
func ( s * SSPI ) Name ( ) string {
return "sspi"
}
2019-11-23 02:33:31 +03:00
// Free releases resources used by the global websspi.Authenticator object
func ( s * SSPI ) Free ( ) error {
return sspiAuth . Free ( )
}
2021-06-09 20:53:16 +03:00
// Verify uses SSPI (Windows implementation of SPNEGO) to authenticate the request.
2021-07-08 14:38:13 +03:00
// If authentication is successful, returns the corresponding user object.
2019-11-23 02:33:31 +03:00
// If negotiation should continue or authentication fails, immediately returns a 401 HTTP
// response code, as required by the SPNEGO protocol.
2021-11-24 12:49:20 +03:00
func ( s * SSPI ) Verify ( req * http . Request , w http . ResponseWriter , store DataStore , sess SessionStore ) * user_model . User {
2021-01-06 04:38:00 +03:00
if ! s . shouldAuthenticate ( req ) {
2019-11-23 02:33:31 +03:00
return nil
}
cfg , err := s . getConfig ( )
if err != nil {
log . Error ( "could not get SSPI config: %v" , err )
return nil
}
2021-05-09 19:04:53 +03:00
log . Trace ( "SSPI Authorization: Attempting to authenticate" )
2021-01-06 04:38:00 +03:00
userInfo , outToken , err := sspiAuth . Authenticate ( req , w )
2019-11-23 02:33:31 +03:00
if err != nil {
log . Warn ( "Authentication failed with error: %v\n" , err )
2021-01-06 04:38:00 +03:00
sspiAuth . AppendAuthenticateHeader ( w , outToken )
2019-11-23 02:33:31 +03:00
// Include the user login page in the 401 response to allow the user
// to login with another authentication method if SSPI authentication
// fails
2021-01-06 04:38:00 +03:00
store . GetData ( ) [ "Flash" ] = map [ string ] string {
2021-01-14 18:35:10 +03:00
"ErrorMsg" : err . Error ( ) ,
2021-01-06 04:38:00 +03:00
}
store . GetData ( ) [ "EnableOpenIDSignIn" ] = setting . Service . EnableOpenIDSignIn
store . GetData ( ) [ "EnableSSPI" ] = true
2022-03-23 07:54:07 +03:00
err := s . rnd . HTML ( w , http . StatusUnauthorized , string ( tplSignIn ) , templates . BaseVars ( ) . Merge ( store . GetData ( ) ) )
2021-01-06 04:38:00 +03:00
if err != nil {
log . Error ( "%v" , err )
}
2019-11-23 02:33:31 +03:00
return nil
}
if outToken != "" {
2021-01-06 04:38:00 +03:00
sspiAuth . AppendAuthenticateHeader ( w , outToken )
2019-11-23 02:33:31 +03:00
}
username := sanitizeUsername ( userInfo . Username , cfg )
if len ( username ) == 0 {
return nil
}
log . Info ( "Authenticated as %s\n" , username )
2022-05-20 17:08:52 +03:00
user , err := user_model . GetUserByName ( req . Context ( ) , username )
2019-11-23 02:33:31 +03:00
if err != nil {
2021-11-24 12:49:20 +03:00
if ! user_model . IsErrUserNotExist ( err ) {
2019-11-23 02:33:31 +03:00
log . Error ( "GetUserByName: %v" , err )
return nil
}
if ! cfg . AutoCreateUsers {
log . Error ( "User '%s' not found" , username )
return nil
}
2021-01-06 04:38:00 +03:00
user , err = s . newUser ( username , cfg )
2019-11-23 02:33:31 +03:00
if err != nil {
log . Error ( "CreateUser: %v" , err )
return nil
}
}
// Make sure requests to API paths and PWA resources do not create a new session
2021-01-30 11:55:53 +03:00
if ! middleware . IsAPIPath ( req ) && ! isAttachmentDownload ( req ) {
2021-01-06 04:38:00 +03:00
handleSignIn ( w , req , sess , user )
2019-11-23 02:33:31 +03:00
}
2021-05-09 19:04:53 +03:00
log . Trace ( "SSPI Authorization: Logged in user %-v" , user )
2019-11-23 02:33:31 +03:00
return user
}
// getConfig retrieves the SSPI configuration from login sources
2021-07-24 13:16:34 +03:00
func ( s * SSPI ) getConfig ( ) ( * sspi . Source , error ) {
2022-01-02 16:12:35 +03:00
sources , err := auth . ActiveSources ( auth . SSPI )
2019-11-23 02:33:31 +03:00
if err != nil {
return nil , err
}
if len ( sources ) == 0 {
return nil , errors . New ( "no active login sources of type SSPI found" )
}
if len ( sources ) > 1 {
return nil , errors . New ( "more than one active login source of type SSPI found" )
}
2021-07-24 13:16:34 +03:00
return sources [ 0 ] . Cfg . ( * sspi . Source ) , nil
2019-11-23 02:33:31 +03:00
}
2021-01-05 16:05:40 +03:00
func ( s * SSPI ) shouldAuthenticate ( req * http . Request ) ( shouldAuth bool ) {
2019-11-23 02:33:31 +03:00
shouldAuth = false
2021-01-05 16:05:40 +03:00
path := strings . TrimSuffix ( req . URL . Path , "/" )
2019-11-23 02:33:31 +03:00
if path == "/user/login" {
2021-01-05 16:05:40 +03:00
if req . FormValue ( "user_name" ) != "" && req . FormValue ( "password" ) != "" {
2019-11-23 02:33:31 +03:00
shouldAuth = false
2021-01-06 04:38:00 +03:00
} else if req . FormValue ( "auth_with_sspi" ) == "1" {
2019-11-23 02:33:31 +03:00
shouldAuth = true
}
2021-01-30 11:55:53 +03:00
} else if middleware . IsAPIPath ( req ) || isAttachmentDownload ( req ) {
2019-11-23 02:33:31 +03:00
shouldAuth = true
}
2022-06-20 13:02:49 +03:00
return shouldAuth
2019-11-23 02:33:31 +03:00
}
// newUser creates a new user object for the purpose of automatic registration
// and populates its name and email with the information present in request headers.
2021-11-24 12:49:20 +03:00
func ( s * SSPI ) newUser ( username string , cfg * sspi . Source ) ( * user_model . User , error ) {
2020-06-18 12:18:44 +03:00
email := gouuid . New ( ) . String ( ) + "@localhost.localdomain"
2021-11-24 12:49:20 +03:00
user := & user_model . User {
2022-04-29 22:38:11 +03:00
Name : username ,
Email : email ,
Passwd : gouuid . New ( ) . String ( ) ,
Language : cfg . DefaultLanguage ,
UseCustomAvatar : true ,
Avatar : avatars . DefaultAvatarLink ( ) ,
2019-11-23 02:33:31 +03:00
}
2022-04-29 22:38:11 +03:00
emailNotificationPreference := user_model . EmailNotificationsDisabled
overwriteDefault := & user_model . CreateUserOverwriteOptions {
IsActive : util . OptionalBoolOf ( cfg . AutoActivateUsers ) ,
KeepEmailPrivate : util . OptionalBoolTrue ,
EmailNotificationsPreference : & emailNotificationPreference ,
}
if err := user_model . CreateUser ( user , overwriteDefault ) ; err != nil {
2019-11-23 02:33:31 +03:00
return nil , err
}
2021-08-12 10:26:33 +03:00
mailer . SendRegisterNotifyMail ( user )
2019-11-23 02:33:31 +03:00
return user , nil
}
// stripDomainNames removes NETBIOS domain name and separator from down-level logon names
// (eg. "DOMAIN\user" becomes "user"), and removes the UPN suffix (domain name) and separator
// from UPNs (eg. "user@domain.local" becomes "user")
func stripDomainNames ( username string ) string {
if strings . Contains ( username , "\\" ) {
parts := strings . SplitN ( username , "\\" , 2 )
if len ( parts ) > 1 {
username = parts [ 1 ]
}
} else if strings . Contains ( username , "@" ) {
parts := strings . Split ( username , "@" )
if len ( parts ) > 1 {
username = parts [ 0 ]
}
}
return username
}
2021-07-24 13:16:34 +03:00
func replaceSeparators ( username string , cfg * sspi . Source ) string {
2019-11-23 02:33:31 +03:00
newSep := cfg . SeparatorReplacement
username = strings . ReplaceAll ( username , "\\" , newSep )
username = strings . ReplaceAll ( username , "/" , newSep )
username = strings . ReplaceAll ( username , "@" , newSep )
return username
}
2021-07-24 13:16:34 +03:00
func sanitizeUsername ( username string , cfg * sspi . Source ) string {
2019-11-23 02:33:31 +03:00
if len ( username ) == 0 {
return ""
}
if cfg . StripDomainNames {
username = stripDomainNames ( username )
}
// Replace separators even if we have already stripped the domain name part,
// as the username can contain several separators: eg. "MICROSOFT\useremail@live.com"
username = replaceSeparators ( username , cfg )
return username
}