mirror of
https://codeberg.org/forgejo/forgejo.git
synced 2024-12-27 14:13:52 +03:00
48872d11ca
We have some actions that leverage the Gitea API that began receiving 401 errors, with a message that the user was not found. These actions use the `ACTIONS_RUNTIME_TOKEN` env var in the actions job to authenticate with the Gitea API. The format of this env var in actions jobs changed with go-gitea/gitea/pull/28885 to be a JWT (with a corresponding update to `act_runner`) Since it was a JWT, the OAuth parsing logic attempted to parse it as an OAuth token, and would return user not found, instead of falling back to look up the running task and assigning it to the actions user. Make ACTIONS_RUNTIME_TOKEN in action runners could be used, attempting to parse Oauth JWTs. The code to parse potential old `ACTION_RUNTIME_TOKEN` was kept in case someone is running an older version of act_runner that doesn't support the Actions JWT. (cherry picked from commit 407b6e6dfc7ee9ebb8a16c7f1a786e4c24d0516e) Conflicts: services/auth/oauth2.go trivial context conflicts because OAuth2 scopes are in Forgejo and not yet in Gitea
107 lines
2.3 KiB
Go
107 lines
2.3 KiB
Go
// Copyright 2024 The Gitea Authors. All rights reserved.
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
package actions
|
|
|
|
import (
|
|
"fmt"
|
|
"net/http"
|
|
"strings"
|
|
"time"
|
|
|
|
"code.gitea.io/gitea/modules/json"
|
|
"code.gitea.io/gitea/modules/log"
|
|
"code.gitea.io/gitea/modules/setting"
|
|
|
|
"github.com/golang-jwt/jwt/v5"
|
|
)
|
|
|
|
type actionsClaims struct {
|
|
jwt.RegisteredClaims
|
|
Scp string `json:"scp"`
|
|
TaskID int64
|
|
RunID int64
|
|
JobID int64
|
|
Ac string `json:"ac"`
|
|
}
|
|
|
|
type actionsCacheScope struct {
|
|
Scope string
|
|
Permission actionsCachePermission
|
|
}
|
|
|
|
type actionsCachePermission int
|
|
|
|
const (
|
|
actionsCachePermissionRead = 1 << iota
|
|
actionsCachePermissionWrite
|
|
)
|
|
|
|
func CreateAuthorizationToken(taskID, runID, jobID int64) (string, error) {
|
|
now := time.Now()
|
|
|
|
ac, err := json.Marshal(&[]actionsCacheScope{
|
|
{
|
|
Scope: "",
|
|
Permission: actionsCachePermissionWrite,
|
|
},
|
|
})
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
claims := actionsClaims{
|
|
RegisteredClaims: jwt.RegisteredClaims{
|
|
ExpiresAt: jwt.NewNumericDate(now.Add(24 * time.Hour)),
|
|
NotBefore: jwt.NewNumericDate(now),
|
|
},
|
|
Scp: fmt.Sprintf("Actions.Results:%d:%d", runID, jobID),
|
|
Ac: string(ac),
|
|
TaskID: taskID,
|
|
RunID: runID,
|
|
JobID: jobID,
|
|
}
|
|
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
|
|
|
|
tokenString, err := token.SignedString(setting.GetGeneralTokenSigningSecret())
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
return tokenString, nil
|
|
}
|
|
|
|
func ParseAuthorizationToken(req *http.Request) (int64, error) {
|
|
h := req.Header.Get("Authorization")
|
|
if h == "" {
|
|
return 0, 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 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 setting.GetGeneralTokenSigningSecret(), nil
|
|
})
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
|
|
c, ok := parsedToken.Claims.(*actionsClaims)
|
|
if !parsedToken.Valid || !ok {
|
|
return 0, fmt.Errorf("invalid token claim")
|
|
}
|
|
|
|
return c.TaskID, nil
|
|
}
|