Merge pull request '[gitea] week 2024-48-v9.0 cherry pick (gitea/main -> v9.0/forgejo)' (#6064) from earl-warren/wcp/2024-48-v9.0 into v9.0/forgejo
Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/6064 Reviewed-by: Gusted <gusted@noreply.codeberg.org>
This commit is contained in:
commit
a494510972
10 changed files with 186 additions and 34 deletions
|
@ -1,3 +1,22 @@
|
|||
-
|
||||
id: 46
|
||||
attempt: 3
|
||||
runner_id: 1
|
||||
status: 3 # 3 is the status code for "cancelled"
|
||||
started: 1683636528
|
||||
stopped: 1683636626
|
||||
repo_id: 4
|
||||
owner_id: 1
|
||||
commit_sha: c2d72f548424103f01ee1dc02889c1e2bff816b0
|
||||
is_fork_pull_request: 0
|
||||
token_hash: 6d8ef48297195edcc8e22c70b3020eaa06c52976db67d39b4260c64a69a2cc1508825121b7b8394e48e00b1bf8718b2aaaaa
|
||||
token_salt: eeeeeeee
|
||||
token_last_eight: eeeeeeee
|
||||
log_filename: artifact-test2/2f/47.log
|
||||
log_in_storage: 1
|
||||
log_length: 707
|
||||
log_size: 90179
|
||||
log_expired: 0
|
||||
-
|
||||
id: 47
|
||||
job_id: 192
|
||||
|
|
|
@ -332,6 +332,7 @@
|
|||
repo_admin_change_team_access: false
|
||||
theme: ""
|
||||
keep_activity_private: false
|
||||
created_unix: 1730468968
|
||||
|
||||
-
|
||||
id: 10
|
||||
|
|
|
@ -50,19 +50,19 @@ const (
|
|||
UserTypeIndividual UserType = iota // Historic reason to make it starts at 0.
|
||||
|
||||
// UserTypeOrganization defines an organization
|
||||
UserTypeOrganization
|
||||
UserTypeOrganization // 1
|
||||
|
||||
// UserTypeUserReserved reserves a (non-existing) user, i.e. to prevent a spam user from re-registering after being deleted, or to reserve the name until the user is actually created later on
|
||||
UserTypeUserReserved
|
||||
UserTypeUserReserved // 2
|
||||
|
||||
// UserTypeOrganizationReserved reserves a (non-existing) organization, to be used in combination with UserTypeUserReserved
|
||||
UserTypeOrganizationReserved
|
||||
UserTypeOrganizationReserved // 3
|
||||
|
||||
// UserTypeBot defines a bot user
|
||||
UserTypeBot
|
||||
UserTypeBot // 4
|
||||
|
||||
// UserTypeRemoteUser defines a remote user for federated users
|
||||
UserTypeRemoteUser
|
||||
UserTypeRemoteUser // 5
|
||||
)
|
||||
|
||||
const (
|
||||
|
@ -917,7 +917,13 @@ func UpdateUserCols(ctx context.Context, u *User, cols ...string) error {
|
|||
|
||||
// GetInactiveUsers gets all inactive users
|
||||
func GetInactiveUsers(ctx context.Context, olderThan time.Duration) ([]*User, error) {
|
||||
var cond builder.Cond = builder.Eq{"is_active": false}
|
||||
cond := builder.And(
|
||||
builder.Eq{"is_active": false},
|
||||
builder.Or( // only plain user
|
||||
builder.Eq{"`type`": UserTypeIndividual},
|
||||
builder.Eq{"`type`": UserTypeUserReserved},
|
||||
),
|
||||
)
|
||||
|
||||
if olderThan > 0 {
|
||||
cond = cond.And(builder.Lt{"created_unix": time.Now().Add(-olderThan).Unix()})
|
||||
|
|
|
@ -765,3 +765,17 @@ func TestVerifyUserAuthorizationToken(t *testing.T) {
|
|||
assert.Nil(t, authToken)
|
||||
})
|
||||
}
|
||||
|
||||
func TestGetInactiveUsers(t *testing.T) {
|
||||
require.NoError(t, unittest.PrepareTestDatabase())
|
||||
|
||||
// all inactive users
|
||||
// user1's createdunix is 1730468968
|
||||
users, err := user_model.GetInactiveUsers(db.DefaultContext, 0)
|
||||
require.NoError(t, err)
|
||||
assert.Len(t, users, 1)
|
||||
interval := time.Now().Unix() - 1730468968 + 3600*24
|
||||
users, err = user_model.GetInactiveUsers(db.DefaultContext, time.Duration(interval*int64(time.Second)))
|
||||
require.NoError(t, err)
|
||||
require.Empty(t, users)
|
||||
}
|
||||
|
|
|
@ -17,6 +17,8 @@ import (
|
|||
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
|
||||
"github.com/go-git/go-git/v5/config"
|
||||
)
|
||||
|
||||
// Commit represents a git commit.
|
||||
|
@ -365,37 +367,32 @@ func (c *Commit) GetSubModules() (*ObjectCache, error) {
|
|||
return nil, err
|
||||
}
|
||||
|
||||
rd, err := entry.Blob().DataAsync()
|
||||
content, err := entry.Blob().GetBlobContent(10 * 1024)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
defer rd.Close()
|
||||
scanner := bufio.NewScanner(rd)
|
||||
c.submoduleCache = newObjectCache()
|
||||
var ismodule bool
|
||||
var path string
|
||||
for scanner.Scan() {
|
||||
if strings.HasPrefix(scanner.Text(), "[submodule") {
|
||||
ismodule = true
|
||||
continue
|
||||
}
|
||||
if ismodule {
|
||||
fields := strings.Split(scanner.Text(), "=")
|
||||
k := strings.TrimSpace(fields[0])
|
||||
if k == "path" {
|
||||
path = strings.TrimSpace(fields[1])
|
||||
} else if k == "url" {
|
||||
c.submoduleCache.Set(path, &SubModule{path, strings.TrimSpace(fields[1])})
|
||||
ismodule = false
|
||||
}
|
||||
}
|
||||
c.submoduleCache, err = parseSubmoduleContent([]byte(content))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err = scanner.Err(); err != nil {
|
||||
return nil, fmt.Errorf("GetSubModules scan: %w", err)
|
||||
return c.submoduleCache, nil
|
||||
}
|
||||
|
||||
func parseSubmoduleContent(bs []byte) (*ObjectCache, error) {
|
||||
cfg := config.NewModules()
|
||||
if err := cfg.Unmarshal(bs); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
submoduleCache := newObjectCache()
|
||||
if len(cfg.Submodules) == 0 {
|
||||
return nil, fmt.Errorf("no submodules found")
|
||||
}
|
||||
for _, subModule := range cfg.Submodules {
|
||||
submoduleCache.Set(subModule.Path, subModule.URL)
|
||||
}
|
||||
|
||||
return c.submoduleCache, nil
|
||||
return submoduleCache, nil
|
||||
}
|
||||
|
||||
// GetSubModule get the sub module according entryname
|
||||
|
|
|
@ -369,3 +369,33 @@ func TestParseCommitRenames(t *testing.T) {
|
|||
assert.Equal(t, testcase.renames, renames)
|
||||
}
|
||||
}
|
||||
|
||||
func Test_parseSubmoduleContent(t *testing.T) {
|
||||
submoduleFiles := []struct {
|
||||
fileContent string
|
||||
expectedPath string
|
||||
expectedURL string
|
||||
}{
|
||||
{
|
||||
fileContent: `[submodule "jakarta-servlet"]
|
||||
url = ../../ALP-pool/jakarta-servlet
|
||||
path = jakarta-servlet`,
|
||||
expectedPath: "jakarta-servlet",
|
||||
expectedURL: "../../ALP-pool/jakarta-servlet",
|
||||
},
|
||||
{
|
||||
fileContent: `[submodule "jakarta-servlet"]
|
||||
path = jakarta-servlet
|
||||
url = ../../ALP-pool/jakarta-servlet`,
|
||||
expectedPath: "jakarta-servlet",
|
||||
expectedURL: "../../ALP-pool/jakarta-servlet",
|
||||
},
|
||||
}
|
||||
for _, kase := range submoduleFiles {
|
||||
submodule, err := parseSubmoduleContent([]byte(kase.fileContent))
|
||||
require.NoError(t, err)
|
||||
v, ok := submodule.Get(kase.expectedPath)
|
||||
assert.True(t, ok)
|
||||
assert.Equal(t, kase.expectedURL, v)
|
||||
}
|
||||
}
|
||||
|
|
3
release-notes/6064.md
Normal file
3
release-notes/6064.md
Normal file
|
@ -0,0 +1,3 @@
|
|||
fix: [commit](https://codeberg.org/forgejo/forgejo/commit/bf520f5184327eea9590ac5eb52d98f16af43c12) Fix GetInactiveUsers
|
||||
fix: [commit](https://codeberg.org/forgejo/forgejo/commit/1c04f8f10a7ebc02f41af3d2db6a2a4e85127441) Fix submodule parsing
|
||||
fix: [commit](https://codeberg.org/forgejo/forgejo/commit/48872d11ca920849ec174f76c0d667ca2b289aef) allow the actions user to login via the jwt token
|
|
@ -83,7 +83,12 @@ func ParseAuthorizationToken(req *http.Request) (int64, error) {
|
|||
return 0, fmt.Errorf("split token failed")
|
||||
}
|
||||
|
||||
token, err := jwt.ParseWithClaims(parts[1], &actionsClaims{}, func(t *jwt.Token) (any, error) {
|
||||
return TokenToTaskID(parts[1])
|
||||
}
|
||||
|
||||
// TokenToTaskID returns the TaskID associated with the provided JWT token
|
||||
func TokenToTaskID(token string) (int64, error) {
|
||||
parsedToken, err := jwt.ParseWithClaims(token, &actionsClaims{}, func(t *jwt.Token) (any, error) {
|
||||
if _, ok := t.Method.(*jwt.SigningMethodHMAC); !ok {
|
||||
return nil, fmt.Errorf("unexpected signing method: %v", t.Header["alg"])
|
||||
}
|
||||
|
@ -93,8 +98,8 @@ func ParseAuthorizationToken(req *http.Request) (int64, error) {
|
|||
return 0, err
|
||||
}
|
||||
|
||||
c, ok := token.Claims.(*actionsClaims)
|
||||
if !token.Valid || !ok {
|
||||
c, ok := parsedToken.Claims.(*actionsClaims)
|
||||
if !parsedToken.Valid || !ok {
|
||||
return 0, fmt.Errorf("invalid token claim")
|
||||
}
|
||||
|
||||
|
|
|
@ -18,6 +18,7 @@ import (
|
|||
"code.gitea.io/gitea/modules/setting"
|
||||
"code.gitea.io/gitea/modules/timeutil"
|
||||
"code.gitea.io/gitea/modules/web/middleware"
|
||||
"code.gitea.io/gitea/services/actions"
|
||||
"code.gitea.io/gitea/services/auth/source/oauth2"
|
||||
)
|
||||
|
||||
|
@ -94,6 +95,18 @@ func CheckOAuthAccessToken(ctx context.Context, accessToken string) (int64, stri
|
|||
return grant.UserID, grantScopes
|
||||
}
|
||||
|
||||
// CheckTaskIsRunning verifies that the TaskID corresponds to a running task
|
||||
func CheckTaskIsRunning(ctx context.Context, taskID int64) bool {
|
||||
// Verify the task exists
|
||||
task, err := actions_model.GetTaskByID(ctx, taskID)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
// Verify that it's running
|
||||
return task.Status == actions_model.StatusRunning
|
||||
}
|
||||
|
||||
// OAuth2 implements the Auth interface and authenticates requests
|
||||
// (API requests only) by looking for an OAuth token in query parameters or the
|
||||
// "Authorization" header.
|
||||
|
@ -137,8 +150,17 @@ func parseToken(req *http.Request) (string, bool) {
|
|||
func (o *OAuth2) userIDFromToken(ctx context.Context, tokenSHA string, store DataStore) int64 {
|
||||
// Let's see if token is valid.
|
||||
if strings.Contains(tokenSHA, ".") {
|
||||
uid, grantScopes := CheckOAuthAccessToken(ctx, tokenSHA)
|
||||
// First attempt to decode an actions JWT, returning the actions user
|
||||
if taskID, err := actions.TokenToTaskID(tokenSHA); err == nil {
|
||||
if CheckTaskIsRunning(ctx, taskID) {
|
||||
store.GetData()["IsActionsToken"] = true
|
||||
store.GetData()["ActionsTaskID"] = taskID
|
||||
return user_model.ActionsUserID
|
||||
}
|
||||
}
|
||||
|
||||
// Otherwise, check if this is an OAuth access token
|
||||
uid, grantScopes := CheckOAuthAccessToken(ctx, tokenSHA)
|
||||
if uid != 0 {
|
||||
store.GetData()["IsApiToken"] = true
|
||||
if grantScopes != "" {
|
||||
|
|
55
services/auth/oauth2_test.go
Normal file
55
services/auth/oauth2_test.go
Normal file
|
@ -0,0 +1,55 @@
|
|||
// Copyright 2024 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package auth
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"code.gitea.io/gitea/models/unittest"
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
"code.gitea.io/gitea/modules/web/middleware"
|
||||
"code.gitea.io/gitea/services/actions"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestUserIDFromToken(t *testing.T) {
|
||||
require.NoError(t, unittest.PrepareTestDatabase())
|
||||
|
||||
t.Run("Actions JWT", func(t *testing.T) {
|
||||
const RunningTaskID = 47
|
||||
token, err := actions.CreateAuthorizationToken(RunningTaskID, 1, 2)
|
||||
require.NoError(t, err)
|
||||
|
||||
ds := make(middleware.ContextData)
|
||||
|
||||
o := OAuth2{}
|
||||
uid := o.userIDFromToken(context.Background(), token, ds)
|
||||
assert.Equal(t, int64(user_model.ActionsUserID), uid)
|
||||
assert.Equal(t, true, ds["IsActionsToken"])
|
||||
assert.Equal(t, ds["ActionsTaskID"], int64(RunningTaskID))
|
||||
})
|
||||
}
|
||||
|
||||
func TestCheckTaskIsRunning(t *testing.T) {
|
||||
require.NoError(t, unittest.PrepareTestDatabase())
|
||||
cases := map[string]struct {
|
||||
TaskID int64
|
||||
Expected bool
|
||||
}{
|
||||
"Running": {TaskID: 47, Expected: true},
|
||||
"Missing": {TaskID: 1, Expected: false},
|
||||
"Cancelled": {TaskID: 46, Expected: false},
|
||||
}
|
||||
|
||||
for name := range cases {
|
||||
c := cases[name]
|
||||
t.Run(name, func(t *testing.T) {
|
||||
actual := CheckTaskIsRunning(context.Background(), c.TaskID)
|
||||
assert.Equal(t, c.Expected, actual)
|
||||
})
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue