From 0c5ab540ee897d43e5db2b859d16795a41a74d00 Mon Sep 17 00:00:00 2001 From: qwerty287 Date: Tue, 26 May 2026 09:08:31 +0200 Subject: [PATCH 1/4] Fix org lookup panic --- server/api/org.go | 5 ++++- server/store/datastore/org.go | 16 ++++++++++++++++ server/store/store.go | 1 + 3 files changed, 21 insertions(+), 1 deletion(-) diff --git a/server/api/org.go b/server/api/org.go index c2b0a4d7793..ba26ec29f62 100644 --- a/server/api/org.go +++ b/server/api/org.go @@ -129,13 +129,16 @@ func LookupOrg(c *gin.Context) { org, err := _store.OrgFindByName(orgFullName, user.ForgeID) if err != nil { + if err.Error() == "found more than one org with this name" { + _ = c.AbortWithError(http.StatusBadRequest, err) + return + } handleDBError(c, err) return } // don't leak private org infos if org.Private { - user := session.User(c) if user == nil { c.AbortWithStatus(http.StatusNotFound) return diff --git a/server/store/datastore/org.go b/server/store/datastore/org.go index b6d9f410d9a..bddbe662d1a 100644 --- a/server/store/datastore/org.go +++ b/server/store/datastore/org.go @@ -21,6 +21,7 @@ import ( "xorm.io/xorm" "go.woodpecker-ci.org/woodpecker/v3/server/model" + "go.woodpecker-ci.org/woodpecker/v3/server/store/types" ) func (s storage) OrgCreate(org *model.Org) error { @@ -82,6 +83,21 @@ func (s storage) orgFindByName(sess *xorm.Session, name string, forgeID int64) ( return org, wrapGet(sess.Where("LOWER(name) = ?", strings.ToLower(name)).And("forge_id = ?", forgeID).Get(org)) } +func (s storage) OrgLookup(name string) (*model.Org, error) { + var orgs []*model.Org + err := s.engine.Where("LOWER(name) = ?", strings.ToLower(name)).Find(&orgs) + if err != nil { + return nil, err + } + if len(orgs) < 1 { + return nil, types.ErrRecordNotExist + } + if len(orgs) > 1 { + return nil, fmt.Errorf("found more than one org with this name") + } + return orgs[0], nil +} + func (s storage) OrgRepoList(org *model.Org, p *model.ListOptions) ([]*model.Repo, error) { var repos []*model.Repo return repos, s.paginate(p).OrderBy("id").Where("org_id = ?", org.ID).Find(&repos) diff --git a/server/store/store.go b/server/store/store.go index 34efb8249ee..1174fb711c4 100644 --- a/server/store/store.go +++ b/server/store/store.go @@ -195,6 +195,7 @@ type Store interface { OrgCreate(*model.Org) error OrgGet(int64) (*model.Org, error) OrgFindByName(string, int64) (*model.Org, error) + OrgLookup(string) (*model.Org, error) OrgUpdate(*model.Org) error OrgDelete(int64) error OrgList(*model.ListOptions) ([]*model.Org, error) From e46ca2de9bf584cab8d036b7858e41582a7299e4 Mon Sep 17 00:00:00 2001 From: qwerty287 Date: Wed, 27 May 2026 08:12:06 +0200 Subject: [PATCH 2/4] add mocks, fix impl --- server/api/org.go | 21 +++++++---- server/store/mocks/mock_Store.go | 62 ++++++++++++++++++++++++++++++++ 2 files changed, 77 insertions(+), 6 deletions(-) diff --git a/server/api/org.go b/server/api/org.go index ba26ec29f62..9eff5fcb96e 100644 --- a/server/api/org.go +++ b/server/api/org.go @@ -127,14 +127,23 @@ func LookupOrg(c *gin.Context) { orgFullName := strings.TrimLeft(c.Param("org_full_name"), "/") - org, err := _store.OrgFindByName(orgFullName, user.ForgeID) - if err != nil { - if err.Error() == "found more than one org with this name" { - _ = c.AbortWithError(http.StatusBadRequest, err) + var org *model.Org + if user == nil { + org, err = _store.OrgLookup(orgFullName) + if err != nil { + if err.Error() == "found more than one org with this name" { + _ = c.AbortWithError(http.StatusBadRequest, err) + return + } + handleDBError(c, err) + return + } + } else { + org, err = _store.OrgFindByName(orgFullName, user.ForgeID) + if err != nil { + handleDBError(c, err) return } - handleDBError(c, err) - return } // don't leak private org infos diff --git a/server/store/mocks/mock_Store.go b/server/store/mocks/mock_Store.go index 3344bbb874c..51acf2a34f1 100644 --- a/server/store/mocks/mock_Store.go +++ b/server/store/mocks/mock_Store.go @@ -3816,6 +3816,68 @@ func (_c *MockStore_OrgList_Call) RunAndReturn(run func(listOptions *model.ListO return _c } +// OrgLookup provides a mock function for the type MockStore +func (_mock *MockStore) OrgLookup(s string) (*model.Org, error) { + ret := _mock.Called(s) + + if len(ret) == 0 { + panic("no return value specified for OrgLookup") + } + + var r0 *model.Org + var r1 error + if returnFunc, ok := ret.Get(0).(func(string) (*model.Org, error)); ok { + return returnFunc(s) + } + if returnFunc, ok := ret.Get(0).(func(string) *model.Org); ok { + r0 = returnFunc(s) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*model.Org) + } + } + if returnFunc, ok := ret.Get(1).(func(string) error); ok { + r1 = returnFunc(s) + } else { + r1 = ret.Error(1) + } + return r0, r1 +} + +// MockStore_OrgLookup_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'OrgLookup' +type MockStore_OrgLookup_Call struct { + *mock.Call +} + +// OrgLookup is a helper method to define mock.On call +// - s string +func (_e *MockStore_Expecter) OrgLookup(s interface{}) *MockStore_OrgLookup_Call { + return &MockStore_OrgLookup_Call{Call: _e.mock.On("OrgLookup", s)} +} + +func (_c *MockStore_OrgLookup_Call) Run(run func(s string)) *MockStore_OrgLookup_Call { + _c.Call.Run(func(args mock.Arguments) { + var arg0 string + if args[0] != nil { + arg0 = args[0].(string) + } + run( + arg0, + ) + }) + return _c +} + +func (_c *MockStore_OrgLookup_Call) Return(org *model.Org, err error) *MockStore_OrgLookup_Call { + _c.Call.Return(org, err) + return _c +} + +func (_c *MockStore_OrgLookup_Call) RunAndReturn(run func(s string) (*model.Org, error)) *MockStore_OrgLookup_Call { + _c.Call.Return(run) + return _c +} + // OrgRegistryFind provides a mock function for the type MockStore func (_mock *MockStore) OrgRegistryFind(n int64, s string) (*model.Registry, error) { ret := _mock.Called(n, s) From af904c67abcb18dfeede328fa918944cfd7a92d5 Mon Sep 17 00:00:00 2001 From: qwerty287 Date: Wed, 27 May 2026 08:13:17 +0200 Subject: [PATCH 3/4] limit --- server/store/datastore/org.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/server/store/datastore/org.go b/server/store/datastore/org.go index bddbe662d1a..35145627a20 100644 --- a/server/store/datastore/org.go +++ b/server/store/datastore/org.go @@ -85,7 +85,8 @@ func (s storage) orgFindByName(sess *xorm.Session, name string, forgeID int64) ( func (s storage) OrgLookup(name string) (*model.Org, error) { var orgs []*model.Org - err := s.engine.Where("LOWER(name) = ?", strings.ToLower(name)).Find(&orgs) + // we limit to 2 orgs, if we have >= 2 we return an error anyways. + err := s.engine.Where("LOWER(name) = ?", strings.ToLower(name)).Limit(2).Find(&orgs) if err != nil { return nil, err } From 241581b276967f916909245b2c9a218d87f88815 Mon Sep 17 00:00:00 2001 From: 6543 <6543@obermui.de> Date: Wed, 27 May 2026 13:05:32 +0200 Subject: [PATCH 4/4] ignore lint issue, its intentional --- server/store/datastore/org.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/store/datastore/org.go b/server/store/datastore/org.go index 35145627a20..997a10b2982 100644 --- a/server/store/datastore/org.go +++ b/server/store/datastore/org.go @@ -86,7 +86,7 @@ func (s storage) orgFindByName(sess *xorm.Session, name string, forgeID int64) ( func (s storage) OrgLookup(name string) (*model.Org, error) { var orgs []*model.Org // we limit to 2 orgs, if we have >= 2 we return an error anyways. - err := s.engine.Where("LOWER(name) = ?", strings.ToLower(name)).Limit(2).Find(&orgs) + err := s.engine.Where("LOWER(name) = ?", strings.ToLower(name)).Limit(2).Find(&orgs) //nolint:mnd if err != nil { return nil, err }