mirror of https://github.com/go-gitea/gitea.git
Backport #31967 by @lunny Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
This commit is contained in:
parent
244fb11c6b
commit
b39aa8528b
|
@ -309,6 +309,22 @@ func (s AccessTokenScope) HasScope(scopes ...AccessTokenScope) (bool, error) {
|
||||||
return true, nil
|
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
|
// hasScope returns true if the string has the given scope
|
||||||
func (bitmap accessTokenScopeBitmap) hasScope(scope AccessTokenScope) (bool, error) {
|
func (bitmap accessTokenScopeBitmap) hasScope(scope AccessTokenScope) (bool, error) {
|
||||||
expectedBits, ok := allAccessTokenScopeBits[scope]
|
expectedBits, ok := allAccessTokenScopeBits[scope]
|
||||||
|
|
|
@ -22,21 +22,25 @@ 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, store auth.DataStore, sess auth.SessionStore) (*user_model.User, error) {
|
||||||
uid, err := packages.ParseAuthorizationToken(req)
|
packageMeta, err := packages.ParseAuthorizationRequest(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Trace("ParseAuthorizationToken: %v", err)
|
log.Trace("ParseAuthorizationToken: %v", err)
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if uid == 0 {
|
if packageMeta == nil || packageMeta.UserID == 0 {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
u, err := user_model.GetUserByID(req.Context(), uid)
|
u, err := user_model.GetUserByID(req.Context(), packageMeta.UserID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("GetUserByID: %v", err)
|
log.Error("GetUserByID: %v", err)
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
if packageMeta.Scope != "" {
|
||||||
|
store.GetData()["IsApiToken"] = true
|
||||||
|
store.GetData()["ApiTokenScope"] = packageMeta.Scope
|
||||||
|
}
|
||||||
|
|
||||||
return u, nil
|
return u, nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,6 +11,7 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
auth_model "code.gitea.io/gitea/models/auth"
|
||||||
"code.gitea.io/gitea/models/db"
|
"code.gitea.io/gitea/models/db"
|
||||||
packages_model "code.gitea.io/gitea/models/packages"
|
packages_model "code.gitea.io/gitea/models/packages"
|
||||||
conan_model "code.gitea.io/gitea/models/packages/conan"
|
conan_model "code.gitea.io/gitea/models/packages/conan"
|
||||||
|
@ -21,6 +22,7 @@ import (
|
||||||
conan_module "code.gitea.io/gitea/modules/packages/conan"
|
conan_module "code.gitea.io/gitea/modules/packages/conan"
|
||||||
"code.gitea.io/gitea/modules/setting"
|
"code.gitea.io/gitea/modules/setting"
|
||||||
"code.gitea.io/gitea/routers/api/packages/helper"
|
"code.gitea.io/gitea/routers/api/packages/helper"
|
||||||
|
auth_service "code.gitea.io/gitea/services/auth"
|
||||||
"code.gitea.io/gitea/services/context"
|
"code.gitea.io/gitea/services/context"
|
||||||
notify_service "code.gitea.io/gitea/services/notify"
|
notify_service "code.gitea.io/gitea/services/notify"
|
||||||
packages_service "code.gitea.io/gitea/services/packages"
|
packages_service "code.gitea.io/gitea/services/packages"
|
||||||
|
@ -117,7 +119,20 @@ func Authenticate(ctx *context.Context) {
|
||||||
return
|
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 {
|
if err != nil {
|
||||||
apiError(ctx, http.StatusInternalServerError, err)
|
apiError(ctx, http.StatusInternalServerError, err)
|
||||||
return
|
return
|
||||||
|
@ -130,9 +145,23 @@ func Authenticate(ctx *context.Context) {
|
||||||
func CheckCredentials(ctx *context.Context) {
|
func CheckCredentials(ctx *context.Context) {
|
||||||
if ctx.Doer == nil {
|
if ctx.Doer == nil {
|
||||||
ctx.Status(http.StatusUnauthorized)
|
ctx.Status(http.StatusUnauthorized)
|
||||||
} else {
|
return
|
||||||
ctx.Status(http.StatusOK)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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
|
// RecipeSnapshot displays the recipe files with their md5 hash
|
||||||
|
|
|
@ -23,21 +23,26 @@ 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, store auth.DataStore, sess auth.SessionStore) (*user_model.User, error) {
|
||||||
uid, err := packages.ParseAuthorizationToken(req)
|
packageMeta, err := packages.ParseAuthorizationRequest(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Trace("ParseAuthorizationToken: %v", err)
|
log.Trace("ParseAuthorizationToken: %v", err)
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if uid == 0 {
|
if packageMeta == nil || packageMeta.UserID == 0 {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
u, err := user_model.GetPossibleUserByID(req.Context(), uid)
|
u, err := user_model.GetPossibleUserByID(req.Context(), packageMeta.UserID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("GetPossibleUserByID: %v", err)
|
log.Error("GetPossibleUserByID: %v", err)
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if packageMeta.Scope != "" {
|
||||||
|
store.GetData()["IsApiToken"] = true
|
||||||
|
store.GetData()["ApiTokenScope"] = packageMeta.Scope
|
||||||
|
}
|
||||||
|
|
||||||
return u, nil
|
return u, nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,6 +14,7 @@ import (
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
auth_model "code.gitea.io/gitea/models/auth"
|
||||||
packages_model "code.gitea.io/gitea/models/packages"
|
packages_model "code.gitea.io/gitea/models/packages"
|
||||||
container_model "code.gitea.io/gitea/models/packages/container"
|
container_model "code.gitea.io/gitea/models/packages/container"
|
||||||
user_model "code.gitea.io/gitea/models/user"
|
user_model "code.gitea.io/gitea/models/user"
|
||||||
|
@ -25,6 +26,7 @@ import (
|
||||||
"code.gitea.io/gitea/modules/setting"
|
"code.gitea.io/gitea/modules/setting"
|
||||||
"code.gitea.io/gitea/modules/util"
|
"code.gitea.io/gitea/modules/util"
|
||||||
"code.gitea.io/gitea/routers/api/packages/helper"
|
"code.gitea.io/gitea/routers/api/packages/helper"
|
||||||
|
auth_service "code.gitea.io/gitea/services/auth"
|
||||||
"code.gitea.io/gitea/services/context"
|
"code.gitea.io/gitea/services/context"
|
||||||
packages_service "code.gitea.io/gitea/services/packages"
|
packages_service "code.gitea.io/gitea/services/packages"
|
||||||
container_service "code.gitea.io/gitea/services/packages/container"
|
container_service "code.gitea.io/gitea/services/packages/container"
|
||||||
|
@ -148,6 +150,7 @@ func DetermineSupport(ctx *context.Context) {
|
||||||
// If the current user is anonymous, the ghost user is used unless RequireSignInView is enabled.
|
// If the current user is anonymous, the ghost user is used unless RequireSignInView is enabled.
|
||||||
func Authenticate(ctx *context.Context) {
|
func Authenticate(ctx *context.Context) {
|
||||||
u := ctx.Doer
|
u := ctx.Doer
|
||||||
|
packageScope := auth_service.GetAccessScope(ctx.Data)
|
||||||
if u == nil {
|
if u == nil {
|
||||||
if setting.Service.RequireSignInView {
|
if setting.Service.RequireSignInView {
|
||||||
apiUnauthorizedError(ctx)
|
apiUnauthorizedError(ctx)
|
||||||
|
@ -155,9 +158,21 @@ func Authenticate(ctx *context.Context) {
|
||||||
}
|
}
|
||||||
|
|
||||||
u = user_model.NewGhostUser()
|
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 {
|
if err != nil {
|
||||||
apiError(ctx, http.StatusInternalServerError, err)
|
apiError(ctx, http.StatusInternalServerError, err)
|
||||||
return
|
return
|
||||||
|
|
|
@ -43,5 +43,8 @@ func (a *Auth) Verify(req *http.Request, w http.ResponseWriter, store auth.DataS
|
||||||
log.Error("UpdateAccessToken: %v", err)
|
log.Error("UpdateAccessToken: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
store.GetData()["IsApiToken"] = true
|
||||||
|
store.GetData()["ApiToken"] = token
|
||||||
|
|
||||||
return u, nil
|
return u, nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,7 +25,12 @@ var (
|
||||||
)
|
)
|
||||||
|
|
||||||
// BasicMethodName is the constant name of the basic authentication method
|
// 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
|
// 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"
|
||||||
|
@ -82,6 +87,7 @@ func (b *Basic) Verify(req *http.Request, w http.ResponseWriter, store DataStore
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
store.GetData()["LoginMethod"] = OAuth2TokenMethodName
|
||||||
store.GetData()["IsApiToken"] = true
|
store.GetData()["IsApiToken"] = true
|
||||||
return u, nil
|
return u, nil
|
||||||
}
|
}
|
||||||
|
@ -101,6 +107,7 @@ func (b *Basic) Verify(req *http.Request, w http.ResponseWriter, store DataStore
|
||||||
log.Error("UpdateAccessToken: %v", err)
|
log.Error("UpdateAccessToken: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
store.GetData()["LoginMethod"] = AccessTokenMethodName
|
||||||
store.GetData()["IsApiToken"] = true
|
store.GetData()["IsApiToken"] = true
|
||||||
store.GetData()["ApiTokenScope"] = token.Scope
|
store.GetData()["ApiTokenScope"] = token.Scope
|
||||||
return u, nil
|
return u, nil
|
||||||
|
@ -113,6 +120,7 @@ func (b *Basic) Verify(req *http.Request, w http.ResponseWriter, store DataStore
|
||||||
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)
|
||||||
|
|
||||||
|
store.GetData()["LoginMethod"] = ActionTokenMethodName
|
||||||
store.GetData()["IsActionsToken"] = true
|
store.GetData()["IsActionsToken"] = true
|
||||||
store.GetData()["ActionsTaskID"] = task.ID
|
store.GetData()["ActionsTaskID"] = task.ID
|
||||||
|
|
||||||
|
@ -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)
|
log.Trace("Basic Authorization: Logged in user %-v", u)
|
||||||
|
|
||||||
return u, nil
|
return u, nil
|
||||||
|
@ -159,3 +168,19 @@ func validateTOTP(req *http.Request, u *user_model.User) error {
|
||||||
}
|
}
|
||||||
return nil
|
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 ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -9,6 +9,7 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
auth_model "code.gitea.io/gitea/models/auth"
|
||||||
user_model "code.gitea.io/gitea/models/user"
|
user_model "code.gitea.io/gitea/models/user"
|
||||||
"code.gitea.io/gitea/modules/log"
|
"code.gitea.io/gitea/modules/log"
|
||||||
"code.gitea.io/gitea/modules/setting"
|
"code.gitea.io/gitea/modules/setting"
|
||||||
|
@ -18,10 +19,14 @@ import (
|
||||||
|
|
||||||
type packageClaims struct {
|
type packageClaims struct {
|
||||||
jwt.RegisteredClaims
|
jwt.RegisteredClaims
|
||||||
|
PackageMeta
|
||||||
|
}
|
||||||
|
type PackageMeta struct {
|
||||||
UserID int64
|
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()
|
now := time.Now()
|
||||||
|
|
||||||
claims := packageClaims{
|
claims := packageClaims{
|
||||||
|
@ -29,7 +34,10 @@ func CreateAuthorizationToken(u *user_model.User) (string, error) {
|
||||||
ExpiresAt: jwt.NewNumericDate(now.Add(24 * time.Hour)),
|
ExpiresAt: jwt.NewNumericDate(now.Add(24 * time.Hour)),
|
||||||
NotBefore: jwt.NewNumericDate(now),
|
NotBefore: jwt.NewNumericDate(now),
|
||||||
},
|
},
|
||||||
UserID: u.ID,
|
PackageMeta: PackageMeta{
|
||||||
|
UserID: u.ID,
|
||||||
|
Scope: packageScope,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
|
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
|
||||||
|
|
||||||
|
@ -41,32 +49,36 @@ func CreateAuthorizationToken(u *user_model.User) (string, error) {
|
||||||
return tokenString, nil
|
return tokenString, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func ParseAuthorizationToken(req *http.Request) (int64, error) {
|
func ParseAuthorizationRequest(req *http.Request) (*PackageMeta, error) {
|
||||||
h := req.Header.Get("Authorization")
|
h := req.Header.Get("Authorization")
|
||||||
if h == "" {
|
if h == "" {
|
||||||
return 0, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
parts := strings.SplitN(h, " ", 2)
|
parts := strings.SplitN(h, " ", 2)
|
||||||
if len(parts) != 2 {
|
if len(parts) != 2 {
|
||||||
log.Error("split token failed: %s", h)
|
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 {
|
if _, ok := t.Method.(*jwt.SigningMethodHMAC); !ok {
|
||||||
return nil, fmt.Errorf("unexpected signing method: %v", t.Header["alg"])
|
return nil, fmt.Errorf("unexpected signing method: %v", t.Header["alg"])
|
||||||
}
|
}
|
||||||
return setting.GetGeneralTokenSigningSecret(), nil
|
return setting.GetGeneralTokenSigningSecret(), nil
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
c, ok := token.Claims.(*packageClaims)
|
c, ok := token.Claims.(*packageClaims)
|
||||||
if !token.Valid || !ok {
|
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
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,6 +11,7 @@ import (
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
auth_model "code.gitea.io/gitea/models/auth"
|
||||||
"code.gitea.io/gitea/models/db"
|
"code.gitea.io/gitea/models/db"
|
||||||
"code.gitea.io/gitea/models/packages"
|
"code.gitea.io/gitea/models/packages"
|
||||||
conan_model "code.gitea.io/gitea/models/packages/conan"
|
conan_model "code.gitea.io/gitea/models/packages/conan"
|
||||||
|
@ -19,6 +20,7 @@ import (
|
||||||
conan_module "code.gitea.io/gitea/modules/packages/conan"
|
conan_module "code.gitea.io/gitea/modules/packages/conan"
|
||||||
"code.gitea.io/gitea/modules/setting"
|
"code.gitea.io/gitea/modules/setting"
|
||||||
conan_router "code.gitea.io/gitea/routers/api/packages/conan"
|
conan_router "code.gitea.io/gitea/routers/api/packages/conan"
|
||||||
|
package_service "code.gitea.io/gitea/services/packages"
|
||||||
"code.gitea.io/gitea/tests"
|
"code.gitea.io/gitea/tests"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
@ -225,7 +227,7 @@ func TestPackageConan(t *testing.T) {
|
||||||
|
|
||||||
token := ""
|
token := ""
|
||||||
|
|
||||||
t.Run("Authenticate", func(t *testing.T) {
|
t.Run("UserName/Password Authenticate", func(t *testing.T) {
|
||||||
defer tests.PrintCurrentTest(t)()
|
defer tests.PrintCurrentTest(t)()
|
||||||
|
|
||||||
req := NewRequest(t, "GET", fmt.Sprintf("%s/v1/users/authenticate", url)).
|
req := NewRequest(t, "GET", fmt.Sprintf("%s/v1/users/authenticate", url)).
|
||||||
|
@ -234,6 +236,73 @@ func TestPackageConan(t *testing.T) {
|
||||||
|
|
||||||
token = resp.Body.String()
|
token = resp.Body.String()
|
||||||
assert.NotEmpty(t, token)
|
assert.NotEmpty(t, token)
|
||||||
|
|
||||||
|
pkgMeta, err := package_service.ParseAuthorizationToken(token)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, user.ID, pkgMeta.UserID)
|
||||||
|
assert.Equal(t, auth_model.AccessTokenScopeAll, pkgMeta.Scope)
|
||||||
|
})
|
||||||
|
|
||||||
|
badToken := ""
|
||||||
|
t.Run("Token Scope Authentication", func(t *testing.T) {
|
||||||
|
defer tests.PrintCurrentTest(t)()
|
||||||
|
|
||||||
|
session := loginUser(t, user.Name)
|
||||||
|
|
||||||
|
badToken = getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadNotification)
|
||||||
|
|
||||||
|
testCase := func(t *testing.T, scope auth_model.AccessTokenScope, expectedAuthStatusCode, expectedStatusCode int) {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
|
token := getTokenForLoggedInUser(t, session, scope)
|
||||||
|
|
||||||
|
req := NewRequest(t, "GET", fmt.Sprintf("%s/v1/users/authenticate", url)).
|
||||||
|
AddTokenAuth(token)
|
||||||
|
resp := MakeRequest(t, req, expectedAuthStatusCode)
|
||||||
|
if expectedAuthStatusCode != http.StatusOK {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
body := resp.Body.String()
|
||||||
|
assert.NotEmpty(t, body)
|
||||||
|
|
||||||
|
pkgMeta, err := package_service.ParseAuthorizationToken(body)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, user.ID, pkgMeta.UserID)
|
||||||
|
assert.Equal(t, scope, pkgMeta.Scope)
|
||||||
|
|
||||||
|
recipeURL := fmt.Sprintf("%s/v1/conans/%s/%s/%s/%s", url, "TestScope", version1, "testing", channel1)
|
||||||
|
|
||||||
|
req = NewRequestWithJSON(t, "POST", fmt.Sprintf("%s/upload_urls", recipeURL), map[string]int64{
|
||||||
|
conanfileName: 64,
|
||||||
|
"removed.txt": 0,
|
||||||
|
}).AddTokenAuth(token)
|
||||||
|
MakeRequest(t, req, expectedStatusCode)
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Run("No Package permission", func(t *testing.T) {
|
||||||
|
defer tests.PrintCurrentTest(t)()
|
||||||
|
|
||||||
|
testCase(t, auth_model.AccessTokenScopeReadNotification, http.StatusUnauthorized, http.StatusForbidden)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("Package Read permission", func(t *testing.T) {
|
||||||
|
defer tests.PrintCurrentTest(t)()
|
||||||
|
|
||||||
|
testCase(t, auth_model.AccessTokenScopeReadPackage, http.StatusOK, http.StatusUnauthorized)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("Package Write permission", func(t *testing.T) {
|
||||||
|
defer tests.PrintCurrentTest(t)()
|
||||||
|
|
||||||
|
testCase(t, auth_model.AccessTokenScopeWritePackage, http.StatusOK, http.StatusOK)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("All permission", func(t *testing.T) {
|
||||||
|
defer tests.PrintCurrentTest(t)()
|
||||||
|
|
||||||
|
testCase(t, auth_model.AccessTokenScopeAll, http.StatusOK, http.StatusOK)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("CheckCredentials", func(t *testing.T) {
|
t.Run("CheckCredentials", func(t *testing.T) {
|
||||||
|
@ -431,6 +500,11 @@ func TestPackageConan(t *testing.T) {
|
||||||
|
|
||||||
req := NewRequestWithJSON(t, "POST", fmt.Sprintf("%s/v1/conans/%s/%s/%s/%s/packages/delete", url, name, version1, user1, c.Channel), map[string][]string{
|
req := NewRequestWithJSON(t, "POST", fmt.Sprintf("%s/v1/conans/%s/%s/%s/%s/packages/delete", url, name, version1, user1, c.Channel), map[string][]string{
|
||||||
"package_ids": c.References,
|
"package_ids": c.References,
|
||||||
|
}).AddTokenAuth(badToken)
|
||||||
|
MakeRequest(t, req, http.StatusUnauthorized)
|
||||||
|
|
||||||
|
req = NewRequestWithJSON(t, "POST", fmt.Sprintf("%s/v1/conans/%s/%s/%s/%s/packages/delete", url, name, version1, user1, c.Channel), map[string][]string{
|
||||||
|
"package_ids": c.References,
|
||||||
}).AddTokenAuth(token)
|
}).AddTokenAuth(token)
|
||||||
MakeRequest(t, req, http.StatusOK)
|
MakeRequest(t, req, http.StatusOK)
|
||||||
|
|
||||||
|
@ -457,6 +531,10 @@ func TestPackageConan(t *testing.T) {
|
||||||
assert.NotEmpty(t, revisions)
|
assert.NotEmpty(t, revisions)
|
||||||
|
|
||||||
req := NewRequest(t, "DELETE", fmt.Sprintf("%s/v1/conans/%s/%s/%s/%s", url, name, version1, user1, c.Channel)).
|
req := NewRequest(t, "DELETE", fmt.Sprintf("%s/v1/conans/%s/%s/%s/%s", url, name, version1, user1, c.Channel)).
|
||||||
|
AddTokenAuth(badToken)
|
||||||
|
MakeRequest(t, req, http.StatusUnauthorized)
|
||||||
|
|
||||||
|
req = NewRequest(t, "DELETE", fmt.Sprintf("%s/v1/conans/%s/%s/%s/%s", url, name, version1, user1, c.Channel)).
|
||||||
AddTokenAuth(token)
|
AddTokenAuth(token)
|
||||||
MakeRequest(t, req, http.StatusOK)
|
MakeRequest(t, req, http.StatusOK)
|
||||||
|
|
||||||
|
@ -480,7 +558,7 @@ func TestPackageConan(t *testing.T) {
|
||||||
|
|
||||||
token := ""
|
token := ""
|
||||||
|
|
||||||
t.Run("Authenticate", func(t *testing.T) {
|
t.Run("UserName/Password Authenticate", func(t *testing.T) {
|
||||||
defer tests.PrintCurrentTest(t)()
|
defer tests.PrintCurrentTest(t)()
|
||||||
|
|
||||||
req := NewRequest(t, "GET", fmt.Sprintf("%s/v2/users/authenticate", url)).
|
req := NewRequest(t, "GET", fmt.Sprintf("%s/v2/users/authenticate", url)).
|
||||||
|
@ -490,9 +568,75 @@ func TestPackageConan(t *testing.T) {
|
||||||
body := resp.Body.String()
|
body := resp.Body.String()
|
||||||
assert.NotEmpty(t, body)
|
assert.NotEmpty(t, body)
|
||||||
|
|
||||||
|
pkgMeta, err := package_service.ParseAuthorizationToken(body)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, user.ID, pkgMeta.UserID)
|
||||||
|
assert.Equal(t, auth_model.AccessTokenScopeAll, pkgMeta.Scope)
|
||||||
|
|
||||||
token = fmt.Sprintf("Bearer %s", body)
|
token = fmt.Sprintf("Bearer %s", body)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
badToken := ""
|
||||||
|
|
||||||
|
t.Run("Token Scope Authentication", func(t *testing.T) {
|
||||||
|
defer tests.PrintCurrentTest(t)()
|
||||||
|
|
||||||
|
session := loginUser(t, user.Name)
|
||||||
|
|
||||||
|
badToken = getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadNotification)
|
||||||
|
|
||||||
|
testCase := func(t *testing.T, scope auth_model.AccessTokenScope, expectedAuthStatusCode, expectedStatusCode int) {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
|
token := getTokenForLoggedInUser(t, session, scope)
|
||||||
|
|
||||||
|
req := NewRequest(t, "GET", fmt.Sprintf("%s/v2/users/authenticate", url)).
|
||||||
|
AddTokenAuth(token)
|
||||||
|
resp := MakeRequest(t, req, expectedAuthStatusCode)
|
||||||
|
if expectedAuthStatusCode != http.StatusOK {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
body := resp.Body.String()
|
||||||
|
assert.NotEmpty(t, body)
|
||||||
|
|
||||||
|
pkgMeta, err := package_service.ParseAuthorizationToken(body)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, user.ID, pkgMeta.UserID)
|
||||||
|
assert.Equal(t, scope, pkgMeta.Scope)
|
||||||
|
|
||||||
|
recipeURL := fmt.Sprintf("%s/v2/conans/%s/%s/%s/%s/revisions/%s", url, "TestScope", version1, "testing", channel1, revision1)
|
||||||
|
|
||||||
|
req = NewRequestWithBody(t, "PUT", fmt.Sprintf("%s/files/%s", recipeURL, conanfileName), strings.NewReader("Demo Conan file")).
|
||||||
|
AddTokenAuth(token)
|
||||||
|
MakeRequest(t, req, expectedStatusCode)
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Run("No Package permission", func(t *testing.T) {
|
||||||
|
defer tests.PrintCurrentTest(t)()
|
||||||
|
|
||||||
|
testCase(t, auth_model.AccessTokenScopeReadNotification, http.StatusUnauthorized, http.StatusUnauthorized)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("Package Read permission", func(t *testing.T) {
|
||||||
|
defer tests.PrintCurrentTest(t)()
|
||||||
|
|
||||||
|
testCase(t, auth_model.AccessTokenScopeReadPackage, http.StatusOK, http.StatusUnauthorized)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("Package Write permission", func(t *testing.T) {
|
||||||
|
defer tests.PrintCurrentTest(t)()
|
||||||
|
|
||||||
|
testCase(t, auth_model.AccessTokenScopeWritePackage, http.StatusOK, http.StatusCreated)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("All permission", func(t *testing.T) {
|
||||||
|
defer tests.PrintCurrentTest(t)()
|
||||||
|
|
||||||
|
testCase(t, auth_model.AccessTokenScopeAll, http.StatusOK, http.StatusCreated)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
t.Run("CheckCredentials", func(t *testing.T) {
|
t.Run("CheckCredentials", func(t *testing.T) {
|
||||||
defer tests.PrintCurrentTest(t)()
|
defer tests.PrintCurrentTest(t)()
|
||||||
|
|
||||||
|
@ -511,7 +655,7 @@ func TestPackageConan(t *testing.T) {
|
||||||
|
|
||||||
pvs, err := packages.GetVersionsByPackageType(db.DefaultContext, user.ID, packages.TypeConan)
|
pvs, err := packages.GetVersionsByPackageType(db.DefaultContext, user.ID, packages.TypeConan)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Len(t, pvs, 2)
|
assert.Len(t, pvs, 3)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -663,11 +807,19 @@ func TestPackageConan(t *testing.T) {
|
||||||
checkPackageRevisionCount(2)
|
checkPackageRevisionCount(2)
|
||||||
|
|
||||||
req := NewRequest(t, "DELETE", fmt.Sprintf("%s/v2/conans/%s/%s/%s/%s/revisions/%s/packages/%s/revisions/%s", url, name, version1, user1, channel1, revision1, conanPackageReference, revision1)).
|
req := NewRequest(t, "DELETE", fmt.Sprintf("%s/v2/conans/%s/%s/%s/%s/revisions/%s/packages/%s/revisions/%s", url, name, version1, user1, channel1, revision1, conanPackageReference, revision1)).
|
||||||
|
AddTokenAuth(badToken)
|
||||||
|
MakeRequest(t, req, http.StatusUnauthorized)
|
||||||
|
|
||||||
|
req = NewRequest(t, "DELETE", fmt.Sprintf("%s/v2/conans/%s/%s/%s/%s/revisions/%s/packages/%s/revisions/%s", url, name, version1, user1, channel1, revision1, conanPackageReference, revision1)).
|
||||||
AddTokenAuth(token)
|
AddTokenAuth(token)
|
||||||
MakeRequest(t, req, http.StatusOK)
|
MakeRequest(t, req, http.StatusOK)
|
||||||
|
|
||||||
checkPackageRevisionCount(1)
|
checkPackageRevisionCount(1)
|
||||||
|
|
||||||
|
req = NewRequest(t, "DELETE", fmt.Sprintf("%s/v2/conans/%s/%s/%s/%s/revisions/%s/packages/%s", url, name, version1, user1, channel1, revision1, conanPackageReference)).
|
||||||
|
AddTokenAuth(badToken)
|
||||||
|
MakeRequest(t, req, http.StatusUnauthorized)
|
||||||
|
|
||||||
req = NewRequest(t, "DELETE", fmt.Sprintf("%s/v2/conans/%s/%s/%s/%s/revisions/%s/packages/%s", url, name, version1, user1, channel1, revision1, conanPackageReference)).
|
req = NewRequest(t, "DELETE", fmt.Sprintf("%s/v2/conans/%s/%s/%s/%s/revisions/%s/packages/%s", url, name, version1, user1, channel1, revision1, conanPackageReference)).
|
||||||
AddTokenAuth(token)
|
AddTokenAuth(token)
|
||||||
MakeRequest(t, req, http.StatusOK)
|
MakeRequest(t, req, http.StatusOK)
|
||||||
|
@ -678,6 +830,10 @@ func TestPackageConan(t *testing.T) {
|
||||||
|
|
||||||
checkPackageReferenceCount(1)
|
checkPackageReferenceCount(1)
|
||||||
|
|
||||||
|
req = NewRequest(t, "DELETE", fmt.Sprintf("%s/v2/conans/%s/%s/%s/%s/revisions/%s/packages", url, name, version1, user1, channel1, revision2)).
|
||||||
|
AddTokenAuth(badToken)
|
||||||
|
MakeRequest(t, req, http.StatusUnauthorized)
|
||||||
|
|
||||||
req = NewRequest(t, "DELETE", fmt.Sprintf("%s/v2/conans/%s/%s/%s/%s/revisions/%s/packages", url, name, version1, user1, channel1, revision2)).
|
req = NewRequest(t, "DELETE", fmt.Sprintf("%s/v2/conans/%s/%s/%s/%s/revisions/%s/packages", url, name, version1, user1, channel1, revision2)).
|
||||||
AddTokenAuth(token)
|
AddTokenAuth(token)
|
||||||
MakeRequest(t, req, http.StatusOK)
|
MakeRequest(t, req, http.StatusOK)
|
||||||
|
@ -699,11 +855,19 @@ func TestPackageConan(t *testing.T) {
|
||||||
checkRecipeRevisionCount(2)
|
checkRecipeRevisionCount(2)
|
||||||
|
|
||||||
req := NewRequest(t, "DELETE", fmt.Sprintf("%s/v2/conans/%s/%s/%s/%s/revisions/%s", url, name, version1, user1, channel1, revision1)).
|
req := NewRequest(t, "DELETE", fmt.Sprintf("%s/v2/conans/%s/%s/%s/%s/revisions/%s", url, name, version1, user1, channel1, revision1)).
|
||||||
|
AddTokenAuth(badToken)
|
||||||
|
MakeRequest(t, req, http.StatusUnauthorized)
|
||||||
|
|
||||||
|
req = NewRequest(t, "DELETE", fmt.Sprintf("%s/v2/conans/%s/%s/%s/%s/revisions/%s", url, name, version1, user1, channel1, revision1)).
|
||||||
AddTokenAuth(token)
|
AddTokenAuth(token)
|
||||||
MakeRequest(t, req, http.StatusOK)
|
MakeRequest(t, req, http.StatusOK)
|
||||||
|
|
||||||
checkRecipeRevisionCount(1)
|
checkRecipeRevisionCount(1)
|
||||||
|
|
||||||
|
req = NewRequest(t, "DELETE", fmt.Sprintf("%s/v2/conans/%s/%s/%s/%s", url, name, version1, user1, channel1)).
|
||||||
|
AddTokenAuth(badToken)
|
||||||
|
MakeRequest(t, req, http.StatusUnauthorized)
|
||||||
|
|
||||||
req = NewRequest(t, "DELETE", fmt.Sprintf("%s/v2/conans/%s/%s/%s/%s", url, name, version1, user1, channel1)).
|
req = NewRequest(t, "DELETE", fmt.Sprintf("%s/v2/conans/%s/%s/%s/%s", url, name, version1, user1, channel1)).
|
||||||
AddTokenAuth(token)
|
AddTokenAuth(token)
|
||||||
MakeRequest(t, req, http.StatusOK)
|
MakeRequest(t, req, http.StatusOK)
|
||||||
|
|
|
@ -23,6 +23,7 @@ import (
|
||||||
"code.gitea.io/gitea/modules/setting"
|
"code.gitea.io/gitea/modules/setting"
|
||||||
api "code.gitea.io/gitea/modules/structs"
|
api "code.gitea.io/gitea/modules/structs"
|
||||||
"code.gitea.io/gitea/modules/test"
|
"code.gitea.io/gitea/modules/test"
|
||||||
|
package_service "code.gitea.io/gitea/services/packages"
|
||||||
"code.gitea.io/gitea/tests"
|
"code.gitea.io/gitea/tests"
|
||||||
|
|
||||||
oci "github.com/opencontainers/image-spec/specs-go/v1"
|
oci "github.com/opencontainers/image-spec/specs-go/v1"
|
||||||
|
@ -78,6 +79,8 @@ func TestPackageContainer(t *testing.T) {
|
||||||
|
|
||||||
anonymousToken := ""
|
anonymousToken := ""
|
||||||
userToken := ""
|
userToken := ""
|
||||||
|
readToken := ""
|
||||||
|
badToken := ""
|
||||||
|
|
||||||
t.Run("Authenticate", func(t *testing.T) {
|
t.Run("Authenticate", func(t *testing.T) {
|
||||||
type TokenResponse struct {
|
type TokenResponse struct {
|
||||||
|
@ -123,7 +126,7 @@ func TestPackageContainer(t *testing.T) {
|
||||||
assert.Equal(t, `Bearer realm="https://domain:8443/v2/token",service="container_registry",scope="*"`, resp.Header().Get("WWW-Authenticate"))
|
assert.Equal(t, `Bearer realm="https://domain:8443/v2/token",service="container_registry",scope="*"`, resp.Header().Get("WWW-Authenticate"))
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("User", func(t *testing.T) {
|
t.Run("UserName/Password", func(t *testing.T) {
|
||||||
defer tests.PrintCurrentTest(t)()
|
defer tests.PrintCurrentTest(t)()
|
||||||
|
|
||||||
req := NewRequest(t, "GET", fmt.Sprintf("%sv2", setting.AppURL))
|
req := NewRequest(t, "GET", fmt.Sprintf("%sv2", setting.AppURL))
|
||||||
|
@ -139,6 +142,10 @@ func TestPackageContainer(t *testing.T) {
|
||||||
DecodeJSON(t, resp, &tokenResponse)
|
DecodeJSON(t, resp, &tokenResponse)
|
||||||
|
|
||||||
assert.NotEmpty(t, tokenResponse.Token)
|
assert.NotEmpty(t, tokenResponse.Token)
|
||||||
|
pkgMeta, err := package_service.ParseAuthorizationToken(tokenResponse.Token)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, user.ID, pkgMeta.UserID)
|
||||||
|
assert.Equal(t, auth_model.AccessTokenScopeAll, pkgMeta.Scope)
|
||||||
|
|
||||||
userToken = fmt.Sprintf("Bearer %s", tokenResponse.Token)
|
userToken = fmt.Sprintf("Bearer %s", tokenResponse.Token)
|
||||||
|
|
||||||
|
@ -146,6 +153,52 @@ func TestPackageContainer(t *testing.T) {
|
||||||
AddTokenAuth(userToken)
|
AddTokenAuth(userToken)
|
||||||
MakeRequest(t, req, http.StatusOK)
|
MakeRequest(t, req, http.StatusOK)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// Token that should enforce the read scope.
|
||||||
|
t.Run("AccessToken", func(t *testing.T) {
|
||||||
|
defer tests.PrintCurrentTest(t)()
|
||||||
|
|
||||||
|
session := loginUser(t, user.Name)
|
||||||
|
|
||||||
|
readToken = getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadPackage)
|
||||||
|
req := NewRequest(t, "GET", fmt.Sprintf("%sv2/token", setting.AppURL))
|
||||||
|
req.Request.SetBasicAuth(user.Name, readToken)
|
||||||
|
resp := MakeRequest(t, req, http.StatusOK)
|
||||||
|
tokenResponse := &TokenResponse{}
|
||||||
|
DecodeJSON(t, resp, &tokenResponse)
|
||||||
|
|
||||||
|
readToken = fmt.Sprintf("Bearer %s", tokenResponse.Token)
|
||||||
|
|
||||||
|
badToken = getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadNotification)
|
||||||
|
req = NewRequest(t, "GET", fmt.Sprintf("%sv2/token", setting.AppURL))
|
||||||
|
req.Request.SetBasicAuth(user.Name, badToken)
|
||||||
|
MakeRequest(t, req, http.StatusUnauthorized)
|
||||||
|
|
||||||
|
testCase := func(scope auth_model.AccessTokenScope, expectedAuthStatus, expectedStatus int) {
|
||||||
|
token := getTokenForLoggedInUser(t, session, scope)
|
||||||
|
|
||||||
|
req := NewRequest(t, "GET", fmt.Sprintf("%sv2/token", setting.AppURL))
|
||||||
|
req.SetBasicAuth(user.Name, token)
|
||||||
|
|
||||||
|
resp := MakeRequest(t, req, expectedAuthStatus)
|
||||||
|
if expectedAuthStatus != http.StatusOK {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
tokenResponse := &TokenResponse{}
|
||||||
|
DecodeJSON(t, resp, &tokenResponse)
|
||||||
|
|
||||||
|
assert.NotEmpty(t, tokenResponse.Token)
|
||||||
|
|
||||||
|
req = NewRequest(t, "GET", fmt.Sprintf("%sv2", setting.AppURL)).
|
||||||
|
AddTokenAuth(fmt.Sprintf("Bearer %s", tokenResponse.Token))
|
||||||
|
MakeRequest(t, req, expectedStatus)
|
||||||
|
}
|
||||||
|
testCase(auth_model.AccessTokenScopeReadPackage, http.StatusOK, http.StatusOK)
|
||||||
|
testCase(auth_model.AccessTokenScopeAll, http.StatusOK, http.StatusOK)
|
||||||
|
testCase(auth_model.AccessTokenScopeReadNotification, http.StatusUnauthorized, http.StatusUnauthorized)
|
||||||
|
testCase(auth_model.AccessTokenScopeWritePackage, http.StatusOK, http.StatusOK)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("DetermineSupport", func(t *testing.T) {
|
t.Run("DetermineSupport", func(t *testing.T) {
|
||||||
|
@ -155,6 +208,15 @@ func TestPackageContainer(t *testing.T) {
|
||||||
AddTokenAuth(userToken)
|
AddTokenAuth(userToken)
|
||||||
resp := MakeRequest(t, req, http.StatusOK)
|
resp := MakeRequest(t, req, http.StatusOK)
|
||||||
assert.Equal(t, "registry/2.0", resp.Header().Get("Docker-Distribution-Api-Version"))
|
assert.Equal(t, "registry/2.0", resp.Header().Get("Docker-Distribution-Api-Version"))
|
||||||
|
|
||||||
|
req = NewRequest(t, "GET", fmt.Sprintf("%sv2", setting.AppURL)).
|
||||||
|
AddTokenAuth(readToken)
|
||||||
|
resp = MakeRequest(t, req, http.StatusOK)
|
||||||
|
assert.Equal(t, "registry/2.0", resp.Header().Get("Docker-Distribution-Api-Version"))
|
||||||
|
|
||||||
|
req = NewRequest(t, "GET", fmt.Sprintf("%sv2", setting.AppURL)).
|
||||||
|
AddTokenAuth(badToken)
|
||||||
|
MakeRequest(t, req, http.StatusUnauthorized)
|
||||||
})
|
})
|
||||||
|
|
||||||
for _, image := range images {
|
for _, image := range images {
|
||||||
|
@ -168,6 +230,14 @@ func TestPackageContainer(t *testing.T) {
|
||||||
AddTokenAuth(anonymousToken)
|
AddTokenAuth(anonymousToken)
|
||||||
MakeRequest(t, req, http.StatusUnauthorized)
|
MakeRequest(t, req, http.StatusUnauthorized)
|
||||||
|
|
||||||
|
req = NewRequest(t, "POST", fmt.Sprintf("%s/blobs/uploads", url)).
|
||||||
|
AddTokenAuth(readToken)
|
||||||
|
MakeRequest(t, req, http.StatusUnauthorized)
|
||||||
|
|
||||||
|
req = NewRequest(t, "POST", fmt.Sprintf("%s/blobs/uploads", url)).
|
||||||
|
AddTokenAuth(badToken)
|
||||||
|
MakeRequest(t, req, http.StatusUnauthorized)
|
||||||
|
|
||||||
req = NewRequestWithBody(t, "POST", fmt.Sprintf("%s/blobs/uploads?digest=%s", url, unknownDigest), bytes.NewReader(blobContent)).
|
req = NewRequestWithBody(t, "POST", fmt.Sprintf("%s/blobs/uploads?digest=%s", url, unknownDigest), bytes.NewReader(blobContent)).
|
||||||
AddTokenAuth(userToken)
|
AddTokenAuth(userToken)
|
||||||
MakeRequest(t, req, http.StatusBadRequest)
|
MakeRequest(t, req, http.StatusBadRequest)
|
||||||
|
@ -195,6 +265,14 @@ func TestPackageContainer(t *testing.T) {
|
||||||
defer tests.PrintCurrentTest(t)()
|
defer tests.PrintCurrentTest(t)()
|
||||||
|
|
||||||
req := NewRequest(t, "POST", fmt.Sprintf("%s/blobs/uploads", url)).
|
req := NewRequest(t, "POST", fmt.Sprintf("%s/blobs/uploads", url)).
|
||||||
|
AddTokenAuth(readToken)
|
||||||
|
MakeRequest(t, req, http.StatusUnauthorized)
|
||||||
|
|
||||||
|
req = NewRequest(t, "POST", fmt.Sprintf("%s/blobs/uploads", url)).
|
||||||
|
AddTokenAuth(badToken)
|
||||||
|
MakeRequest(t, req, http.StatusUnauthorized)
|
||||||
|
|
||||||
|
req = NewRequest(t, "POST", fmt.Sprintf("%s/blobs/uploads", url)).
|
||||||
AddTokenAuth(userToken)
|
AddTokenAuth(userToken)
|
||||||
resp := MakeRequest(t, req, http.StatusAccepted)
|
resp := MakeRequest(t, req, http.StatusAccepted)
|
||||||
|
|
||||||
|
|
|
@ -26,6 +26,7 @@ import (
|
||||||
"code.gitea.io/gitea/modules/setting"
|
"code.gitea.io/gitea/modules/setting"
|
||||||
"code.gitea.io/gitea/modules/structs"
|
"code.gitea.io/gitea/modules/structs"
|
||||||
"code.gitea.io/gitea/routers/api/packages/nuget"
|
"code.gitea.io/gitea/routers/api/packages/nuget"
|
||||||
|
packageService "code.gitea.io/gitea/services/packages"
|
||||||
"code.gitea.io/gitea/tests"
|
"code.gitea.io/gitea/tests"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
@ -81,7 +82,9 @@ func TestPackageNuGet(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
|
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
|
||||||
token := getUserToken(t, user.Name, auth_model.AccessTokenScopeWritePackage)
|
writeToken := getUserToken(t, user.Name, auth_model.AccessTokenScopeWritePackage)
|
||||||
|
readToken := getUserToken(t, user.Name, auth_model.AccessTokenScopeReadPackage)
|
||||||
|
badToken := getUserToken(t, user.Name, auth_model.AccessTokenScopeReadNotification)
|
||||||
|
|
||||||
packageName := "test.package"
|
packageName := "test.package"
|
||||||
packageVersion := "1.0.3"
|
packageVersion := "1.0.3"
|
||||||
|
@ -127,34 +130,44 @@ func TestPackageNuGet(t *testing.T) {
|
||||||
privateUser := unittest.AssertExistsAndLoadBean(t, &user_model.User{Visibility: structs.VisibleTypePrivate})
|
privateUser := unittest.AssertExistsAndLoadBean(t, &user_model.User{Visibility: structs.VisibleTypePrivate})
|
||||||
|
|
||||||
cases := []struct {
|
cases := []struct {
|
||||||
Owner string
|
Owner string
|
||||||
UseBasicAuth bool
|
UseBasicAuth bool
|
||||||
UseTokenAuth bool
|
token string
|
||||||
|
expectedStatus int
|
||||||
}{
|
}{
|
||||||
{privateUser.Name, false, false},
|
{privateUser.Name, false, "", http.StatusOK},
|
||||||
{privateUser.Name, true, false},
|
{privateUser.Name, true, "", http.StatusOK},
|
||||||
{privateUser.Name, false, true},
|
{privateUser.Name, false, writeToken, http.StatusOK},
|
||||||
{user.Name, false, false},
|
{privateUser.Name, false, readToken, http.StatusOK},
|
||||||
{user.Name, true, false},
|
{privateUser.Name, false, badToken, http.StatusOK},
|
||||||
{user.Name, false, true},
|
{user.Name, false, "", http.StatusOK},
|
||||||
|
{user.Name, true, "", http.StatusOK},
|
||||||
|
{user.Name, false, writeToken, http.StatusOK},
|
||||||
|
{user.Name, false, readToken, http.StatusOK},
|
||||||
|
{user.Name, false, badToken, http.StatusOK},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, c := range cases {
|
for _, c := range cases {
|
||||||
url := fmt.Sprintf("/api/packages/%s/nuget", c.Owner)
|
t.Run(c.Owner, func(t *testing.T) {
|
||||||
|
url := fmt.Sprintf("/api/packages/%s/nuget", c.Owner)
|
||||||
|
|
||||||
req := NewRequest(t, "GET", url)
|
req := NewRequest(t, "GET", url)
|
||||||
if c.UseBasicAuth {
|
if c.UseBasicAuth {
|
||||||
req.AddBasicAuth(user.Name)
|
req.AddBasicAuth(user.Name)
|
||||||
} else if c.UseTokenAuth {
|
} else if c.token != "" {
|
||||||
addNuGetAPIKeyHeader(req, token)
|
addNuGetAPIKeyHeader(req, c.token)
|
||||||
}
|
}
|
||||||
resp := MakeRequest(t, req, http.StatusOK)
|
resp := MakeRequest(t, req, c.expectedStatus)
|
||||||
|
if c.expectedStatus != http.StatusOK {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
var result nuget.ServiceIndexResponseV2
|
var result nuget.ServiceIndexResponseV2
|
||||||
decodeXML(t, resp, &result)
|
decodeXML(t, resp, &result)
|
||||||
|
|
||||||
assert.Equal(t, setting.AppURL+url[1:], result.Base)
|
assert.Equal(t, setting.AppURL+url[1:], result.Base)
|
||||||
assert.Equal(t, "Packages", result.Workspace.Collection.Href)
|
assert.Equal(t, "Packages", result.Workspace.Collection.Href)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -164,56 +177,67 @@ func TestPackageNuGet(t *testing.T) {
|
||||||
privateUser := unittest.AssertExistsAndLoadBean(t, &user_model.User{Visibility: structs.VisibleTypePrivate})
|
privateUser := unittest.AssertExistsAndLoadBean(t, &user_model.User{Visibility: structs.VisibleTypePrivate})
|
||||||
|
|
||||||
cases := []struct {
|
cases := []struct {
|
||||||
Owner string
|
Owner string
|
||||||
UseBasicAuth bool
|
UseBasicAuth bool
|
||||||
UseTokenAuth bool
|
token string
|
||||||
|
expectedStatus int
|
||||||
}{
|
}{
|
||||||
{privateUser.Name, false, false},
|
{privateUser.Name, false, "", http.StatusOK},
|
||||||
{privateUser.Name, true, false},
|
{privateUser.Name, true, "", http.StatusOK},
|
||||||
{privateUser.Name, false, true},
|
{privateUser.Name, false, writeToken, http.StatusOK},
|
||||||
{user.Name, false, false},
|
{privateUser.Name, false, readToken, http.StatusOK},
|
||||||
{user.Name, true, false},
|
{privateUser.Name, false, badToken, http.StatusOK},
|
||||||
{user.Name, false, true},
|
{user.Name, false, "", http.StatusOK},
|
||||||
|
{user.Name, true, "", http.StatusOK},
|
||||||
|
{user.Name, false, writeToken, http.StatusOK},
|
||||||
|
{user.Name, false, readToken, http.StatusOK},
|
||||||
|
{user.Name, false, badToken, http.StatusOK},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, c := range cases {
|
for _, c := range cases {
|
||||||
url := fmt.Sprintf("/api/packages/%s/nuget", c.Owner)
|
t.Run(c.Owner, func(t *testing.T) {
|
||||||
|
url := fmt.Sprintf("/api/packages/%s/nuget", c.Owner)
|
||||||
|
|
||||||
req := NewRequest(t, "GET", fmt.Sprintf("%s/index.json", url))
|
req := NewRequest(t, "GET", fmt.Sprintf("%s/index.json", url))
|
||||||
if c.UseBasicAuth {
|
if c.UseBasicAuth {
|
||||||
req.AddBasicAuth(user.Name)
|
req.AddBasicAuth(user.Name)
|
||||||
} else if c.UseTokenAuth {
|
} else if c.token != "" {
|
||||||
addNuGetAPIKeyHeader(req, token)
|
addNuGetAPIKeyHeader(req, c.token)
|
||||||
}
|
|
||||||
resp := MakeRequest(t, req, http.StatusOK)
|
|
||||||
|
|
||||||
var result nuget.ServiceIndexResponseV3
|
|
||||||
DecodeJSON(t, resp, &result)
|
|
||||||
|
|
||||||
assert.Equal(t, "3.0.0", result.Version)
|
|
||||||
assert.NotEmpty(t, result.Resources)
|
|
||||||
|
|
||||||
root := setting.AppURL + url[1:]
|
|
||||||
for _, r := range result.Resources {
|
|
||||||
switch r.Type {
|
|
||||||
case "SearchQueryService":
|
|
||||||
fallthrough
|
|
||||||
case "SearchQueryService/3.0.0-beta":
|
|
||||||
fallthrough
|
|
||||||
case "SearchQueryService/3.0.0-rc":
|
|
||||||
assert.Equal(t, root+"/query", r.ID)
|
|
||||||
case "RegistrationsBaseUrl":
|
|
||||||
fallthrough
|
|
||||||
case "RegistrationsBaseUrl/3.0.0-beta":
|
|
||||||
fallthrough
|
|
||||||
case "RegistrationsBaseUrl/3.0.0-rc":
|
|
||||||
assert.Equal(t, root+"/registration", r.ID)
|
|
||||||
case "PackageBaseAddress/3.0.0":
|
|
||||||
assert.Equal(t, root+"/package", r.ID)
|
|
||||||
case "PackagePublish/2.0.0":
|
|
||||||
assert.Equal(t, root, r.ID)
|
|
||||||
}
|
}
|
||||||
}
|
resp := MakeRequest(t, req, c.expectedStatus)
|
||||||
|
|
||||||
|
if c.expectedStatus != http.StatusOK {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var result nuget.ServiceIndexResponseV3
|
||||||
|
DecodeJSON(t, resp, &result)
|
||||||
|
|
||||||
|
assert.Equal(t, "3.0.0", result.Version)
|
||||||
|
assert.NotEmpty(t, result.Resources)
|
||||||
|
|
||||||
|
root := setting.AppURL + url[1:]
|
||||||
|
for _, r := range result.Resources {
|
||||||
|
switch r.Type {
|
||||||
|
case "SearchQueryService":
|
||||||
|
fallthrough
|
||||||
|
case "SearchQueryService/3.0.0-beta":
|
||||||
|
fallthrough
|
||||||
|
case "SearchQueryService/3.0.0-rc":
|
||||||
|
assert.Equal(t, root+"/query", r.ID)
|
||||||
|
case "RegistrationsBaseUrl":
|
||||||
|
fallthrough
|
||||||
|
case "RegistrationsBaseUrl/3.0.0-beta":
|
||||||
|
fallthrough
|
||||||
|
case "RegistrationsBaseUrl/3.0.0-rc":
|
||||||
|
assert.Equal(t, root+"/registration", r.ID)
|
||||||
|
case "PackageBaseAddress/3.0.0":
|
||||||
|
assert.Equal(t, root+"/package", r.ID)
|
||||||
|
case "PackagePublish/2.0.0":
|
||||||
|
assert.Equal(t, root, r.ID)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
@ -222,6 +246,7 @@ func TestPackageNuGet(t *testing.T) {
|
||||||
t.Run("DependencyPackage", func(t *testing.T) {
|
t.Run("DependencyPackage", func(t *testing.T) {
|
||||||
defer tests.PrintCurrentTest(t)()
|
defer tests.PrintCurrentTest(t)()
|
||||||
|
|
||||||
|
// create with username/password
|
||||||
req := NewRequestWithBody(t, "PUT", url, bytes.NewReader(content)).
|
req := NewRequestWithBody(t, "PUT", url, bytes.NewReader(content)).
|
||||||
AddBasicAuth(user.Name)
|
AddBasicAuth(user.Name)
|
||||||
MakeRequest(t, req, http.StatusCreated)
|
MakeRequest(t, req, http.StatusCreated)
|
||||||
|
@ -258,6 +283,52 @@ func TestPackageNuGet(t *testing.T) {
|
||||||
req = NewRequestWithBody(t, "PUT", url, bytes.NewReader(content)).
|
req = NewRequestWithBody(t, "PUT", url, bytes.NewReader(content)).
|
||||||
AddBasicAuth(user.Name)
|
AddBasicAuth(user.Name)
|
||||||
MakeRequest(t, req, http.StatusConflict)
|
MakeRequest(t, req, http.StatusConflict)
|
||||||
|
|
||||||
|
// delete the package
|
||||||
|
assert.NoError(t, packageService.DeletePackageVersionAndReferences(db.DefaultContext, pvs[0]))
|
||||||
|
|
||||||
|
// create failure with token without write access
|
||||||
|
req = NewRequestWithBody(t, "PUT", url, bytes.NewReader(content)).
|
||||||
|
AddTokenAuth(readToken)
|
||||||
|
MakeRequest(t, req, http.StatusUnauthorized)
|
||||||
|
|
||||||
|
// create with token
|
||||||
|
req = NewRequestWithBody(t, "PUT", url, bytes.NewReader(content)).
|
||||||
|
AddTokenAuth(writeToken)
|
||||||
|
MakeRequest(t, req, http.StatusCreated)
|
||||||
|
|
||||||
|
pvs, err = packages.GetVersionsByPackageType(db.DefaultContext, user.ID, packages.TypeNuGet)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Len(t, pvs, 1, "Should have one version")
|
||||||
|
|
||||||
|
pd, err = packages.GetPackageDescriptor(db.DefaultContext, pvs[0])
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.NotNil(t, pd.SemVer)
|
||||||
|
assert.IsType(t, &nuget_module.Metadata{}, pd.Metadata)
|
||||||
|
assert.Equal(t, packageName, pd.Package.Name)
|
||||||
|
assert.Equal(t, packageVersion, pd.Version.Version)
|
||||||
|
|
||||||
|
pfs, err = packages.GetFilesByVersionID(db.DefaultContext, pvs[0].ID)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Len(t, pfs, 2, "Should have 2 files: nuget and nuspec")
|
||||||
|
for _, pf := range pfs {
|
||||||
|
switch pf.Name {
|
||||||
|
case fmt.Sprintf("%s.%s.nupkg", packageName, packageVersion):
|
||||||
|
assert.True(t, pf.IsLead)
|
||||||
|
|
||||||
|
pb, err := packages.GetBlobByID(db.DefaultContext, pf.BlobID)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, int64(len(content)), pb.Size)
|
||||||
|
case fmt.Sprintf("%s.nuspec", packageName):
|
||||||
|
assert.False(t, pf.IsLead)
|
||||||
|
default:
|
||||||
|
assert.Fail(t, "unexpected filename: %v", pf.Name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
req = NewRequestWithBody(t, "PUT", url, bytes.NewReader(content)).
|
||||||
|
AddBasicAuth(user.Name)
|
||||||
|
MakeRequest(t, req, http.StatusConflict)
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("SymbolPackage", func(t *testing.T) {
|
t.Run("SymbolPackage", func(t *testing.T) {
|
||||||
|
|
Loading…
Reference in New Issue