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" | 	"strings" | ||||||
| 
 | 
 | ||||||
| 	"code.gitea.io/gitea/models" | 	"code.gitea.io/gitea/models" | ||||||
| 	"code.gitea.io/gitea/modules/auth/sso" |  | ||||||
| 	"code.gitea.io/gitea/modules/git" | 	"code.gitea.io/gitea/modules/git" | ||||||
| 	"code.gitea.io/gitea/modules/log" | 	"code.gitea.io/gitea/modules/log" | ||||||
| 	"code.gitea.io/gitea/modules/setting" | 	"code.gitea.io/gitea/modules/setting" | ||||||
| 	"code.gitea.io/gitea/modules/web/middleware" | 	"code.gitea.io/gitea/modules/web/middleware" | ||||||
|  | 	"code.gitea.io/gitea/services/auth" | ||||||
| 
 | 
 | ||||||
| 	"gitea.com/go-chi/session" | 	"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
 | // APIContexter returns apicontext as middleware
 | ||||||
| func APIContexter() func(http.Handler) http.Handler { | func APIContexter() func(http.Handler) http.Handler { | ||||||
| 	var csrfOpts = getCsrfOpts() | 	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.Resp.Header().Set(`X-Frame-Options`, `SAMEORIGIN`) | ||||||
| 
 | 
 | ||||||
| 			ctx.Data["CsrfToken"] = html.EscapeString(ctx.csrf.GetToken()) | 			ctx.Data["CsrfToken"] = html.EscapeString(ctx.csrf.GetToken()) | ||||||
|  |  | ||||||
|  | @ -21,7 +21,6 @@ import ( | ||||||
| 	"time" | 	"time" | ||||||
| 
 | 
 | ||||||
| 	"code.gitea.io/gitea/models" | 	"code.gitea.io/gitea/models" | ||||||
| 	"code.gitea.io/gitea/modules/auth/sso" |  | ||||||
| 	"code.gitea.io/gitea/modules/base" | 	"code.gitea.io/gitea/modules/base" | ||||||
| 	mc "code.gitea.io/gitea/modules/cache" | 	mc "code.gitea.io/gitea/modules/cache" | ||||||
| 	"code.gitea.io/gitea/modules/log" | 	"code.gitea.io/gitea/modules/log" | ||||||
|  | @ -29,6 +28,7 @@ import ( | ||||||
| 	"code.gitea.io/gitea/modules/templates" | 	"code.gitea.io/gitea/modules/templates" | ||||||
| 	"code.gitea.io/gitea/modules/translation" | 	"code.gitea.io/gitea/modules/translation" | ||||||
| 	"code.gitea.io/gitea/modules/web/middleware" | 	"code.gitea.io/gitea/modules/web/middleware" | ||||||
|  | 	"code.gitea.io/gitea/services/auth" | ||||||
| 
 | 
 | ||||||
| 	"gitea.com/go-chi/cache" | 	"gitea.com/go-chi/cache" | ||||||
| 	"gitea.com/go-chi/session" | 	"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.
 | // Contexter initializes a classic context for a request.
 | ||||||
| func Contexter() func(next http.Handler) http.Handler { | func Contexter() func(next http.Handler) http.Handler { | ||||||
| 	var rnd = templates.HTMLRenderer() | 	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.Resp.Header().Set(`X-Frame-Options`, `SAMEORIGIN`) | ||||||
| 
 | 
 | ||||||
| 			ctx.Data["CsrfToken"] = html.EscapeString(ctx.csrf.GetToken()) | 			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/settings" | ||||||
| 	_ "code.gitea.io/gitea/routers/api/v1/swagger" // for swagger generation
 | 	_ "code.gitea.io/gitea/routers/api/v1/swagger" // for swagger generation
 | ||||||
| 	"code.gitea.io/gitea/routers/api/v1/user" | 	"code.gitea.io/gitea/routers/api/v1/user" | ||||||
|  | 	"code.gitea.io/gitea/services/auth" | ||||||
| 	"code.gitea.io/gitea/services/forms" | 	"code.gitea.io/gitea/services/forms" | ||||||
| 
 | 
 | ||||||
| 	"gitea.com/go-chi/binding" | 	"gitea.com/go-chi/binding" | ||||||
|  | @ -573,6 +574,9 @@ func Routes() *web.Route { | ||||||
| 	} | 	} | ||||||
| 	m.Use(context.APIContexter()) | 	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{ | 	m.Use(context.ToggleAPI(&context.ToggleOptions{ | ||||||
| 		SignInRequired: setting.Service.RequireSignInView, | 		SignInRequired: setting.Service.RequireSignInView, | ||||||
| 	})) | 	})) | ||||||
|  |  | ||||||
|  | @ -9,7 +9,6 @@ import ( | ||||||
| 	"strings" | 	"strings" | ||||||
| 
 | 
 | ||||||
| 	"code.gitea.io/gitea/models" | 	"code.gitea.io/gitea/models" | ||||||
| 	"code.gitea.io/gitea/modules/auth/sso" |  | ||||||
| 	"code.gitea.io/gitea/modules/cache" | 	"code.gitea.io/gitea/modules/cache" | ||||||
| 	"code.gitea.io/gitea/modules/cron" | 	"code.gitea.io/gitea/modules/cron" | ||||||
| 	"code.gitea.io/gitea/modules/eventsource" | 	"code.gitea.io/gitea/modules/eventsource" | ||||||
|  | @ -34,6 +33,7 @@ import ( | ||||||
| 	"code.gitea.io/gitea/routers/common" | 	"code.gitea.io/gitea/routers/common" | ||||||
| 	"code.gitea.io/gitea/routers/private" | 	"code.gitea.io/gitea/routers/private" | ||||||
| 	web_routers "code.gitea.io/gitea/routers/web" | 	web_routers "code.gitea.io/gitea/routers/web" | ||||||
|  | 	"code.gitea.io/gitea/services/auth" | ||||||
| 	"code.gitea.io/gitea/services/mailer" | 	"code.gitea.io/gitea/services/mailer" | ||||||
| 	mirror_service "code.gitea.io/gitea/services/mirror" | 	mirror_service "code.gitea.io/gitea/services/mirror" | ||||||
| 	pull_service "code.gitea.io/gitea/services/pull" | 	pull_service "code.gitea.io/gitea/services/pull" | ||||||
|  | @ -134,7 +134,7 @@ func GlobalInit(ctx context.Context) { | ||||||
| 	} else { | 	} else { | ||||||
| 		ssh.Unused() | 		ssh.Unused() | ||||||
| 	} | 	} | ||||||
| 	sso.Init() | 	auth.Init() | ||||||
| 
 | 
 | ||||||
| 	svg.Init() | 	svg.Init() | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -15,7 +15,6 @@ import ( | ||||||
| 	"strings" | 	"strings" | ||||||
| 
 | 
 | ||||||
| 	"code.gitea.io/gitea/models" | 	"code.gitea.io/gitea/models" | ||||||
| 	"code.gitea.io/gitea/modules/auth/sso" |  | ||||||
| 	"code.gitea.io/gitea/modules/context" | 	"code.gitea.io/gitea/modules/context" | ||||||
| 	"code.gitea.io/gitea/modules/httpcache" | 	"code.gitea.io/gitea/modules/httpcache" | ||||||
| 	"code.gitea.io/gitea/modules/log" | 	"code.gitea.io/gitea/modules/log" | ||||||
|  | @ -23,6 +22,7 @@ import ( | ||||||
| 	"code.gitea.io/gitea/modules/storage" | 	"code.gitea.io/gitea/modules/storage" | ||||||
| 	"code.gitea.io/gitea/modules/templates" | 	"code.gitea.io/gitea/modules/templates" | ||||||
| 	"code.gitea.io/gitea/modules/web/middleware" | 	"code.gitea.io/gitea/modules/web/middleware" | ||||||
|  | 	"code.gitea.io/gitea/services/auth" | ||||||
| 
 | 
 | ||||||
| 	"gitea.com/go-chi/session" | 	"gitea.com/go-chi/session" | ||||||
| ) | ) | ||||||
|  | @ -158,7 +158,7 @@ func Recovery() func(next http.Handler) http.Handler { | ||||||
| 					} | 					} | ||||||
| 					if user == nil { | 					if user == nil { | ||||||
| 						// Get user from session if logged in - do not attempt to sign-in
 | 						// Get user from session if logged in - do not attempt to sign-in
 | ||||||
| 						user = sso.SessionUser(sessionStore) | 						user = auth.SessionUser(sessionStore) | ||||||
| 					} | 					} | ||||||
| 					if user != nil { | 					if user != nil { | ||||||
| 						store["IsSigned"] = true | 						store["IsSigned"] = true | ||||||
|  |  | ||||||
|  | @ -13,13 +13,13 @@ import ( | ||||||
| 	"strings" | 	"strings" | ||||||
| 
 | 
 | ||||||
| 	"code.gitea.io/gitea/models" | 	"code.gitea.io/gitea/models" | ||||||
| 	"code.gitea.io/gitea/modules/auth/sso" |  | ||||||
| 	"code.gitea.io/gitea/modules/base" | 	"code.gitea.io/gitea/modules/base" | ||||||
| 	"code.gitea.io/gitea/modules/context" | 	"code.gitea.io/gitea/modules/context" | ||||||
| 	"code.gitea.io/gitea/modules/log" | 	"code.gitea.io/gitea/modules/log" | ||||||
| 	"code.gitea.io/gitea/modules/setting" | 	"code.gitea.io/gitea/modules/setting" | ||||||
| 	"code.gitea.io/gitea/modules/timeutil" | 	"code.gitea.io/gitea/modules/timeutil" | ||||||
| 	"code.gitea.io/gitea/modules/web" | 	"code.gitea.io/gitea/modules/web" | ||||||
|  | 	"code.gitea.io/gitea/services/auth" | ||||||
| 	"code.gitea.io/gitea/services/forms" | 	"code.gitea.io/gitea/services/forms" | ||||||
| 
 | 
 | ||||||
| 	"gitea.com/go-chi/binding" | 	"gitea.com/go-chi/binding" | ||||||
|  | @ -228,7 +228,7 @@ func InfoOAuth(ctx *context.Context) { | ||||||
| 		ctx.HandleText(http.StatusUnauthorized, "no valid auth token authorization") | 		ctx.HandleText(http.StatusUnauthorized, "no valid auth token authorization") | ||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
| 	uid := sso.CheckOAuthAccessToken(auths[1]) | 	uid := auth.CheckOAuthAccessToken(auths[1]) | ||||||
| 	if uid == 0 { | 	if uid == 0 { | ||||||
| 		handleBearerTokenError(ctx, BearerTokenError{ | 		handleBearerTokenError(ctx, BearerTokenError{ | ||||||
| 			ErrorCode:        BearerTokenErrorCodeInvalidToken, | 			ErrorCode:        BearerTokenErrorCodeInvalidToken, | ||||||
|  |  | ||||||
|  | @ -31,6 +31,7 @@ import ( | ||||||
| 	"code.gitea.io/gitea/routers/web/repo" | 	"code.gitea.io/gitea/routers/web/repo" | ||||||
| 	"code.gitea.io/gitea/routers/web/user" | 	"code.gitea.io/gitea/routers/web/user" | ||||||
| 	userSetting "code.gitea.io/gitea/routers/web/user/setting" | 	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/forms" | ||||||
| 	"code.gitea.io/gitea/services/lfs" | 	"code.gitea.io/gitea/services/lfs" | ||||||
| 	"code.gitea.io/gitea/services/mailer" | 	"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
 | 	// Removed: toolbox.Toolboxer middleware will provide debug informations which seems unnecessary
 | ||||||
| 	common = append(common, context.Contexter()) | 	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
 | 	// GetHead allows a HEAD request redirect to GET if HEAD method is not defined for that route
 | ||||||
| 	common = append(common, middleware.GetHead) | 	common = append(common, middleware.GetHead) | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -3,7 +3,7 @@ | ||||||
| // Use of this source code is governed by a MIT-style
 | // Use of this source code is governed by a MIT-style
 | ||||||
| // license that can be found in the LICENSE file.
 | // license that can be found in the LICENSE file.
 | ||||||
| 
 | 
 | ||||||
| package sso | package auth | ||||||
| 
 | 
 | ||||||
| import ( | import ( | ||||||
| 	"fmt" | 	"fmt" | ||||||
|  | @ -18,7 +18,7 @@ import ( | ||||||
| 	"code.gitea.io/gitea/modules/web/middleware" | 	"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.
 | // executed.
 | ||||||
| //
 | //
 | ||||||
| // The OAuth2 plugin is expected to be executed first, as it must ignore the user id stored
 | // 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
 | // The Session plugin is expected to be executed second, in order to skip authentication
 | ||||||
| // for users that have already signed in.
 | // for users that have already signed in.
 | ||||||
| var ssoMethods = []SingleSignOn{ | var authMethods = []Auth{ | ||||||
| 	&OAuth2{}, | 	&OAuth2{}, | ||||||
| 	&Basic{}, | 	&Basic{}, | ||||||
| 	&Session{}, | 	&Session{}, | ||||||
| 	&ReverseProxy{}, |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // The purpose of the following three function variables is to let the linter know that
 | // The purpose of the following three function variables is to let the linter know that
 | ||||||
|  | @ -40,65 +39,42 @@ var ( | ||||||
| 	_ = handleSignIn | 	_ = handleSignIn | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| // Methods returns the instances of all registered SSO methods
 | // Methods returns the instances of all registered methods
 | ||||||
| func Methods() []SingleSignOn { | func Methods() []Auth { | ||||||
| 	return ssoMethods | 	return authMethods | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // Register adds the specified instance to the list of available SSO methods
 | // Register adds the specified instance to the list of available methods
 | ||||||
| func Register(method SingleSignOn) { | func Register(method Auth) { | ||||||
| 	ssoMethods = append(ssoMethods, method) | 	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
 | // to allocate necessary resources
 | ||||||
| func Init() { | func Init() { | ||||||
|  | 	if setting.Service.EnableReverseProxyAuth { | ||||||
|  | 		Register(&ReverseProxy{}) | ||||||
|  | 	} | ||||||
|  | 	specialInit() | ||||||
| 	for _, method := range Methods() { | 	for _, method := range Methods() { | ||||||
| 		err := method.Init() | 		err := method.Init() | ||||||
| 		if err != nil { | 		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
 | // to release necessary resources
 | ||||||
| func Free() { | func Free() { | ||||||
| 	for _, method := range Methods() { | 	for _, method := range Methods() { | ||||||
| 		err := method.Free() | 		err := method.Free() | ||||||
| 		if err != nil { | 		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
 | // isAttachmentDownload check if request is a file download (GET) with URL to an attachment
 | ||||||
| func isAttachmentDownload(req *http.Request) bool { | func isAttachmentDownload(req *http.Request) bool { | ||||||
| 	return strings.HasPrefix(req.URL.Path, "/attachments/") && req.Method == "GET" | 	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
 | // Use of this source code is governed by a MIT-style
 | ||||||
| // license that can be found in the LICENSE file.
 | // license that can be found in the LICENSE file.
 | ||||||
| 
 | 
 | ||||||
| package sso | package auth | ||||||
| 
 | 
 | ||||||
| import ( | import ( | ||||||
| 	"net/http" | 	"net/http" | ||||||
|  | @ -3,7 +3,7 @@ | ||||||
| // Use of this source code is governed by a MIT-style
 | // Use of this source code is governed by a MIT-style
 | ||||||
| // license that can be found in the LICENSE file.
 | // license that can be found in the LICENSE file.
 | ||||||
| 
 | 
 | ||||||
| package sso | package auth | ||||||
| 
 | 
 | ||||||
| import ( | import ( | ||||||
| 	"net/http" | 	"net/http" | ||||||
|  | @ -19,15 +19,20 @@ import ( | ||||||
| 
 | 
 | ||||||
| // Ensure the struct implements the interface.
 | // Ensure the struct implements the interface.
 | ||||||
| var ( | 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"
 | // only) by looking for Basic authentication data or "x-oauth-basic" token in the "Authorization"
 | ||||||
| // header.
 | // header.
 | ||||||
| type Basic struct { | 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
 | // Init does nothing as the Basic implementation does not need to allocate any resources
 | ||||||
| func (b *Basic) Init() error { | func (b *Basic) Init() error { | ||||||
| 	return nil | 	return nil | ||||||
|  | @ -38,20 +43,13 @@ func (b *Basic) Free() error { | ||||||
| 	return nil | 	return nil | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // IsEnabled returns true as this plugin is enabled by default and its not possible to disable
 | // Verify extracts and validates Basic data (username and password/token) from the
 | ||||||
| // it from settings.
 |  | ||||||
| func (b *Basic) IsEnabled() bool { |  | ||||||
| 	return true |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // VerifyAuthData extracts and validates Basic data (username and password/token) from the
 |  | ||||||
| // "Authorization" header of the request and returns the corresponding user object for that
 | // "Authorization" header of the request and returns the corresponding user object for that
 | ||||||
| // name/token on successful validation.
 | // name/token on successful validation.
 | ||||||
| // Returns nil if header is empty or validation fails.
 | // 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
 | 	// 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 | 		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
 | // Use of this source code is governed by a MIT-style
 | ||||||
| // license that can be found in the LICENSE file.
 | // license that can be found in the LICENSE file.
 | ||||||
| 
 | 
 | ||||||
| package sso | package auth | ||||||
| 
 | 
 | ||||||
| import ( | import ( | ||||||
| 	"net/http" | 	"net/http" | ||||||
|  | @ -18,8 +18,10 @@ type DataStore middleware.DataStore | ||||||
| // SessionStore represents a session store
 | // SessionStore represents a session store
 | ||||||
| type SessionStore session.Store | type SessionStore session.Store | ||||||
| 
 | 
 | ||||||
| // SingleSignOn represents a SSO authentication method (plugin) for HTTP requests.
 | // Auth represents an authentication method (plugin) for HTTP requests.
 | ||||||
| type SingleSignOn interface { | type Auth interface { | ||||||
|  | 	Name() string | ||||||
|  | 
 | ||||||
| 	// Init should be called exactly once before using any of the other methods,
 | 	// Init should be called exactly once before using any of the other methods,
 | ||||||
| 	// in order to allow the plugin to allocate necessary resources
 | 	// in order to allow the plugin to allocate necessary resources
 | ||||||
| 	Init() error | 	Init() error | ||||||
|  | @ -28,13 +30,10 @@ type SingleSignOn interface { | ||||||
| 	// give chance to the plugin to free any allocated resources
 | 	// give chance to the plugin to free any allocated resources
 | ||||||
| 	Free() error | 	Free() error | ||||||
| 
 | 
 | ||||||
| 	// IsEnabled checks if the current SSO method has been enabled in settings.
 | 	// Verify tries to verify the authentication data contained in the request.
 | ||||||
| 	IsEnabled() bool |  | ||||||
| 
 |  | ||||||
| 	// VerifyAuthData tries to verify the SSO authentication data contained in the request.
 |  | ||||||
| 	// If verification is successful returns either an existing user object (with id > 0)
 | 	// 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
 | 	// or a new user object (with id = 0) populated with the information that was found
 | ||||||
| 	// in the authentication data (username or email).
 | 	// in the authentication data (username or email).
 | ||||||
| 	// Returns nil if verification fails.
 | 	// 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
 | // Use of this source code is governed by a MIT-style
 | ||||||
| // license that can be found in the LICENSE file.
 | // license that can be found in the LICENSE file.
 | ||||||
| 
 | 
 | ||||||
| package sso | package auth | ||||||
| 
 | 
 | ||||||
| import ( | import ( | ||||||
| 	"net/http" | 	"net/http" | ||||||
|  | @ -18,7 +18,7 @@ import ( | ||||||
| 
 | 
 | ||||||
| // Ensure the struct implements the interface.
 | // Ensure the struct implements the interface.
 | ||||||
| var ( | var ( | ||||||
| 	_ SingleSignOn = &OAuth2{} | 	_ Auth = &OAuth2{} | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| // CheckOAuthAccessToken returns uid of user from oauth token
 | // CheckOAuthAccessToken returns uid of user from oauth token
 | ||||||
|  | @ -45,7 +45,7 @@ func CheckOAuthAccessToken(accessToken string) int64 { | ||||||
| 	return grant.UserID | 	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
 | // (API requests only) by looking for an OAuth token in query parameters or the
 | ||||||
| // "Authorization" header.
 | // "Authorization" header.
 | ||||||
| type OAuth2 struct { | type OAuth2 struct { | ||||||
|  | @ -56,6 +56,11 @@ func (o *OAuth2) Init() error { | ||||||
| 	return nil | 	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
 | // Free does nothing as the OAuth2 implementation does not have to release any resources
 | ||||||
| func (o *OAuth2) Free() error { | func (o *OAuth2) Free() error { | ||||||
| 	return nil | 	return nil | ||||||
|  | @ -107,22 +112,16 @@ func (o *OAuth2) userIDFromToken(req *http.Request, store DataStore) int64 { | ||||||
| 	return t.UID | 	return t.UID | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // IsEnabled returns true as this plugin is enabled by default and its not possible
 | // Verify extracts the user ID from the OAuth token in the query parameters
 | ||||||
| // 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
 |  | ||||||
| // or the "Authorization" header and returns the corresponding user object for that ID.
 | // or the "Authorization" header and returns the corresponding user object for that ID.
 | ||||||
| // If verification is successful returns an existing user object.
 | // If verification is successful returns an existing user object.
 | ||||||
| // Returns nil if verification fails.
 | // 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 { | 	if !models.HasEngine { | ||||||
| 		return nil | 		return nil | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	if middleware.IsInternalPath(req) || !middleware.IsAPIPath(req) && !isAttachmentDownload(req) { | 	if !middleware.IsAPIPath(req) && !isAttachmentDownload(req) { | ||||||
| 		return nil | 		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
 | // Use of this source code is governed by a MIT-style
 | ||||||
| // license that can be found in the LICENSE file.
 | // license that can be found in the LICENSE file.
 | ||||||
| 
 | 
 | ||||||
| package sso | package auth | ||||||
| 
 | 
 | ||||||
| import ( | import ( | ||||||
| 	"net/http" | 	"net/http" | ||||||
|  | @ -19,10 +19,10 @@ import ( | ||||||
| 
 | 
 | ||||||
| // Ensure the struct implements the interface.
 | // Ensure the struct implements the interface.
 | ||||||
| var ( | 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.
 | // a reverse proxy for authentication of users.
 | ||||||
| // On successful authentication the proxy is expected to populate the username in the
 | // 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
 | // "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 | 	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
 | // Init does nothing as the ReverseProxy implementation does not need initialization
 | ||||||
| func (r *ReverseProxy) Init() error { | func (r *ReverseProxy) Init() error { | ||||||
| 	return nil | 	return nil | ||||||
|  | @ -49,19 +54,14 @@ func (r *ReverseProxy) Free() error { | ||||||
| 	return nil | 	return nil | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // IsEnabled checks if EnableReverseProxyAuth setting is true
 | // Verify extracts the username from the "setting.ReverseProxyAuthUser" header
 | ||||||
| func (r *ReverseProxy) IsEnabled() bool { |  | ||||||
| 	return setting.Service.EnableReverseProxyAuth |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // VerifyAuthData extracts the username from the "setting.ReverseProxyAuthUser" header
 |  | ||||||
| // of the request and returns the corresponding user object for that name.
 | // 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
 | // Verification of header data is not performed as it should have already been done by
 | ||||||
| // the revese proxy.
 | // the revese proxy.
 | ||||||
| // If a username is available in the "setting.ReverseProxyAuthUser" header an existing
 | // If a username is available in the "setting.ReverseProxyAuthUser" header an existing
 | ||||||
| // user object is returned (populated with username or email found in header).
 | // user object is returned (populated with username or email found in header).
 | ||||||
| // Returns nil if header is empty.
 | // 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) | 	username := r.getUserName(req) | ||||||
| 	if len(username) == 0 { | 	if len(username) == 0 { | ||||||
| 		return nil | 		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
 | // Use of this source code is governed by a MIT-style
 | ||||||
| // license that can be found in the LICENSE file.
 | // license that can be found in the LICENSE file.
 | ||||||
| 
 | 
 | ||||||
| package sso | package auth | ||||||
| 
 | 
 | ||||||
| import ( | import ( | ||||||
| 	"errors" | 	"errors" | ||||||
|  | @ -32,7 +32,7 @@ var ( | ||||||
| 	sspiAuth *websspi.Authenticator | 	sspiAuth *websspi.Authenticator | ||||||
| 
 | 
 | ||||||
| 	// Ensure the struct implements the interface.
 | 	// Ensure the struct implements the interface.
 | ||||||
| 	_ SingleSignOn = &SSPI{} | 	_ Auth = &SSPI{} | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| // SSPI implements the SingleSignOn interface and authenticates requests
 | // SSPI implements the SingleSignOn interface and authenticates requests
 | ||||||
|  | @ -62,21 +62,21 @@ func (s *SSPI) Init() error { | ||||||
| 	return nil | 	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
 | // Free releases resources used by the global websspi.Authenticator object
 | ||||||
| func (s *SSPI) Free() error { | func (s *SSPI) Free() error { | ||||||
| 	return sspiAuth.Free() | 	return sspiAuth.Free() | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // IsEnabled checks if there is an active SSPI authentication source
 | // Verify uses SSPI (Windows implementation of SPNEGO) to authenticate the request.
 | ||||||
| func (s *SSPI) IsEnabled() bool { |  | ||||||
| 	return models.IsSSPIEnabled() |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // VerifyAuthData uses SSPI (Windows implementation of SPNEGO) to authenticate the request.
 |  | ||||||
| // If authentication is successful, returs the corresponding user object.
 | // If authentication is successful, returs the corresponding user object.
 | ||||||
| // If negotiation should continue or authentication fails, immediately returns a 401 HTTP
 | // If negotiation should continue or authentication fails, immediately returns a 401 HTTP
 | ||||||
| // response code, as required by the SPNEGO protocol.
 | // 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) { | 	if !s.shouldAuthenticate(req) { | ||||||
| 		return nil | 		return nil | ||||||
| 	} | 	} | ||||||
|  | @ -169,8 +169,6 @@ func (s *SSPI) shouldAuthenticate(req *http.Request) (shouldAuth bool) { | ||||||
| 		} else if req.FormValue("auth_with_sspi") == "1" { | 		} else if req.FormValue("auth_with_sspi") == "1" { | ||||||
| 			shouldAuth = true | 			shouldAuth = true | ||||||
| 		} | 		} | ||||||
| 	} else if middleware.IsInternalPath(req) { |  | ||||||
| 		shouldAuth = false |  | ||||||
| 	} else if middleware.IsAPIPath(req) || isAttachmentDownload(req) { | 	} else if middleware.IsAPIPath(req) || isAttachmentDownload(req) { | ||||||
| 		shouldAuth = true | 		shouldAuth = true | ||||||
| 	} | 	} | ||||||
|  | @ -237,10 +235,12 @@ func sanitizeUsername(username string, cfg *models.SSPIConfig) string { | ||||||
| 	return username | 	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
 | // 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
 | // fails (or if negotiation should continue), which would prevent other authentication methods
 | ||||||
| // to execute at all.
 | // to execute at all.
 | ||||||
| func init() { | func specialInit() { | ||||||
|  | 	if models.IsSSPIEnabled() { | ||||||
| 		Register(&SSPI{}) | 		Register(&SSPI{}) | ||||||
|  | 	} | ||||||
| } | } | ||||||
		Loading…
	
		Reference in a new issue