Skip to content
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

Add user webhooks #21563

Merged
merged 30 commits into from
Mar 10, 2023
Merged
Show file tree
Hide file tree
Changes from 20 commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
a3c56ac
Add user webhooks.
KN4CK3R Oct 23, 2022
fb2df43
Merge branch 'main' of https://github.com/go-gitea/gitea into feature…
KN4CK3R Oct 23, 2022
2b9459c
Add migration.
KN4CK3R Oct 23, 2022
2c1cb14
Merge branch 'main' of https://github.com/go-gitea/gitea into feature…
KN4CK3R Nov 2, 2022
e4e51a3
Merge branch 'main' of https://github.com/go-gitea/gitea into feature…
KN4CK3R Nov 5, 2022
9960582
Merge branch 'main' of https://github.com/go-gitea/gitea into feature…
KN4CK3R Nov 17, 2022
989da06
lint
KN4CK3R Nov 17, 2022
9cee676
Merge branch 'main' of https://github.com/go-gitea/gitea into feature…
KN4CK3R Nov 23, 2022
0ddb01f
Merge branch 'main' of https://github.com/go-gitea/gitea into feature…
KN4CK3R Nov 24, 2022
502888b
Fix fixture.
KN4CK3R Nov 24, 2022
6a4543a
Merge branch 'main' of https://github.com/go-gitea/gitea into feature…
KN4CK3R Dec 19, 2022
7cf0e95
Add suggestions.
KN4CK3R Dec 20, 2022
6bb8d13
Merge branch 'main' of https://github.com/go-gitea/gitea into feature…
KN4CK3R Dec 20, 2022
21e481b
Merge branch 'main' of https://github.com/go-gitea/gitea into feature…
KN4CK3R Jan 14, 2023
bc0284f
Fix migration.
KN4CK3R Jan 15, 2023
5ebaf78
Merge branch 'main' of https://github.com/go-gitea/gitea into feature…
KN4CK3R Jan 15, 2023
78772e1
fmt
KN4CK3R Jan 15, 2023
42f710e
Merge branch 'main' of https://github.com/go-gitea/gitea into feature…
KN4CK3R Jan 21, 2023
ebd031e
Merge branch 'main' of https://github.com/go-gitea/gitea into feature…
KN4CK3R Feb 1, 2023
381162a
Merge branch 'main' into feature-user-webhooks
zeripath Feb 6, 2023
08ec5d8
Merge branch 'main' of https://github.com/go-gitea/gitea into feature…
KN4CK3R Feb 17, 2023
3fe01b6
Merge branch 'main' of https://github.com/go-gitea/gitea into feature…
KN4CK3R Feb 19, 2023
530f094
Merge branch 'main' into feature-user-webhooks
lunny Feb 19, 2023
3dedb26
Merge branch 'main' of https://github.com/go-gitea/gitea into feature…
KN4CK3R Feb 19, 2023
a6d42d9
Merge branch 'main' of https://github.com/go-gitea/gitea into feature…
KN4CK3R Feb 20, 2023
8703215
Merge branch 'main' of https://github.com/go-gitea/gitea into feature…
KN4CK3R Mar 9, 2023
4bf1491
Use new database checks.
KN4CK3R Mar 9, 2023
5f580eb
Merge branch 'main' of https://github.com/go-gitea/gitea into feature…
KN4CK3R Mar 9, 2023
8973f99
Fix column name in new methods.
KN4CK3R Mar 10, 2023
5c577dd
Merge branch 'main' of https://github.com/go-gitea/gitea into feature…
KN4CK3R Mar 10, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/content/doc/developers/oauth2-provider.en-us.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ Gitea supports the following scopes for tokens:
|     **write:public_key** | Grant read/write access to public keys |
|     **read:public_key** | Grant read-only access to public keys |
| **admin:org_hook** | Grants full access to organizational-level hooks |
| **admin:user_hook** | Grants full access to user-level hooks |
| **notification** | Grants full access to notifications |
| **user** | Grants full access to user profile info |
|     **read:user** | Grants read access to user's profile |
Expand Down
10 changes: 8 additions & 2 deletions models/auth/token_scope.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ const (

AccessTokenScopeAdminOrgHook AccessTokenScope = "admin:org_hook"

AccessTokenScopeAdminUserHook AccessTokenScope = "admin:user_hook"

AccessTokenScopeNotification AccessTokenScope = "notification"

AccessTokenScopeUser AccessTokenScope = "user"
Expand Down Expand Up @@ -64,7 +66,7 @@ type AccessTokenScopeBitmap uint64
const (
// AccessTokenScopeAllBits is the bitmap of all access token scopes, except `sudo`.
AccessTokenScopeAllBits AccessTokenScopeBitmap = AccessTokenScopeRepoBits |
AccessTokenScopeAdminOrgBits | AccessTokenScopeAdminPublicKeyBits | AccessTokenScopeAdminOrgHookBits |
AccessTokenScopeAdminOrgBits | AccessTokenScopeAdminPublicKeyBits | AccessTokenScopeAdminOrgHookBits | AccessTokenScopeAdminUserHookBits |
AccessTokenScopeNotificationBits | AccessTokenScopeUserBits | AccessTokenScopeDeleteRepoBits |
AccessTokenScopePackageBits | AccessTokenScopeAdminGPGKeyBits | AccessTokenScopeAdminApplicationBits

Expand All @@ -86,6 +88,8 @@ const (

AccessTokenScopeAdminOrgHookBits AccessTokenScopeBitmap = 1 << iota

AccessTokenScopeAdminUserHookBits AccessTokenScopeBitmap = 1 << iota

AccessTokenScopeNotificationBits AccessTokenScopeBitmap = 1 << iota

AccessTokenScopeUserBits AccessTokenScopeBitmap = 1<<iota | AccessTokenScopeReadUserBits | AccessTokenScopeUserEmailBits | AccessTokenScopeUserFollowBits
Expand Down Expand Up @@ -123,6 +127,7 @@ var allAccessTokenScopes = []AccessTokenScope{
AccessTokenScopeAdminPublicKey, AccessTokenScopeWritePublicKey, AccessTokenScopeReadPublicKey,
AccessTokenScopeAdminRepoHook, AccessTokenScopeWriteRepoHook, AccessTokenScopeReadRepoHook,
AccessTokenScopeAdminOrgHook,
AccessTokenScopeAdminUserHook,
AccessTokenScopeNotification,
AccessTokenScopeUser, AccessTokenScopeReadUser, AccessTokenScopeUserEmail, AccessTokenScopeUserFollow,
AccessTokenScopeDeleteRepo,
Expand All @@ -147,6 +152,7 @@ var allAccessTokenScopeBits = map[AccessTokenScope]AccessTokenScopeBitmap{
AccessTokenScopeWriteRepoHook: AccessTokenScopeWriteRepoHookBits,
AccessTokenScopeReadRepoHook: AccessTokenScopeReadRepoHookBits,
AccessTokenScopeAdminOrgHook: AccessTokenScopeAdminOrgHookBits,
AccessTokenScopeAdminUserHook: AccessTokenScopeAdminUserHookBits,
AccessTokenScopeNotification: AccessTokenScopeNotificationBits,
AccessTokenScopeUser: AccessTokenScopeUserBits,
AccessTokenScopeReadUser: AccessTokenScopeReadUserBits,
Expand Down Expand Up @@ -244,7 +250,7 @@ func (bitmap AccessTokenScopeBitmap) ToScope() AccessTokenScope {
scope := AccessTokenScope(strings.Join(scopes, ","))
scope = AccessTokenScope(strings.ReplaceAll(
string(scope),
"repo,admin:org,admin:public_key,admin:org_hook,notification,user,delete_repo,package,admin:gpg_key,admin:application",
"repo,admin:org,admin:public_key,admin:org_hook,admin:user_hook,notification,user,delete_repo,package,admin:gpg_key,admin:application",
"all",
))
return scope
Expand Down
4 changes: 2 additions & 2 deletions models/auth/token_scope_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,8 @@ func TestAccessTokenScope_Normalize(t *testing.T) {
{"admin:gpg_key,write:gpg_key,user", "user,admin:gpg_key", nil},
{"admin:application,write:application,user", "user,admin:application", nil},
{"all", "all", nil},
{"repo,admin:org,admin:public_key,admin:repo_hook,admin:org_hook,notification,user,delete_repo,package,admin:gpg_key,admin:application", "all", nil},
{"repo,admin:org,admin:public_key,admin:repo_hook,admin:org_hook,notification,user,delete_repo,package,admin:gpg_key,admin:application,sudo", "all,sudo", nil},
{"repo,admin:org,admin:public_key,admin:repo_hook,admin:org_hook,admin:user_hook,notification,user,delete_repo,package,admin:gpg_key,admin:application", "all", nil},
{"repo,admin:org,admin:public_key,admin:repo_hook,admin:org_hook,admin:user_hook,notification,user,delete_repo,package,admin:gpg_key,admin:application,sudo", "all,sudo", nil},
}

for _, test := range tests {
Expand Down
2 changes: 1 addition & 1 deletion models/fixtures/webhook.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@

-
id: 3
org_id: 3
owner_id: 3
repo_id: 3
url: www.example.com/url3
content_type: 1 # json
Expand Down
2 changes: 2 additions & 0 deletions models/migrations/migrations.go
Original file line number Diff line number Diff line change
Expand Up @@ -455,6 +455,8 @@ var migrations = []Migration{
NewMigration("Add scope for access_token", v1_19.AddScopeForAccessTokens),
// v240 -> v241
NewMigration("Add actions tables", v1_19.AddActionsTables),
// v241 -> v242
NewMigration("Rename Webhook org_id to owner_id", v1_19.RenameWebhookOrgToOwner),
}

// GetCurrentDBVersion returns the current db version
Expand Down
74 changes: 74 additions & 0 deletions models/migrations/v1_19/v241.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
// Copyright 2023 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT

package v1_19 //nolint

import (
"context"
"fmt"

"code.gitea.io/gitea/models/migrations/base"
"code.gitea.io/gitea/modules/setting"

"xorm.io/xorm"
)

func RenameWebhookOrgToOwner(x *xorm.Engine) error {
type Webhook struct {
OrgID int64 `xorm:"INDEX"`
}

// This migration maybe rerun so that we should check if it has been run
ownerExist, err := x.Dialect().IsColumnExist(x.DB(), context.Background(), "webhook", "owner_id")
if err != nil {
return err
}

if ownerExist {
orgExist, err := x.Dialect().IsColumnExist(x.DB(), context.Background(), "webhook", "org_id")
if err != nil {
return err
}
if !orgExist {
return nil
}
}

sess := x.NewSession()
defer sess.Close()
if err := sess.Begin(); err != nil {
return err
}

if err := sess.Sync2(new(Webhook)); err != nil {
return err
}

if ownerExist {
if err := base.DropTableColumns(sess, "webhook", "owner_id"); err != nil {
return err
}
}

switch {
case setting.Database.UseMySQL:
inferredTable, err := x.TableInfo(new(Webhook))
if err != nil {
return err
}
sqlType := x.Dialect().SQLType(inferredTable.GetColumn("org_id"))
if _, err := sess.Exec(fmt.Sprintf("ALTER TABLE `webhook` CHANGE org_id owner_id %s", sqlType)); err != nil {
return err
}
case setting.Database.UseMSSQL:
if _, err := sess.Exec("sp_rename 'webhook.org_id', 'owner_id', 'COLUMN'"); err != nil {
return err
}
default:
if _, err := sess.Exec("ALTER TABLE `webhook` RENAME COLUMN org_id TO owner_id"); err != nil {
return err
}
}

return sess.Commit()
}
24 changes: 12 additions & 12 deletions models/webhook/webhook.go
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ func IsValidHookContentType(name string) bool {
type Webhook struct {
ID int64 `xorm:"pk autoincr"`
RepoID int64 `xorm:"INDEX"` // An ID of 0 indicates either a default or system webhook
OrgID int64 `xorm:"INDEX"`
OwnerID int64 `xorm:"INDEX"`
IsSystemWebhook bool
URL string `xorm:"url TEXT"`
HTTPMethod string `xorm:"http_method"`
Expand Down Expand Up @@ -412,19 +412,19 @@ func GetWebhookByRepoID(repoID, id int64) (*Webhook, error) {
})
}

// GetWebhookByOrgID returns webhook of organization by given ID.
func GetWebhookByOrgID(orgID, id int64) (*Webhook, error) {
// GetWebhookByOwnerID returns webhook of a user or organization by given ID.
func GetWebhookByOwnerID(ownerID, id int64) (*Webhook, error) {
return getWebhook(&Webhook{
ID: id,
OrgID: orgID,
ID: id,
OwnerID: ownerID,
})
}

// ListWebhookOptions are options to filter webhooks on ListWebhooksByOpts
type ListWebhookOptions struct {
db.ListOptions
RepoID int64
OrgID int64
OwnerID int64
IsActive util.OptionalBool
}

Expand All @@ -433,8 +433,8 @@ func (opts *ListWebhookOptions) toCond() builder.Cond {
if opts.RepoID != 0 {
cond = cond.And(builder.Eq{"webhook.repo_id": opts.RepoID})
}
if opts.OrgID != 0 {
cond = cond.And(builder.Eq{"webhook.org_id": opts.OrgID})
if opts.OwnerID != 0 {
cond = cond.And(builder.Eq{"webhook.owner_id": opts.OwnerID})
}
if !opts.IsActive.IsNone() {
cond = cond.And(builder.Eq{"webhook.is_active": opts.IsActive.IsTrue()})
Expand Down Expand Up @@ -503,10 +503,10 @@ func DeleteWebhookByRepoID(repoID, id int64) error {
})
}

// DeleteWebhookByOrgID deletes webhook of organization by given ID.
func DeleteWebhookByOrgID(orgID, id int64) error {
// DeleteWebhookByOwnerID deletes webhook of a user or organization by given ID.
func DeleteWebhookByOwnerID(ownerID, id int64) error {
return deleteWebhook(&Webhook{
ID: id,
OrgID: orgID,
ID: id,
OwnerID: ownerID,
})
}
24 changes: 12 additions & 12 deletions models/webhook/webhook_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -109,13 +109,13 @@ func TestGetWebhookByRepoID(t *testing.T) {
assert.True(t, IsErrWebhookNotExist(err))
}

func TestGetWebhookByOrgID(t *testing.T) {
func TestGetWebhookByOwnerID(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase())
hook, err := GetWebhookByOrgID(3, 3)
hook, err := GetWebhookByOwnerID(3, 3)
assert.NoError(t, err)
assert.Equal(t, int64(3), hook.ID)

_, err = GetWebhookByOrgID(unittest.NonexistentID, unittest.NonexistentID)
_, err = GetWebhookByOwnerID(unittest.NonexistentID, unittest.NonexistentID)
assert.Error(t, err)
assert.True(t, IsErrWebhookNotExist(err))
}
Expand All @@ -140,19 +140,19 @@ func TestGetWebhooksByRepoID(t *testing.T) {
}
}

func TestGetActiveWebhooksByOrgID(t *testing.T) {
func TestGetActiveWebhooksByOwnerID(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase())
hooks, err := ListWebhooksByOpts(db.DefaultContext, &ListWebhookOptions{OrgID: 3, IsActive: util.OptionalBoolTrue})
hooks, err := ListWebhooksByOpts(db.DefaultContext, &ListWebhookOptions{OwnerID: 3, IsActive: util.OptionalBoolTrue})
assert.NoError(t, err)
if assert.Len(t, hooks, 1) {
assert.Equal(t, int64(3), hooks[0].ID)
assert.True(t, hooks[0].IsActive)
}
}

func TestGetWebhooksByOrgID(t *testing.T) {
func TestGetWebhooksByOwnerID(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase())
hooks, err := ListWebhooksByOpts(db.DefaultContext, &ListWebhookOptions{OrgID: 3})
hooks, err := ListWebhooksByOpts(db.DefaultContext, &ListWebhookOptions{OwnerID: 3})
assert.NoError(t, err)
if assert.Len(t, hooks, 1) {
assert.Equal(t, int64(3), hooks[0].ID)
Expand Down Expand Up @@ -181,13 +181,13 @@ func TestDeleteWebhookByRepoID(t *testing.T) {
assert.True(t, IsErrWebhookNotExist(err))
}

func TestDeleteWebhookByOrgID(t *testing.T) {
func TestDeleteWebhookByOwnerID(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase())
unittest.AssertExistsAndLoadBean(t, &Webhook{ID: 3, OrgID: 3})
assert.NoError(t, DeleteWebhookByOrgID(3, 3))
unittest.AssertNotExistsBean(t, &Webhook{ID: 3, OrgID: 3})
unittest.AssertExistsAndLoadBean(t, &Webhook{ID: 3, OwnerID: 3})
assert.NoError(t, DeleteWebhookByOwnerID(3, 3))
unittest.AssertNotExistsBean(t, &Webhook{ID: 3, OwnerID: 3})

err := DeleteWebhookByOrgID(unittest.NonexistentID, unittest.NonexistentID)
err := DeleteWebhookByOwnerID(unittest.NonexistentID, unittest.NonexistentID)
assert.Error(t, err)
assert.True(t, IsErrWebhookNotExist(err))
}
Expand Down
2 changes: 2 additions & 0 deletions options/locale/locale_en-US.ini
Original file line number Diff line number Diff line change
Expand Up @@ -819,6 +819,8 @@ remove_account_link = Remove Linked Account
remove_account_link_desc = Removing a linked account will revoke its access to your Gitea account. Continue?
remove_account_link_success = The linked account has been removed.

hooks.desc = Add webhooks which will be triggered for <strong>all repositories</strong> owned by this user.

orgs_none = You are not a member of any organizations.
repos_none = You do not own any repositories

Expand Down
5 changes: 1 addition & 4 deletions routers/api/v1/admin/hooks.go
Original file line number Diff line number Diff line change
Expand Up @@ -105,10 +105,7 @@ func CreateHook(ctx *context.APIContext) {
// "$ref": "#/responses/Hook"

form := web.GetForm(ctx).(*api.CreateHookOption)
// TODO in body params
if !utils.CheckCreateHookOption(ctx, form) {
return
}

utils.AddSystemHook(ctx, form)
}

Expand Down
7 changes: 7 additions & 0 deletions routers/api/v1/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -835,6 +835,13 @@ func Routes(ctx gocontext.Context) *web.Route {
m.Get("/stopwatches", reqToken(auth_model.AccessTokenScopeRepo), repo.GetStopwatches)
m.Get("/subscriptions", reqToken(auth_model.AccessTokenScopeRepo), user.GetMyWatchedRepos)
m.Get("/teams", reqToken(auth_model.AccessTokenScopeRepo), org.ListUserTeams)
m.Group("/hooks", func() {
m.Combo("").Get(user.ListHooks).
Post(bind(api.CreateHookOption{}), user.CreateHook)
m.Combo("/{id}").Get(user.GetHook).
Patch(bind(api.EditHookOption{}), user.EditHook).
Delete(user.DeleteHook)
}, reqToken(auth_model.AccessTokenScopeAdminUserHook), reqWebhooksEnabled())
}, reqToken(""))

// Repositories
Expand Down
Loading