Skip to content

Commit

Permalink
Fix nuget/conan/container packages upload bugs (#31967)
Browse files Browse the repository at this point in the history
  • Loading branch information
lunny authored Sep 5, 2024
1 parent 74b1c58 commit 5c05ddd
Show file tree
Hide file tree
Showing 11 changed files with 512 additions and 90 deletions.
16 changes: 16 additions & 0 deletions models/auth/access_token_scope.go
Original file line number Diff line number Diff line change
Expand Up @@ -309,6 +309,22 @@ func (s AccessTokenScope) HasScope(scopes ...AccessTokenScope) (bool, error) {
return true, nil
}

// HasAnyScope returns true if any of the scopes is contained in the string
func (s AccessTokenScope) HasAnyScope(scopes ...AccessTokenScope) (bool, error) {
bitmap, err := s.parse()
if err != nil {
return false, err
}

for _, s := range scopes {
if has, err := bitmap.hasScope(s); has || err != nil {
return has, err
}
}

return false, nil
}

// hasScope returns true if the string has the given scope
func (bitmap accessTokenScopeBitmap) hasScope(scope AccessTokenScope) (bool, error) {
expectedBits, ok := allAccessTokenScopeBits[scope]
Expand Down
10 changes: 7 additions & 3 deletions routers/api/packages/conan/auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,21 +22,25 @@ func (a *Auth) Name() string {

// 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) {
uid, err := packages.ParseAuthorizationToken(req)
packageMeta, err := packages.ParseAuthorizationRequest(req)
if err != nil {
log.Trace("ParseAuthorizationToken: %v", err)
return nil, err
}

if uid == 0 {
if packageMeta == nil || packageMeta.UserID == 0 {
return nil, nil
}

u, err := user_model.GetUserByID(req.Context(), uid)
u, err := user_model.GetUserByID(req.Context(), packageMeta.UserID)
if err != nil {
log.Error("GetUserByID: %v", err)
return nil, err
}
if packageMeta.Scope != "" {
store.GetData()["IsApiToken"] = true
store.GetData()["ApiTokenScope"] = packageMeta.Scope
}

return u, nil
}
35 changes: 32 additions & 3 deletions routers/api/packages/conan/conan.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
"strings"
"time"

auth_model "code.gitea.io/gitea/models/auth"
"code.gitea.io/gitea/models/db"
packages_model "code.gitea.io/gitea/models/packages"
conan_model "code.gitea.io/gitea/models/packages/conan"
Expand All @@ -21,6 +22,7 @@ import (
conan_module "code.gitea.io/gitea/modules/packages/conan"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/routers/api/packages/helper"
auth_service "code.gitea.io/gitea/services/auth"
"code.gitea.io/gitea/services/context"
notify_service "code.gitea.io/gitea/services/notify"
packages_service "code.gitea.io/gitea/services/packages"
Expand Down Expand Up @@ -117,7 +119,20 @@ func Authenticate(ctx *context.Context) {
return
}

token, err := packages_service.CreateAuthorizationToken(ctx.Doer)
packageScope := auth_service.GetAccessScope(ctx.Data)
if has, err := packageScope.HasAnyScope(
auth_model.AccessTokenScopeReadPackage,
auth_model.AccessTokenScopeWritePackage,
auth_model.AccessTokenScopeAll,
); !has {
if err != nil {
log.Error("Error checking access scope: %v", err)
}
apiError(ctx, http.StatusForbidden, nil)
return
}

token, err := packages_service.CreateAuthorizationToken(ctx.Doer, packageScope)
if err != nil {
apiError(ctx, http.StatusInternalServerError, err)
return
Expand All @@ -130,9 +145,23 @@ func Authenticate(ctx *context.Context) {
func CheckCredentials(ctx *context.Context) {
if ctx.Doer == nil {
ctx.Status(http.StatusUnauthorized)
} else {
ctx.Status(http.StatusOK)
return
}

packageScope := auth_service.GetAccessScope(ctx.Data)
if has, err := packageScope.HasAnyScope(
auth_model.AccessTokenScopeReadPackage,
auth_model.AccessTokenScopeWritePackage,
auth_model.AccessTokenScopeAll,
); !has {
if err != nil {
log.Error("Error checking access scope: %v", err)
}
ctx.Status(http.StatusForbidden)
return
}

ctx.Status(http.StatusOK)
}

// RecipeSnapshot displays the recipe files with their md5 hash
Expand Down
11 changes: 8 additions & 3 deletions routers/api/packages/container/auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,21 +23,26 @@ func (a *Auth) Name() string {
// Verify extracts the user from the Bearer token
// 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) {
uid, err := packages.ParseAuthorizationToken(req)
packageMeta, err := packages.ParseAuthorizationRequest(req)
if err != nil {
log.Trace("ParseAuthorizationToken: %v", err)
return nil, err
}

if uid == 0 {
if packageMeta == nil || packageMeta.UserID == 0 {
return nil, nil
}

u, err := user_model.GetPossibleUserByID(req.Context(), uid)
u, err := user_model.GetPossibleUserByID(req.Context(), packageMeta.UserID)
if err != nil {
log.Error("GetPossibleUserByID: %v", err)
return nil, err
}

if packageMeta.Scope != "" {
store.GetData()["IsApiToken"] = true
store.GetData()["ApiTokenScope"] = packageMeta.Scope
}

return u, nil
}
17 changes: 16 additions & 1 deletion routers/api/packages/container/container.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import (
"strconv"
"strings"

auth_model "code.gitea.io/gitea/models/auth"
packages_model "code.gitea.io/gitea/models/packages"
container_model "code.gitea.io/gitea/models/packages/container"
user_model "code.gitea.io/gitea/models/user"
Expand All @@ -25,6 +26,7 @@ import (
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/routers/api/packages/helper"
auth_service "code.gitea.io/gitea/services/auth"
"code.gitea.io/gitea/services/context"
packages_service "code.gitea.io/gitea/services/packages"
container_service "code.gitea.io/gitea/services/packages/container"
Expand Down Expand Up @@ -148,16 +150,29 @@ func DetermineSupport(ctx *context.Context) {
// If the current user is anonymous, the ghost user is used unless RequireSignInView is enabled.
func Authenticate(ctx *context.Context) {
u := ctx.Doer
packageScope := auth_service.GetAccessScope(ctx.Data)
if u == nil {
if setting.Service.RequireSignInView {
apiUnauthorizedError(ctx)
return
}

u = user_model.NewGhostUser()
} else {
if has, err := packageScope.HasAnyScope(
auth_model.AccessTokenScopeReadPackage,
auth_model.AccessTokenScopeWritePackage,
auth_model.AccessTokenScopeAll,
); !has {
if err != nil {
log.Error("Error checking access scope: %v", err)
}
apiUnauthorizedError(ctx)
return
}
}

token, err := packages_service.CreateAuthorizationToken(u)
token, err := packages_service.CreateAuthorizationToken(u, packageScope)
if err != nil {
apiError(ctx, http.StatusInternalServerError, err)
return
Expand Down
3 changes: 3 additions & 0 deletions routers/api/packages/nuget/auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,5 +43,8 @@ func (a *Auth) Verify(req *http.Request, w http.ResponseWriter, store auth.DataS
log.Error("UpdateAccessToken: %v", err)
}

store.GetData()["IsApiToken"] = true
store.GetData()["ApiToken"] = token

return u, nil
}
27 changes: 26 additions & 1 deletion services/auth/basic.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,12 @@ var (
)

// BasicMethodName is the constant name of the basic authentication method
const BasicMethodName = "basic"
const (
BasicMethodName = "basic"
AccessTokenMethodName = "access_token"
OAuth2TokenMethodName = "oauth2_token"
ActionTokenMethodName = "action_token"
)

// 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"
Expand Down Expand Up @@ -82,6 +87,7 @@ func (b *Basic) Verify(req *http.Request, w http.ResponseWriter, store DataStore
return nil, err
}

store.GetData()["LoginMethod"] = OAuth2TokenMethodName
store.GetData()["IsApiToken"] = true
return u, nil
}
Expand All @@ -101,6 +107,7 @@ func (b *Basic) Verify(req *http.Request, w http.ResponseWriter, store DataStore
log.Error("UpdateAccessToken: %v", err)
}

store.GetData()["LoginMethod"] = AccessTokenMethodName
store.GetData()["IsApiToken"] = true
store.GetData()["ApiTokenScope"] = token.Scope
return u, nil
Expand All @@ -113,6 +120,7 @@ func (b *Basic) Verify(req *http.Request, w http.ResponseWriter, store DataStore
if err == nil && task != nil {
log.Trace("Basic Authorization: Valid AccessToken for task[%d]", task.ID)

store.GetData()["LoginMethod"] = ActionTokenMethodName
store.GetData()["IsActionsToken"] = true
store.GetData()["ActionsTaskID"] = task.ID

Expand All @@ -138,6 +146,7 @@ func (b *Basic) Verify(req *http.Request, w http.ResponseWriter, store DataStore
}
}

store.GetData()["LoginMethod"] = BasicMethodName
log.Trace("Basic Authorization: Logged in user %-v", u)

return u, nil
Expand All @@ -159,3 +168,19 @@ func validateTOTP(req *http.Request, u *user_model.User) error {
}
return nil
}

func GetAccessScope(store DataStore) auth_model.AccessTokenScope {
if v, ok := store.GetData()["ApiTokenScope"]; ok {
return v.(auth_model.AccessTokenScope)
}
switch store.GetData()["LoginMethod"] {
case OAuth2TokenMethodName:
fallthrough
case BasicMethodName, AccessTokenMethodName:
return auth_model.AccessTokenScopeAll
case ActionTokenMethodName:
fallthrough
default:
return ""
}
}
30 changes: 21 additions & 9 deletions services/packages/auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"strings"
"time"

auth_model "code.gitea.io/gitea/models/auth"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
Expand All @@ -18,18 +19,25 @@ import (

type packageClaims struct {
jwt.RegisteredClaims
PackageMeta
}
type PackageMeta struct {
UserID int64
Scope auth_model.AccessTokenScope
}

func CreateAuthorizationToken(u *user_model.User) (string, error) {
func CreateAuthorizationToken(u *user_model.User, packageScope auth_model.AccessTokenScope) (string, error) {
now := time.Now()

claims := packageClaims{
RegisteredClaims: jwt.RegisteredClaims{
ExpiresAt: jwt.NewNumericDate(now.Add(24 * time.Hour)),
NotBefore: jwt.NewNumericDate(now),
},
UserID: u.ID,
PackageMeta: PackageMeta{
UserID: u.ID,
Scope: packageScope,
},
}
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)

Expand All @@ -41,32 +49,36 @@ func CreateAuthorizationToken(u *user_model.User) (string, error) {
return tokenString, nil
}

func ParseAuthorizationToken(req *http.Request) (int64, error) {
func ParseAuthorizationRequest(req *http.Request) (*PackageMeta, error) {
h := req.Header.Get("Authorization")
if h == "" {
return 0, nil
return nil, nil
}

parts := strings.SplitN(h, " ", 2)
if len(parts) != 2 {
log.Error("split token failed: %s", h)
return 0, fmt.Errorf("split token failed")
return nil, fmt.Errorf("split token failed")
}

token, err := jwt.ParseWithClaims(parts[1], &packageClaims{}, func(t *jwt.Token) (any, error) {
return ParseAuthorizationToken(parts[1])
}

func ParseAuthorizationToken(tokenStr string) (*PackageMeta, error) {
token, err := jwt.ParseWithClaims(tokenStr, &packageClaims{}, func(t *jwt.Token) (any, error) {
if _, ok := t.Method.(*jwt.SigningMethodHMAC); !ok {
return nil, fmt.Errorf("unexpected signing method: %v", t.Header["alg"])
}
return setting.GetGeneralTokenSigningSecret(), nil
})
if err != nil {
return 0, err
return nil, err
}

c, ok := token.Claims.(*packageClaims)
if !token.Valid || !ok {
return 0, fmt.Errorf("invalid token claim")
return nil, fmt.Errorf("invalid token claim")
}

return c.UserID, nil
return &c.PackageMeta, nil
}
Loading

0 comments on commit 5c05ddd

Please sign in to comment.