-
-
Notifications
You must be signed in to change notification settings - Fork 5.6k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
WIP: Add authorization header support for Gitea webhooks #20267
Changes from all commits
1fdabab
08b4317
3111036
f8d2768
5b822f9
36e0414
fcfc79f
8e210e6
88b734e
3f33b2d
182e192
4c13cdc
b69dd15
a4ad516
96c608c
e67bd91
34118fe
f3e71fd
6209f2c
2eae69c
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -20,12 +20,14 @@ import ( | |
"time" | ||
|
||
webhook_model "code.gitea.io/gitea/models/webhook" | ||
"code.gitea.io/gitea/modules/base" | ||
"code.gitea.io/gitea/modules/graceful" | ||
"code.gitea.io/gitea/modules/hostmatcher" | ||
"code.gitea.io/gitea/modules/log" | ||
"code.gitea.io/gitea/modules/process" | ||
"code.gitea.io/gitea/modules/proxy" | ||
"code.gitea.io/gitea/modules/queue" | ||
"code.gitea.io/gitea/modules/secret" | ||
"code.gitea.io/gitea/modules/setting" | ||
|
||
"github.com/gobwas/glob" | ||
|
@@ -145,6 +147,20 @@ func Deliver(ctx context.Context, t *webhook_model.HookTask) error { | |
Headers: map[string]string{}, | ||
} | ||
|
||
if w.Type == webhook_model.GITEA { | ||
meta := GetGiteaHook(w, secret.DecryptSecret) | ||
if meta.AuthHeaderEnabled { | ||
var content string | ||
switch meta.AuthHeader.Type { | ||
case webhook_model.BASICAUTH: | ||
content = fmt.Sprintf("Basic %s", base.BasicAuthEncode(meta.AuthHeader.Username, meta.AuthHeader.Password)) | ||
case webhook_model.TOKENAUTH: | ||
content = fmt.Sprintf("token %s", meta.AuthHeader.Token) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should it be up to the admin if and what is prepended? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What would be the use-case for that? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Sometimes the token must be provided with There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Instead of handling this in the backend, maybe it could be exclusively handled in the frontend:
This way the backend logic can be simplified a bit, while keeping maximum flexibility. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah, I also thought about frontend code for it. But IIRC, it is preferred to do most logical code in the backend. So I went that way. 🙂 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. That |
||
} | ||
req.Header.Add(meta.AuthHeader.Name, content) | ||
} | ||
} | ||
|
||
defer func() { | ||
t.Delivered = time.Now().UnixNano() | ||
if t.IsSucceed { | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,111 @@ | ||
// Copyright 2022 The Gitea Authors. All rights reserved. | ||
// Use of this source code is governed by a MIT-style | ||
// license that can be found in the LICENSE file. | ||
|
||
package webhook | ||
|
||
import ( | ||
webhook_model "code.gitea.io/gitea/models/webhook" | ||
"code.gitea.io/gitea/modules/json" | ||
"code.gitea.io/gitea/modules/log" | ||
"code.gitea.io/gitea/modules/secret" | ||
"code.gitea.io/gitea/modules/setting" | ||
"code.gitea.io/gitea/services/forms" | ||
) | ||
|
||
type ( | ||
// GiteaAuthHeaderMeta contains the authentication header metadata | ||
GiteaAuthHeaderMeta struct { | ||
Name string `json:"name"` | ||
Type webhook_model.AuthHeaderType `json:"type"` | ||
Username string `json:"username,omitempty"` | ||
Password string `json:"password,omitempty"` | ||
Token string `json:"token,omitempty"` | ||
} | ||
|
||
// GiteaMeta contains the gitea webhook metadata | ||
GiteaMeta struct { | ||
AuthHeaderEnabled bool `json:"auth_header_enabled"` | ||
AuthHeaderData string `json:"auth_header,omitempty"` | ||
AuthHeader GiteaAuthHeaderMeta `json:"-"` | ||
} | ||
) | ||
|
||
// GetGiteaHook returns decrypted gitea metadata | ||
func GetGiteaHook(w *webhook_model.Webhook, decryptFn secret.DecryptSecretCallable) *GiteaMeta { | ||
s := &GiteaMeta{} | ||
|
||
// Legacy webhook configuration has no stored metadata | ||
if w.Meta == "" { | ||
return s | ||
} | ||
justusbunsi marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
if err := json.Unmarshal([]byte(w.Meta), s); err != nil { | ||
log.Error("webhook.GetGiteaHook(%d): %v", w.ID, err) | ||
return nil | ||
} | ||
|
||
if !s.AuthHeaderEnabled { | ||
return s | ||
} | ||
|
||
headerData, err := decryptFn(setting.SecretKey, s.AuthHeaderData) | ||
if err != nil { | ||
log.Error("webhook.GetGiteaHook(%d): %v", w.ID, err) | ||
return nil | ||
} | ||
|
||
h := GiteaAuthHeaderMeta{} | ||
if err := json.Unmarshal([]byte(headerData), &h); err != nil { | ||
log.Error("webhook.GetGiteaHook(%d): %v", w.ID, err) | ||
return nil | ||
} | ||
|
||
// Replace encrypted content with decrypted settings | ||
s.AuthHeaderData = "" | ||
s.AuthHeader = h | ||
|
||
return s | ||
} | ||
|
||
// CreateGiteaHook creates a gitea metadata string with encrypted auth header data, | ||
// while it ensures to store the least necessary data in the database. | ||
func CreateGiteaHook(form *forms.NewWebhookForm, encryptFn secret.EncryptSecretCallable) (string, error) { | ||
metaObject := &GiteaMeta{ | ||
AuthHeaderEnabled: form.AuthHeaderActive, | ||
} | ||
|
||
if form.AuthHeaderActive { | ||
headerMeta := GiteaAuthHeaderMeta{ | ||
Name: form.AuthHeaderName, | ||
Type: form.AuthHeaderType, | ||
} | ||
|
||
switch form.AuthHeaderType { | ||
case webhook_model.BASICAUTH: | ||
headerMeta.Username = form.AuthHeaderUsername | ||
headerMeta.Password = form.AuthHeaderPassword | ||
case webhook_model.TOKENAUTH: | ||
headerMeta.Token = form.AuthHeaderToken | ||
} | ||
|
||
headerData, err := json.Marshal(headerMeta) | ||
if err != nil { | ||
return "", err | ||
} | ||
|
||
encryptedHeaderData, err := encryptFn(setting.SecretKey, string(headerData)) | ||
if err != nil { | ||
return "", err | ||
} | ||
|
||
metaObject.AuthHeaderData = encryptedHeaderData | ||
} | ||
|
||
meta, err := json.Marshal(metaObject) | ||
if err != nil { | ||
return "", err | ||
} | ||
|
||
return string(meta), nil | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
TBH, I am not sure about the naming here. Based on MDN docs12 the naming would be "Authentication schema" instead of "Header content type" and the "Token authentication" would better fit with "Bearer scheme".
Footnotes
https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Authorization ↩
https://developer.mozilla.org/en-US/docs/Web/HTTP/Authentication#authentication_schemes ↩