+-
+ 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
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"])
}
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")
}
"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/oauth2_provider"
)
return grant.UserID
}
+// 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.
func (o *OAuth2) userIDFromToken(ctx context.Context, tokenSHA string, store DataStore) int64 {
// Let's see if token is valid.
if strings.Contains(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 := CheckOAuthAccessToken(ctx, tokenSHA)
if uid != 0 {
store.GetData()["IsApiToken"] = true
--- /dev/null
+// 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"
+)
+
+func TestUserIDFromToken(t *testing.T) {
+ assert.NoError(t, unittest.PrepareTestDatabase())
+
+ t.Run("Actions JWT", func(t *testing.T) {
+ const RunningTaskID = 47
+ token, err := actions.CreateAuthorizationToken(RunningTaskID, 1, 2)
+ assert.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, ds["IsActionsToken"], true)
+ assert.Equal(t, ds["ActionsTaskID"], int64(RunningTaskID))
+ })
+}
+
+func TestCheckTaskIsRunning(t *testing.T) {
+ assert.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)
+ })
+ }
+}