mirror of
https://github.com/woodpecker-ci/woodpecker.git
synced 2024-11-25 11:21:02 +00:00
Use IDs for tokens (#3695)
This commit is contained in:
parent
6132136d55
commit
fd57e4ad05
8 changed files with 83 additions and 33 deletions
|
@ -2,6 +2,13 @@
|
|||
|
||||
Some versions need some changes to the server configuration or the pipeline configuration files.
|
||||
|
||||
<!--
|
||||
## 3.0.0
|
||||
|
||||
- Update all webhooks by pressing the "Repair all" button in the admin settings as the webhook token claims have changed
|
||||
|
||||
-->
|
||||
|
||||
## `next`
|
||||
|
||||
- Deprecated `steps.[name].group` in favor of `steps.[name].depends_on` (see [workflow syntax](./20-usage/20-workflow-syntax.md#depends_on) to learn how to set dependencies)
|
||||
|
|
|
@ -20,6 +20,7 @@ import (
|
|||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/rs/zerolog/log"
|
||||
|
@ -157,7 +158,7 @@ func PostHook(c *gin.Context) {
|
|||
c.Status(http.StatusNoContent)
|
||||
return
|
||||
}
|
||||
oldFullName := repo.FullName
|
||||
currentRepoFullName := repo.FullName
|
||||
|
||||
if repo.UserID == 0 {
|
||||
log.Warn().Msgf("ignoring hook. repo %s has no owner.", repo.FullName)
|
||||
|
@ -177,7 +178,7 @@ func PostHook(c *gin.Context) {
|
|||
//
|
||||
|
||||
// get the token and verify the hook is authorized
|
||||
parsed, err := token.ParseRequest(c.Request, func(_ *token.Token) (string, error) {
|
||||
parsedToken, err := token.ParseRequest(c.Request, func(_ *token.Token) (string, error) {
|
||||
return repo.Hash, nil
|
||||
})
|
||||
if err != nil {
|
||||
|
@ -186,7 +187,9 @@ func PostHook(c *gin.Context) {
|
|||
c.String(http.StatusBadRequest, msg)
|
||||
return
|
||||
}
|
||||
verifiedKey := parsed.Text == oldFullName
|
||||
|
||||
// TODO: remove fallback for text full name in next major release
|
||||
verifiedKey := parsedToken.Get("repo-id") == strconv.FormatInt(repo.ID, 10) || parsedToken.Get("text") == currentRepoFullName
|
||||
if !verifiedKey {
|
||||
verifiedKey, err = _store.HasRedirectionForRepo(repo.ID, repo.FullName)
|
||||
if err != nil {
|
||||
|
@ -198,7 +201,7 @@ func PostHook(c *gin.Context) {
|
|||
}
|
||||
|
||||
if !verifiedKey {
|
||||
msg := fmt.Sprintf("failure to verify token from hook. Expected %s, got %s", repo.FullName, parsed.Text)
|
||||
msg := fmt.Sprintf("failure to verify token from hook. Expected %s, got %s", repo.FullName, parsedToken.Get("text"))
|
||||
log.Debug().Msg(msg)
|
||||
c.String(http.StatusForbidden, msg)
|
||||
return
|
||||
|
@ -208,7 +211,7 @@ func PostHook(c *gin.Context) {
|
|||
// 4. Update repo
|
||||
//
|
||||
|
||||
if oldFullName != tmpRepo.FullName {
|
||||
if currentRepoFullName != tmpRepo.FullName {
|
||||
// create a redirection
|
||||
err = _store.CreateRedirection(&model.Redirection{RepoID: repo.ID, FullName: repo.FullName})
|
||||
if err != nil {
|
||||
|
|
|
@ -18,6 +18,7 @@ import (
|
|||
"encoding/base32"
|
||||
"errors"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
|
@ -138,7 +139,7 @@ func HandleAuth(c *gin.Context) {
|
|||
ForgeID: u.ForgeID,
|
||||
}
|
||||
if err := _store.OrgCreate(org); err != nil {
|
||||
log.Error().Err(err).Msgf("on user creation, could create org for user")
|
||||
log.Error().Err(err).Msgf("on user creation, could not create org for user")
|
||||
}
|
||||
u.OrgID = org.ID
|
||||
}
|
||||
|
@ -185,7 +186,9 @@ func HandleAuth(c *gin.Context) {
|
|||
}
|
||||
|
||||
exp := time.Now().Add(server.Config.Server.SessionExpires).Unix()
|
||||
tokenString, err := token.New(token.SessToken, u.Login).SignExpires(u.Hash, exp)
|
||||
_token := token.New(token.SessToken)
|
||||
_token.Set("user-id", strconv.FormatInt(u.ID, 10))
|
||||
tokenString, err := _token.SignExpires(u.Hash, exp)
|
||||
if err != nil {
|
||||
log.Error().Msgf("cannot create token for %s", u.Login)
|
||||
c.Redirect(http.StatusSeeOther, server.Config.Server.RootPath+"/login?error=internal_error")
|
||||
|
@ -262,7 +265,8 @@ func GetLoginToken(c *gin.Context) {
|
|||
}
|
||||
|
||||
exp := time.Now().Add(server.Config.Server.SessionExpires).Unix()
|
||||
newToken := token.New(token.SessToken, user.Login)
|
||||
newToken := token.New(token.SessToken)
|
||||
newToken.Set("user-id", strconv.FormatInt(user.ID, 10))
|
||||
tokenStr, err := newToken.SignExpires(user.Hash, exp)
|
||||
if err != nil {
|
||||
_ = c.AbortWithError(http.StatusInternalServerError, err)
|
||||
|
|
|
@ -118,7 +118,8 @@ func PostRepo(c *gin.Context) {
|
|||
}
|
||||
|
||||
// creates the jwt token used to verify the repository
|
||||
t := token.New(token.HookToken, repo.FullName)
|
||||
t := token.New(token.HookToken)
|
||||
t.Set("repo-id", strconv.FormatInt(repo.ID, 10))
|
||||
sig, err := t.Sign(repo.Hash)
|
||||
if err != nil {
|
||||
msg := "could not generate new jwt token."
|
||||
|
@ -520,7 +521,8 @@ func MoveRepo(c *gin.Context) {
|
|||
}
|
||||
|
||||
// creates the jwt token used to verify the repository
|
||||
t := token.New(token.HookToken, repo.FullName)
|
||||
t := token.New(token.HookToken)
|
||||
t.Set("repo-id", strconv.FormatInt(repo.ID, 10))
|
||||
sig, err := t.Sign(repo.Hash)
|
||||
if err != nil {
|
||||
c.String(http.StatusInternalServerError, err.Error())
|
||||
|
@ -536,7 +538,7 @@ func MoveRepo(c *gin.Context) {
|
|||
)
|
||||
|
||||
if err := _forge.Deactivate(c, user, repo, host); err != nil {
|
||||
log.Trace().Err(err).Msgf("deactivate repo '%s' for move to activate later, got an error", repo.FullName)
|
||||
log.Trace().Err(err).Msgf("deactivate repo '%s' for move to activate later, got an error", strconv.FormatInt(repo.ID, 10))
|
||||
}
|
||||
if err := _forge.Activate(c, user, repo, hookURL); err != nil {
|
||||
c.String(http.StatusInternalServerError, err.Error())
|
||||
|
@ -622,7 +624,8 @@ func repairRepo(c *gin.Context, repo *model.Repo, withPerms, skipOnErr bool) {
|
|||
}
|
||||
|
||||
// creates the jwt token used to verify the repository
|
||||
t := token.New(token.HookToken, repo.FullName)
|
||||
t := token.New(token.HookToken)
|
||||
t.Set("repo-id", strconv.FormatInt(repo.ID, 10))
|
||||
sig, err := t.Sign(repo.Hash)
|
||||
if err != nil {
|
||||
c.String(http.StatusInternalServerError, err.Error())
|
||||
|
|
|
@ -153,7 +153,9 @@ func GetRepos(c *gin.Context) {
|
|||
// @Param Authorization header string true "Insert your personal access token" default(Bearer <personal access token>)
|
||||
func PostToken(c *gin.Context) {
|
||||
user := session.User(c)
|
||||
tokenString, err := token.New(token.UserToken, user.Login).Sign(user.Hash)
|
||||
t := token.New(token.UserToken)
|
||||
t.Set("user-id", strconv.FormatInt(user.ID, 10))
|
||||
tokenString, err := t.Sign(user.Hash)
|
||||
if err != nil {
|
||||
_ = c.AbortWithError(http.StatusInternalServerError, err)
|
||||
return
|
||||
|
@ -182,7 +184,9 @@ func DeleteToken(c *gin.Context) {
|
|||
return
|
||||
}
|
||||
|
||||
tokenString, err := token.New(token.UserToken, user.Login).Sign(user.Hash)
|
||||
t := token.New(token.UserToken)
|
||||
t.Set("user-id", strconv.FormatInt(user.ID, 10))
|
||||
tokenString, err := t.Sign(user.Hash)
|
||||
if err != nil {
|
||||
_ = c.AbortWithError(http.StatusInternalServerError, err)
|
||||
return
|
||||
|
|
|
@ -45,7 +45,11 @@ func SetUser() gin.HandlerFunc {
|
|||
|
||||
t, err := token.ParseRequest(c.Request, func(t *token.Token) (string, error) {
|
||||
var err error
|
||||
user, err = store.FromContext(c).GetUserLogin(t.Text)
|
||||
userID, err := strconv.ParseInt(t.Get("user-id"), 10, 64)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
user, err = store.FromContext(c).GetUser(userID)
|
||||
return user.Hash, err
|
||||
})
|
||||
if err == nil {
|
||||
|
|
|
@ -17,6 +17,7 @@ package web
|
|||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"text/template"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
|
@ -33,10 +34,9 @@ func Config(c *gin.Context) {
|
|||
|
||||
var csrf string
|
||||
if user != nil {
|
||||
csrf, _ = token.New(
|
||||
token.CsrfToken,
|
||||
user.Login,
|
||||
).Sign(user.Hash)
|
||||
t := token.New(token.CsrfToken)
|
||||
t.Set("user-id", strconv.FormatInt(user.ID, 10))
|
||||
csrf, _ = t.Sign(user.Hash)
|
||||
}
|
||||
|
||||
// TODO: remove this and use the forge type from the corresponding repo
|
||||
|
|
|
@ -36,12 +36,14 @@ const (
|
|||
const SignerAlgo = "HS256"
|
||||
|
||||
type Token struct {
|
||||
Kind string
|
||||
Text string
|
||||
Kind string
|
||||
claims jwt.MapClaims
|
||||
}
|
||||
|
||||
func parse(raw string, fn SecretFunc) (*Token, error) {
|
||||
token := &Token{}
|
||||
token := &Token{
|
||||
claims: jwt.MapClaims{},
|
||||
}
|
||||
parsed, err := jwt.Parse(raw, keyFunc(token, fn))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -99,8 +101,8 @@ func CheckCsrf(r *http.Request, fn SecretFunc) error {
|
|||
return err
|
||||
}
|
||||
|
||||
func New(kind, text string) *Token {
|
||||
return &Token{Kind: kind, Text: text}
|
||||
func New(kind string) *Token {
|
||||
return &Token{Kind: kind, claims: jwt.MapClaims{}}
|
||||
}
|
||||
|
||||
// Sign signs the token using the given secret hash
|
||||
|
@ -117,14 +119,32 @@ func (t *Token) SignExpires(secret string, exp int64) (string, error) {
|
|||
if !ok {
|
||||
return "", fmt.Errorf("token claim is not a MapClaims")
|
||||
}
|
||||
|
||||
for k, v := range t.claims {
|
||||
claims[k] = v
|
||||
}
|
||||
|
||||
claims["type"] = t.Kind
|
||||
claims["text"] = t.Text
|
||||
if exp > 0 {
|
||||
claims["exp"] = float64(exp)
|
||||
}
|
||||
|
||||
return token.SignedString([]byte(secret))
|
||||
}
|
||||
|
||||
func (t *Token) Set(key, value string) {
|
||||
t.claims[key] = value
|
||||
}
|
||||
|
||||
func (t *Token) Get(key string) string {
|
||||
claim, ok := t.claims[key].(string)
|
||||
if !ok {
|
||||
return ""
|
||||
}
|
||||
|
||||
return claim
|
||||
}
|
||||
|
||||
func keyFunc(token *Token, fn SecretFunc) jwt.Keyfunc {
|
||||
return func(t *jwt.Token) (any, error) {
|
||||
claims, ok := t.Claims.(jwt.MapClaims)
|
||||
|
@ -137,21 +157,26 @@ func keyFunc(token *Token, fn SecretFunc) jwt.Keyfunc {
|
|||
return nil, jwt.ErrSignatureInvalid
|
||||
}
|
||||
|
||||
// extract the token kind and cast to
|
||||
// the expected type.
|
||||
// extract the token kind and cast to the expected type
|
||||
kind, ok := claims["type"]
|
||||
if !ok {
|
||||
return nil, jwt.ErrInvalidType
|
||||
}
|
||||
token.Kind, _ = kind.(string)
|
||||
|
||||
// extract the token value and cast to
|
||||
// expected type.
|
||||
text, ok := claims["text"]
|
||||
if !ok {
|
||||
return nil, jwt.ErrInvalidType
|
||||
// copy custom claims
|
||||
for k, v := range claims {
|
||||
// skip the reserved claims https://datatracker.ietf.org/doc/html/rfc7519#section-4.1
|
||||
if k == "iss" || k == "sub" || k == "aud" || k == "exp" || k == "nbf" || k == "iat" || k == "jti" {
|
||||
continue
|
||||
}
|
||||
|
||||
if k == "type" {
|
||||
continue
|
||||
}
|
||||
|
||||
token.claims[k] = v
|
||||
}
|
||||
token.Text, _ = text.(string)
|
||||
|
||||
// invoke the callback function to retrieve
|
||||
// the secret key used to verify
|
||||
|
|
Loading…
Reference in a new issue