Add sso.Group, context.Auth, context.APIAuth to allow auth special routes (#16086)
* Add sso.Group, context.Auth, context.APIAuth to allow auth special routes * Remove unnecessary check * Rename sso -> auth * remove unused method of Auth interface
This commit is contained in:
		
							parent
							
								
									da057996d5
								
							
						
					
					
						commit
						fb3ffeb18d
					
				
					 19 changed files with 286 additions and 220 deletions
				
			
		|  | @ -1,48 +0,0 @@ | |||
| // 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.
 | ||||
| 
 | ||||
| package sso | ||||
| 
 | ||||
| import ( | ||||
| 	"net/http" | ||||
| 
 | ||||
| 	"code.gitea.io/gitea/models" | ||||
| ) | ||||
| 
 | ||||
| // Ensure the struct implements the interface.
 | ||||
| var ( | ||||
| 	_ SingleSignOn = &Session{} | ||||
| ) | ||||
| 
 | ||||
| // Session checks if there is a user uid stored in the session and returns the user
 | ||||
| // object for that uid.
 | ||||
| type Session struct { | ||||
| } | ||||
| 
 | ||||
| // Init does nothing as the Session implementation does not need to allocate any resources
 | ||||
| func (s *Session) Init() error { | ||||
| 	return nil | ||||
| } | ||||
| 
 | ||||
| // Free does nothing as the Session implementation does not have to release any resources
 | ||||
| func (s *Session) Free() error { | ||||
| 	return nil | ||||
| } | ||||
| 
 | ||||
| // IsEnabled returns true as this plugin is enabled by default and its not possible to disable
 | ||||
| // it from settings.
 | ||||
| func (s *Session) IsEnabled() bool { | ||||
| 	return true | ||||
| } | ||||
| 
 | ||||
| // VerifyAuthData checks if there is a user uid stored in the session and returns the user
 | ||||
| // object for that uid.
 | ||||
| // Returns nil if there is no user uid stored in the session.
 | ||||
| func (s *Session) VerifyAuthData(req *http.Request, w http.ResponseWriter, store DataStore, sess SessionStore) *models.User { | ||||
| 	user := SessionUser(sess) | ||||
| 	if user != nil { | ||||
| 		return user | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
|  | @ -1,33 +0,0 @@ | |||
| // Copyright 2020 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.
 | ||||
| 
 | ||||
| package sso | ||||
| 
 | ||||
| import ( | ||||
| 	"net/http" | ||||
| 
 | ||||
| 	"code.gitea.io/gitea/models" | ||||
| ) | ||||
| 
 | ||||
| // SignedInUser returns the user object of signed user.
 | ||||
| // It returns a bool value to indicate whether user uses basic auth or not.
 | ||||
| func SignedInUser(req *http.Request, w http.ResponseWriter, ds DataStore, sess SessionStore) (*models.User, bool) { | ||||
| 	if !models.HasEngine { | ||||
| 		return nil, false | ||||
| 	} | ||||
| 
 | ||||
| 	// Try to sign in with each of the enabled plugins
 | ||||
| 	for _, ssoMethod := range Methods() { | ||||
| 		if !ssoMethod.IsEnabled() { | ||||
| 			continue | ||||
| 		} | ||||
| 		user := ssoMethod.VerifyAuthData(req, w, ds, sess) | ||||
| 		if user != nil { | ||||
| 			_, isBasic := ssoMethod.(*Basic) | ||||
| 			return user, isBasic | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	return nil, false | ||||
| } | ||||
|  | @ -14,11 +14,11 @@ import ( | |||
| 	"strings" | ||||
| 
 | ||||
| 	"code.gitea.io/gitea/models" | ||||
| 	"code.gitea.io/gitea/modules/auth/sso" | ||||
| 	"code.gitea.io/gitea/modules/git" | ||||
| 	"code.gitea.io/gitea/modules/log" | ||||
| 	"code.gitea.io/gitea/modules/setting" | ||||
| 	"code.gitea.io/gitea/modules/web/middleware" | ||||
| 	"code.gitea.io/gitea/services/auth" | ||||
| 
 | ||||
| 	"gitea.com/go-chi/session" | ||||
| ) | ||||
|  | @ -217,6 +217,26 @@ func (ctx *APIContext) CheckForOTP() { | |||
| 	} | ||||
| } | ||||
| 
 | ||||
| // APIAuth converts auth.Auth as a middleware
 | ||||
| func APIAuth(authMethod auth.Auth) func(*APIContext) { | ||||
| 	return func(ctx *APIContext) { | ||||
| 		// Get user from session if logged in.
 | ||||
| 		ctx.User = authMethod.Verify(ctx.Req, ctx.Resp, ctx, ctx.Session) | ||||
| 		if ctx.User != nil { | ||||
| 			ctx.IsBasicAuth = ctx.Data["AuthedMethod"].(string) == new(auth.Basic).Name() | ||||
| 			ctx.IsSigned = true | ||||
| 			ctx.Data["IsSigned"] = ctx.IsSigned | ||||
| 			ctx.Data["SignedUser"] = ctx.User | ||||
| 			ctx.Data["SignedUserID"] = ctx.User.ID | ||||
| 			ctx.Data["SignedUserName"] = ctx.User.Name | ||||
| 			ctx.Data["IsAdmin"] = ctx.User.IsAdmin | ||||
| 		} else { | ||||
| 			ctx.Data["SignedUserID"] = int64(0) | ||||
| 			ctx.Data["SignedUserName"] = "" | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| // APIContexter returns apicontext as middleware
 | ||||
| func APIContexter() func(http.Handler) http.Handler { | ||||
| 	var csrfOpts = getCsrfOpts() | ||||
|  | @ -250,20 +270,6 @@ func APIContexter() func(http.Handler) http.Handler { | |||
| 				} | ||||
| 			} | ||||
| 
 | ||||
| 			// Get user from session if logged in.
 | ||||
| 			ctx.User, ctx.IsBasicAuth = sso.SignedInUser(ctx.Req, ctx.Resp, &ctx, ctx.Session) | ||||
| 			if ctx.User != nil { | ||||
| 				ctx.IsSigned = true | ||||
| 				ctx.Data["IsSigned"] = ctx.IsSigned | ||||
| 				ctx.Data["SignedUser"] = ctx.User | ||||
| 				ctx.Data["SignedUserID"] = ctx.User.ID | ||||
| 				ctx.Data["SignedUserName"] = ctx.User.Name | ||||
| 				ctx.Data["IsAdmin"] = ctx.User.IsAdmin | ||||
| 			} else { | ||||
| 				ctx.Data["SignedUserID"] = int64(0) | ||||
| 				ctx.Data["SignedUserName"] = "" | ||||
| 			} | ||||
| 
 | ||||
| 			ctx.Resp.Header().Set(`X-Frame-Options`, `SAMEORIGIN`) | ||||
| 
 | ||||
| 			ctx.Data["CsrfToken"] = html.EscapeString(ctx.csrf.GetToken()) | ||||
|  |  | |||
|  | @ -21,7 +21,6 @@ import ( | |||
| 	"time" | ||||
| 
 | ||||
| 	"code.gitea.io/gitea/models" | ||||
| 	"code.gitea.io/gitea/modules/auth/sso" | ||||
| 	"code.gitea.io/gitea/modules/base" | ||||
| 	mc "code.gitea.io/gitea/modules/cache" | ||||
| 	"code.gitea.io/gitea/modules/log" | ||||
|  | @ -29,6 +28,7 @@ import ( | |||
| 	"code.gitea.io/gitea/modules/templates" | ||||
| 	"code.gitea.io/gitea/modules/translation" | ||||
| 	"code.gitea.io/gitea/modules/web/middleware" | ||||
| 	"code.gitea.io/gitea/services/auth" | ||||
| 
 | ||||
| 	"gitea.com/go-chi/cache" | ||||
| 	"gitea.com/go-chi/session" | ||||
|  | @ -605,6 +605,28 @@ func getCsrfOpts() CsrfOptions { | |||
| 	} | ||||
| } | ||||
| 
 | ||||
| // Auth converts auth.Auth as a middleware
 | ||||
| func Auth(authMethod auth.Auth) func(*Context) { | ||||
| 	return func(ctx *Context) { | ||||
| 		ctx.User = authMethod.Verify(ctx.Req, ctx.Resp, ctx, ctx.Session) | ||||
| 		if ctx.User != nil { | ||||
| 			ctx.IsBasicAuth = ctx.Data["AuthedMethod"].(string) == new(auth.Basic).Name() | ||||
| 			ctx.IsSigned = true | ||||
| 			ctx.Data["IsSigned"] = ctx.IsSigned | ||||
| 			ctx.Data["SignedUser"] = ctx.User | ||||
| 			ctx.Data["SignedUserID"] = ctx.User.ID | ||||
| 			ctx.Data["SignedUserName"] = ctx.User.Name | ||||
| 			ctx.Data["IsAdmin"] = ctx.User.IsAdmin | ||||
| 		} else { | ||||
| 			ctx.Data["SignedUserID"] = int64(0) | ||||
| 			ctx.Data["SignedUserName"] = "" | ||||
| 
 | ||||
| 			// ensure the session uid is deleted
 | ||||
| 			_ = ctx.Session.Delete("uid") | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| // Contexter initializes a classic context for a request.
 | ||||
| func Contexter() func(next http.Handler) http.Handler { | ||||
| 	var rnd = templates.HTMLRenderer() | ||||
|  | @ -690,24 +712,6 @@ func Contexter() func(next http.Handler) http.Handler { | |||
| 				} | ||||
| 			} | ||||
| 
 | ||||
| 			// Get user from session if logged in.
 | ||||
| 			ctx.User, ctx.IsBasicAuth = sso.SignedInUser(ctx.Req, ctx.Resp, &ctx, ctx.Session) | ||||
| 
 | ||||
| 			if ctx.User != nil { | ||||
| 				ctx.IsSigned = true | ||||
| 				ctx.Data["IsSigned"] = ctx.IsSigned | ||||
| 				ctx.Data["SignedUser"] = ctx.User | ||||
| 				ctx.Data["SignedUserID"] = ctx.User.ID | ||||
| 				ctx.Data["SignedUserName"] = ctx.User.Name | ||||
| 				ctx.Data["IsAdmin"] = ctx.User.IsAdmin | ||||
| 			} else { | ||||
| 				ctx.Data["SignedUserID"] = int64(0) | ||||
| 				ctx.Data["SignedUserName"] = "" | ||||
| 
 | ||||
| 				// ensure the session uid is deleted
 | ||||
| 				_ = ctx.Session.Delete("uid") | ||||
| 			} | ||||
| 
 | ||||
| 			ctx.Resp.Header().Set(`X-Frame-Options`, `SAMEORIGIN`) | ||||
| 
 | ||||
| 			ctx.Data["CsrfToken"] = html.EscapeString(ctx.csrf.GetToken()) | ||||
|  |  | |||
|  | @ -83,6 +83,7 @@ import ( | |||
| 	"code.gitea.io/gitea/routers/api/v1/settings" | ||||
| 	_ "code.gitea.io/gitea/routers/api/v1/swagger" // for swagger generation
 | ||||
| 	"code.gitea.io/gitea/routers/api/v1/user" | ||||
| 	"code.gitea.io/gitea/services/auth" | ||||
| 	"code.gitea.io/gitea/services/forms" | ||||
| 
 | ||||
| 	"gitea.com/go-chi/binding" | ||||
|  | @ -573,6 +574,9 @@ func Routes() *web.Route { | |||
| 	} | ||||
| 	m.Use(context.APIContexter()) | ||||
| 
 | ||||
| 	// Get user from session if logged in.
 | ||||
| 	m.Use(context.APIAuth(auth.NewGroup(auth.Methods()...))) | ||||
| 
 | ||||
| 	m.Use(context.ToggleAPI(&context.ToggleOptions{ | ||||
| 		SignInRequired: setting.Service.RequireSignInView, | ||||
| 	})) | ||||
|  |  | |||
|  | @ -9,7 +9,6 @@ import ( | |||
| 	"strings" | ||||
| 
 | ||||
| 	"code.gitea.io/gitea/models" | ||||
| 	"code.gitea.io/gitea/modules/auth/sso" | ||||
| 	"code.gitea.io/gitea/modules/cache" | ||||
| 	"code.gitea.io/gitea/modules/cron" | ||||
| 	"code.gitea.io/gitea/modules/eventsource" | ||||
|  | @ -34,6 +33,7 @@ import ( | |||
| 	"code.gitea.io/gitea/routers/common" | ||||
| 	"code.gitea.io/gitea/routers/private" | ||||
| 	web_routers "code.gitea.io/gitea/routers/web" | ||||
| 	"code.gitea.io/gitea/services/auth" | ||||
| 	"code.gitea.io/gitea/services/mailer" | ||||
| 	mirror_service "code.gitea.io/gitea/services/mirror" | ||||
| 	pull_service "code.gitea.io/gitea/services/pull" | ||||
|  | @ -134,7 +134,7 @@ func GlobalInit(ctx context.Context) { | |||
| 	} else { | ||||
| 		ssh.Unused() | ||||
| 	} | ||||
| 	sso.Init() | ||||
| 	auth.Init() | ||||
| 
 | ||||
| 	svg.Init() | ||||
| } | ||||
|  |  | |||
|  | @ -15,7 +15,6 @@ import ( | |||
| 	"strings" | ||||
| 
 | ||||
| 	"code.gitea.io/gitea/models" | ||||
| 	"code.gitea.io/gitea/modules/auth/sso" | ||||
| 	"code.gitea.io/gitea/modules/context" | ||||
| 	"code.gitea.io/gitea/modules/httpcache" | ||||
| 	"code.gitea.io/gitea/modules/log" | ||||
|  | @ -23,6 +22,7 @@ import ( | |||
| 	"code.gitea.io/gitea/modules/storage" | ||||
| 	"code.gitea.io/gitea/modules/templates" | ||||
| 	"code.gitea.io/gitea/modules/web/middleware" | ||||
| 	"code.gitea.io/gitea/services/auth" | ||||
| 
 | ||||
| 	"gitea.com/go-chi/session" | ||||
| ) | ||||
|  | @ -158,7 +158,7 @@ func Recovery() func(next http.Handler) http.Handler { | |||
| 					} | ||||
| 					if user == nil { | ||||
| 						// Get user from session if logged in - do not attempt to sign-in
 | ||||
| 						user = sso.SessionUser(sessionStore) | ||||
| 						user = auth.SessionUser(sessionStore) | ||||
| 					} | ||||
| 					if user != nil { | ||||
| 						store["IsSigned"] = true | ||||
|  |  | |||
|  | @ -13,13 +13,13 @@ import ( | |||
| 	"strings" | ||||
| 
 | ||||
| 	"code.gitea.io/gitea/models" | ||||
| 	"code.gitea.io/gitea/modules/auth/sso" | ||||
| 	"code.gitea.io/gitea/modules/base" | ||||
| 	"code.gitea.io/gitea/modules/context" | ||||
| 	"code.gitea.io/gitea/modules/log" | ||||
| 	"code.gitea.io/gitea/modules/setting" | ||||
| 	"code.gitea.io/gitea/modules/timeutil" | ||||
| 	"code.gitea.io/gitea/modules/web" | ||||
| 	"code.gitea.io/gitea/services/auth" | ||||
| 	"code.gitea.io/gitea/services/forms" | ||||
| 
 | ||||
| 	"gitea.com/go-chi/binding" | ||||
|  | @ -228,7 +228,7 @@ func InfoOAuth(ctx *context.Context) { | |||
| 		ctx.HandleText(http.StatusUnauthorized, "no valid auth token authorization") | ||||
| 		return | ||||
| 	} | ||||
| 	uid := sso.CheckOAuthAccessToken(auths[1]) | ||||
| 	uid := auth.CheckOAuthAccessToken(auths[1]) | ||||
| 	if uid == 0 { | ||||
| 		handleBearerTokenError(ctx, BearerTokenError{ | ||||
| 			ErrorCode:        BearerTokenErrorCodeInvalidToken, | ||||
|  |  | |||
|  | @ -31,6 +31,7 @@ import ( | |||
| 	"code.gitea.io/gitea/routers/web/repo" | ||||
| 	"code.gitea.io/gitea/routers/web/user" | ||||
| 	userSetting "code.gitea.io/gitea/routers/web/user/setting" | ||||
| 	"code.gitea.io/gitea/services/auth" | ||||
| 	"code.gitea.io/gitea/services/forms" | ||||
| 	"code.gitea.io/gitea/services/lfs" | ||||
| 	"code.gitea.io/gitea/services/mailer" | ||||
|  | @ -149,6 +150,9 @@ func Routes() *web.Route { | |||
| 	// Removed: toolbox.Toolboxer middleware will provide debug informations which seems unnecessary
 | ||||
| 	common = append(common, context.Contexter()) | ||||
| 
 | ||||
| 	// Get user from session if logged in.
 | ||||
| 	common = append(common, context.Auth(auth.NewGroup(auth.Methods()...))) | ||||
| 
 | ||||
| 	// GetHead allows a HEAD request redirect to GET if HEAD method is not defined for that route
 | ||||
| 	common = append(common, middleware.GetHead) | ||||
| 
 | ||||
|  |  | |||
|  | @ -3,7 +3,7 @@ | |||
| // Use of this source code is governed by a MIT-style
 | ||||
| // license that can be found in the LICENSE file.
 | ||||
| 
 | ||||
| package sso | ||||
| package auth | ||||
| 
 | ||||
| import ( | ||||
| 	"fmt" | ||||
|  | @ -18,7 +18,7 @@ import ( | |||
| 	"code.gitea.io/gitea/modules/web/middleware" | ||||
| ) | ||||
| 
 | ||||
| // ssoMethods contains the list of SSO authentication plugins in the order they are expected to be
 | ||||
| // authMethods contains the list of authentication plugins in the order they are expected to be
 | ||||
| // executed.
 | ||||
| //
 | ||||
| // The OAuth2 plugin is expected to be executed first, as it must ignore the user id stored
 | ||||
|  | @ -27,11 +27,10 @@ import ( | |||
| //
 | ||||
| // The Session plugin is expected to be executed second, in order to skip authentication
 | ||||
| // for users that have already signed in.
 | ||||
| var ssoMethods = []SingleSignOn{ | ||||
| var authMethods = []Auth{ | ||||
| 	&OAuth2{}, | ||||
| 	&Basic{}, | ||||
| 	&Session{}, | ||||
| 	&ReverseProxy{}, | ||||
| } | ||||
| 
 | ||||
| // The purpose of the following three function variables is to let the linter know that
 | ||||
|  | @ -40,65 +39,42 @@ var ( | |||
| 	_ = handleSignIn | ||||
| ) | ||||
| 
 | ||||
| // Methods returns the instances of all registered SSO methods
 | ||||
| func Methods() []SingleSignOn { | ||||
| 	return ssoMethods | ||||
| // Methods returns the instances of all registered methods
 | ||||
| func Methods() []Auth { | ||||
| 	return authMethods | ||||
| } | ||||
| 
 | ||||
| // Register adds the specified instance to the list of available SSO methods
 | ||||
| func Register(method SingleSignOn) { | ||||
| 	ssoMethods = append(ssoMethods, method) | ||||
| // Register adds the specified instance to the list of available methods
 | ||||
| func Register(method Auth) { | ||||
| 	authMethods = append(authMethods, method) | ||||
| } | ||||
| 
 | ||||
| // Init should be called exactly once when the application starts to allow SSO plugins
 | ||||
| // Init should be called exactly once when the application starts to allow plugins
 | ||||
| // to allocate necessary resources
 | ||||
| func Init() { | ||||
| 	if setting.Service.EnableReverseProxyAuth { | ||||
| 		Register(&ReverseProxy{}) | ||||
| 	} | ||||
| 	specialInit() | ||||
| 	for _, method := range Methods() { | ||||
| 		err := method.Init() | ||||
| 		if err != nil { | ||||
| 			log.Error("Could not initialize '%s' SSO method, error: %s", reflect.TypeOf(method).String(), err) | ||||
| 			log.Error("Could not initialize '%s' auth method, error: %s", reflect.TypeOf(method).String(), err) | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| // Free should be called exactly once when the application is terminating to allow SSO plugins
 | ||||
| // Free should be called exactly once when the application is terminating to allow Auth plugins
 | ||||
| // to release necessary resources
 | ||||
| func Free() { | ||||
| 	for _, method := range Methods() { | ||||
| 		err := method.Free() | ||||
| 		if err != nil { | ||||
| 			log.Error("Could not free '%s' SSO method, error: %s", reflect.TypeOf(method).String(), err) | ||||
| 			log.Error("Could not free '%s' auth method, error: %s", reflect.TypeOf(method).String(), err) | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| // SessionUser returns the user object corresponding to the "uid" session variable.
 | ||||
| func SessionUser(sess SessionStore) *models.User { | ||||
| 	// Get user ID
 | ||||
| 	uid := sess.Get("uid") | ||||
| 	if uid == nil { | ||||
| 		return nil | ||||
| 	} | ||||
| 	log.Trace("Session Authorization: Found user[%d]", uid) | ||||
| 
 | ||||
| 	id, ok := uid.(int64) | ||||
| 	if !ok { | ||||
| 		return nil | ||||
| 	} | ||||
| 
 | ||||
| 	// Get user object
 | ||||
| 	user, err := models.GetUserByID(id) | ||||
| 	if err != nil { | ||||
| 		if !models.IsErrUserNotExist(err) { | ||||
| 			log.Error("GetUserById: %v", err) | ||||
| 		} | ||||
| 		return nil | ||||
| 	} | ||||
| 
 | ||||
| 	log.Trace("Session Authorization: Logged in user %-v", user) | ||||
| 	return user | ||||
| } | ||||
| 
 | ||||
| // isAttachmentDownload check if request is a file download (GET) with URL to an attachment
 | ||||
| func isAttachmentDownload(req *http.Request) bool { | ||||
| 	return strings.HasPrefix(req.URL.Path, "/attachments/") && req.Method == "GET" | ||||
|  | @ -3,7 +3,7 @@ | |||
| // Use of this source code is governed by a MIT-style
 | ||||
| // license that can be found in the LICENSE file.
 | ||||
| 
 | ||||
| package sso | ||||
| package auth | ||||
| 
 | ||||
| import ( | ||||
| 	"net/http" | ||||
|  | @ -3,7 +3,7 @@ | |||
| // Use of this source code is governed by a MIT-style
 | ||||
| // license that can be found in the LICENSE file.
 | ||||
| 
 | ||||
| package sso | ||||
| package auth | ||||
| 
 | ||||
| import ( | ||||
| 	"net/http" | ||||
|  | @ -19,15 +19,20 @@ import ( | |||
| 
 | ||||
| // Ensure the struct implements the interface.
 | ||||
| var ( | ||||
| 	_ SingleSignOn = &Basic{} | ||||
| 	_ Auth = &Basic{} | ||||
| ) | ||||
| 
 | ||||
| // Basic implements the SingleSignOn interface and authenticates requests (API requests
 | ||||
| // Basic implements the Auth interface and authenticates requests (API requests
 | ||||
| // only) by looking for Basic authentication data or "x-oauth-basic" token in the "Authorization"
 | ||||
| // header.
 | ||||
| type Basic struct { | ||||
| } | ||||
| 
 | ||||
| // Name represents the name of auth method
 | ||||
| func (b *Basic) Name() string { | ||||
| 	return "basic" | ||||
| } | ||||
| 
 | ||||
| // Init does nothing as the Basic implementation does not need to allocate any resources
 | ||||
| func (b *Basic) Init() error { | ||||
| 	return nil | ||||
|  | @ -38,20 +43,13 @@ func (b *Basic) Free() error { | |||
| 	return nil | ||||
| } | ||||
| 
 | ||||
| // IsEnabled returns true as this plugin is enabled by default and its not possible to disable
 | ||||
| // it from settings.
 | ||||
| func (b *Basic) IsEnabled() bool { | ||||
| 	return true | ||||
| } | ||||
| 
 | ||||
| // VerifyAuthData extracts and validates Basic data (username and password/token) from the
 | ||||
| // Verify extracts and validates Basic data (username and password/token) from the
 | ||||
| // "Authorization" header of the request and returns the corresponding user object for that
 | ||||
| // name/token on successful validation.
 | ||||
| // Returns nil if header is empty or validation fails.
 | ||||
| func (b *Basic) VerifyAuthData(req *http.Request, w http.ResponseWriter, store DataStore, sess SessionStore) *models.User { | ||||
| 
 | ||||
| func (b *Basic) Verify(req *http.Request, w http.ResponseWriter, store DataStore, sess SessionStore) *models.User { | ||||
| 	// Basic authentication should only fire on API, Download or on Git or LFSPaths
 | ||||
| 	if middleware.IsInternalPath(req) || !middleware.IsAPIPath(req) && !isAttachmentDownload(req) && !isGitRawOrLFSPath(req) { | ||||
| 	if !middleware.IsAPIPath(req) && !isAttachmentDownload(req) && !isGitRawOrLFSPath(req) { | ||||
| 		return nil | ||||
| 	} | ||||
| 
 | ||||
							
								
								
									
										73
									
								
								services/auth/group.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										73
									
								
								services/auth/group.go
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,73 @@ | |||
| // Copyright 2021 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.
 | ||||
| 
 | ||||
| package auth | ||||
| 
 | ||||
| import ( | ||||
| 	"net/http" | ||||
| 
 | ||||
| 	"code.gitea.io/gitea/models" | ||||
| ) | ||||
| 
 | ||||
| // Ensure the struct implements the interface.
 | ||||
| var ( | ||||
| 	_ Auth = &Group{} | ||||
| ) | ||||
| 
 | ||||
| // Group implements the Auth interface with serval Auth.
 | ||||
| type Group struct { | ||||
| 	methods []Auth | ||||
| } | ||||
| 
 | ||||
| // NewGroup creates a new auth group
 | ||||
| func NewGroup(methods ...Auth) *Group { | ||||
| 	return &Group{ | ||||
| 		methods: methods, | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| // Name represents the name of auth method
 | ||||
| func (b *Group) Name() string { | ||||
| 	return "group" | ||||
| } | ||||
| 
 | ||||
| // Init does nothing as the Basic implementation does not need to allocate any resources
 | ||||
| func (b *Group) Init() error { | ||||
| 	for _, m := range b.methods { | ||||
| 		if err := m.Init(); err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
| 
 | ||||
| // Free does nothing as the Basic implementation does not have to release any resources
 | ||||
| func (b *Group) Free() error { | ||||
| 	for _, m := range b.methods { | ||||
| 		if err := m.Free(); err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
| 
 | ||||
| // Verify extracts and validates
 | ||||
| func (b *Group) Verify(req *http.Request, w http.ResponseWriter, store DataStore, sess SessionStore) *models.User { | ||||
| 	if !models.HasEngine { | ||||
| 		return nil | ||||
| 	} | ||||
| 
 | ||||
| 	// Try to sign in with each of the enabled plugins
 | ||||
| 	for _, ssoMethod := range b.methods { | ||||
| 		user := ssoMethod.Verify(req, w, store, sess) | ||||
| 		if user != nil { | ||||
| 			if store.GetData()["AuthedMethod"] == nil { | ||||
| 				store.GetData()["AuthedMethod"] = ssoMethod.Name() | ||||
| 			} | ||||
| 			return user | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	return nil | ||||
| } | ||||
|  | @ -2,7 +2,7 @@ | |||
| // Use of this source code is governed by a MIT-style
 | ||||
| // license that can be found in the LICENSE file.
 | ||||
| 
 | ||||
| package sso | ||||
| package auth | ||||
| 
 | ||||
| import ( | ||||
| 	"net/http" | ||||
|  | @ -18,8 +18,10 @@ type DataStore middleware.DataStore | |||
| // SessionStore represents a session store
 | ||||
| type SessionStore session.Store | ||||
| 
 | ||||
| // SingleSignOn represents a SSO authentication method (plugin) for HTTP requests.
 | ||||
| type SingleSignOn interface { | ||||
| // Auth represents an authentication method (plugin) for HTTP requests.
 | ||||
| type Auth interface { | ||||
| 	Name() string | ||||
| 
 | ||||
| 	// Init should be called exactly once before using any of the other methods,
 | ||||
| 	// in order to allow the plugin to allocate necessary resources
 | ||||
| 	Init() error | ||||
|  | @ -28,13 +30,10 @@ type SingleSignOn interface { | |||
| 	// give chance to the plugin to free any allocated resources
 | ||||
| 	Free() error | ||||
| 
 | ||||
| 	// IsEnabled checks if the current SSO method has been enabled in settings.
 | ||||
| 	IsEnabled() bool | ||||
| 
 | ||||
| 	// VerifyAuthData tries to verify the SSO authentication data contained in the request.
 | ||||
| 	// Verify tries to verify the authentication data contained in the request.
 | ||||
| 	// If verification is successful returns either an existing user object (with id > 0)
 | ||||
| 	// or a new user object (with id = 0) populated with the information that was found
 | ||||
| 	// in the authentication data (username or email).
 | ||||
| 	// Returns nil if verification fails.
 | ||||
| 	VerifyAuthData(http *http.Request, w http.ResponseWriter, store DataStore, sess SessionStore) *models.User | ||||
| 	Verify(http *http.Request, w http.ResponseWriter, store DataStore, sess SessionStore) *models.User | ||||
| } | ||||
|  | @ -3,7 +3,7 @@ | |||
| // Use of this source code is governed by a MIT-style
 | ||||
| // license that can be found in the LICENSE file.
 | ||||
| 
 | ||||
| package sso | ||||
| package auth | ||||
| 
 | ||||
| import ( | ||||
| 	"net/http" | ||||
|  | @ -18,7 +18,7 @@ import ( | |||
| 
 | ||||
| // Ensure the struct implements the interface.
 | ||||
| var ( | ||||
| 	_ SingleSignOn = &OAuth2{} | ||||
| 	_ Auth = &OAuth2{} | ||||
| ) | ||||
| 
 | ||||
| // CheckOAuthAccessToken returns uid of user from oauth token
 | ||||
|  | @ -45,7 +45,7 @@ func CheckOAuthAccessToken(accessToken string) int64 { | |||
| 	return grant.UserID | ||||
| } | ||||
| 
 | ||||
| // OAuth2 implements the SingleSignOn interface and authenticates requests
 | ||||
| // OAuth2 implements the Auth interface and authenticates requests
 | ||||
| // (API requests only) by looking for an OAuth token in query parameters or the
 | ||||
| // "Authorization" header.
 | ||||
| type OAuth2 struct { | ||||
|  | @ -56,6 +56,11 @@ func (o *OAuth2) Init() error { | |||
| 	return nil | ||||
| } | ||||
| 
 | ||||
| // Name represents the name of auth method
 | ||||
| func (o *OAuth2) Name() string { | ||||
| 	return "oauth2" | ||||
| } | ||||
| 
 | ||||
| // Free does nothing as the OAuth2 implementation does not have to release any resources
 | ||||
| func (o *OAuth2) Free() error { | ||||
| 	return nil | ||||
|  | @ -107,22 +112,16 @@ func (o *OAuth2) userIDFromToken(req *http.Request, store DataStore) int64 { | |||
| 	return t.UID | ||||
| } | ||||
| 
 | ||||
| // IsEnabled returns true as this plugin is enabled by default and its not possible
 | ||||
| // to disable it from settings.
 | ||||
| func (o *OAuth2) IsEnabled() bool { | ||||
| 	return true | ||||
| } | ||||
| 
 | ||||
| // VerifyAuthData extracts the user ID from the OAuth token in the query parameters
 | ||||
| // Verify extracts the user ID from the OAuth token in the query parameters
 | ||||
| // or the "Authorization" header and returns the corresponding user object for that ID.
 | ||||
| // If verification is successful returns an existing user object.
 | ||||
| // Returns nil if verification fails.
 | ||||
| func (o *OAuth2) VerifyAuthData(req *http.Request, w http.ResponseWriter, store DataStore, sess SessionStore) *models.User { | ||||
| func (o *OAuth2) Verify(req *http.Request, w http.ResponseWriter, store DataStore, sess SessionStore) *models.User { | ||||
| 	if !models.HasEngine { | ||||
| 		return nil | ||||
| 	} | ||||
| 
 | ||||
| 	if middleware.IsInternalPath(req) || !middleware.IsAPIPath(req) && !isAttachmentDownload(req) { | ||||
| 	if !middleware.IsAPIPath(req) && !isAttachmentDownload(req) { | ||||
| 		return nil | ||||
| 	} | ||||
| 
 | ||||
							
								
								
									
										9
									
								
								services/auth/placeholder.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								services/auth/placeholder.go
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,9 @@ | |||
| // Copyright 2021 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.
 | ||||
| 
 | ||||
| // +build !windows
 | ||||
| 
 | ||||
| package auth | ||||
| 
 | ||||
| func specialInit() {} | ||||
|  | @ -3,7 +3,7 @@ | |||
| // Use of this source code is governed by a MIT-style
 | ||||
| // license that can be found in the LICENSE file.
 | ||||
| 
 | ||||
| package sso | ||||
| package auth | ||||
| 
 | ||||
| import ( | ||||
| 	"net/http" | ||||
|  | @ -19,10 +19,10 @@ import ( | |||
| 
 | ||||
| // Ensure the struct implements the interface.
 | ||||
| var ( | ||||
| 	_ SingleSignOn = &ReverseProxy{} | ||||
| 	_ Auth = &ReverseProxy{} | ||||
| ) | ||||
| 
 | ||||
| // ReverseProxy implements the SingleSignOn interface, but actually relies on
 | ||||
| // ReverseProxy implements the Auth interface, but actually relies on
 | ||||
| // a reverse proxy for authentication of users.
 | ||||
| // On successful authentication the proxy is expected to populate the username in the
 | ||||
| // "setting.ReverseProxyAuthUser" header. Optionally it can also populate the email of the
 | ||||
|  | @ -39,6 +39,11 @@ func (r *ReverseProxy) getUserName(req *http.Request) string { | |||
| 	return webAuthUser | ||||
| } | ||||
| 
 | ||||
| // Name represents the name of auth method
 | ||||
| func (r *ReverseProxy) Name() string { | ||||
| 	return "reverse_proxy" | ||||
| } | ||||
| 
 | ||||
| // Init does nothing as the ReverseProxy implementation does not need initialization
 | ||||
| func (r *ReverseProxy) Init() error { | ||||
| 	return nil | ||||
|  | @ -49,19 +54,14 @@ func (r *ReverseProxy) Free() error { | |||
| 	return nil | ||||
| } | ||||
| 
 | ||||
| // IsEnabled checks if EnableReverseProxyAuth setting is true
 | ||||
| func (r *ReverseProxy) IsEnabled() bool { | ||||
| 	return setting.Service.EnableReverseProxyAuth | ||||
| } | ||||
| 
 | ||||
| // VerifyAuthData extracts the username from the "setting.ReverseProxyAuthUser" header
 | ||||
| // Verify extracts the username from the "setting.ReverseProxyAuthUser" header
 | ||||
| // of the request and returns the corresponding user object for that name.
 | ||||
| // Verification of header data is not performed as it should have already been done by
 | ||||
| // the revese proxy.
 | ||||
| // If a username is available in the "setting.ReverseProxyAuthUser" header an existing
 | ||||
| // user object is returned (populated with username or email found in header).
 | ||||
| // Returns nil if header is empty.
 | ||||
| func (r *ReverseProxy) VerifyAuthData(req *http.Request, w http.ResponseWriter, store DataStore, sess SessionStore) *models.User { | ||||
| func (r *ReverseProxy) Verify(req *http.Request, w http.ResponseWriter, store DataStore, sess SessionStore) *models.User { | ||||
| 	username := r.getUserName(req) | ||||
| 	if len(username) == 0 { | ||||
| 		return nil | ||||
							
								
								
									
										75
									
								
								services/auth/session.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										75
									
								
								services/auth/session.go
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,75 @@ | |||
| // 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.
 | ||||
| 
 | ||||
| package auth | ||||
| 
 | ||||
| import ( | ||||
| 	"net/http" | ||||
| 
 | ||||
| 	"code.gitea.io/gitea/models" | ||||
| 	"code.gitea.io/gitea/modules/log" | ||||
| ) | ||||
| 
 | ||||
| // Ensure the struct implements the interface.
 | ||||
| var ( | ||||
| 	_ Auth = &Session{} | ||||
| ) | ||||
| 
 | ||||
| // Session checks if there is a user uid stored in the session and returns the user
 | ||||
| // object for that uid.
 | ||||
| type Session struct { | ||||
| } | ||||
| 
 | ||||
| // Init does nothing as the Session implementation does not need to allocate any resources
 | ||||
| func (s *Session) Init() error { | ||||
| 	return nil | ||||
| } | ||||
| 
 | ||||
| // Name represents the name of auth method
 | ||||
| func (s *Session) Name() string { | ||||
| 	return "session" | ||||
| } | ||||
| 
 | ||||
| // Free does nothing as the Session implementation does not have to release any resources
 | ||||
| func (s *Session) Free() error { | ||||
| 	return nil | ||||
| } | ||||
| 
 | ||||
| // Verify checks if there is a user uid stored in the session and returns the user
 | ||||
| // object for that uid.
 | ||||
| // Returns nil if there is no user uid stored in the session.
 | ||||
| func (s *Session) Verify(req *http.Request, w http.ResponseWriter, store DataStore, sess SessionStore) *models.User { | ||||
| 	user := SessionUser(sess) | ||||
| 	if user != nil { | ||||
| 		return user | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
| 
 | ||||
| // SessionUser returns the user object corresponding to the "uid" session variable.
 | ||||
| func SessionUser(sess SessionStore) *models.User { | ||||
| 	// Get user ID
 | ||||
| 	uid := sess.Get("uid") | ||||
| 	if uid == nil { | ||||
| 		return nil | ||||
| 	} | ||||
| 	log.Trace("Session Authorization: Found user[%d]", uid) | ||||
| 
 | ||||
| 	id, ok := uid.(int64) | ||||
| 	if !ok { | ||||
| 		return nil | ||||
| 	} | ||||
| 
 | ||||
| 	// Get user object
 | ||||
| 	user, err := models.GetUserByID(id) | ||||
| 	if err != nil { | ||||
| 		if !models.IsErrUserNotExist(err) { | ||||
| 			log.Error("GetUserById: %v", err) | ||||
| 		} | ||||
| 		return nil | ||||
| 	} | ||||
| 
 | ||||
| 	log.Trace("Session Authorization: Logged in user %-v", user) | ||||
| 	return user | ||||
| } | ||||
|  | @ -2,7 +2,7 @@ | |||
| // Use of this source code is governed by a MIT-style
 | ||||
| // license that can be found in the LICENSE file.
 | ||||
| 
 | ||||
| package sso | ||||
| package auth | ||||
| 
 | ||||
| import ( | ||||
| 	"errors" | ||||
|  | @ -32,7 +32,7 @@ var ( | |||
| 	sspiAuth *websspi.Authenticator | ||||
| 
 | ||||
| 	// Ensure the struct implements the interface.
 | ||||
| 	_ SingleSignOn = &SSPI{} | ||||
| 	_ Auth = &SSPI{} | ||||
| ) | ||||
| 
 | ||||
| // SSPI implements the SingleSignOn interface and authenticates requests
 | ||||
|  | @ -62,21 +62,21 @@ func (s *SSPI) Init() error { | |||
| 	return nil | ||||
| } | ||||
| 
 | ||||
| // Name represents the name of auth method
 | ||||
| func (s *SSPI) Name() string { | ||||
| 	return "sspi" | ||||
| } | ||||
| 
 | ||||
| // Free releases resources used by the global websspi.Authenticator object
 | ||||
| func (s *SSPI) Free() error { | ||||
| 	return sspiAuth.Free() | ||||
| } | ||||
| 
 | ||||
| // IsEnabled checks if there is an active SSPI authentication source
 | ||||
| func (s *SSPI) IsEnabled() bool { | ||||
| 	return models.IsSSPIEnabled() | ||||
| } | ||||
| 
 | ||||
| // VerifyAuthData uses SSPI (Windows implementation of SPNEGO) to authenticate the request.
 | ||||
| // Verify uses SSPI (Windows implementation of SPNEGO) to authenticate the request.
 | ||||
| // If authentication is successful, returs the corresponding user object.
 | ||||
| // If negotiation should continue or authentication fails, immediately returns a 401 HTTP
 | ||||
| // response code, as required by the SPNEGO protocol.
 | ||||
| func (s *SSPI) VerifyAuthData(req *http.Request, w http.ResponseWriter, store DataStore, sess SessionStore) *models.User { | ||||
| func (s *SSPI) Verify(req *http.Request, w http.ResponseWriter, store DataStore, sess SessionStore) *models.User { | ||||
| 	if !s.shouldAuthenticate(req) { | ||||
| 		return nil | ||||
| 	} | ||||
|  | @ -169,8 +169,6 @@ func (s *SSPI) shouldAuthenticate(req *http.Request) (shouldAuth bool) { | |||
| 		} else if req.FormValue("auth_with_sspi") == "1" { | ||||
| 			shouldAuth = true | ||||
| 		} | ||||
| 	} else if middleware.IsInternalPath(req) { | ||||
| 		shouldAuth = false | ||||
| 	} else if middleware.IsAPIPath(req) || isAttachmentDownload(req) { | ||||
| 		shouldAuth = true | ||||
| 	} | ||||
|  | @ -237,10 +235,12 @@ func sanitizeUsername(username string, cfg *models.SSPIConfig) string { | |||
| 	return username | ||||
| } | ||||
| 
 | ||||
| // init registers the SSPI auth method as the last method in the list.
 | ||||
| // specialInit registers the SSPI auth method as the last method in the list.
 | ||||
| // The SSPI plugin is expected to be executed last, as it returns 401 status code if negotiation
 | ||||
| // fails (or if negotiation should continue), which would prevent other authentication methods
 | ||||
| // to execute at all.
 | ||||
| func init() { | ||||
| func specialInit() { | ||||
| 	if models.IsSSPIEnabled() { | ||||
| 		Register(&SSPI{}) | ||||
| 	} | ||||
| } | ||||
		Loading…
	
		Reference in a new issue