Skip to content

Commit a584ba7

Browse files
committed
Extract AccessToken from Basic auth
1 parent 5ac876a commit a584ba7

File tree

6 files changed

+128
-27
lines changed

6 files changed

+128
-27
lines changed

routers/api/packages/api.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,7 @@ func CommonRoutes() *web.Router {
115115

116116
verifyAuth(r, []auth.Method{
117117
&auth.OAuth2{},
118+
&auth.AccessToken{},
118119
&auth.Basic{},
119120
&nuget.Auth{},
120121
&conan.Auth{},
@@ -671,6 +672,7 @@ func ContainerRoutes() *web.Router {
671672
r.Use(context.PackageContexter())
672673

673674
verifyAuth(r, []auth.Method{
675+
&auth.AccessToken{},
674676
&auth.Basic{},
675677
&container.Auth{},
676678
})

routers/api/v1/api.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -739,6 +739,7 @@ func buildAuthGroup() *auth.Group {
739739
group := auth.NewGroup(
740740
&auth.OAuth2{},
741741
&auth.HTTPSign{},
742+
&auth.AccessToken{},
742743
&auth.Basic{}, // FIXME: this should be removed once we don't allow basic auth in API
743744
)
744745
if setting.Service.EnableReverseProxyAuthAPI {

routers/web/web.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,8 @@ func optionsCorsHandler() func(next http.Handler) http.Handler {
9999
func buildAuthGroup() *auth_service.Group {
100100
group := auth_service.NewGroup()
101101
group.Add(&auth_service.OAuth2{}) // FIXME: this should be removed and only applied in download and oauth related routers
102-
group.Add(&auth_service.Basic{}) // FIXME: this should be removed and only applied in download and git/lfs routers
102+
group.Add(&auth_service.AccessToken{})
103+
group.Add(&auth_service.Basic{}) // FIXME: this should be removed and only applied in download and git/lfs routers
103104

104105
if setting.Service.EnableReverseProxyAuth {
105106
group.Add(&auth_service.ReverseProxy{}) // reverseproxy should before Session, otherwise the header will be ignored if user has login

services/auth/access_token.go

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
// Copyright 2024 The Gitea Authors. All rights reserved.
2+
// SPDX-License-Identifier: MIT
3+
4+
package auth
5+
6+
import (
7+
"net/http"
8+
"strings"
9+
10+
auth_model "code.gitea.io/gitea/models/auth"
11+
user_model "code.gitea.io/gitea/models/user"
12+
"code.gitea.io/gitea/modules/base"
13+
"code.gitea.io/gitea/modules/log"
14+
"code.gitea.io/gitea/modules/timeutil"
15+
"code.gitea.io/gitea/modules/web/middleware"
16+
)
17+
18+
// Ensure the struct implements the interface.
19+
var (
20+
_ Method = &AccessToken{}
21+
)
22+
23+
// BasicMethodName is the constant name of the basic authentication method
24+
const (
25+
AccessTokenMethodName = "access_token"
26+
)
27+
28+
// AccessToken implements the Auth interface and authenticates requests (API requests
29+
// only) by looking for access token
30+
type AccessToken struct{}
31+
32+
// Name represents the name of auth method
33+
func (b *AccessToken) Name() string {
34+
return AccessTokenMethodName
35+
}
36+
37+
// Match returns true if the request matched AccessToken requirements
38+
// TODO: remove path check once AccessToken will not be a global middleware but only
39+
// for specific routes
40+
func (b *AccessToken) Match(req *http.Request) bool {
41+
if !middleware.IsAPIPath(req) && !isContainerPath(req) && !isAttachmentDownload(req) && !isGitRawOrAttachOrLFSPath(req) {
42+
return false
43+
}
44+
baHead := req.Header.Get("Authorization")
45+
if baHead == "" {
46+
return false
47+
}
48+
auths := strings.SplitN(baHead, " ", 2)
49+
if len(auths) != 2 || (strings.ToLower(auths[0]) != "basic") {
50+
return false
51+
}
52+
return true
53+
}
54+
55+
// Verify extracts and validates Basic data (username and password/token) from the
56+
// "Authorization" header of the request and returns the corresponding user object for that
57+
// name/token on successful validation.
58+
// Returns nil if header is empty or validation fails.
59+
func (b *AccessToken) Verify(req *http.Request, w http.ResponseWriter, store DataStore, sess SessionStore) (*user_model.User, error) {
60+
// Basic authentication should only fire on API, Download or on Git or LFSPaths
61+
if !middleware.IsAPIPath(req) && !isContainerPath(req) && !isAttachmentDownload(req) && !isGitRawOrAttachOrLFSPath(req) {
62+
return nil, nil
63+
}
64+
65+
baHead := req.Header.Get("Authorization")
66+
if len(baHead) == 0 {
67+
return nil, nil
68+
}
69+
70+
auths := strings.SplitN(baHead, " ", 2)
71+
if len(auths) != 2 || (strings.ToLower(auths[0]) != "basic") {
72+
return nil, nil
73+
}
74+
75+
uname, passwd, _ := base.BasicAuthDecode(auths[1])
76+
77+
// Check if username or password is a token
78+
isUsernameToken := len(passwd) == 0 || passwd == "x-oauth-basic"
79+
// Assume username is token
80+
authToken := uname
81+
if !isUsernameToken {
82+
log.Trace("Basic Authorization: Attempting login for: %s", uname)
83+
// Assume password is token
84+
authToken = passwd
85+
} else {
86+
log.Trace("Basic Authorization: Attempting login with username as token")
87+
}
88+
89+
// check personal access token
90+
token, err := auth_model.GetAccessTokenBySHA(req.Context(), authToken)
91+
if err == nil {
92+
log.Trace("Basic Authorization: Valid AccessToken for user[%d]", token.UID)
93+
u, err := user_model.GetUserByID(req.Context(), token.UID)
94+
if err != nil {
95+
log.Error("GetUserByID: %v", err)
96+
return nil, err
97+
}
98+
99+
token.UpdatedUnix = timeutil.TimeStampNow()
100+
if err = auth_model.UpdateAccessToken(req.Context(), token); err != nil {
101+
log.Error("UpdateAccessToken: %v", err)
102+
}
103+
104+
store.GetData()["LoginMethod"] = AccessTokenMethodName
105+
store.GetData()["IsApiToken"] = true
106+
store.GetData()["ApiTokenScope"] = token.Scope
107+
return u, nil
108+
} else if !auth_model.IsErrAccessTokenNotExist(err) && !auth_model.IsErrAccessTokenEmpty(err) {
109+
log.Error("GetAccessTokenBySha: %v", err)
110+
}
111+
112+
return nil, nil
113+
}

services/auth/basic.go

Lines changed: 0 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@ import (
1414
"code.gitea.io/gitea/modules/base"
1515
"code.gitea.io/gitea/modules/log"
1616
"code.gitea.io/gitea/modules/setting"
17-
"code.gitea.io/gitea/modules/timeutil"
1817
"code.gitea.io/gitea/modules/util"
1918
"code.gitea.io/gitea/modules/web/middleware"
2019
)
@@ -27,7 +26,6 @@ var (
2726
// BasicMethodName is the constant name of the basic authentication method
2827
const (
2928
BasicMethodName = "basic"
30-
AccessTokenMethodName = "access_token"
3129
OAuth2TokenMethodName = "oauth2_token"
3230
ActionTokenMethodName = "action_token"
3331
)
@@ -96,29 +94,6 @@ func (b *Basic) Verify(req *http.Request, w http.ResponseWriter, store DataStore
9694
return u, nil
9795
}
9896

99-
// check personal access token
100-
token, err := auth_model.GetAccessTokenBySHA(req.Context(), authToken)
101-
if err == nil {
102-
log.Trace("Basic Authorization: Valid AccessToken for user[%d]", uid)
103-
u, err := user_model.GetUserByID(req.Context(), token.UID)
104-
if err != nil {
105-
log.Error("GetUserByID: %v", err)
106-
return nil, err
107-
}
108-
109-
token.UpdatedUnix = timeutil.TimeStampNow()
110-
if err = auth_model.UpdateAccessToken(req.Context(), token); err != nil {
111-
log.Error("UpdateAccessToken: %v", err)
112-
}
113-
114-
store.GetData()["LoginMethod"] = AccessTokenMethodName
115-
store.GetData()["IsApiToken"] = true
116-
store.GetData()["ApiTokenScope"] = token.Scope
117-
return u, nil
118-
} else if !auth_model.IsErrAccessTokenNotExist(err) && !auth_model.IsErrAccessTokenEmpty(err) {
119-
log.Error("GetAccessTokenBySha: %v", err)
120-
}
121-
12297
// check task token
12398
task, err := actions_model.GetRunningTaskByToken(req.Context(), authToken)
12499
if err == nil && task != nil {

services/auth/oauth2.go

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -131,8 +131,17 @@ func (o *OAuth2) userIDFromToken(ctx context.Context, tokenSHA string, store Dat
131131
return t.UID
132132
}
133133

134+
// Match returns true if the request matched OAuth2 requirements
135+
// TODO: remove path check once AccessToken will not be a global middleware but only
136+
// for specific routes
134137
func (o *OAuth2) Match(req *http.Request) bool {
135-
return true
138+
if !middleware.IsAPIPath(req) && !isAttachmentDownload(req) && !isAuthenticatedTokenRequest(req) &&
139+
!isGitRawOrAttachPath(req) && !isArchivePath(req) {
140+
return false
141+
}
142+
143+
_, ok := parseToken(req)
144+
return ok
136145
}
137146

138147
// Verify extracts the user ID from the OAuth token in the query parameters

0 commit comments

Comments
 (0)