mirror of
https://codeberg.org/forgejo/forgejo.git
synced 2026-05-12 22:10:25 +00:00
refactor: change authentication to return structured data (#12202)
Currently authentication methods return information in two forms: they return who was authenticated as a `*user_model.User`, and then they insert key-values into `ctx.Data` which has critical impact on how the authenticated request is treated. This PR changes the authentication methods to return structured data in the form of an `AuthenticationResult`, with all the key-value information in `ctx.Data` being moved into methods on the `AuthenticationResult` interface. Authentication workflows in Forgejo are a real mess. This is the first step in trying to clean it up and make the code predictable and reasonable, and is both follow-up work that was identified from the repo-specific access tokens (where the `"ApiTokenReducer"` key-value was added), and is pre-requisite work to future JWT enhancements that are [being discussed](https://codeberg.org/forgejo/forgejo/issues/3571#issuecomment-13268004). ## Checklist The [contributor guide](https://forgejo.org/docs/next/contributor/) contains information that will be helpful to first time contributors. All work and communication must conform to Forgejo's [AI Agreement](https://codeberg.org/forgejo/governance/src/branch/main/AIAgreement.md). There also are a few [conditions for merging Pull Requests in Forgejo repositories](https://codeberg.org/forgejo/governance/src/branch/main/PullRequestsAgreement.md). You are also welcome to join the [Forgejo development chatroom](https://matrix.to/#/#forgejo-development:matrix.org). ### Tests for Go changes - I added test coverage for Go changes... - [ ] in their respective `*_test.go` for unit tests. - [ ] in the `tests/integration` directory if it involves interactions with a live Forgejo server. - All changes, at least in theory, are refactors of existing logic and are not expected to have functional deviations -- existing regression tests are the only planned testing. - I ran... - [x] `make pr-go` before pushing ### Documentation - [ ] I created a pull request [to the documentation](https://codeberg.org/forgejo/docs) to explain to Forgejo users how to use this change. - [x] I did not document these changes and I do not expect someone else to do it. ### Release notes - [ ] This change will be noticed by a Forgejo user or admin (feature, bug fix, performance, etc.). I suggest to include a release note for this change. - [x] This change is not visible to a Forgejo user or admin (refactor, dependency upgrade, etc.). I think there is no need to add a release note for this change. Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/12202 Reviewed-by: Andreas Ahlenstorf <aahlenst@noreply.codeberg.org>
This commit is contained in:
parent
2ed98ac848
commit
1ddd5faa5c
45 changed files with 620 additions and 348 deletions
|
|
@ -347,21 +347,6 @@ linters:
|
||||||
- path: services/actions/trust.go
|
- path: services/actions/trust.go
|
||||||
linters:
|
linters:
|
||||||
- nilnil
|
- nilnil
|
||||||
- path: services/auth/basic.go
|
|
||||||
linters:
|
|
||||||
- nilnil
|
|
||||||
- path: services/auth/httpsign.go
|
|
||||||
linters:
|
|
||||||
- nilnil
|
|
||||||
- path: services/auth/oauth2.go
|
|
||||||
linters:
|
|
||||||
- nilnil
|
|
||||||
- path: services/auth/reverseproxy.go
|
|
||||||
linters:
|
|
||||||
- nilnil
|
|
||||||
- path: services/auth/session.go
|
|
||||||
linters:
|
|
||||||
- nilnil
|
|
||||||
- path: services/contexttest/context_tests.go
|
- path: services/contexttest/context_tests.go
|
||||||
linters:
|
linters:
|
||||||
- nilnil
|
- nilnil
|
||||||
|
|
|
||||||
|
|
@ -38,14 +38,13 @@ import (
|
||||||
"forgejo.org/routers/api/packages/swift"
|
"forgejo.org/routers/api/packages/swift"
|
||||||
"forgejo.org/routers/api/packages/vagrant"
|
"forgejo.org/routers/api/packages/vagrant"
|
||||||
"forgejo.org/services/auth"
|
"forgejo.org/services/auth"
|
||||||
|
auth_method "forgejo.org/services/auth/method"
|
||||||
"forgejo.org/services/context"
|
"forgejo.org/services/context"
|
||||||
)
|
)
|
||||||
|
|
||||||
func reqPackageAccess(accessMode perm.AccessMode) func(ctx *context.Context) {
|
func reqPackageAccess(accessMode perm.AccessMode) func(ctx *context.Context) {
|
||||||
return func(ctx *context.Context) {
|
return func(ctx *context.Context) {
|
||||||
if ctx.Data["IsApiToken"] == true {
|
if hasScope, scope := ctx.Authentication.Scope().Get(); hasScope {
|
||||||
scope, ok := ctx.Data["ApiTokenScope"].(auth_model.AccessTokenScope)
|
|
||||||
if ok { // it's a personal access token but not oauth2 token
|
|
||||||
scopeMatched := false
|
scopeMatched := false
|
||||||
var err error
|
var err error
|
||||||
switch accessMode {
|
switch accessMode {
|
||||||
|
|
@ -82,7 +81,6 @@ func reqPackageAccess(accessMode perm.AccessMode) func(ctx *context.Context) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if ctx.Package.AccessMode < accessMode && !ctx.IsUserSiteAdmin() {
|
if ctx.Package.AccessMode < accessMode && !ctx.IsUserSiteAdmin() {
|
||||||
ctx.Resp.Header().Set("WWW-Authenticate", `Basic realm="Gitea Package API"`)
|
ctx.Resp.Header().Set("WWW-Authenticate", `Basic realm="Gitea Package API"`)
|
||||||
|
|
@ -116,38 +114,48 @@ func enforcePackagesQuota() func(ctx *context.Context) {
|
||||||
|
|
||||||
func verifyAuth(r *web.Route, authMethods []auth.Method) {
|
func verifyAuth(r *web.Route, authMethods []auth.Method) {
|
||||||
if setting.Service.EnableReverseProxyAuth {
|
if setting.Service.EnableReverseProxyAuth {
|
||||||
authMethods = append(authMethods, &auth.ReverseProxy{})
|
authMethods = append(authMethods, &auth_method.ReverseProxy{})
|
||||||
}
|
}
|
||||||
authGroup := auth.NewGroup(authMethods...)
|
authGroup := auth_method.NewGroup(authMethods...)
|
||||||
|
|
||||||
r.Use(func(ctx *context.Context) {
|
r.Use(func(ctx *context.Context) {
|
||||||
var err error
|
authResult, err := authGroup.Verify(ctx.Req, ctx.Resp, ctx.Session)
|
||||||
ctx.Doer, err = authGroup.Verify(ctx.Req, ctx.Resp, ctx, ctx.Session)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Info("Failed to verify user: %v", err)
|
log.Info("Failed to verify user: %v", err)
|
||||||
ctx.Error(http.StatusUnauthorized, "authGroup.Verify")
|
ctx.Error(http.StatusUnauthorized, "authGroup.Verify")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if authResult == nil {
|
||||||
|
ctx.Error(http.StatusInternalServerError, "verifyAuth nil authentication result")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ctx.Doer = authResult.User()
|
||||||
ctx.IsSigned = ctx.Doer != nil
|
ctx.IsSigned = ctx.Doer != nil
|
||||||
|
ctx.Authentication = authResult
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func verifyContainerAuth(r *web.Route, authMethods []auth.Method) {
|
func verifyContainerAuth(r *web.Route, authMethods []auth.Method) {
|
||||||
if setting.Service.EnableReverseProxyAuth {
|
if setting.Service.EnableReverseProxyAuth {
|
||||||
authMethods = append(authMethods, &auth.ReverseProxy{})
|
authMethods = append(authMethods, &auth_method.ReverseProxy{})
|
||||||
}
|
}
|
||||||
authGroup := auth.NewGroup(authMethods...)
|
authGroup := auth_method.NewGroup(authMethods...)
|
||||||
|
|
||||||
r.Use(func(ctx *context.Context) {
|
r.Use(func(ctx *context.Context) {
|
||||||
var err error
|
authResult, err := authGroup.Verify(ctx.Req, ctx.Resp, ctx.Session)
|
||||||
ctx.Doer, err = authGroup.Verify(ctx.Req, ctx.Resp, ctx, ctx.Session)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Info("Failed to verify user: %v", err)
|
log.Info("Failed to verify user: %v", err)
|
||||||
container.APIUnauthorizedError(ctx)
|
container.APIUnauthorizedError(ctx)
|
||||||
ctx.Error(http.StatusUnauthorized, "authGroup.Verify")
|
ctx.Error(http.StatusUnauthorized, "authGroup.Verify")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if authResult == nil {
|
||||||
|
ctx.Error(http.StatusInternalServerError, "verifyContainerAuth nil authentication result")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ctx.Doer = authResult.User()
|
||||||
ctx.IsSigned = ctx.Doer != nil
|
ctx.IsSigned = ctx.Doer != nil
|
||||||
|
ctx.Authentication = authResult
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -159,8 +167,8 @@ func CommonRoutes() *web.Route {
|
||||||
r.Use(context.PackageContexter())
|
r.Use(context.PackageContexter())
|
||||||
|
|
||||||
verifyAuth(r, []auth.Method{
|
verifyAuth(r, []auth.Method{
|
||||||
&auth.OAuth2{},
|
&auth_method.OAuth2{},
|
||||||
&auth.Basic{},
|
&auth_method.Basic{},
|
||||||
&nuget.Auth{},
|
&nuget.Auth{},
|
||||||
&conan.Auth{},
|
&conan.Auth{},
|
||||||
&chef.Auth{},
|
&chef.Auth{},
|
||||||
|
|
@ -804,7 +812,7 @@ func ContainerRoutes() *web.Route {
|
||||||
|
|
||||||
r.Use(context.PackageContexter())
|
r.Use(context.PackageContexter())
|
||||||
|
|
||||||
verifyContainerAuth(r, []auth.Method{&auth.Basic{}, &container.Auth{}})
|
verifyContainerAuth(r, []auth.Method{&auth_method.Basic{}, &container.Auth{}})
|
||||||
|
|
||||||
r.Get("", container.ReqContainerAccess, container.DetermineSupport)
|
r.Get("", container.ReqContainerAccess, container.DetermineSupport)
|
||||||
r.Group("/token", func() {
|
r.Group("/token", func() {
|
||||||
|
|
|
||||||
|
|
@ -40,8 +40,18 @@ var (
|
||||||
authorizationPattern = regexp.MustCompile(`\AX-Ops-Authorization-(\d+)`)
|
authorizationPattern = regexp.MustCompile(`\AX-Ops-Authorization-(\d+)`)
|
||||||
|
|
||||||
_ auth.Method = &Auth{}
|
_ auth.Method = &Auth{}
|
||||||
|
_ auth.AuthenticationResult = &chefAuthenticationResult{}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type chefAuthenticationResult struct {
|
||||||
|
*auth.BaseAuthenticationResult
|
||||||
|
user *user_model.User
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *chefAuthenticationResult) User() *user_model.User {
|
||||||
|
return r.user
|
||||||
|
}
|
||||||
|
|
||||||
// Documentation:
|
// Documentation:
|
||||||
// https://docs.chef.io/server/api_chef_server/#required-headers
|
// https://docs.chef.io/server/api_chef_server/#required-headers
|
||||||
// https://github.com/chef-boneyard/chef-rfc/blob/master/rfc065-sign-v1.3.md
|
// https://github.com/chef-boneyard/chef-rfc/blob/master/rfc065-sign-v1.3.md
|
||||||
|
|
@ -55,13 +65,13 @@ func (a *Auth) Name() string {
|
||||||
|
|
||||||
// Verify extracts the user from the signed request
|
// Verify extracts the user from the signed request
|
||||||
// If the request is signed with the user private key the user is verified.
|
// If the request is signed with the user private key the user is verified.
|
||||||
func (a *Auth) Verify(req *http.Request, w http.ResponseWriter, store auth.DataStore, sess auth.SessionStore) (*user_model.User, error) {
|
func (a *Auth) Verify(req *http.Request, w http.ResponseWriter, sess auth.SessionStore) (auth.AuthenticationResult, error) {
|
||||||
u, err := getUserFromRequest(req)
|
u, err := getUserFromRequest(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if u == nil {
|
if u == nil {
|
||||||
return nil, nil
|
return &auth.UnauthenticatedResult{}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
pub, err := getUserPublicKey(req.Context(), u)
|
pub, err := getUserPublicKey(req.Context(), u)
|
||||||
|
|
@ -82,7 +92,7 @@ func (a *Auth) Verify(req *http.Request, w http.ResponseWriter, store auth.DataS
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return u, nil
|
return &chefAuthenticationResult{user: u}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func getUserFromRequest(req *http.Request) (*user_model.User, error) {
|
func getUserFromRequest(req *http.Request) (*user_model.User, error) {
|
||||||
|
|
|
||||||
|
|
@ -6,13 +6,32 @@ package conan
|
||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
|
auth_model "forgejo.org/models/auth"
|
||||||
user_model "forgejo.org/models/user"
|
user_model "forgejo.org/models/user"
|
||||||
"forgejo.org/modules/log"
|
"forgejo.org/modules/log"
|
||||||
|
"forgejo.org/modules/optional"
|
||||||
"forgejo.org/services/auth"
|
"forgejo.org/services/auth"
|
||||||
"forgejo.org/services/packages"
|
"forgejo.org/services/packages"
|
||||||
)
|
)
|
||||||
|
|
||||||
var _ auth.Method = &Auth{}
|
var (
|
||||||
|
_ auth.Method = &Auth{}
|
||||||
|
_ auth.AuthenticationResult = &conanAuthenticationResult{}
|
||||||
|
)
|
||||||
|
|
||||||
|
type conanAuthenticationResult struct {
|
||||||
|
*auth.BaseAuthenticationResult
|
||||||
|
user *user_model.User
|
||||||
|
scope optional.Option[auth_model.AccessTokenScope]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *conanAuthenticationResult) Scope() optional.Option[auth_model.AccessTokenScope] {
|
||||||
|
return r.scope
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *conanAuthenticationResult) User() *user_model.User {
|
||||||
|
return r.user
|
||||||
|
}
|
||||||
|
|
||||||
type Auth struct{}
|
type Auth struct{}
|
||||||
|
|
||||||
|
|
@ -21,7 +40,7 @@ func (a *Auth) Name() string {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Verify extracts the user from the Bearer token
|
// Verify extracts the user from the Bearer token
|
||||||
func (a *Auth) Verify(req *http.Request, w http.ResponseWriter, store auth.DataStore, sess auth.SessionStore) (*user_model.User, error) {
|
func (a *Auth) Verify(req *http.Request, w http.ResponseWriter, sess auth.SessionStore) (auth.AuthenticationResult, error) {
|
||||||
uid, scope, err := packages.ParseAuthorizationToken(req)
|
uid, scope, err := packages.ParseAuthorizationToken(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Trace("ParseAuthorizationToken: %v", err)
|
log.Trace("ParseAuthorizationToken: %v", err)
|
||||||
|
|
@ -29,13 +48,13 @@ func (a *Auth) Verify(req *http.Request, w http.ResponseWriter, store auth.DataS
|
||||||
}
|
}
|
||||||
|
|
||||||
if uid == 0 {
|
if uid == 0 {
|
||||||
return nil, nil
|
return &auth.UnauthenticatedResult{}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Propagate scope of the authorization token.
|
// Propagate scope of the authorization token.
|
||||||
|
authScope := optional.None[auth_model.AccessTokenScope]()
|
||||||
if scope != "" {
|
if scope != "" {
|
||||||
store.GetData()["IsApiToken"] = true
|
authScope = optional.Some(scope)
|
||||||
store.GetData()["ApiTokenScope"] = scope
|
|
||||||
}
|
}
|
||||||
|
|
||||||
u, err := user_model.GetUserByID(req.Context(), uid)
|
u, err := user_model.GetUserByID(req.Context(), uid)
|
||||||
|
|
@ -44,5 +63,5 @@ func (a *Auth) Verify(req *http.Request, w http.ResponseWriter, store auth.DataS
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return u, nil
|
return &conanAuthenticationResult{user: u, scope: authScope}, nil
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,6 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
auth_model "forgejo.org/models/auth"
|
|
||||||
"forgejo.org/models/db"
|
"forgejo.org/models/db"
|
||||||
packages_model "forgejo.org/models/packages"
|
packages_model "forgejo.org/models/packages"
|
||||||
conan_model "forgejo.org/models/packages/conan"
|
conan_model "forgejo.org/models/packages/conan"
|
||||||
|
|
@ -119,7 +118,7 @@ func Authenticate(ctx *context.Context) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// If there's an API scope, ensure it propagates.
|
// If there's an API scope, ensure it propagates.
|
||||||
scope, _ := ctx.Data.GetData()["ApiTokenScope"].(auth_model.AccessTokenScope)
|
scope := ctx.Authentication.Scope().ValueOrZeroValue()
|
||||||
|
|
||||||
token, err := packages_service.CreateAuthorizationToken(ctx.Doer, scope)
|
token, err := packages_service.CreateAuthorizationToken(ctx.Doer, scope)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
||||||
|
|
@ -6,13 +6,32 @@ package container
|
||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
|
auth_model "forgejo.org/models/auth"
|
||||||
user_model "forgejo.org/models/user"
|
user_model "forgejo.org/models/user"
|
||||||
"forgejo.org/modules/log"
|
"forgejo.org/modules/log"
|
||||||
|
"forgejo.org/modules/optional"
|
||||||
"forgejo.org/services/auth"
|
"forgejo.org/services/auth"
|
||||||
"forgejo.org/services/packages"
|
"forgejo.org/services/packages"
|
||||||
)
|
)
|
||||||
|
|
||||||
var _ auth.Method = &Auth{}
|
var (
|
||||||
|
_ auth.Method = &Auth{}
|
||||||
|
_ auth.AuthenticationResult = &containerAuthenticationResult{}
|
||||||
|
)
|
||||||
|
|
||||||
|
type containerAuthenticationResult struct {
|
||||||
|
*auth.BaseAuthenticationResult
|
||||||
|
user *user_model.User
|
||||||
|
scope optional.Option[auth_model.AccessTokenScope]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *containerAuthenticationResult) Scope() optional.Option[auth_model.AccessTokenScope] {
|
||||||
|
return r.scope
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *containerAuthenticationResult) User() *user_model.User {
|
||||||
|
return r.user
|
||||||
|
}
|
||||||
|
|
||||||
type Auth struct{}
|
type Auth struct{}
|
||||||
|
|
||||||
|
|
@ -22,7 +41,7 @@ func (a *Auth) Name() string {
|
||||||
|
|
||||||
// Verify extracts the user from the Bearer token
|
// Verify extracts the user from the Bearer token
|
||||||
// If it's an anonymous session a ghost user is returned
|
// If it's an anonymous session a ghost user is returned
|
||||||
func (a *Auth) Verify(req *http.Request, w http.ResponseWriter, store auth.DataStore, sess auth.SessionStore) (*user_model.User, error) {
|
func (a *Auth) Verify(req *http.Request, w http.ResponseWriter, sess auth.SessionStore) (auth.AuthenticationResult, error) {
|
||||||
uid, scope, err := packages.ParseAuthorizationToken(req)
|
uid, scope, err := packages.ParseAuthorizationToken(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Trace("ParseAuthorizationToken: %v", err)
|
log.Trace("ParseAuthorizationToken: %v", err)
|
||||||
|
|
@ -30,13 +49,13 @@ func (a *Auth) Verify(req *http.Request, w http.ResponseWriter, store auth.DataS
|
||||||
}
|
}
|
||||||
|
|
||||||
if uid == 0 {
|
if uid == 0 {
|
||||||
return nil, nil
|
return &auth.UnauthenticatedResult{}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Propagate scope of the authorization token.
|
// Propagate scope of the authorization token.
|
||||||
|
authScope := optional.None[auth_model.AccessTokenScope]()
|
||||||
if scope != "" {
|
if scope != "" {
|
||||||
store.GetData()["IsApiToken"] = true
|
authScope = optional.Some(scope)
|
||||||
store.GetData()["ApiTokenScope"] = scope
|
|
||||||
}
|
}
|
||||||
|
|
||||||
u, err := user_model.GetPossibleUserByID(req.Context(), uid)
|
u, err := user_model.GetPossibleUserByID(req.Context(), uid)
|
||||||
|
|
@ -45,5 +64,5 @@ func (a *Auth) Verify(req *http.Request, w http.ResponseWriter, store auth.DataS
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return u, nil
|
return &containerAuthenticationResult{user: u, scope: authScope}, nil
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,6 @@ import (
|
||||||
"regexp"
|
"regexp"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
auth_model "forgejo.org/models/auth"
|
|
||||||
packages_model "forgejo.org/models/packages"
|
packages_model "forgejo.org/models/packages"
|
||||||
container_model "forgejo.org/models/packages/container"
|
container_model "forgejo.org/models/packages/container"
|
||||||
user_model "forgejo.org/models/user"
|
user_model "forgejo.org/models/user"
|
||||||
|
|
@ -158,7 +157,7 @@ func Authenticate(ctx *context.Context) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// If there's an API scope, ensure it propagates.
|
// If there's an API scope, ensure it propagates.
|
||||||
scope, _ := ctx.Data["ApiTokenScope"].(auth_model.AccessTokenScope)
|
scope := ctx.Authentication.Scope().ValueOrZeroValue()
|
||||||
|
|
||||||
token, err := packages_service.CreateAuthorizationToken(u, scope)
|
token, err := packages_service.CreateAuthorizationToken(u, scope)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,20 @@ import (
|
||||||
"forgejo.org/services/auth"
|
"forgejo.org/services/auth"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
_ auth.Method = &Auth{}
|
||||||
|
_ auth.AuthenticationResult = &nugetAuthenticationResult{}
|
||||||
|
)
|
||||||
|
|
||||||
|
type nugetAuthenticationResult struct {
|
||||||
|
*auth.BaseAuthenticationResult
|
||||||
|
user *user_model.User
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *nugetAuthenticationResult) User() *user_model.User {
|
||||||
|
return r.user
|
||||||
|
}
|
||||||
|
|
||||||
var _ auth.Method = &Auth{}
|
var _ auth.Method = &Auth{}
|
||||||
|
|
||||||
type Auth struct{}
|
type Auth struct{}
|
||||||
|
|
@ -21,14 +35,14 @@ func (a *Auth) Name() string {
|
||||||
}
|
}
|
||||||
|
|
||||||
// https://docs.microsoft.com/en-us/nuget/api/package-publish-resource#request-parameters
|
// https://docs.microsoft.com/en-us/nuget/api/package-publish-resource#request-parameters
|
||||||
func (a *Auth) Verify(req *http.Request, w http.ResponseWriter, store auth.DataStore, sess auth.SessionStore) (*user_model.User, error) {
|
func (a *Auth) Verify(req *http.Request, w http.ResponseWriter, sess auth.SessionStore) (auth.AuthenticationResult, error) {
|
||||||
token, err := auth_model.GetAccessTokenBySHA(req.Context(), req.Header.Get("X-NuGet-ApiKey"))
|
token, err := auth_model.GetAccessTokenBySHA(req.Context(), req.Header.Get("X-NuGet-ApiKey"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if !auth_model.IsErrAccessTokenNotExist(err) && !auth_model.IsErrAccessTokenEmpty(err) {
|
if !auth_model.IsErrAccessTokenNotExist(err) && !auth_model.IsErrAccessTokenEmpty(err) {
|
||||||
log.Error("GetAccessTokenBySHA: %v", err)
|
log.Error("GetAccessTokenBySHA: %v", err)
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return nil, nil
|
return &auth.UnauthenticatedResult{}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
u, err := user_model.GetUserByID(req.Context(), token.UID)
|
u, err := user_model.GetUserByID(req.Context(), token.UID)
|
||||||
|
|
@ -41,5 +55,5 @@ func (a *Auth) Verify(req *http.Request, w http.ResponseWriter, store auth.DataS
|
||||||
log.Error("UpdateLastUsed: %v", err)
|
log.Error("UpdateLastUsed: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return u, nil
|
return &nugetAuthenticationResult{user: u}, nil
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@
|
||||||
package shared
|
package shared
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
|
|
@ -12,6 +13,7 @@ import (
|
||||||
"forgejo.org/modules/setting"
|
"forgejo.org/modules/setting"
|
||||||
"forgejo.org/routers/common"
|
"forgejo.org/routers/common"
|
||||||
"forgejo.org/services/auth"
|
"forgejo.org/services/auth"
|
||||||
|
auth_method "forgejo.org/services/auth/method"
|
||||||
"forgejo.org/services/authz"
|
"forgejo.org/services/authz"
|
||||||
"forgejo.org/services/context"
|
"forgejo.org/services/context"
|
||||||
|
|
||||||
|
|
@ -43,14 +45,14 @@ func Middlewares() (stack []any) {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
func buildAuthGroup() *auth.Group {
|
func buildAuthGroup() *auth_method.Group {
|
||||||
group := auth.NewGroup(
|
group := auth_method.NewGroup(
|
||||||
&auth.OAuth2{},
|
&auth_method.OAuth2{},
|
||||||
&auth.HTTPSign{},
|
&auth_method.HTTPSign{},
|
||||||
&auth.Basic{}, // FIXME: this should be removed once we don't allow basic auth in API
|
&auth_method.Basic{}, // FIXME: this should be removed once we don't allow basic auth in API
|
||||||
)
|
)
|
||||||
if setting.Service.EnableReverseProxyAuthAPI {
|
if setting.Service.EnableReverseProxyAuthAPI {
|
||||||
group.Add(&auth.ReverseProxy{})
|
group.Add(&auth_method.ReverseProxy{})
|
||||||
}
|
}
|
||||||
|
|
||||||
return group
|
return group
|
||||||
|
|
@ -63,15 +65,18 @@ func apiAuthentication(authMethod auth.Method) func(*context.APIContext) {
|
||||||
ctx.Error(http.StatusUnauthorized, "APIAuth", err)
|
ctx.Error(http.StatusUnauthorized, "APIAuth", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
ctx.Doer = ar.Doer
|
if ar == nil {
|
||||||
ctx.IsSigned = ar.Doer != nil
|
ctx.Error(http.StatusInternalServerError, "apiAuthentication nil authentication result", errors.New("nil authentication result"))
|
||||||
ctx.IsBasicAuth = ar.IsBasicAuth
|
return
|
||||||
|
}
|
||||||
|
ctx.Doer = ar.User()
|
||||||
|
ctx.IsSigned = ctx.Doer != nil
|
||||||
|
ctx.Authentication = ar
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func apiAuthorization(ctx *context.APIContext) {
|
func apiAuthorization(ctx *context.APIContext) {
|
||||||
scope, scopeExists := ctx.Data["ApiTokenScope"].(auth_model.AccessTokenScope)
|
if hasScope, scope := ctx.Authentication.Scope().Get(); hasScope {
|
||||||
if scopeExists {
|
|
||||||
publicOnly, err := scope.PublicOnly()
|
publicOnly, err := scope.PublicOnly()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.Error(http.StatusForbidden, "tokenRequiresScope", "parsing public resource scope failed: "+err.Error())
|
ctx.Error(http.StatusForbidden, "tokenRequiresScope", "parsing public resource scope failed: "+err.Error())
|
||||||
|
|
@ -80,13 +85,13 @@ func apiAuthorization(ctx *context.APIContext) {
|
||||||
ctx.PublicOnly = publicOnly
|
ctx.PublicOnly = publicOnly
|
||||||
}
|
}
|
||||||
|
|
||||||
reducer, reducerExists := ctx.Data["ApiTokenReducer"].(authz.AuthorizationReducer)
|
reducer := ctx.Authentication.Reducer()
|
||||||
if reducerExists {
|
if reducer != nil {
|
||||||
ctx.Reducer = reducer
|
ctx.Reducer = reducer
|
||||||
} else {
|
} else {
|
||||||
// No "ApiTokenReducer" will be populated if the auth method wasn't an PAT. In this case, we populate
|
// No Reducer will be populated if the auth method wasn't an PAT. In this case, we populate `ctx.Reducer` so no
|
||||||
// `ctx.Reducer` so no nil checks are needed, and we respect the scope `PublicOnly()` so that it it's safe to
|
// nil checks are needed, and we respect the scope `PublicOnly()` so that it it's safe to just rely on
|
||||||
// just rely on `ctx.Reducer` to account for public-only access:
|
// `ctx.Reducer` to account for public-only access:
|
||||||
if ctx.PublicOnly {
|
if ctx.PublicOnly {
|
||||||
ctx.Reducer = &authz.PublicReposAuthorizationReducer{}
|
ctx.Reducer = &authz.PublicReposAuthorizationReducer{}
|
||||||
} else {
|
} else {
|
||||||
|
|
|
||||||
|
|
@ -85,7 +85,6 @@ import (
|
||||||
"forgejo.org/routers/api/v1/settings"
|
"forgejo.org/routers/api/v1/settings"
|
||||||
"forgejo.org/routers/api/v1/user"
|
"forgejo.org/routers/api/v1/user"
|
||||||
"forgejo.org/services/actions"
|
"forgejo.org/services/actions"
|
||||||
"forgejo.org/services/auth"
|
|
||||||
"forgejo.org/services/context"
|
"forgejo.org/services/context"
|
||||||
"forgejo.org/services/forms"
|
"forgejo.org/services/forms"
|
||||||
redirect_service "forgejo.org/services/redirect"
|
redirect_service "forgejo.org/services/redirect"
|
||||||
|
|
@ -180,8 +179,8 @@ func repoAssignment() func(ctx *context.APIContext) {
|
||||||
repo.Owner = owner
|
repo.Owner = owner
|
||||||
ctx.Repo.Repository = repo
|
ctx.Repo.Repository = repo
|
||||||
|
|
||||||
if ctx.Doer != nil && ctx.Doer.ID == user_model.ActionsUserID {
|
if ctx.Doer != nil && ctx.Doer.ID == user_model.ActionsUserID && ctx.Authentication.ActionsTaskID().Has() {
|
||||||
taskID := ctx.Data["ActionsTaskID"].(int64)
|
_, taskID := ctx.Authentication.ActionsTaskID().Get()
|
||||||
task, err := actions_model.GetTaskByID(ctx, taskID)
|
task, err := actions_model.GetTaskByID(ctx, taskID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.Error(http.StatusInternalServerError, "actions_model.GetTaskByID", err)
|
ctx.Error(http.StatusInternalServerError, "actions_model.GetTaskByID", err)
|
||||||
|
|
@ -330,8 +329,8 @@ func tokenRequiresScopes(requiredScopeCategories ...auth_model.AccessTokenScopeC
|
||||||
}
|
}
|
||||||
|
|
||||||
// Need OAuth2 token to be present.
|
// Need OAuth2 token to be present.
|
||||||
scope, scopeExists := ctx.Data["ApiTokenScope"].(auth_model.AccessTokenScope)
|
hasScope, scope := ctx.Authentication.Scope().Get()
|
||||||
if ctx.Data["IsApiToken"] != true || !scopeExists {
|
if !hasScope {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -372,7 +371,7 @@ func tokenRequiresRepoOwnerScope(ctx *context.APIContext) {
|
||||||
func reqToken() func(ctx *context.APIContext) {
|
func reqToken() func(ctx *context.APIContext) {
|
||||||
return func(ctx *context.APIContext) {
|
return func(ctx *context.APIContext) {
|
||||||
// If actions token is present
|
// If actions token is present
|
||||||
if true == ctx.Data["IsActionsToken"] {
|
if ctx.Authentication.ActionsTaskID().Has() {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -401,13 +400,13 @@ func reqUsersExploreEnabled() func(ctx *context.APIContext) {
|
||||||
|
|
||||||
func reqBasicOrRevProxyAuth() func(ctx *context.APIContext) {
|
func reqBasicOrRevProxyAuth() func(ctx *context.APIContext) {
|
||||||
return func(ctx *context.APIContext) {
|
return func(ctx *context.APIContext) {
|
||||||
if ctx.IsSigned && setting.Service.EnableReverseProxyAuthAPI && ctx.Data["AuthedMethod"].(string) == auth.ReverseProxyMethodName {
|
if ctx.IsSigned && setting.Service.EnableReverseProxyAuthAPI && ctx.Authentication.IsReverseProxyAuthentication() {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Require basic authorization method to be used and that basic
|
// Require basic authorization method to be used and that basic
|
||||||
// authorization used password login to verify the user.
|
// authorization used password login to verify the user.
|
||||||
if passwordLogin, ok := ctx.Data["IsPasswordLogin"].(bool); !ok || !passwordLogin {
|
if !ctx.Authentication.IsPasswordAuthentication() {
|
||||||
ctx.Error(http.StatusUnauthorized, "reqBasicAuth", "auth method not allowed")
|
ctx.Error(http.StatusUnauthorized, "reqBasicAuth", "auth method not allowed")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,32 +4,30 @@
|
||||||
package common
|
package common
|
||||||
|
|
||||||
import (
|
import (
|
||||||
user_model "forgejo.org/models/user"
|
"errors"
|
||||||
|
|
||||||
"forgejo.org/modules/web/middleware"
|
"forgejo.org/modules/web/middleware"
|
||||||
auth_service "forgejo.org/services/auth"
|
auth_service "forgejo.org/services/auth"
|
||||||
"forgejo.org/services/context"
|
"forgejo.org/services/context"
|
||||||
)
|
)
|
||||||
|
|
||||||
type AuthResult struct {
|
func AuthShared(ctx *context.Base, sessionStore auth_service.SessionStore, authMethod auth_service.Method) (ar auth_service.AuthenticationResult, err error) {
|
||||||
Doer *user_model.User
|
ar, err = authMethod.Verify(ctx.Req, ctx.Resp, sessionStore)
|
||||||
IsBasicAuth bool
|
|
||||||
}
|
|
||||||
|
|
||||||
func AuthShared(ctx *context.Base, sessionStore auth_service.SessionStore, authMethod auth_service.Method) (ar AuthResult, err error) {
|
|
||||||
ar.Doer, err = authMethod.Verify(ctx.Req, ctx.Resp, ctx, sessionStore)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return ar, err
|
return ar, err
|
||||||
}
|
}
|
||||||
if ar.Doer != nil {
|
if ar == nil {
|
||||||
if ctx.Locale.Language() != ar.Doer.Language {
|
return nil, errors.New("failure to retrieve AuthenticationResult - nil value")
|
||||||
|
}
|
||||||
|
doer := ar.User()
|
||||||
|
if doer != nil {
|
||||||
|
if ctx.Locale.Language() != doer.Language {
|
||||||
ctx.Locale = middleware.Locale(ctx.Resp, ctx.Req)
|
ctx.Locale = middleware.Locale(ctx.Resp, ctx.Req)
|
||||||
}
|
}
|
||||||
ar.IsBasicAuth = ctx.Data["AuthedMethod"].(string) == auth_service.BasicMethodName
|
|
||||||
|
|
||||||
ctx.Data["IsSigned"] = true
|
ctx.Data["IsSigned"] = true
|
||||||
ctx.Data[middleware.ContextDataKeySignedUser] = ar.Doer
|
ctx.Data[middleware.ContextDataKeySignedUser] = doer
|
||||||
ctx.Data["SignedUserID"] = ar.Doer.ID
|
ctx.Data["SignedUserID"] = doer.ID
|
||||||
ctx.Data["IsAdmin"] = ar.Doer.IsAdmin
|
ctx.Data["IsAdmin"] = doer.IsAdmin
|
||||||
} else {
|
} else {
|
||||||
ctx.Data["IsSigned"] = false
|
ctx.Data["IsSigned"] = false
|
||||||
ctx.Data["SignedUserID"] = int64(0)
|
ctx.Data["SignedUserID"] = int64(0)
|
||||||
|
|
|
||||||
|
|
@ -33,7 +33,7 @@ import (
|
||||||
"forgejo.org/routers/private"
|
"forgejo.org/routers/private"
|
||||||
web_routers "forgejo.org/routers/web"
|
web_routers "forgejo.org/routers/web"
|
||||||
actions_service "forgejo.org/services/actions"
|
actions_service "forgejo.org/services/actions"
|
||||||
"forgejo.org/services/auth"
|
auth_method "forgejo.org/services/auth/method"
|
||||||
"forgejo.org/services/auth/source/oauth2"
|
"forgejo.org/services/auth/source/oauth2"
|
||||||
"forgejo.org/services/automerge"
|
"forgejo.org/services/automerge"
|
||||||
"forgejo.org/services/cron"
|
"forgejo.org/services/cron"
|
||||||
|
|
@ -160,7 +160,7 @@ func InitWebInstalled(ctx context.Context) {
|
||||||
|
|
||||||
mustInitCtx(ctx, ssh.Init)
|
mustInitCtx(ctx, ssh.Init)
|
||||||
|
|
||||||
auth.Init()
|
auth_method.Init()
|
||||||
mustInit(svg.Init)
|
mustInit(svg.Init)
|
||||||
|
|
||||||
actions_service.Init()
|
actions_service.Init()
|
||||||
|
|
|
||||||
|
|
@ -28,6 +28,7 @@ import (
|
||||||
"forgejo.org/modules/web"
|
"forgejo.org/modules/web"
|
||||||
"forgejo.org/modules/web/middleware"
|
"forgejo.org/modules/web/middleware"
|
||||||
auth_service "forgejo.org/services/auth"
|
auth_service "forgejo.org/services/auth"
|
||||||
|
auth_method "forgejo.org/services/auth/method"
|
||||||
"forgejo.org/services/auth/source/oauth2"
|
"forgejo.org/services/auth/source/oauth2"
|
||||||
"forgejo.org/services/context"
|
"forgejo.org/services/context"
|
||||||
"forgejo.org/services/externalaccount"
|
"forgejo.org/services/externalaccount"
|
||||||
|
|
@ -213,7 +214,7 @@ func SignInPost(ctx *context.Context) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
u, source, err := auth_service.UserSignIn(ctx, form.UserName, form.Password)
|
u, source, err := auth_method.UserSignIn(ctx, form.UserName, form.Password)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errors.Is(err, util.ErrNotExist) || errors.Is(err, util.ErrInvalidArgument) {
|
if errors.Is(err, util.ErrNotExist) || errors.Is(err, util.ErrInvalidArgument) {
|
||||||
ctx.RenderWithErr(ctx.Tr("form.username_password_incorrect"), tplSignIn, &form)
|
ctx.RenderWithErr(ctx.Tr("form.username_password_incorrect"), tplSignIn, &form)
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,7 @@ import (
|
||||||
"forgejo.org/modules/setting"
|
"forgejo.org/modules/setting"
|
||||||
"forgejo.org/modules/util"
|
"forgejo.org/modules/util"
|
||||||
"forgejo.org/modules/web"
|
"forgejo.org/modules/web"
|
||||||
auth_service "forgejo.org/services/auth"
|
auth_method "forgejo.org/services/auth/method"
|
||||||
"forgejo.org/services/auth/source/oauth2"
|
"forgejo.org/services/auth/source/oauth2"
|
||||||
"forgejo.org/services/context"
|
"forgejo.org/services/context"
|
||||||
"forgejo.org/services/externalaccount"
|
"forgejo.org/services/externalaccount"
|
||||||
|
|
@ -128,7 +128,7 @@ func LinkAccountPostSignIn(ctx *context.Context) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
u, _, err := auth_service.UserSignIn(ctx, signInForm.UserName, signInForm.Password)
|
u, _, err := auth_method.UserSignIn(ctx, signInForm.UserName, signInForm.Password)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
handleSignInError(ctx, signInForm.UserName, &signInForm, tplLinkAccount, "UserLinkAccount", err)
|
handleSignInError(ctx, signInForm.UserName, &signInForm, tplLinkAccount, "UserLinkAccount", err)
|
||||||
return
|
return
|
||||||
|
|
|
||||||
|
|
@ -33,7 +33,7 @@ import (
|
||||||
"forgejo.org/modules/util"
|
"forgejo.org/modules/util"
|
||||||
"forgejo.org/modules/web"
|
"forgejo.org/modules/web"
|
||||||
"forgejo.org/modules/web/middleware"
|
"forgejo.org/modules/web/middleware"
|
||||||
auth_service "forgejo.org/services/auth"
|
auth_method "forgejo.org/services/auth/method"
|
||||||
source_service "forgejo.org/services/auth/source"
|
source_service "forgejo.org/services/auth/source"
|
||||||
"forgejo.org/services/auth/source/oauth2"
|
"forgejo.org/services/auth/source/oauth2"
|
||||||
"forgejo.org/services/context"
|
"forgejo.org/services/context"
|
||||||
|
|
@ -294,7 +294,7 @@ func ifOnlyPublicGroups(scopes string) bool {
|
||||||
|
|
||||||
// InfoOAuth manages request for userinfo endpoint
|
// InfoOAuth manages request for userinfo endpoint
|
||||||
func InfoOAuth(ctx *context.Context) {
|
func InfoOAuth(ctx *context.Context) {
|
||||||
if ctx.Doer == nil || ctx.Data["AuthedMethod"] != (&auth_service.OAuth2{}).Name() {
|
if ctx.Doer == nil || !ctx.Authentication.IsOAuth2JWTAuthentication() {
|
||||||
ctx.Resp.Header().Set("WWW-Authenticate", `Bearer realm=""`)
|
ctx.Resp.Header().Set("WWW-Authenticate", `Bearer realm=""`)
|
||||||
ctx.PlainText(http.StatusUnauthorized, "no valid authorization")
|
ctx.PlainText(http.StatusUnauthorized, "no valid authorization")
|
||||||
return
|
return
|
||||||
|
|
@ -316,7 +316,7 @@ func InfoOAuth(ctx *context.Context) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_, grantScopes := auth_service.CheckOAuthAccessToken(ctx, token)
|
_, grantScopes := auth_method.CheckOAuthAccessToken(ctx, token)
|
||||||
onlyPublicGroups := ifOnlyPublicGroups(grantScopes)
|
onlyPublicGroups := ifOnlyPublicGroups(grantScopes)
|
||||||
|
|
||||||
groups, err := getOAuthGroupsForUser(ctx, ctx.Doer, onlyPublicGroups)
|
groups, err := getOAuthGroupsForUser(ctx, ctx.Doer, onlyPublicGroups)
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,7 @@ import (
|
||||||
"forgejo.org/modules/setting"
|
"forgejo.org/modules/setting"
|
||||||
"forgejo.org/modules/web"
|
"forgejo.org/modules/web"
|
||||||
"forgejo.org/services/auth"
|
"forgejo.org/services/auth"
|
||||||
|
auth_method "forgejo.org/services/auth/method"
|
||||||
"forgejo.org/services/context"
|
"forgejo.org/services/context"
|
||||||
"forgejo.org/services/forms"
|
"forgejo.org/services/forms"
|
||||||
)
|
)
|
||||||
|
|
@ -266,7 +267,7 @@ func ConnectOpenIDPost(ctx *context.Context) {
|
||||||
ctx.Data["EnableOpenIDSignUp"] = setting.Service.EnableOpenIDSignUp
|
ctx.Data["EnableOpenIDSignUp"] = setting.Service.EnableOpenIDSignUp
|
||||||
ctx.Data["OpenID"] = oid
|
ctx.Data["OpenID"] = oid
|
||||||
|
|
||||||
u, source, err := auth.UserSignIn(ctx, form.UserName, form.Password)
|
u, source, err := auth_method.UserSignIn(ctx, form.UserName, form.Password)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
handleSignInError(ctx, form.UserName, &form, tplConnectOID, "ConnectOpenIDPost", err)
|
handleSignInError(ctx, form.UserName, &form, tplConnectOID, "ConnectOpenIDPost", err)
|
||||||
return
|
return
|
||||||
|
|
|
||||||
|
|
@ -158,7 +158,7 @@ func httpBase(ctx *context.Context) *serviceHandler {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if ctx.IsBasicAuth && ctx.Data["IsApiToken"] != true && ctx.Data["IsActionsToken"] != true {
|
if ctx.Authentication.IsPasswordAuthentication() {
|
||||||
_, err = auth_model.GetTwoFactorByUID(ctx, ctx.Doer.ID)
|
_, err = auth_model.GetTwoFactorByUID(ctx, ctx.Doer.ID)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
// TODO: This response should be changed to "invalid credentials" for security reasons once the expectation behind it (creating an app token to authenticate) is properly documented
|
// TODO: This response should be changed to "invalid credentials" for security reasons once the expectation behind it (creating an app token to authenticate) is properly documented
|
||||||
|
|
@ -187,8 +187,7 @@ func httpBase(ctx *context.Context) *serviceHandler {
|
||||||
// Because of special ref "refs/for" .. , need delay write permission check
|
// Because of special ref "refs/for" .. , need delay write permission check
|
||||||
accessMode = perm.AccessModeRead
|
accessMode = perm.AccessModeRead
|
||||||
|
|
||||||
if ctx.Data["IsActionsToken"] == true {
|
if hasTaskID, taskID := ctx.Authentication.ActionsTaskID().Get(); hasTaskID {
|
||||||
taskID := ctx.Data["ActionsTaskID"].(int64)
|
|
||||||
task, err := actions_model.GetTaskByID(ctx, taskID)
|
task, err := actions_model.GetTaskByID(ctx, taskID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.ServerError("GetTaskByID", err)
|
ctx.ServerError("GetTaskByID", err)
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,7 @@ import (
|
||||||
"forgejo.org/modules/timeutil"
|
"forgejo.org/modules/timeutil"
|
||||||
"forgejo.org/modules/validation"
|
"forgejo.org/modules/validation"
|
||||||
"forgejo.org/modules/web"
|
"forgejo.org/modules/web"
|
||||||
"forgejo.org/services/auth"
|
auth_method "forgejo.org/services/auth/method"
|
||||||
"forgejo.org/services/auth/source/db"
|
"forgejo.org/services/auth/source/db"
|
||||||
"forgejo.org/services/auth/source/smtp"
|
"forgejo.org/services/auth/source/smtp"
|
||||||
"forgejo.org/services/context"
|
"forgejo.org/services/context"
|
||||||
|
|
@ -274,7 +274,7 @@ func DeleteAccount(ctx *context.Context) {
|
||||||
ctx.Data["Title"] = ctx.Tr("settings")
|
ctx.Data["Title"] = ctx.Tr("settings")
|
||||||
ctx.Data["PageIsSettingsAccount"] = true
|
ctx.Data["PageIsSettingsAccount"] = true
|
||||||
|
|
||||||
if _, _, err := auth.UserSignIn(ctx, ctx.Doer.Name, ctx.FormString("password")); err != nil {
|
if _, _, err := auth_method.UserSignIn(ctx, ctx.Doer.Name, ctx.FormString("password")); err != nil {
|
||||||
switch {
|
switch {
|
||||||
case user_model.IsErrUserNotExist(err):
|
case user_model.IsErrUserNotExist(err):
|
||||||
loadAccountData(ctx)
|
loadAccountData(ctx)
|
||||||
|
|
|
||||||
|
|
@ -48,6 +48,7 @@ import (
|
||||||
user_setting "forgejo.org/routers/web/user/setting"
|
user_setting "forgejo.org/routers/web/user/setting"
|
||||||
"forgejo.org/routers/web/user/setting/security"
|
"forgejo.org/routers/web/user/setting/security"
|
||||||
auth_service "forgejo.org/services/auth"
|
auth_service "forgejo.org/services/auth"
|
||||||
|
auth_method "forgejo.org/services/auth/method"
|
||||||
"forgejo.org/services/context"
|
"forgejo.org/services/context"
|
||||||
"forgejo.org/services/forms"
|
"forgejo.org/services/forms"
|
||||||
"forgejo.org/services/lfs"
|
"forgejo.org/services/lfs"
|
||||||
|
|
@ -104,15 +105,15 @@ func optionsCorsHandler() func(next http.Handler) http.Handler {
|
||||||
//
|
//
|
||||||
// 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.
|
||||||
func buildAuthGroup() *auth_service.Group {
|
func buildAuthGroup() *auth_method.Group {
|
||||||
group := auth_service.NewGroup()
|
group := auth_method.NewGroup()
|
||||||
group.Add(&auth_service.OAuth2{}) // FIXME: this should be removed and only applied in download and oauth related routers
|
group.Add(&auth_method.OAuth2{}) // FIXME: this should be removed and only applied in download and oauth related routers
|
||||||
group.Add(&auth_service.Basic{}) // FIXME: this should be removed and only applied in download and git/lfs routers
|
group.Add(&auth_method.Basic{}) // FIXME: this should be removed and only applied in download and git/lfs routers
|
||||||
|
|
||||||
if setting.Service.EnableReverseProxyAuth {
|
if setting.Service.EnableReverseProxyAuth {
|
||||||
group.Add(&auth_service.ReverseProxy{}) // reverseproxy should before Session, otherwise the header will be ignored if user has login
|
group.Add(&auth_method.ReverseProxy{}) // reverseproxy should before Session, otherwise the header will be ignored if user has login
|
||||||
}
|
}
|
||||||
group.Add(&auth_service.Session{})
|
group.Add(&auth_method.Session{})
|
||||||
|
|
||||||
return group
|
return group
|
||||||
}
|
}
|
||||||
|
|
@ -125,9 +126,13 @@ func webAuth(authMethod auth_service.Method) func(*context.Context) {
|
||||||
ctx.Error(http.StatusUnauthorized, ctx.Locale.TrString("auth.unauthorized_credentials", "https://codeberg.org/forgejo/forgejo/issues/2809"))
|
ctx.Error(http.StatusUnauthorized, ctx.Locale.TrString("auth.unauthorized_credentials", "https://codeberg.org/forgejo/forgejo/issues/2809"))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
ctx.Doer = ar.Doer
|
if ar == nil {
|
||||||
ctx.IsSigned = ar.Doer != nil
|
ctx.Error(http.StatusInternalServerError, "webAuth nil authentication result")
|
||||||
ctx.IsBasicAuth = ar.IsBasicAuth
|
return
|
||||||
|
}
|
||||||
|
ctx.Doer = ar.User()
|
||||||
|
ctx.IsSigned = ar.User() != nil
|
||||||
|
ctx.Authentication = ar
|
||||||
if ctx.Doer == nil {
|
if ctx.Doer == nil {
|
||||||
// ensure the session uid is deleted
|
// ensure the session uid is deleted
|
||||||
_ = ctx.Session.Delete("uid")
|
_ = ctx.Session.Delete("uid")
|
||||||
|
|
|
||||||
|
|
@ -7,9 +7,12 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
|
auth_model "forgejo.org/models/auth"
|
||||||
user_model "forgejo.org/models/user"
|
user_model "forgejo.org/models/user"
|
||||||
|
"forgejo.org/modules/optional"
|
||||||
"forgejo.org/modules/session"
|
"forgejo.org/modules/session"
|
||||||
"forgejo.org/modules/web/middleware"
|
"forgejo.org/modules/web/middleware"
|
||||||
|
"forgejo.org/services/authz"
|
||||||
)
|
)
|
||||||
|
|
||||||
// DataStore represents a data store
|
// DataStore represents a data store
|
||||||
|
|
@ -20,15 +23,11 @@ type SessionStore session.Store
|
||||||
|
|
||||||
// Method represents an authentication method (plugin) for HTTP requests.
|
// Method represents an authentication method (plugin) for HTTP requests.
|
||||||
type Method interface {
|
type Method interface {
|
||||||
// Verify tries to verify the authentication data contained in the request.
|
// Verify tries to verify the authentication data contained in the request. If verification is successful returns an
|
||||||
// If verification is successful returns either an existing user object (with id > 0)
|
// AuthenticationResult implementation with details about the authentication, or, may return an
|
||||||
// or a new user object (with id = 0) populated with the information that was found
|
// AnonymousAuthentication if the authentication method doesn't indicate that the request is authenticated. An error
|
||||||
// in the authentication data (username or email).
|
// is only returned if a failure occurred while checking authentication.
|
||||||
// Second argument returns err if verification fails, otherwise
|
Verify(http *http.Request, w http.ResponseWriter, sess SessionStore) (AuthenticationResult, error)
|
||||||
// First return argument returns nil if no matched verification condition
|
|
||||||
Verify(http *http.Request, w http.ResponseWriter, store DataStore, sess SessionStore) (*user_model.User, error)
|
|
||||||
|
|
||||||
Name() string
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// PasswordAuthenticator represents a source of authentication
|
// PasswordAuthenticator represents a source of authentication
|
||||||
|
|
@ -45,3 +44,62 @@ type LocalTwoFASkipper interface {
|
||||||
type SynchronizableSource interface {
|
type SynchronizableSource interface {
|
||||||
Sync(ctx context.Context, updateExisting bool) error
|
Sync(ctx context.Context, updateExisting bool) error
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type AuthenticationResult interface {
|
||||||
|
// May return `nil` to represent an anonymous, unauthenticated user.
|
||||||
|
User() *user_model.User
|
||||||
|
|
||||||
|
// Optional permission scope indicated by the authentication method
|
||||||
|
Scope() optional.Option[auth_model.AccessTokenScope]
|
||||||
|
// Optional authorization reducer indicated by the authentication method
|
||||||
|
Reducer() authz.AuthorizationReducer
|
||||||
|
|
||||||
|
// Identifies if the authentication involved the user's password. If so, and the user has 2FA enabled, some
|
||||||
|
// restrictions may be applied.
|
||||||
|
IsPasswordAuthentication() bool
|
||||||
|
|
||||||
|
// Identifies if the authentication was performed by a reverse proxy.
|
||||||
|
IsReverseProxyAuthentication() bool
|
||||||
|
|
||||||
|
// Identifies specifically that the OAuth2 JWT authentication method was used. If so, some related OAuth2 API
|
||||||
|
// endpoints may be accessible that otherwise wouldn't be.
|
||||||
|
IsOAuth2JWTAuthentication() bool
|
||||||
|
|
||||||
|
// If authenticated as an Actions task (using ${{ forgejo.token }}), then indicates the specific task that performed
|
||||||
|
// the authentication.
|
||||||
|
ActionsTaskID() optional.Option[int64]
|
||||||
|
}
|
||||||
|
|
||||||
|
type BaseAuthenticationResult struct{}
|
||||||
|
|
||||||
|
func (*BaseAuthenticationResult) IsOAuth2JWTAuthentication() bool {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*BaseAuthenticationResult) IsPasswordAuthentication() bool {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*BaseAuthenticationResult) IsReverseProxyAuthentication() bool {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*BaseAuthenticationResult) ActionsTaskID() optional.Option[int64] {
|
||||||
|
return optional.None[int64]()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*BaseAuthenticationResult) Reducer() authz.AuthorizationReducer {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*BaseAuthenticationResult) Scope() optional.Option[auth_model.AccessTokenScope] {
|
||||||
|
return optional.None[auth_model.AccessTokenScope]()
|
||||||
|
}
|
||||||
|
|
||||||
|
type UnauthenticatedResult struct {
|
||||||
|
*BaseAuthenticationResult
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*UnauthenticatedResult) User() *user_model.User {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
package auth
|
package method
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
// Copyright 2019 The Gitea Authors. All rights reserved.
|
// Copyright 2019 The Gitea Authors. All rights reserved.
|
||||||
// SPDX-License-Identifier: MIT
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
package auth
|
package method
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
@ -17,6 +17,7 @@ import (
|
||||||
"forgejo.org/modules/session"
|
"forgejo.org/modules/session"
|
||||||
"forgejo.org/modules/setting"
|
"forgejo.org/modules/setting"
|
||||||
"forgejo.org/modules/web/middleware"
|
"forgejo.org/modules/web/middleware"
|
||||||
|
"forgejo.org/services/auth"
|
||||||
user_service "forgejo.org/services/user"
|
user_service "forgejo.org/services/user"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -63,7 +64,7 @@ func isArchivePath(req *http.Request) bool {
|
||||||
}
|
}
|
||||||
|
|
||||||
// handleSignIn clears existing session variables and stores new ones for the specified user object
|
// handleSignIn clears existing session variables and stores new ones for the specified user object
|
||||||
func handleSignIn(resp http.ResponseWriter, req *http.Request, sess SessionStore, user *user_model.User) {
|
func handleSignIn(resp http.ResponseWriter, req *http.Request, sess auth.SessionStore, user *user_model.User) {
|
||||||
// We need to regenerate the session...
|
// We need to regenerate the session...
|
||||||
newSess, err := session.RegenerateSession(resp, req)
|
newSess, err := session.RegenerateSession(resp, req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
33
services/auth/method/auth_result_accesstoken.go
Normal file
33
services/auth/method/auth_result_accesstoken.go
Normal file
|
|
@ -0,0 +1,33 @@
|
||||||
|
// Copyright 2026 The Forgejo Authors. All rights reserved.
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
package method
|
||||||
|
|
||||||
|
import (
|
||||||
|
auth_model "forgejo.org/models/auth"
|
||||||
|
user_model "forgejo.org/models/user"
|
||||||
|
"forgejo.org/modules/optional"
|
||||||
|
"forgejo.org/services/auth"
|
||||||
|
"forgejo.org/services/authz"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ auth.AuthenticationResult = &accessTokenAuthenticationResult{}
|
||||||
|
|
||||||
|
type accessTokenAuthenticationResult struct {
|
||||||
|
*auth.BaseAuthenticationResult
|
||||||
|
user *user_model.User
|
||||||
|
scope auth_model.AccessTokenScope
|
||||||
|
reducer authz.AuthorizationReducer
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *accessTokenAuthenticationResult) User() *user_model.User {
|
||||||
|
return r.user
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *accessTokenAuthenticationResult) Scope() optional.Option[auth_model.AccessTokenScope] {
|
||||||
|
return optional.Some(r.scope)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *accessTokenAuthenticationResult) Reducer() authz.AuthorizationReducer {
|
||||||
|
return r.reducer
|
||||||
|
}
|
||||||
31
services/auth/method/auth_result_actionstask.go
Normal file
31
services/auth/method/auth_result_actionstask.go
Normal file
|
|
@ -0,0 +1,31 @@
|
||||||
|
// Copyright 2026 The Forgejo Authors. All rights reserved.
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
package method
|
||||||
|
|
||||||
|
import (
|
||||||
|
auth_model "forgejo.org/models/auth"
|
||||||
|
user_model "forgejo.org/models/user"
|
||||||
|
"forgejo.org/modules/optional"
|
||||||
|
"forgejo.org/services/auth"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ auth.AuthenticationResult = &actionsTaskTokenAuthenticationResult{}
|
||||||
|
|
||||||
|
type actionsTaskTokenAuthenticationResult struct {
|
||||||
|
*auth.BaseAuthenticationResult
|
||||||
|
user *user_model.User
|
||||||
|
taskID int64
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *actionsTaskTokenAuthenticationResult) Scope() optional.Option[auth_model.AccessTokenScope] {
|
||||||
|
return optional.None[auth_model.AccessTokenScope]()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *actionsTaskTokenAuthenticationResult) User() *user_model.User {
|
||||||
|
return r.user
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *actionsTaskTokenAuthenticationResult) ActionsTaskID() optional.Option[int64] {
|
||||||
|
return optional.Some(r.taskID)
|
||||||
|
}
|
||||||
24
services/auth/method/auth_result_basicpassword.go
Normal file
24
services/auth/method/auth_result_basicpassword.go
Normal file
|
|
@ -0,0 +1,24 @@
|
||||||
|
// Copyright 2026 The Forgejo Authors. All rights reserved.
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
package method
|
||||||
|
|
||||||
|
import (
|
||||||
|
user_model "forgejo.org/models/user"
|
||||||
|
"forgejo.org/services/auth"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ auth.AuthenticationResult = &basicPaswordAuthenticationResult{}
|
||||||
|
|
||||||
|
type basicPaswordAuthenticationResult struct {
|
||||||
|
*auth.BaseAuthenticationResult
|
||||||
|
user *user_model.User
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*basicPaswordAuthenticationResult) IsPasswordAuthentication() bool {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *basicPaswordAuthenticationResult) User() *user_model.User {
|
||||||
|
return r.user
|
||||||
|
}
|
||||||
20
services/auth/method/auth_result_httpsign.go
Normal file
20
services/auth/method/auth_result_httpsign.go
Normal file
|
|
@ -0,0 +1,20 @@
|
||||||
|
// Copyright 2026 The Forgejo Authors. All rights reserved.
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
package method
|
||||||
|
|
||||||
|
import (
|
||||||
|
user_model "forgejo.org/models/user"
|
||||||
|
"forgejo.org/services/auth"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ auth.AuthenticationResult = &httpSignAuthenticationResult{}
|
||||||
|
|
||||||
|
type httpSignAuthenticationResult struct {
|
||||||
|
*auth.BaseAuthenticationResult
|
||||||
|
user *user_model.User
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *httpSignAuthenticationResult) User() *user_model.User {
|
||||||
|
return r.user
|
||||||
|
}
|
||||||
31
services/auth/method/auth_result_oauth.go
Normal file
31
services/auth/method/auth_result_oauth.go
Normal file
|
|
@ -0,0 +1,31 @@
|
||||||
|
// Copyright 2026 The Forgejo Authors. All rights reserved.
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
package method
|
||||||
|
|
||||||
|
import (
|
||||||
|
auth_model "forgejo.org/models/auth"
|
||||||
|
user_model "forgejo.org/models/user"
|
||||||
|
"forgejo.org/modules/optional"
|
||||||
|
"forgejo.org/services/auth"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ auth.AuthenticationResult = &oAuth2JWTAuthenticationResult{}
|
||||||
|
|
||||||
|
type oAuth2JWTAuthenticationResult struct {
|
||||||
|
*auth.BaseAuthenticationResult
|
||||||
|
user *user_model.User
|
||||||
|
scope optional.Option[auth_model.AccessTokenScope]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*oAuth2JWTAuthenticationResult) IsOAuth2JWTAuthentication() bool {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *oAuth2JWTAuthenticationResult) User() *user_model.User {
|
||||||
|
return r.user
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *oAuth2JWTAuthenticationResult) Scope() optional.Option[auth_model.AccessTokenScope] {
|
||||||
|
return r.scope
|
||||||
|
}
|
||||||
24
services/auth/method/auth_result_reverseproxy.go
Normal file
24
services/auth/method/auth_result_reverseproxy.go
Normal file
|
|
@ -0,0 +1,24 @@
|
||||||
|
// Copyright 2026 The Forgejo Authors. All rights reserved.
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
package method
|
||||||
|
|
||||||
|
import (
|
||||||
|
user_model "forgejo.org/models/user"
|
||||||
|
"forgejo.org/services/auth"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ auth.AuthenticationResult = &reverseProxyAuthenticationResult{}
|
||||||
|
|
||||||
|
type reverseProxyAuthenticationResult struct {
|
||||||
|
*auth.BaseAuthenticationResult
|
||||||
|
user *user_model.User
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *reverseProxyAuthenticationResult) User() *user_model.User {
|
||||||
|
return r.user
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*reverseProxyAuthenticationResult) IsReverseProxyAuthentication() bool {
|
||||||
|
return true
|
||||||
|
}
|
||||||
20
services/auth/method/auth_result_session.go
Normal file
20
services/auth/method/auth_result_session.go
Normal file
|
|
@ -0,0 +1,20 @@
|
||||||
|
// Copyright 2026 The Forgejo Authors. All rights reserved.
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
package method
|
||||||
|
|
||||||
|
import (
|
||||||
|
user_model "forgejo.org/models/user"
|
||||||
|
"forgejo.org/services/auth"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ auth.AuthenticationResult = &sessionAuthenticationResult{}
|
||||||
|
|
||||||
|
type sessionAuthenticationResult struct {
|
||||||
|
*auth.BaseAuthenticationResult
|
||||||
|
user *user_model.User
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *sessionAuthenticationResult) User() *user_model.User {
|
||||||
|
return r.user
|
||||||
|
}
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
// Copyright 2019 The Gitea Authors. All rights reserved.
|
// Copyright 2019 The Gitea Authors. All rights reserved.
|
||||||
// SPDX-License-Identifier: MIT
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
package auth
|
package method
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
// Copyright 2019 The Gitea Authors. All rights reserved.
|
// Copyright 2019 The Gitea Authors. All rights reserved.
|
||||||
// SPDX-License-Identifier: MIT
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
package auth
|
package method
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
|
|
@ -14,48 +14,42 @@ import (
|
||||||
user_model "forgejo.org/models/user"
|
user_model "forgejo.org/models/user"
|
||||||
"forgejo.org/modules/base"
|
"forgejo.org/modules/base"
|
||||||
"forgejo.org/modules/log"
|
"forgejo.org/modules/log"
|
||||||
|
"forgejo.org/modules/optional"
|
||||||
"forgejo.org/modules/setting"
|
"forgejo.org/modules/setting"
|
||||||
"forgejo.org/modules/util"
|
"forgejo.org/modules/util"
|
||||||
"forgejo.org/modules/web/middleware"
|
"forgejo.org/modules/web/middleware"
|
||||||
|
"forgejo.org/services/auth"
|
||||||
"forgejo.org/services/authz"
|
"forgejo.org/services/authz"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Ensure the struct implements the interface.
|
// Ensure the struct implements the interface.
|
||||||
var (
|
var (
|
||||||
_ Method = &Basic{}
|
_ auth.Method = &Basic{}
|
||||||
)
|
)
|
||||||
|
|
||||||
// BasicMethodName is the constant name of the basic authentication method
|
|
||||||
const BasicMethodName = "basic"
|
|
||||||
|
|
||||||
// Basic implements the Auth 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 BasicMethodName
|
|
||||||
}
|
|
||||||
|
|
||||||
// Verify 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
|
// "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) Verify(req *http.Request, w http.ResponseWriter, store DataStore, sess SessionStore) (*user_model.User, error) {
|
func (b *Basic) Verify(req *http.Request, w http.ResponseWriter, _ auth.SessionStore) (auth.AuthenticationResult, error) {
|
||||||
// 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.IsAPIPath(req) && !isContainerPath(req) && !isAttachmentDownload(req) && !isGitRawOrAttachOrLFSPath(req) {
|
if !middleware.IsAPIPath(req) && !isContainerPath(req) && !isAttachmentDownload(req) && !isGitRawOrAttachOrLFSPath(req) {
|
||||||
return nil, nil
|
return &auth.UnauthenticatedResult{}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
baHead := req.Header.Get("Authorization")
|
baHead := req.Header.Get("Authorization")
|
||||||
if len(baHead) == 0 {
|
if len(baHead) == 0 {
|
||||||
return nil, nil
|
return &auth.UnauthenticatedResult{}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
auths := strings.SplitN(baHead, " ", 2)
|
auths := strings.SplitN(baHead, " ", 2)
|
||||||
if len(auths) != 2 || (strings.ToLower(auths[0]) != "basic") {
|
if len(auths) != 2 || (strings.ToLower(auths[0]) != "basic") {
|
||||||
return nil, nil
|
return &auth.UnauthenticatedResult{}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
uname, passwd, _ := base.BasicAuthDecode(auths[1])
|
uname, passwd, _ := base.BasicAuthDecode(auths[1])
|
||||||
|
|
@ -83,13 +77,13 @@ func (b *Basic) Verify(req *http.Request, w http.ResponseWriter, store DataStore
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
store.GetData()["IsApiToken"] = true
|
var scope auth_model.AccessTokenScope
|
||||||
if grantScopes != "" {
|
if grantScopes != "" {
|
||||||
store.GetData()["ApiTokenScope"] = auth_model.AccessTokenScope(grantScopes)
|
scope = auth_model.AccessTokenScope(grantScopes)
|
||||||
} else {
|
} else {
|
||||||
store.GetData()["ApiTokenScope"] = auth_model.AccessTokenScopeAll // fallback to all
|
scope = auth_model.AccessTokenScopeAll // fallback to all
|
||||||
}
|
}
|
||||||
return u, nil
|
return &oAuth2JWTAuthenticationResult{user: u, scope: optional.Some(scope)}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// check personal access token
|
// check personal access token
|
||||||
|
|
@ -106,17 +100,13 @@ func (b *Basic) Verify(req *http.Request, w http.ResponseWriter, store DataStore
|
||||||
log.Error("UpdateLastUsed: %v", err)
|
log.Error("UpdateLastUsed: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
store.GetData()["IsApiToken"] = true
|
|
||||||
store.GetData()["ApiTokenScope"] = token.Scope
|
|
||||||
|
|
||||||
reducer, err := authz.GetAuthorizationReducerForAccessToken(req.Context(), token)
|
reducer, err := authz.GetAuthorizationReducerForAccessToken(req.Context(), token)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("authz.GetAuthorizationReducerForAccessToken: %v", err)
|
log.Error("authz.GetAuthorizationReducerForAccessToken: %v", err)
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
store.GetData()["ApiTokenReducer"] = reducer
|
|
||||||
|
|
||||||
return u, nil
|
return &accessTokenAuthenticationResult{user: u, scope: token.Scope, reducer: reducer}, nil
|
||||||
} else if !auth_model.IsErrAccessTokenNotExist(err) && !auth_model.IsErrAccessTokenEmpty(err) {
|
} else if !auth_model.IsErrAccessTokenNotExist(err) && !auth_model.IsErrAccessTokenEmpty(err) {
|
||||||
log.Error("GetAccessTokenBySha: %v", err)
|
log.Error("GetAccessTokenBySha: %v", err)
|
||||||
}
|
}
|
||||||
|
|
@ -125,15 +115,11 @@ func (b *Basic) Verify(req *http.Request, w http.ResponseWriter, store DataStore
|
||||||
task, err := actions_model.GetRunningTaskByToken(req.Context(), authToken)
|
task, err := actions_model.GetRunningTaskByToken(req.Context(), authToken)
|
||||||
if err == nil && task != nil {
|
if err == nil && task != nil {
|
||||||
log.Trace("Basic Authorization: Valid AccessToken for task[%d]", task.ID)
|
log.Trace("Basic Authorization: Valid AccessToken for task[%d]", task.ID)
|
||||||
|
return &actionsTaskTokenAuthenticationResult{user: user_model.NewActionsUser(), taskID: task.ID}, nil
|
||||||
store.GetData()["IsActionsToken"] = true
|
|
||||||
store.GetData()["ActionsTaskID"] = task.ID
|
|
||||||
|
|
||||||
return user_model.NewActionsUser(), nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if !setting.Service.EnableBasicAuth {
|
if !setting.Service.EnableBasicAuth {
|
||||||
return nil, nil
|
return &auth.UnauthenticatedResult{}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Trace("Basic Authorization: Attempting SignIn for %s", uname)
|
log.Trace("Basic Authorization: Attempting SignIn for %s", uname)
|
||||||
|
|
@ -155,7 +141,7 @@ func (b *Basic) Verify(req *http.Request, w http.ResponseWriter, store DataStore
|
||||||
return nil, errors.New("Basic authorization is not allowed while having security keys enrolled")
|
return nil, errors.New("Basic authorization is not allowed while having security keys enrolled")
|
||||||
}
|
}
|
||||||
|
|
||||||
if skipper, ok := source.Cfg.(LocalTwoFASkipper); !ok || !skipper.IsSkipLocalTwoFA() {
|
if skipper, ok := source.Cfg.(auth.LocalTwoFASkipper); !ok || !skipper.IsSkipLocalTwoFA() {
|
||||||
if err := validateTOTP(req, u); err != nil {
|
if err := validateTOTP(req, u); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
@ -163,8 +149,7 @@ func (b *Basic) Verify(req *http.Request, w http.ResponseWriter, store DataStore
|
||||||
|
|
||||||
log.Trace("Basic Authorization: Logged in user %-v", u)
|
log.Trace("Basic Authorization: Logged in user %-v", u)
|
||||||
|
|
||||||
store.GetData()["IsPasswordLogin"] = true
|
return &basicPaswordAuthenticationResult{user: u}, nil
|
||||||
return u, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func getOtpHeader(header http.Header) string {
|
func getOtpHeader(header http.Header) string {
|
||||||
|
|
@ -1,51 +1,41 @@
|
||||||
// Copyright 2021 The Gitea Authors. All rights reserved.
|
// Copyright 2021 The Gitea Authors. All rights reserved.
|
||||||
// SPDX-License-Identifier: MIT
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
package auth
|
package method
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
|
||||||
|
|
||||||
user_model "forgejo.org/models/user"
|
"forgejo.org/services/auth"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Ensure the struct implements the interface.
|
// Ensure the struct implements the interface.
|
||||||
var (
|
var (
|
||||||
_ Method = &Group{}
|
_ auth.Method = &Group{}
|
||||||
)
|
)
|
||||||
|
|
||||||
// Group implements the Auth interface with serval Auth.
|
// Group implements the Auth interface with serval Auth.
|
||||||
type Group struct {
|
type Group struct {
|
||||||
methods []Method
|
methods []auth.Method
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewGroup creates a new auth group
|
// NewGroup creates a new auth group
|
||||||
func NewGroup(methods ...Method) *Group {
|
func NewGroup(methods ...auth.Method) *Group {
|
||||||
return &Group{
|
return &Group{
|
||||||
methods: methods,
|
methods: methods,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add adds a new method to group
|
// Add adds a new method to group
|
||||||
func (b *Group) Add(method Method) {
|
func (b *Group) Add(method auth.Method) {
|
||||||
b.methods = append(b.methods, method)
|
b.methods = append(b.methods, method)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Name returns group's methods name
|
func (b *Group) Verify(req *http.Request, w http.ResponseWriter, sess auth.SessionStore) (auth.AuthenticationResult, error) {
|
||||||
func (b *Group) Name() string {
|
|
||||||
names := make([]string, 0, len(b.methods))
|
|
||||||
for _, m := range b.methods {
|
|
||||||
names = append(names, m.Name())
|
|
||||||
}
|
|
||||||
return strings.Join(names, ",")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *Group) Verify(req *http.Request, w http.ResponseWriter, store DataStore, sess SessionStore) (*user_model.User, error) {
|
|
||||||
// Try to sign in with each of the enabled plugins
|
// Try to sign in with each of the enabled plugins
|
||||||
var retErr error
|
var retErr error
|
||||||
for _, m := range b.methods {
|
for _, m := range b.methods {
|
||||||
user, err := m.Verify(req, w, store, sess)
|
authResult, err := m.Verify(req, w, sess)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if retErr == nil {
|
if retErr == nil {
|
||||||
retErr = err
|
retErr = err
|
||||||
|
|
@ -57,16 +47,17 @@ func (b *Group) Verify(req *http.Request, w http.ResponseWriter, store DataStore
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
// If any method returns a user, we can stop trying.
|
// If any method returns an authenticated result, we can stop trying. Return and ignore any error returned by
|
||||||
// Return the user and ignore any error returned by previous methods.
|
// previous methods.
|
||||||
if user != nil {
|
if authResult.User() != nil {
|
||||||
if store.GetData()["AuthedMethod"] == nil {
|
return authResult, nil
|
||||||
store.GetData()["AuthedMethod"] = m.Name()
|
|
||||||
}
|
|
||||||
return user, nil
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if retErr != nil {
|
||||||
// If no method returns a user, return the error returned by the first method.
|
// If no method returns a user, return the error returned by the first method.
|
||||||
return nil, retErr
|
return nil, retErr
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return &auth.UnauthenticatedResult{}, nil
|
||||||
|
}
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
// Copyright 2021 The Gitea Authors. All rights reserved.
|
// Copyright 2021 The Gitea Authors. All rights reserved.
|
||||||
// SPDX-License-Identifier: MIT
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
package auth
|
package method
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
|
@ -16,6 +16,7 @@ import (
|
||||||
user_model "forgejo.org/models/user"
|
user_model "forgejo.org/models/user"
|
||||||
"forgejo.org/modules/log"
|
"forgejo.org/modules/log"
|
||||||
"forgejo.org/modules/setting"
|
"forgejo.org/modules/setting"
|
||||||
|
"forgejo.org/services/auth"
|
||||||
|
|
||||||
"github.com/42wim/httpsig"
|
"github.com/42wim/httpsig"
|
||||||
"golang.org/x/crypto/ssh"
|
"golang.org/x/crypto/ssh"
|
||||||
|
|
@ -23,7 +24,7 @@ import (
|
||||||
|
|
||||||
// Ensure the struct implements the interface.
|
// Ensure the struct implements the interface.
|
||||||
var (
|
var (
|
||||||
_ Method = &HTTPSign{}
|
_ auth.Method = &HTTPSign{}
|
||||||
)
|
)
|
||||||
|
|
||||||
// HTTPSign implements the Auth interface and authenticates requests (API requests
|
// HTTPSign implements the Auth interface and authenticates requests (API requests
|
||||||
|
|
@ -31,18 +32,13 @@ var (
|
||||||
// more information can be found on https://github.com/go-fed/httpsig
|
// more information can be found on https://github.com/go-fed/httpsig
|
||||||
type HTTPSign struct{}
|
type HTTPSign struct{}
|
||||||
|
|
||||||
// Name represents the name of auth method
|
|
||||||
func (h *HTTPSign) Name() string {
|
|
||||||
return "httpsign"
|
|
||||||
}
|
|
||||||
|
|
||||||
// Verify extracts and validates HTTPsign from the Signature header of the request and returns
|
// Verify extracts and validates HTTPsign from the Signature header of the request and returns
|
||||||
// the corresponding user object on successful validation.
|
// the corresponding user object on successful validation.
|
||||||
// Returns nil if header is empty or validation fails.
|
// Returns nil if header is empty or validation fails.
|
||||||
func (h *HTTPSign) Verify(req *http.Request, w http.ResponseWriter, store DataStore, sess SessionStore) (*user_model.User, error) {
|
func (h *HTTPSign) Verify(req *http.Request, w http.ResponseWriter, _ auth.SessionStore) (auth.AuthenticationResult, error) {
|
||||||
sigHead := req.Header.Get("Signature")
|
sigHead := req.Header.Get("Signature")
|
||||||
if len(sigHead) == 0 {
|
if len(sigHead) == 0 {
|
||||||
return nil, nil
|
return &auth.UnauthenticatedResult{}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
|
@ -53,14 +49,14 @@ func (h *HTTPSign) Verify(req *http.Request, w http.ResponseWriter, store DataSt
|
||||||
if len(req.Header.Get("X-Ssh-Certificate")) != 0 {
|
if len(req.Header.Get("X-Ssh-Certificate")) != 0 {
|
||||||
// Handle Signature signed by SSH certificates
|
// Handle Signature signed by SSH certificates
|
||||||
if len(setting.SSH.TrustedUserCAKeys) == 0 {
|
if len(setting.SSH.TrustedUserCAKeys) == 0 {
|
||||||
return nil, nil
|
return &auth.UnauthenticatedResult{}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
publicKey, err = VerifyCert(req)
|
publicKey, err = VerifyCert(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Debug("VerifyCert on request from %s: failed: %v", req.RemoteAddr, err)
|
log.Debug("VerifyCert on request from %s: failed: %v", req.RemoteAddr, err)
|
||||||
log.Warn("Failed authentication attempt from %s", req.RemoteAddr)
|
log.Warn("Failed authentication attempt from %s", req.RemoteAddr)
|
||||||
return nil, nil
|
return &auth.UnauthenticatedResult{}, nil
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Handle Signature signed by Public Key
|
// Handle Signature signed by Public Key
|
||||||
|
|
@ -68,7 +64,7 @@ func (h *HTTPSign) Verify(req *http.Request, w http.ResponseWriter, store DataSt
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Debug("VerifyPubKey on request from %s: failed: %v", req.RemoteAddr, err)
|
log.Debug("VerifyPubKey on request from %s: failed: %v", req.RemoteAddr, err)
|
||||||
log.Warn("Failed authentication attempt from %s", req.RemoteAddr)
|
log.Warn("Failed authentication attempt from %s", req.RemoteAddr)
|
||||||
return nil, nil
|
return &auth.UnauthenticatedResult{}, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -78,11 +74,8 @@ func (h *HTTPSign) Verify(req *http.Request, w http.ResponseWriter, store DataSt
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
store.GetData()["IsApiToken"] = true
|
|
||||||
|
|
||||||
log.Trace("HTTP Sign: Logged in user %-v", u)
|
log.Trace("HTTP Sign: Logged in user %-v", u)
|
||||||
|
return &httpSignAuthenticationResult{user: u}, nil
|
||||||
return u, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func VerifyPubKey(r *http.Request) (*asymkey_model.PublicKey, error) {
|
func VerifyPubKey(r *http.Request) (*asymkey_model.PublicKey, error) {
|
||||||
14
services/auth/method/main_test.go
Normal file
14
services/auth/method/main_test.go
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
// Copyright 2023 The Gitea Authors. All rights reserved.
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
package method
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"forgejo.org/models/unittest"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestMain(m *testing.M) {
|
||||||
|
unittest.MainTest(m)
|
||||||
|
}
|
||||||
|
|
@ -2,10 +2,11 @@
|
||||||
// Copyright 2019 The Gitea Authors. All rights reserved.
|
// Copyright 2019 The Gitea Authors. All rights reserved.
|
||||||
// SPDX-License-Identifier: MIT
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
package auth
|
package method
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"errors"
|
||||||
"net/http"
|
"net/http"
|
||||||
"slices"
|
"slices"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
@ -15,17 +16,19 @@ import (
|
||||||
auth_model "forgejo.org/models/auth"
|
auth_model "forgejo.org/models/auth"
|
||||||
user_model "forgejo.org/models/user"
|
user_model "forgejo.org/models/user"
|
||||||
"forgejo.org/modules/log"
|
"forgejo.org/modules/log"
|
||||||
|
"forgejo.org/modules/optional"
|
||||||
"forgejo.org/modules/setting"
|
"forgejo.org/modules/setting"
|
||||||
"forgejo.org/modules/util"
|
"forgejo.org/modules/util"
|
||||||
"forgejo.org/modules/web/middleware"
|
"forgejo.org/modules/web/middleware"
|
||||||
"forgejo.org/services/actions"
|
"forgejo.org/services/actions"
|
||||||
|
"forgejo.org/services/auth"
|
||||||
"forgejo.org/services/auth/source/oauth2"
|
"forgejo.org/services/auth/source/oauth2"
|
||||||
"forgejo.org/services/authz"
|
"forgejo.org/services/authz"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Ensure the struct implements the interface.
|
// Ensure the struct implements the interface.
|
||||||
var (
|
var (
|
||||||
_ Method = &OAuth2{}
|
_ auth.Method = &OAuth2{}
|
||||||
)
|
)
|
||||||
|
|
||||||
// grantAdditionalScopes returns valid scopes coming from grant
|
// grantAdditionalScopes returns valid scopes coming from grant
|
||||||
|
|
@ -113,11 +116,6 @@ func CheckTaskIsRunning(ctx context.Context, taskID int64) bool {
|
||||||
// "Authorization" header.
|
// "Authorization" header.
|
||||||
type OAuth2 struct{}
|
type OAuth2 struct{}
|
||||||
|
|
||||||
// Name represents the name of auth method
|
|
||||||
func (o *OAuth2) Name() string {
|
|
||||||
return "oauth2"
|
|
||||||
}
|
|
||||||
|
|
||||||
// parseToken returns the token from request, and a boolean value
|
// parseToken returns the token from request, and a boolean value
|
||||||
// representing whether the token exists or not
|
// representing whether the token exists or not
|
||||||
func parseToken(req *http.Request) (string, bool) {
|
func parseToken(req *http.Request) (string, bool) {
|
||||||
|
|
@ -148,33 +146,39 @@ func parseToken(req *http.Request) (string, bool) {
|
||||||
// userIDFromToken returns the user id corresponding to the OAuth token.
|
// userIDFromToken returns the user id corresponding to the OAuth token.
|
||||||
// It will set 'IsApiToken' to true if the token is an API token and
|
// It will set 'IsApiToken' to true if the token is an API token and
|
||||||
// set 'ApiTokenScope' to the scope of the access token
|
// set 'ApiTokenScope' to the scope of the access token
|
||||||
func (o *OAuth2) userIDFromToken(ctx context.Context, tokenSHA string, store DataStore) (int64, error) {
|
func (o *OAuth2) userIDFromToken(ctx context.Context, tokenSHA string) (auth.AuthenticationResult, error) {
|
||||||
if tokenSHA == "" {
|
if tokenSHA == "" {
|
||||||
return 0, auth_model.ErrAccessTokenEmpty{}
|
return nil, auth_model.ErrAccessTokenEmpty{}
|
||||||
}
|
}
|
||||||
// Let's see if token is valid.
|
// Let's see if token is valid.
|
||||||
if strings.Contains(tokenSHA, ".") {
|
if strings.Contains(tokenSHA, ".") {
|
||||||
// First attempt to decode an actions JWT, returning the actions user
|
// First attempt to decode an actions JWT, returning the actions user
|
||||||
if taskID, err := actions.TokenToTaskID(tokenSHA); err == nil {
|
if taskID, err := actions.TokenToTaskID(tokenSHA); err == nil {
|
||||||
if CheckTaskIsRunning(ctx, taskID) {
|
if CheckTaskIsRunning(ctx, taskID) {
|
||||||
store.GetData()["IsActionsToken"] = true
|
return &actionsTaskTokenAuthenticationResult{user: user_model.NewActionsUser(), taskID: taskID}, nil
|
||||||
store.GetData()["ActionsTaskID"] = taskID
|
|
||||||
return user_model.ActionsUserID, nil
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Otherwise, check if this is an OAuth access token
|
// Otherwise, check if this is an OAuth access token
|
||||||
uid, grantScopes := CheckOAuthAccessToken(ctx, tokenSHA)
|
uid, grantScopes := CheckOAuthAccessToken(ctx, tokenSHA)
|
||||||
|
var accessTokenScope optional.Option[auth_model.AccessTokenScope]
|
||||||
if uid != 0 {
|
if uid != 0 {
|
||||||
store.GetData()["IsApiToken"] = true
|
|
||||||
if grantScopes != "" {
|
if grantScopes != "" {
|
||||||
store.GetData()["ApiTokenScope"] = auth_model.AccessTokenScope(grantScopes)
|
accessTokenScope = optional.Some(auth_model.AccessTokenScope(grantScopes))
|
||||||
} else {
|
} else {
|
||||||
store.GetData()["ApiTokenScope"] = auth_model.AccessTokenScopeAll // fallback to all
|
accessTokenScope = optional.Some(auth_model.AccessTokenScopeAll) // fallback to all
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return uid, nil
|
user, err := user_model.GetPossibleUserByID(ctx, uid)
|
||||||
|
if err != nil {
|
||||||
|
if !user_model.IsErrUserNotExist(err) {
|
||||||
|
log.Error("GetUserByName: %v", err)
|
||||||
}
|
}
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &oAuth2JWTAuthenticationResult{user: user, scope: accessTokenScope}, nil
|
||||||
|
}
|
||||||
|
|
||||||
t, err := auth_model.GetAccessTokenBySHA(ctx, tokenSHA)
|
t, err := auth_model.GetAccessTokenBySHA(ctx, tokenSHA)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if auth_model.IsErrAccessTokenNotExist(err) {
|
if auth_model.IsErrAccessTokenNotExist(err) {
|
||||||
|
|
@ -182,68 +186,63 @@ func (o *OAuth2) userIDFromToken(ctx context.Context, tokenSHA string, store Dat
|
||||||
task, err := actions_model.GetRunningTaskByToken(ctx, tokenSHA)
|
task, err := actions_model.GetRunningTaskByToken(ctx, tokenSHA)
|
||||||
if err == nil && task != nil {
|
if err == nil && task != nil {
|
||||||
log.Trace("Basic Authorization: Valid AccessToken for task[%d]", task.ID)
|
log.Trace("Basic Authorization: Valid AccessToken for task[%d]", task.ID)
|
||||||
|
return &actionsTaskTokenAuthenticationResult{user: user_model.NewActionsUser(), taskID: task.ID}, nil
|
||||||
store.GetData()["IsActionsToken"] = true
|
|
||||||
store.GetData()["ActionsTaskID"] = task.ID
|
|
||||||
|
|
||||||
return user_model.ActionsUserID, nil
|
|
||||||
}
|
}
|
||||||
} else if !auth_model.IsErrAccessTokenNotExist(err) && !auth_model.IsErrAccessTokenEmpty(err) {
|
} else if !auth_model.IsErrAccessTokenNotExist(err) && !auth_model.IsErrAccessTokenEmpty(err) {
|
||||||
log.Error("GetAccessTokenBySHA: %v", err)
|
log.Error("GetAccessTokenBySHA: %v", err)
|
||||||
|
return nil, err
|
||||||
}
|
}
|
||||||
return 0, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if err := t.UpdateLastUsed(ctx); err != nil {
|
if err := t.UpdateLastUsed(ctx); err != nil {
|
||||||
log.Error("UpdateLastUsed: %v", err)
|
log.Error("UpdateLastUsed: %v", err)
|
||||||
}
|
}
|
||||||
if t.UID == 0 {
|
if t.UID == 0 {
|
||||||
return 0, auth_model.ErrAccessTokenNotExist{}
|
return nil, auth_model.ErrAccessTokenNotExist{}
|
||||||
}
|
}
|
||||||
store.GetData()["IsApiToken"] = true
|
|
||||||
store.GetData()["ApiTokenScope"] = t.Scope
|
|
||||||
|
|
||||||
reducer, err := authz.GetAuthorizationReducerForAccessToken(ctx, t)
|
reducer, err := authz.GetAuthorizationReducerForAccessToken(ctx, t)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("authz.GetAuthorizationReducerForAccessToken: %v", err)
|
log.Error("authz.GetAuthorizationReducerForAccessToken: %v", err)
|
||||||
return 0, err
|
return nil, err
|
||||||
}
|
}
|
||||||
store.GetData()["ApiTokenReducer"] = reducer
|
|
||||||
|
|
||||||
return t.UID, nil
|
u, err := user_model.GetUserByID(ctx, t.UID)
|
||||||
|
if err != nil {
|
||||||
|
log.Error("GetUserByID: %v", err)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &accessTokenAuthenticationResult{user: u, scope: t.Scope, reducer: reducer}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Verify 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.
|
// 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) Verify(req *http.Request, w http.ResponseWriter, store DataStore, sess SessionStore) (*user_model.User, error) {
|
func (o *OAuth2) Verify(req *http.Request, w http.ResponseWriter, _ auth.SessionStore) (auth.AuthenticationResult, error) {
|
||||||
// These paths are not API paths, but we still want to check for tokens because they maybe in the API returned URLs
|
// These paths are not API paths, but we still want to check for tokens because they maybe in the API returned URLs
|
||||||
if !middleware.IsAPIPath(req) && !isAttachmentDownload(req) && !isAuthenticatedTokenRequest(req) &&
|
if !middleware.IsAPIPath(req) && !isAttachmentDownload(req) && !isAuthenticatedTokenRequest(req) &&
|
||||||
!isGitRawOrAttachPath(req) && !isArchivePath(req) {
|
!isGitRawOrAttachPath(req) && !isArchivePath(req) {
|
||||||
return nil, nil
|
return &auth.UnauthenticatedResult{}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
token, ok := parseToken(req)
|
token, ok := parseToken(req)
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil, nil
|
return &auth.UnauthenticatedResult{}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
id, err := o.userIDFromToken(req.Context(), token, store)
|
auth, err := o.userIDFromToken(req.Context(), token)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
} else if auth.User() == nil {
|
||||||
|
// Having successfully found a token, it's now expected that the only valid outcomes are either errors that
|
||||||
|
// result in 401s (if the token wasn't valid), or valid authentication as a user. If we reach here, we've missed
|
||||||
|
// those expected outcomes and somehow returned an unauthenticated response even though a token was provided.
|
||||||
|
return nil, errors.New("unexpected unauthenticated result from userIDFromToken")
|
||||||
}
|
}
|
||||||
log.Trace("OAuth2 Authorization: Found token for user[%d]", id)
|
log.Trace("OAuth2 Authorization: Logged in user %-v", auth.User())
|
||||||
|
return auth, nil
|
||||||
user, err := user_model.GetPossibleUserByID(req.Context(), id)
|
|
||||||
if err != nil {
|
|
||||||
if !user_model.IsErrUserNotExist(err) {
|
|
||||||
log.Error("GetUserByName: %v", err)
|
|
||||||
}
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Trace("OAuth2 Authorization: Logged in user %-v", user)
|
|
||||||
return user, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func isAuthenticatedTokenRequest(req *http.Request) bool {
|
func isAuthenticatedTokenRequest(req *http.Request) bool {
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
// Copyright 2024 The Gitea Authors. All rights reserved.
|
// Copyright 2024 The Gitea Authors. All rights reserved.
|
||||||
// SPDX-License-Identifier: MIT
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
package auth
|
package method
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
@ -11,7 +11,6 @@ import (
|
||||||
"forgejo.org/models/auth"
|
"forgejo.org/models/auth"
|
||||||
"forgejo.org/models/unittest"
|
"forgejo.org/models/unittest"
|
||||||
user_model "forgejo.org/models/user"
|
user_model "forgejo.org/models/user"
|
||||||
"forgejo.org/modules/web/middleware"
|
|
||||||
"forgejo.org/services/actions"
|
"forgejo.org/services/actions"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
|
@ -33,14 +32,13 @@ func TestUserIDFromToken(t *testing.T) {
|
||||||
token, err := actions.CreateAuthorizationToken(task, map[string]any{}, false)
|
token, err := actions.CreateAuthorizationToken(task, map[string]any{}, false)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
ds := make(middleware.ContextData)
|
|
||||||
|
|
||||||
o := OAuth2{}
|
o := OAuth2{}
|
||||||
uid, err := o.userIDFromToken(t.Context(), token, ds)
|
authResult, err := o.userIDFromToken(t.Context(), token)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
assert.Equal(t, int64(user_model.ActionsUserID), uid)
|
assert.Equal(t, int64(user_model.ActionsUserID), authResult.User().ID)
|
||||||
assert.Equal(t, true, ds["IsActionsToken"])
|
isActionsToken, authTaskID := authResult.ActionsTaskID().Get()
|
||||||
assert.Equal(t, ds["ActionsTaskID"], int64(RunningTaskID))
|
assert.True(t, isActionsToken)
|
||||||
|
assert.Equal(t, int64(RunningTaskID), authTaskID)
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("Actions error-JWT", func(t *testing.T) {
|
t.Run("Actions error-JWT", func(t *testing.T) {
|
||||||
|
|
@ -52,13 +50,12 @@ func TestUserIDFromToken(t *testing.T) {
|
||||||
"To short": {"abc", auth.ErrAccessTokenNotExist{Token: "abc"}},
|
"To short": {"abc", auth.ErrAccessTokenNotExist{Token: "abc"}},
|
||||||
}
|
}
|
||||||
|
|
||||||
ds := make(middleware.ContextData)
|
|
||||||
o := OAuth2{}
|
o := OAuth2{}
|
||||||
for name, c := range cases {
|
for name, c := range cases {
|
||||||
t.Run(name, func(t *testing.T) {
|
t.Run(name, func(t *testing.T) {
|
||||||
uid, err := o.userIDFromToken(t.Context(), c.Token, ds)
|
authResult, err := o.userIDFromToken(t.Context(), c.Token)
|
||||||
require.ErrorIs(t, err, c.Error)
|
require.ErrorIs(t, err, c.Error)
|
||||||
assert.Equal(t, int64(0), uid)
|
assert.Nil(t, authResult)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
@ -2,9 +2,10 @@
|
||||||
// Copyright 2019 The Gitea Authors. All rights reserved.
|
// Copyright 2019 The Gitea Authors. All rights reserved.
|
||||||
// SPDX-License-Identifier: MIT
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
package auth
|
package method
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
|
@ -12,14 +13,16 @@ import (
|
||||||
"forgejo.org/modules/log"
|
"forgejo.org/modules/log"
|
||||||
"forgejo.org/modules/optional"
|
"forgejo.org/modules/optional"
|
||||||
"forgejo.org/modules/setting"
|
"forgejo.org/modules/setting"
|
||||||
|
"forgejo.org/modules/util"
|
||||||
"forgejo.org/modules/web/middleware"
|
"forgejo.org/modules/web/middleware"
|
||||||
|
"forgejo.org/services/auth"
|
||||||
|
|
||||||
gouuid "github.com/google/uuid"
|
gouuid "github.com/google/uuid"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Ensure the struct implements the interface.
|
// Ensure the struct implements the interface.
|
||||||
var (
|
var (
|
||||||
_ Method = &ReverseProxy{}
|
_ auth.Method = &ReverseProxy{}
|
||||||
)
|
)
|
||||||
|
|
||||||
// ReverseProxyMethodName is the constant name of the ReverseProxy authentication method
|
// ReverseProxyMethodName is the constant name of the ReverseProxy authentication method
|
||||||
|
|
@ -37,11 +40,6 @@ func (r *ReverseProxy) getUserName(req *http.Request) string {
|
||||||
return strings.TrimSpace(req.Header.Get(setting.ReverseProxyAuthUser))
|
return strings.TrimSpace(req.Header.Get(setting.ReverseProxyAuthUser))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Name represents the name of auth method
|
|
||||||
func (r *ReverseProxy) Name() string {
|
|
||||||
return ReverseProxyMethodName
|
|
||||||
}
|
|
||||||
|
|
||||||
// getUserFromAuthUser extracts the username from the "setting.ReverseProxyAuthUser" header
|
// getUserFromAuthUser 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
|
||||||
|
|
@ -52,7 +50,7 @@ func (r *ReverseProxy) Name() string {
|
||||||
func (r *ReverseProxy) getUserFromAuthUser(req *http.Request) (*user_model.User, error) {
|
func (r *ReverseProxy) getUserFromAuthUser(req *http.Request) (*user_model.User, error) {
|
||||||
username := r.getUserName(req)
|
username := r.getUserName(req)
|
||||||
if len(username) == 0 {
|
if len(username) == 0 {
|
||||||
return nil, nil
|
return nil, util.ErrNotExist
|
||||||
}
|
}
|
||||||
log.Trace("ReverseProxy Authorization: Found username: %s", username)
|
log.Trace("ReverseProxy Authorization: Found username: %s", username)
|
||||||
|
|
||||||
|
|
@ -104,15 +102,15 @@ func (r *ReverseProxy) getUserFromAuthEmail(req *http.Request) *user_model.User
|
||||||
// First it will attempt to load it based on the username (see docs for getUserFromAuthUser),
|
// First it will attempt to load it based on the username (see docs for getUserFromAuthUser),
|
||||||
// and failing that it will attempt to load it based on the email (see docs for getUserFromAuthEmail).
|
// and failing that it will attempt to load it based on the email (see docs for getUserFromAuthEmail).
|
||||||
// Returns nil if the headers are empty or the user is not found.
|
// Returns nil if the headers are empty or the user is not found.
|
||||||
func (r *ReverseProxy) Verify(req *http.Request, w http.ResponseWriter, store DataStore, sess SessionStore) (*user_model.User, error) {
|
func (r *ReverseProxy) Verify(req *http.Request, w http.ResponseWriter, sess auth.SessionStore) (auth.AuthenticationResult, error) {
|
||||||
user, err := r.getUserFromAuthUser(req)
|
user, err := r.getUserFromAuthUser(req)
|
||||||
if err != nil {
|
if err != nil && !errors.Is(err, util.ErrNotExist) {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if user == nil {
|
if user == nil {
|
||||||
user = r.getUserFromAuthEmail(req)
|
user = r.getUserFromAuthEmail(req)
|
||||||
if user == nil {
|
if user == nil {
|
||||||
return nil, nil
|
return &auth.UnauthenticatedResult{}, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -122,10 +120,9 @@ func (r *ReverseProxy) Verify(req *http.Request, w http.ResponseWriter, store Da
|
||||||
handleSignIn(w, req, sess, user)
|
handleSignIn(w, req, sess, user)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
store.GetData()["IsReverseProxy"] = true
|
|
||||||
|
|
||||||
log.Trace("ReverseProxy Authorization: Logged in user %-v", user)
|
log.Trace("ReverseProxy Authorization: Logged in user %-v", user)
|
||||||
return user, nil
|
return &reverseProxyAuthenticationResult{user: user}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// isAutoRegisterAllowed checks if EnableReverseProxyAutoRegister setting is true
|
// isAutoRegisterAllowed checks if EnableReverseProxyAutoRegister setting is true
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
// Copyright 2024 The Forgejo Authors. All rights reserved.
|
// Copyright 2024 The Forgejo Authors. All rights reserved.
|
||||||
// SPDX-License-Identifier: MIT
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
package auth
|
package method
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
@ -1,47 +1,43 @@
|
||||||
// Copyright 2019 The Gitea Authors. All rights reserved.
|
// Copyright 2019 The Gitea Authors. All rights reserved.
|
||||||
// SPDX-License-Identifier: MIT
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
package auth
|
package method
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
user_model "forgejo.org/models/user"
|
user_model "forgejo.org/models/user"
|
||||||
"forgejo.org/modules/log"
|
"forgejo.org/modules/log"
|
||||||
|
"forgejo.org/services/auth"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Ensure the struct implements the interface.
|
// Ensure the struct implements the interface.
|
||||||
var (
|
var (
|
||||||
_ Method = &Session{}
|
_ auth.Method = &Session{}
|
||||||
)
|
)
|
||||||
|
|
||||||
// Session checks if there is a user uid stored in the session and returns the user
|
// Session checks if there is a user uid stored in the session and returns the user
|
||||||
// object for that uid.
|
// object for that uid.
|
||||||
type Session struct{}
|
type Session struct{}
|
||||||
|
|
||||||
// Name represents the name of auth method
|
|
||||||
func (s *Session) Name() string {
|
|
||||||
return "session"
|
|
||||||
}
|
|
||||||
|
|
||||||
// Verify checks if there is a user uid stored in the session and returns the user
|
// Verify checks if there is a user uid stored in the session and returns the user
|
||||||
// object for that uid.
|
// object for that uid.
|
||||||
// Returns nil if there is no user uid stored in the session.
|
// 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) (*user_model.User, error) {
|
func (s *Session) Verify(req *http.Request, w http.ResponseWriter, sess auth.SessionStore) (auth.AuthenticationResult, error) {
|
||||||
if sess == nil {
|
if sess == nil {
|
||||||
return nil, nil
|
return &auth.UnauthenticatedResult{}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get user ID
|
// Get user ID
|
||||||
uid := sess.Get("uid")
|
uid := sess.Get("uid")
|
||||||
if uid == nil {
|
if uid == nil {
|
||||||
return nil, nil
|
return &auth.UnauthenticatedResult{}, nil
|
||||||
}
|
}
|
||||||
log.Trace("Session Authorization: Found user[%d]", uid)
|
log.Trace("Session Authorization: Found user[%d]", uid)
|
||||||
|
|
||||||
id, ok := uid.(int64)
|
id, ok := uid.(int64)
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil, nil
|
return &auth.UnauthenticatedResult{}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get user object
|
// Get user object
|
||||||
|
|
@ -52,9 +48,9 @@ func (s *Session) Verify(req *http.Request, w http.ResponseWriter, store DataSto
|
||||||
// Return the err as-is to keep current signed-in session, in case the err is something like context.Canceled. Otherwise non-existing user (nil, nil) will make the caller clear the signed-in session.
|
// Return the err as-is to keep current signed-in session, in case the err is something like context.Canceled. Otherwise non-existing user (nil, nil) will make the caller clear the signed-in session.
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return nil, nil
|
return &auth.UnauthenticatedResult{}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Trace("Session Authorization: Logged in user %-v", user)
|
log.Trace("Session Authorization: Logged in user %-v", user)
|
||||||
return user, nil
|
return &sessionAuthenticationResult{user: user}, nil
|
||||||
}
|
}
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
// Copyright 2021 The Gitea Authors. All rights reserved.
|
// Copyright 2021 The Gitea Authors. All rights reserved.
|
||||||
// SPDX-License-Identifier: MIT
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
package auth
|
package method
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
|
@ -12,6 +12,7 @@ import (
|
||||||
user_model "forgejo.org/models/user"
|
user_model "forgejo.org/models/user"
|
||||||
"forgejo.org/modules/log"
|
"forgejo.org/modules/log"
|
||||||
"forgejo.org/modules/optional"
|
"forgejo.org/modules/optional"
|
||||||
|
auth_service "forgejo.org/services/auth"
|
||||||
"forgejo.org/services/auth/source/oauth2"
|
"forgejo.org/services/auth/source/oauth2"
|
||||||
"forgejo.org/services/auth/source/smtp"
|
"forgejo.org/services/auth/source/smtp"
|
||||||
|
|
||||||
|
|
@ -65,7 +66,7 @@ func UserSignIn(ctx context.Context, username, password string) (*user_model.Use
|
||||||
return nil, nil, oauth2.ErrAuthSourceNotActivated
|
return nil, nil, oauth2.ErrAuthSourceNotActivated
|
||||||
}
|
}
|
||||||
|
|
||||||
authenticator, ok := source.Cfg.(PasswordAuthenticator)
|
authenticator, ok := source.Cfg.(auth_service.PasswordAuthenticator)
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil, nil, smtp.ErrUnsupportedLoginType
|
return nil, nil, smtp.ErrUnsupportedLoginType
|
||||||
}
|
}
|
||||||
|
|
@ -98,7 +99,7 @@ func UserSignIn(ctx context.Context, username, password string) (*user_model.Use
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
authenticator, ok := source.Cfg.(PasswordAuthenticator)
|
authenticator, ok := source.Cfg.(auth_service.PasswordAuthenticator)
|
||||||
if !ok {
|
if !ok {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
@ -25,6 +25,7 @@ import (
|
||||||
"forgejo.org/modules/setting"
|
"forgejo.org/modules/setting"
|
||||||
"forgejo.org/modules/web"
|
"forgejo.org/modules/web"
|
||||||
web_types "forgejo.org/modules/web/types"
|
web_types "forgejo.org/modules/web/types"
|
||||||
|
"forgejo.org/services/auth"
|
||||||
"forgejo.org/services/authz"
|
"forgejo.org/services/authz"
|
||||||
|
|
||||||
"code.forgejo.org/go-chi/cache"
|
"code.forgejo.org/go-chi/cache"
|
||||||
|
|
@ -38,7 +39,7 @@ type APIContext struct {
|
||||||
|
|
||||||
Doer *user_model.User // current signed-in user
|
Doer *user_model.User // current signed-in user
|
||||||
IsSigned bool
|
IsSigned bool
|
||||||
IsBasicAuth bool
|
Authentication auth.AuthenticationResult
|
||||||
|
|
||||||
ContextUser *user_model.User // the user which is being visited, in most cases it differs from Doer
|
ContextUser *user_model.User // the user which is being visited, in most cases it differs from Doer
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -25,6 +25,7 @@ import (
|
||||||
"forgejo.org/modules/web"
|
"forgejo.org/modules/web"
|
||||||
"forgejo.org/modules/web/middleware"
|
"forgejo.org/modules/web/middleware"
|
||||||
web_types "forgejo.org/modules/web/types"
|
web_types "forgejo.org/modules/web/types"
|
||||||
|
"forgejo.org/services/auth"
|
||||||
|
|
||||||
"code.forgejo.org/go-chi/cache"
|
"code.forgejo.org/go-chi/cache"
|
||||||
"code.forgejo.org/go-chi/session"
|
"code.forgejo.org/go-chi/session"
|
||||||
|
|
@ -53,7 +54,7 @@ type Context struct {
|
||||||
|
|
||||||
Doer *user_model.User // current signed-in user
|
Doer *user_model.User // current signed-in user
|
||||||
IsSigned bool
|
IsSigned bool
|
||||||
IsBasicAuth bool
|
Authentication auth.AuthenticationResult
|
||||||
|
|
||||||
ContextUser *user_model.User // the user which is being visited, in most cases it differs from Doer
|
ContextUser *user_model.User // the user which is being visited, in most cases it differs from Doer
|
||||||
|
|
||||||
|
|
@ -112,6 +113,8 @@ func NewWebContext(base *Base, render Render, session session.Store) *Context {
|
||||||
Link: setting.AppSubURL + strings.TrimSuffix(base.Req.URL.EscapedPath(), "/"),
|
Link: setting.AppSubURL + strings.TrimSuffix(base.Req.URL.EscapedPath(), "/"),
|
||||||
Repo: &Repository{PullRequest: &PullRequest{}},
|
Repo: &Repository{PullRequest: &PullRequest{}},
|
||||||
Org: &Organization{},
|
Org: &Organization{},
|
||||||
|
|
||||||
|
Authentication: &auth.UnauthenticatedResult{},
|
||||||
}
|
}
|
||||||
ctx.TemplateContext = NewTemplateContextForWeb(ctx)
|
ctx.TemplateContext = NewTemplateContextForWeb(ctx)
|
||||||
ctx.Flash = &middleware.Flash{DataStore: ctx, Values: url.Values{}}
|
ctx.Flash = &middleware.Flash{DataStore: ctx, Values: url.Values{}}
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,6 @@ import (
|
||||||
repo_model "forgejo.org/models/repo"
|
repo_model "forgejo.org/models/repo"
|
||||||
"forgejo.org/models/unit"
|
"forgejo.org/models/unit"
|
||||||
"forgejo.org/modules/log"
|
"forgejo.org/modules/log"
|
||||||
"forgejo.org/services/authz"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// RequireRepoAdmin returns a middleware for requiring repository admin permission
|
// RequireRepoAdmin returns a middleware for requiring repository admin permission
|
||||||
|
|
@ -125,12 +124,7 @@ func CheckRepoDelegateActionTrust(ctx *Context) bool {
|
||||||
|
|
||||||
// CheckRepoScopedToken check whether personal access token has repo scope
|
// CheckRepoScopedToken check whether personal access token has repo scope
|
||||||
func CheckRepoScopedToken(ctx *Context, repo *repo_model.Repository, level auth_model.AccessTokenScopeLevel) {
|
func CheckRepoScopedToken(ctx *Context, repo *repo_model.Repository, level auth_model.AccessTokenScopeLevel) {
|
||||||
if !ctx.IsBasicAuth || ctx.Data["IsApiToken"] != true {
|
if hasScope, scope := ctx.Authentication.Scope().Get(); hasScope {
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
scope, ok := ctx.Data["ApiTokenScope"].(auth_model.AccessTokenScope)
|
|
||||||
if ok { // it's a personal access token but not oauth2 token
|
|
||||||
var scopeMatched bool
|
var scopeMatched bool
|
||||||
|
|
||||||
requiredScopes := auth_model.GetRequiredScopes(level, auth_model.AccessTokenScopeCategoryRepository)
|
requiredScopes := auth_model.GetRequiredScopes(level, auth_model.AccessTokenScopeCategoryRepository)
|
||||||
|
|
@ -159,8 +153,7 @@ func CheckRepoScopedToken(ctx *Context, repo *repo_model.Repository, level auth_
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
reducer, ok := ctx.Data["ApiTokenReducer"].(authz.AuthorizationReducer)
|
if reducer := ctx.Authentication.Reducer(); reducer != nil {
|
||||||
if ok {
|
|
||||||
var accessMode perm.AccessMode
|
var accessMode perm.AccessMode
|
||||||
switch level {
|
switch level {
|
||||||
case auth_model.Read:
|
case auth_model.Read:
|
||||||
|
|
@ -184,8 +177,7 @@ func CheckRepoScopedToken(ctx *Context, repo *repo_model.Repository, level auth_
|
||||||
}
|
}
|
||||||
|
|
||||||
func CheckRuntimeDeterminedScope(ctx *APIContext, scopeCategory auth_model.AccessTokenScopeCategory, level auth_model.AccessTokenScopeLevel, msg string) {
|
func CheckRuntimeDeterminedScope(ctx *APIContext, scopeCategory auth_model.AccessTokenScopeCategory, level auth_model.AccessTokenScopeLevel, msg string) {
|
||||||
scope, ok := ctx.Data["ApiTokenScope"].(auth_model.AccessTokenScope)
|
if hasScope, scope := ctx.Authentication.Scope().Get(); hasScope {
|
||||||
if ok {
|
|
||||||
var scopeMatched bool
|
var scopeMatched bool
|
||||||
|
|
||||||
requiredScopes := auth_model.GetRequiredScopes(level, scopeCategory)
|
requiredScopes := auth_model.GetRequiredScopes(level, scopeCategory)
|
||||||
|
|
|
||||||
|
|
@ -539,8 +539,7 @@ func authenticate(ctx *context.Context, repository *repo_model.Repository, autho
|
||||||
accessMode = perm.AccessModeWrite
|
accessMode = perm.AccessModeWrite
|
||||||
}
|
}
|
||||||
|
|
||||||
if ctx.Data["IsActionsToken"] == true {
|
if hasTaskID, taskID := ctx.Authentication.ActionsTaskID().Get(); hasTaskID {
|
||||||
taskID := ctx.Data["ActionsTaskID"].(int64)
|
|
||||||
task, err := actions_model.GetTaskByID(ctx, taskID)
|
task, err := actions_model.GetTaskByID(ctx, taskID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("Unable to GetTaskByID for task[%d] Error: %v", taskID, err)
|
log.Error("Unable to GetTaskByID for task[%d] Error: %v", taskID, err)
|
||||||
|
|
|
||||||
|
|
@ -94,9 +94,10 @@ nwIDAQAB
|
||||||
defer tests.PrintCurrentTest(t)()
|
defer tests.PrintCurrentTest(t)()
|
||||||
|
|
||||||
req := NewRequest(t, "POST", "/dummy")
|
req := NewRequest(t, "POST", "/dummy")
|
||||||
u, err := auth.Verify(req.Request, nil, nil, nil)
|
u, err := auth.Verify(req.Request, nil, nil)
|
||||||
assert.Nil(t, u)
|
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
require.NotNil(t, u)
|
||||||
|
assert.Nil(t, u.User())
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("NotExistingUser", func(t *testing.T) {
|
t.Run("NotExistingUser", func(t *testing.T) {
|
||||||
|
|
@ -104,7 +105,7 @@ nwIDAQAB
|
||||||
|
|
||||||
req := NewRequest(t, "POST", "/dummy").
|
req := NewRequest(t, "POST", "/dummy").
|
||||||
SetHeader("X-Ops-Userid", "not-existing-user")
|
SetHeader("X-Ops-Userid", "not-existing-user")
|
||||||
u, err := auth.Verify(req.Request, nil, nil, nil)
|
u, err := auth.Verify(req.Request, nil, nil)
|
||||||
assert.Nil(t, u)
|
assert.Nil(t, u)
|
||||||
require.Error(t, err)
|
require.Error(t, err)
|
||||||
})
|
})
|
||||||
|
|
@ -114,12 +115,12 @@ nwIDAQAB
|
||||||
|
|
||||||
req := NewRequest(t, "POST", "/dummy").
|
req := NewRequest(t, "POST", "/dummy").
|
||||||
SetHeader("X-Ops-Userid", user.Name)
|
SetHeader("X-Ops-Userid", user.Name)
|
||||||
u, err := auth.Verify(req.Request, nil, nil, nil)
|
u, err := auth.Verify(req.Request, nil, nil)
|
||||||
assert.Nil(t, u)
|
assert.Nil(t, u)
|
||||||
require.Error(t, err)
|
require.Error(t, err)
|
||||||
|
|
||||||
req.SetHeader("X-Ops-Timestamp", "2023-01-01T00:00:00Z")
|
req.SetHeader("X-Ops-Timestamp", "2023-01-01T00:00:00Z")
|
||||||
u, err = auth.Verify(req.Request, nil, nil, nil)
|
u, err = auth.Verify(req.Request, nil, nil)
|
||||||
assert.Nil(t, u)
|
assert.Nil(t, u)
|
||||||
require.Error(t, err)
|
require.Error(t, err)
|
||||||
})
|
})
|
||||||
|
|
@ -130,27 +131,27 @@ nwIDAQAB
|
||||||
req := NewRequest(t, "POST", "/dummy").
|
req := NewRequest(t, "POST", "/dummy").
|
||||||
SetHeader("X-Ops-Userid", user.Name).
|
SetHeader("X-Ops-Userid", user.Name).
|
||||||
SetHeader("X-Ops-Timestamp", time.Now().UTC().Format(time.RFC3339))
|
SetHeader("X-Ops-Timestamp", time.Now().UTC().Format(time.RFC3339))
|
||||||
u, err := auth.Verify(req.Request, nil, nil, nil)
|
u, err := auth.Verify(req.Request, nil, nil)
|
||||||
assert.Nil(t, u)
|
assert.Nil(t, u)
|
||||||
require.Error(t, err)
|
require.Error(t, err)
|
||||||
|
|
||||||
req.SetHeader("X-Ops-Sign", "version=none")
|
req.SetHeader("X-Ops-Sign", "version=none")
|
||||||
u, err = auth.Verify(req.Request, nil, nil, nil)
|
u, err = auth.Verify(req.Request, nil, nil)
|
||||||
assert.Nil(t, u)
|
assert.Nil(t, u)
|
||||||
require.Error(t, err)
|
require.Error(t, err)
|
||||||
|
|
||||||
req.SetHeader("X-Ops-Sign", "version=1.4")
|
req.SetHeader("X-Ops-Sign", "version=1.4")
|
||||||
u, err = auth.Verify(req.Request, nil, nil, nil)
|
u, err = auth.Verify(req.Request, nil, nil)
|
||||||
assert.Nil(t, u)
|
assert.Nil(t, u)
|
||||||
require.Error(t, err)
|
require.Error(t, err)
|
||||||
|
|
||||||
req.SetHeader("X-Ops-Sign", "version=1.0;algorithm=sha2")
|
req.SetHeader("X-Ops-Sign", "version=1.0;algorithm=sha2")
|
||||||
u, err = auth.Verify(req.Request, nil, nil, nil)
|
u, err = auth.Verify(req.Request, nil, nil)
|
||||||
assert.Nil(t, u)
|
assert.Nil(t, u)
|
||||||
require.Error(t, err)
|
require.Error(t, err)
|
||||||
|
|
||||||
req.SetHeader("X-Ops-Sign", "version=1.0;algorithm=sha256")
|
req.SetHeader("X-Ops-Sign", "version=1.0;algorithm=sha256")
|
||||||
u, err = auth.Verify(req.Request, nil, nil, nil)
|
u, err = auth.Verify(req.Request, nil, nil)
|
||||||
assert.Nil(t, u)
|
assert.Nil(t, u)
|
||||||
require.Error(t, err)
|
require.Error(t, err)
|
||||||
})
|
})
|
||||||
|
|
@ -166,7 +167,7 @@ nwIDAQAB
|
||||||
SetHeader("X-Ops-Sign", "version=1.0;algorithm=sha1").
|
SetHeader("X-Ops-Sign", "version=1.0;algorithm=sha1").
|
||||||
SetHeader("X-Ops-Content-Hash", "unused").
|
SetHeader("X-Ops-Content-Hash", "unused").
|
||||||
SetHeader("X-Ops-Authorization-4", "dummy")
|
SetHeader("X-Ops-Authorization-4", "dummy")
|
||||||
u, err := auth.Verify(req.Request, nil, nil, nil)
|
u, err := auth.Verify(req.Request, nil, nil)
|
||||||
assert.Nil(t, u)
|
assert.Nil(t, u)
|
||||||
require.Error(t, err)
|
require.Error(t, err)
|
||||||
|
|
||||||
|
|
@ -257,7 +258,7 @@ nwIDAQAB
|
||||||
defer tests.PrintCurrentTest(t)()
|
defer tests.PrintCurrentTest(t)()
|
||||||
|
|
||||||
signRequest(req, v)
|
signRequest(req, v)
|
||||||
u, err = auth.Verify(req.Request, nil, nil, nil)
|
u, err = auth.Verify(req.Request, nil, nil)
|
||||||
assert.NotNil(t, u)
|
assert.NotNil(t, u)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
})
|
})
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue