From aefac78df7baae393d05f39a825bb394890d98f1 Mon Sep 17 00:00:00 2001 From: wxiaoguang Date: Thu, 3 Jul 2025 09:39:43 +0800 Subject: [PATCH] fix parser --- modules/auth/httpauth/httpauth.go | 49 ++++++++++-------- modules/auth/httpauth/httpauth_test.go | 71 +++++++++++--------------- routers/web/auth/oauth2_provider.go | 12 +++-- services/auth/basic.go | 6 ++- services/auth/oauth2.go | 5 +- services/lfs/server.go | 6 +-- 6 files changed, 76 insertions(+), 73 deletions(-) diff --git a/modules/auth/httpauth/httpauth.go b/modules/auth/httpauth/httpauth.go index 74a660de5d..7f1f1ee152 100644 --- a/modules/auth/httpauth/httpauth.go +++ b/modules/auth/httpauth/httpauth.go @@ -10,31 +10,38 @@ import ( "code.gitea.io/gitea/modules/util" ) -func ParseAuthorizationHeaderBasic(header string) (string, string, bool) { - parts := strings.Fields(header) - if len(parts) != 2 { - return "", "", false - } - if !util.AsciiEqualFold(parts[0], "basic") { - return "", "", false - } - s, err := base64.StdEncoding.DecodeString(parts[1]) - if err != nil { - return "", "", false - } - if u, p, ok := strings.Cut(string(s), ":"); ok { - return u, p, true - } - return "", "", false +type BasicAuth struct { + Username, Password string } -func ParseAuthorizationHeaderBearerToken(header string) (string, bool) { +type BearerToken struct { + Token string +} + +type ParsedAuthorizationHeader struct { + BasicAuth *BasicAuth + BearerToken *BearerToken +} + +func ParseAuthorizationHeader(header string) (ret ParsedAuthorizationHeader, _ bool) { parts := strings.Fields(header) if len(parts) != 2 { - return "", false + return ret, false } - if util.AsciiEqualFold(parts[0], "token") || util.AsciiEqualFold(parts[0], "bearer") { - return parts[1], true + if util.AsciiEqualFold(parts[0], "basic") { + s, err := base64.StdEncoding.DecodeString(parts[1]) + if err != nil { + return ret, false + } + u, p, ok := strings.Cut(string(s), ":") + if !ok { + return ret, false + } + ret.BasicAuth = &BasicAuth{Username: u, Password: p} + return ret, true + } else if util.AsciiEqualFold(parts[0], "token") || util.AsciiEqualFold(parts[0], "bearer") { + ret.BearerToken = &BearerToken{Token: parts[1]} + return ret, true } - return "", false + return ret, false } diff --git a/modules/auth/httpauth/httpauth_test.go b/modules/auth/httpauth/httpauth_test.go index 0e7a65424f..087b86917f 100644 --- a/modules/auth/httpauth/httpauth_test.go +++ b/modules/auth/httpauth/httpauth_test.go @@ -11,46 +11,33 @@ import ( ) func TestParseAuthorizationHeader(t *testing.T) { - t.Run("Basic", func(t *testing.T) { - cases := []struct { - headerValue string - user, pass string - ok bool - }{ - {"", "", "", false}, - {"?", "", "", false}, - {"foo", "", "", false}, - {"Basic ?", "", "", false}, - {"Basic " + base64.StdEncoding.EncodeToString([]byte("foo")), "", "", false}, - {"Basic " + base64.StdEncoding.EncodeToString([]byte("foo:bar")), "foo", "bar", true}, - {"basic " + base64.StdEncoding.EncodeToString([]byte("foo:bar")), "foo", "bar", true}, - } - for _, c := range cases { - user, pass, ok := ParseAuthorizationHeaderBasic(c.headerValue) - assert.Equal(t, c.ok, ok, "header %q", c.headerValue) - assert.Equal(t, c.user, user, "header %q", c.headerValue) - assert.Equal(t, c.pass, pass, "header %q", c.headerValue) - } - }) - t.Run("BearerToken", func(t *testing.T) { - cases := []struct { - headerValue string - expected string - ok bool - }{ - {"", "", false}, - {"?", "", false}, - {"any value", "", false}, - {"token value", "value", true}, - {"Token value", "value", true}, - {"bearer value", "value", true}, - {"Bearer value", "value", true}, - {"Bearer wrong value", "", false}, - } - for _, c := range cases { - token, ok := ParseAuthorizationHeaderBearerToken(c.headerValue) - assert.Equal(t, c.ok, ok, "header %q", c.headerValue) - assert.Equal(t, c.expected, token, "header %q", c.headerValue) - } - }) + type parsed = ParsedAuthorizationHeader + type basic = BasicAuth + type bearer = BearerToken + cases := []struct { + headerValue string + expected parsed + ok bool + }{ + {"", parsed{}, false}, + {"?", parsed{}, false}, + {"foo", parsed{}, false}, + {"any value", parsed{}, false}, + + {"Basic ?", parsed{}, false}, + {"Basic " + base64.StdEncoding.EncodeToString([]byte("foo")), parsed{}, false}, + {"Basic " + base64.StdEncoding.EncodeToString([]byte("foo:bar")), parsed{BasicAuth: &basic{"foo", "bar"}}, true}, + {"basic " + base64.StdEncoding.EncodeToString([]byte("foo:bar")), parsed{BasicAuth: &basic{"foo", "bar"}}, true}, + + {"token value", parsed{BearerToken: &bearer{"value"}}, true}, + {"Token value", parsed{BearerToken: &bearer{"value"}}, true}, + {"bearer value", parsed{BearerToken: &bearer{"value"}}, true}, + {"Bearer value", parsed{BearerToken: &bearer{"value"}}, true}, + {"Bearer wrong value", parsed{}, false}, + } + for _, c := range cases { + ret, ok := ParseAuthorizationHeader(c.headerValue) + assert.Equal(t, c.ok, ok, "header %q", c.headerValue) + assert.Equal(t, c.expected, ret, "header %q", c.headerValue) + } } diff --git a/routers/web/auth/oauth2_provider.go b/routers/web/auth/oauth2_provider.go index d99aae6af3..dc9f34fd44 100644 --- a/routers/web/auth/oauth2_provider.go +++ b/routers/web/auth/oauth2_provider.go @@ -106,8 +106,8 @@ func InfoOAuth(ctx *context.Context) { var accessTokenScope auth.AccessTokenScope if auHead := ctx.Req.Header.Get("Authorization"); auHead != "" { - if headerAuthToken, ok := httpauth.ParseAuthorizationHeaderBearerToken(auHead); ok { - accessTokenScope, _ = auth_service.GetOAuthAccessTokenScopeAndUserID(ctx, headerAuthToken) + if parsed, ok := httpauth.ParseAuthorizationHeader(auHead); ok && parsed.BearerToken != nil { + accessTokenScope, _ = auth_service.GetOAuthAccessTokenScopeAndUserID(ctx, parsed.BearerToken.Token) } } @@ -128,7 +128,8 @@ func InfoOAuth(ctx *context.Context) { func IntrospectOAuth(ctx *context.Context) { clientIDValid := false authHeader := ctx.Req.Header.Get("Authorization") - if clientID, clientSecret, ok := httpauth.ParseAuthorizationHeaderBasic(authHeader); ok { + if parsed, ok := httpauth.ParseAuthorizationHeader(authHeader); ok && parsed.BasicAuth != nil { + clientID, clientSecret := parsed.BasicAuth.Username, parsed.BasicAuth.Password app, err := auth.GetOAuth2ApplicationByClientID(ctx, clientID) if err != nil && !auth.IsErrOauthClientIDInvalid(err) { // this is likely a database error; log it and respond without details @@ -456,14 +457,15 @@ func AccessTokenOAuth(ctx *context.Context) { // if there is no ClientID or ClientSecret in the request body, fill these fields by the Authorization header and ensure the provided field matches the Authorization header if form.ClientID == "" || form.ClientSecret == "" { if authHeader := ctx.Req.Header.Get("Authorization"); authHeader != "" { - clientID, clientSecret, ok := httpauth.ParseAuthorizationHeaderBasic(authHeader) - if !ok { + parsed, ok := httpauth.ParseAuthorizationHeader(authHeader) + if !ok || parsed.BasicAuth == nil { handleAccessTokenError(ctx, oauth2_provider.AccessTokenError{ ErrorCode: oauth2_provider.AccessTokenErrorCodeInvalidRequest, ErrorDescription: "cannot parse basic auth header", }) return } + clientID, clientSecret := parsed.BasicAuth.Username, parsed.BasicAuth.Password // validate that any fields present in the form match the Basic auth header if form.ClientID != "" && form.ClientID != clientID { handleAccessTokenError(ctx, oauth2_provider.AccessTokenError{ diff --git a/services/auth/basic.go b/services/auth/basic.go index bc0b80d10e..b2bd14ef5d 100644 --- a/services/auth/basic.go +++ b/services/auth/basic.go @@ -57,7 +57,11 @@ func (b *Basic) Verify(req *http.Request, w http.ResponseWriter, store DataStore if authHeader == "" { return nil, nil } - uname, passwd, _ := httpauth.ParseAuthorizationHeaderBasic(authHeader) + parsed, ok := httpauth.ParseAuthorizationHeader(authHeader) + if !ok || parsed.BasicAuth == nil { + return nil, nil + } + uname, passwd := parsed.BasicAuth.Username, parsed.BasicAuth.Password // Check if username or password is a token isUsernameToken := len(passwd) == 0 || passwd == "x-oauth-basic" diff --git a/services/auth/oauth2.go b/services/auth/oauth2.go index c8079de220..7df6f4638e 100644 --- a/services/auth/oauth2.go +++ b/services/auth/oauth2.go @@ -98,7 +98,10 @@ func parseToken(req *http.Request) (string, bool) { // check header token if auHead := req.Header.Get("Authorization"); auHead != "" { - return httpauth.ParseAuthorizationHeaderBearerToken(auHead) + parsed, ok := httpauth.ParseAuthorizationHeader(auHead) + if ok && parsed.BearerToken != nil { + return parsed.BearerToken.Token, true + } } return "", false } diff --git a/services/lfs/server.go b/services/lfs/server.go index cde731665d..c44cc35e53 100644 --- a/services/lfs/server.go +++ b/services/lfs/server.go @@ -595,11 +595,11 @@ func parseToken(ctx stdCtx.Context, authorization string, target *repo_model.Rep if authorization == "" { return nil, errors.New("no token") } - token, ok := httpauth.ParseAuthorizationHeaderBearerToken(authorization) - if !ok { + parsed, ok := httpauth.ParseAuthorizationHeader(authorization) + if !ok || parsed.BearerToken == nil { return nil, errors.New("token not found") } - return handleLFSToken(ctx, token, target, mode) + return handleLFSToken(ctx, parsed.BearerToken.Token, target, mode) } func requireAuth(ctx *context.Context) {