mirror of
https://codeberg.org/forgejo/forgejo.git
synced 2026-05-12 22:10:25 +00:00
feat: cache OIDC metadata & JWKS when read by authorized integration (#12275)
Enhances authorized integrations (#12261) with a cache of the remote OpenID Connect descriptor file and JSON Web Key Set (JWKS), improving runtime performance and reducing intermittent reliability risks. By default a 10 minute cache is used, configurable through `[authorized_integration].CACHE_TTL`. To mock the cache for testing, mockery code generation is added, and a previous manually generated mock for `AuthorizationReducer` was replaced with the code generation. ## Checklist The [contributor guide](https://forgejo.org/docs/next/contributor/) contains information that will be helpful to first time contributors. All work and communication must conform to Forgejo's [AI Agreement](https://codeberg.org/forgejo/governance/src/branch/main/AIAgreement.md). There also are a few [conditions for merging Pull Requests in Forgejo repositories](https://codeberg.org/forgejo/governance/src/branch/main/PullRequestsAgreement.md). You are also welcome to join the [Forgejo development chatroom](https://matrix.to/#/#forgejo-development:matrix.org). ### Tests for Go changes - I added test coverage for Go changes... - [x] in their respective `*_test.go` for unit tests. - [ ] in the `tests/integration` directory if it involves interactions with a live Forgejo server. - I ran... - [x] `make pr-go` before pushing ### Documentation - [ ] I created a pull request [to the documentation](https://codeberg.org/forgejo/docs) to explain to Forgejo users how to use this change. - [x] I did not document these changes and I do not expect someone else to do it. ### Release notes - [ ] This change will be noticed by a Forgejo user or admin (feature, bug fix, performance, etc.). I suggest to include a release note for this change. - [ ] This change is not visible to a Forgejo user or admin (refactor, dependency upgrade, etc.). I think there is no need to add a release note for this change. - Authorized integrations are not yet exposed to end-users. Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/12275 Reviewed-by: Gusted <gusted@noreply.codeberg.org>
This commit is contained in:
parent
2425ae7725
commit
37412e6a00
15 changed files with 2156 additions and 462 deletions
|
|
@ -222,9 +222,6 @@ forgejo.org/modules/zstd
|
||||||
forgejo.org/routers/web/org
|
forgejo.org/routers/web/org
|
||||||
MustEnableProjects
|
MustEnableProjects
|
||||||
|
|
||||||
forgejo.org/services/auth/method
|
|
||||||
OverrideAuthorizedIntegrationHTTPClient
|
|
||||||
|
|
||||||
forgejo.org/services/context
|
forgejo.org/services/context
|
||||||
GetPrivateContext
|
GetPrivateContext
|
||||||
|
|
||||||
|
|
|
||||||
16
.mockery.yml
Normal file
16
.mockery.yml
Normal file
|
|
@ -0,0 +1,16 @@
|
||||||
|
formatter: gofmt
|
||||||
|
template: testify
|
||||||
|
packages:
|
||||||
|
forgejo.org/modules/nosql:
|
||||||
|
config:
|
||||||
|
filename: mocks.go # make mocks public so that external packages can use
|
||||||
|
forgejo.org/services/authz:
|
||||||
|
config:
|
||||||
|
filename: authorization_reducer_mock.go # make mocks public so that external packages can use
|
||||||
|
code.forgejo.org/go-chi/cache:
|
||||||
|
interfaces:
|
||||||
|
Cache:
|
||||||
|
config:
|
||||||
|
pkgname: cache
|
||||||
|
dir: modules/cache
|
||||||
|
filename: mocks.go # make mocks public, not `_test.go`, so that external packages can mock caching
|
||||||
12
Makefile
12
Makefile
|
|
@ -47,8 +47,8 @@ GO_LICENSES_PACKAGE ?= github.com/google/go-licenses/v2@v2.0.1 # renovate: datas
|
||||||
GOVULNCHECK_PACKAGE ?= golang.org/x/vuln/cmd/govulncheck@v1 # renovate: datasource=go
|
GOVULNCHECK_PACKAGE ?= golang.org/x/vuln/cmd/govulncheck@v1 # renovate: datasource=go
|
||||||
DEADCODE_PACKAGE ?= golang.org/x/tools/cmd/deadcode@v0.44.0 # renovate: datasource=go
|
DEADCODE_PACKAGE ?= golang.org/x/tools/cmd/deadcode@v0.44.0 # renovate: datasource=go
|
||||||
ERRORTYPE_PACKAGE ?= fillmore-labs.com/errortype@v0.0.11 # renovate: datasource=go
|
ERRORTYPE_PACKAGE ?= fillmore-labs.com/errortype@v0.0.11 # renovate: datasource=go
|
||||||
GOMOCK_PACKAGE ?= go.uber.org/mock/mockgen@v0.6.0 # renovate: datasource=go
|
|
||||||
RENOVATE_NPM_PACKAGE ?= renovate@43.141.6 # renovate: datasource=docker packageName=data.forgejo.org/renovate/renovate
|
RENOVATE_NPM_PACKAGE ?= renovate@43.141.6 # renovate: datasource=docker packageName=data.forgejo.org/renovate/renovate
|
||||||
|
MOCKERY_PACKAGE ?= github.com/vektra/mockery/v3@v3.7.0 # renovate: datasource=go
|
||||||
|
|
||||||
# https://github.com/disposable-email-domains/disposable-email-domains/commits/main/
|
# https://github.com/disposable-email-domains/disposable-email-domains/commits/main/
|
||||||
DISPOSABLE_EMAILS_SHA ?= 0c27e671231d27cf66370034d7f6818037416989 # renovate: ...
|
DISPOSABLE_EMAILS_SHA ?= 0c27e671231d27cf66370034d7f6818037416989 # renovate: ...
|
||||||
|
|
@ -245,7 +245,7 @@ help:
|
||||||
@echo " - generate-license update license files"
|
@echo " - generate-license update license files"
|
||||||
@echo " - generate-gitignore update gitignore files"
|
@echo " - generate-gitignore update gitignore files"
|
||||||
@echo " - generate-manpage generate manpage"
|
@echo " - generate-manpage generate manpage"
|
||||||
@echo " - generate-gomock generate gomock files"
|
@echo " - generate-mockery generate mockery files"
|
||||||
@echo " - generate-forgejo-api generate the forgejo API from spec"
|
@echo " - generate-forgejo-api generate the forgejo API from spec"
|
||||||
@echo " - forgejo-api-validate check if the forgejo API matches the specs"
|
@echo " - forgejo-api-validate check if the forgejo API matches the specs"
|
||||||
@echo " - generate-swagger generate the swagger spec from code comments"
|
@echo " - generate-swagger generate the swagger spec from code comments"
|
||||||
|
|
@ -968,8 +968,8 @@ deps-tools:
|
||||||
$(GO) install $(XGO_PACKAGE)
|
$(GO) install $(XGO_PACKAGE)
|
||||||
$(GO) install $(GO_LICENSES_PACKAGE)
|
$(GO) install $(GO_LICENSES_PACKAGE)
|
||||||
$(GO) install $(GOVULNCHECK_PACKAGE)
|
$(GO) install $(GOVULNCHECK_PACKAGE)
|
||||||
$(GO) install $(GOMOCK_PACKAGE)
|
|
||||||
$(GO) install $(ERRORTYPE_PACKAGE)
|
$(GO) install $(ERRORTYPE_PACKAGE)
|
||||||
|
$(GO) install $(MOCKERY_PACKAGE)
|
||||||
|
|
||||||
node_modules: package-lock.json
|
node_modules: package-lock.json
|
||||||
npm install --no-save
|
npm install --no-save
|
||||||
|
|
@ -1024,9 +1024,9 @@ generate-license:
|
||||||
generate-gitignore:
|
generate-gitignore:
|
||||||
$(GO) run build/generate-gitignores.go
|
$(GO) run build/generate-gitignores.go
|
||||||
|
|
||||||
.PHONY: generate-gomock
|
.PHONY: generate-mockery
|
||||||
generate-gomock:
|
generate-mockery:
|
||||||
$(GO) run $(GOMOCK_PACKAGE) -package mock -destination ./modules/queue/mock/redisuniversalclient.go forgejo.org/modules/nosql RedisClient
|
$(GO) run $(MOCKERY_PACKAGE)
|
||||||
|
|
||||||
.PHONY: generate-images
|
.PHONY: generate-images
|
||||||
generate-images: | node_modules
|
generate-images: | node_modules
|
||||||
|
|
|
||||||
|
|
@ -2846,3 +2846,7 @@ LEVEL = Info
|
||||||
;; Default is false.
|
;; Default is false.
|
||||||
;; If a domain is allowed by ALLOWED_DOMAINS, this option will be ignored.
|
;; If a domain is allowed by ALLOWED_DOMAINS, this option will be ignored.
|
||||||
;ALLOW_LOCALNETWORKS = false
|
;ALLOW_LOCALNETWORKS = false
|
||||||
|
;
|
||||||
|
;; Remote requests are cached after being received for the cache time-to-live (TTL). Default is 10 minutes.
|
||||||
|
;; Caching uses the configured adapter in the [cache] config section.
|
||||||
|
;CACHE_TTL = 10m
|
||||||
|
|
|
||||||
1
go.mod
1
go.mod
|
|
@ -100,7 +100,6 @@ require (
|
||||||
github.com/yuin/goldmark v1.8.2
|
github.com/yuin/goldmark v1.8.2
|
||||||
github.com/yuin/goldmark-highlighting/v2 v2.0.0-20230729083705-37449abec8cc
|
github.com/yuin/goldmark-highlighting/v2 v2.0.0-20230729083705-37449abec8cc
|
||||||
gitlab.com/gitlab-org/api/client-go v0.143.2
|
gitlab.com/gitlab-org/api/client-go v0.143.2
|
||||||
go.uber.org/mock v0.6.0
|
|
||||||
go.yaml.in/yaml/v3 v3.0.4
|
go.yaml.in/yaml/v3 v3.0.4
|
||||||
golang.org/x/crypto v0.50.0
|
golang.org/x/crypto v0.50.0
|
||||||
golang.org/x/image v0.39.0
|
golang.org/x/image v0.39.0
|
||||||
|
|
|
||||||
497
modules/cache/mocks.go
vendored
Normal file
497
modules/cache/mocks.go
vendored
Normal file
|
|
@ -0,0 +1,497 @@
|
||||||
|
// Code generated by mockery; DO NOT EDIT.
|
||||||
|
// github.com/vektra/mockery
|
||||||
|
// template: testify
|
||||||
|
|
||||||
|
package cache
|
||||||
|
|
||||||
|
import (
|
||||||
|
"code.forgejo.org/go-chi/cache"
|
||||||
|
mock "github.com/stretchr/testify/mock"
|
||||||
|
)
|
||||||
|
|
||||||
|
// NewMockCache creates a new instance of MockCache. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
|
||||||
|
// The first argument is typically a *testing.T value.
|
||||||
|
func NewMockCache(t interface {
|
||||||
|
mock.TestingT
|
||||||
|
Cleanup(func())
|
||||||
|
},
|
||||||
|
) *MockCache {
|
||||||
|
mock := &MockCache{}
|
||||||
|
mock.Mock.Test(t)
|
||||||
|
|
||||||
|
t.Cleanup(func() { mock.AssertExpectations(t) })
|
||||||
|
|
||||||
|
return mock
|
||||||
|
}
|
||||||
|
|
||||||
|
// MockCache is an autogenerated mock type for the Cache type
|
||||||
|
type MockCache struct {
|
||||||
|
mock.Mock
|
||||||
|
}
|
||||||
|
|
||||||
|
type MockCache_Expecter struct {
|
||||||
|
mock *mock.Mock
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_m *MockCache) EXPECT() *MockCache_Expecter {
|
||||||
|
return &MockCache_Expecter{mock: &_m.Mock}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decr provides a mock function for the type MockCache
|
||||||
|
func (_mock *MockCache) Decr(key string) error {
|
||||||
|
ret := _mock.Called(key)
|
||||||
|
|
||||||
|
if len(ret) == 0 {
|
||||||
|
panic("no return value specified for Decr")
|
||||||
|
}
|
||||||
|
|
||||||
|
var r0 error
|
||||||
|
if returnFunc, ok := ret.Get(0).(func(string) error); ok {
|
||||||
|
r0 = returnFunc(key)
|
||||||
|
} else {
|
||||||
|
r0 = ret.Error(0)
|
||||||
|
}
|
||||||
|
return r0
|
||||||
|
}
|
||||||
|
|
||||||
|
// MockCache_Decr_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Decr'
|
||||||
|
type MockCache_Decr_Call struct {
|
||||||
|
*mock.Call
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decr is a helper method to define mock.On call
|
||||||
|
// - key string
|
||||||
|
func (_e *MockCache_Expecter) Decr(key any) *MockCache_Decr_Call {
|
||||||
|
return &MockCache_Decr_Call{Call: _e.mock.On("Decr", key)}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_c *MockCache_Decr_Call) Run(run func(key string)) *MockCache_Decr_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 *MockCache_Decr_Call) Return(err error) *MockCache_Decr_Call {
|
||||||
|
_c.Call.Return(err)
|
||||||
|
return _c
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_c *MockCache_Decr_Call) RunAndReturn(run func(key string) error) *MockCache_Decr_Call {
|
||||||
|
_c.Call.Return(run)
|
||||||
|
return _c
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete provides a mock function for the type MockCache
|
||||||
|
func (_mock *MockCache) Delete(key string) error {
|
||||||
|
ret := _mock.Called(key)
|
||||||
|
|
||||||
|
if len(ret) == 0 {
|
||||||
|
panic("no return value specified for Delete")
|
||||||
|
}
|
||||||
|
|
||||||
|
var r0 error
|
||||||
|
if returnFunc, ok := ret.Get(0).(func(string) error); ok {
|
||||||
|
r0 = returnFunc(key)
|
||||||
|
} else {
|
||||||
|
r0 = ret.Error(0)
|
||||||
|
}
|
||||||
|
return r0
|
||||||
|
}
|
||||||
|
|
||||||
|
// MockCache_Delete_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Delete'
|
||||||
|
type MockCache_Delete_Call struct {
|
||||||
|
*mock.Call
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete is a helper method to define mock.On call
|
||||||
|
// - key string
|
||||||
|
func (_e *MockCache_Expecter) Delete(key any) *MockCache_Delete_Call {
|
||||||
|
return &MockCache_Delete_Call{Call: _e.mock.On("Delete", key)}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_c *MockCache_Delete_Call) Run(run func(key string)) *MockCache_Delete_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 *MockCache_Delete_Call) Return(err error) *MockCache_Delete_Call {
|
||||||
|
_c.Call.Return(err)
|
||||||
|
return _c
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_c *MockCache_Delete_Call) RunAndReturn(run func(key string) error) *MockCache_Delete_Call {
|
||||||
|
_c.Call.Return(run)
|
||||||
|
return _c
|
||||||
|
}
|
||||||
|
|
||||||
|
// Flush provides a mock function for the type MockCache
|
||||||
|
func (_mock *MockCache) Flush() error {
|
||||||
|
ret := _mock.Called()
|
||||||
|
|
||||||
|
if len(ret) == 0 {
|
||||||
|
panic("no return value specified for Flush")
|
||||||
|
}
|
||||||
|
|
||||||
|
var r0 error
|
||||||
|
if returnFunc, ok := ret.Get(0).(func() error); ok {
|
||||||
|
r0 = returnFunc()
|
||||||
|
} else {
|
||||||
|
r0 = ret.Error(0)
|
||||||
|
}
|
||||||
|
return r0
|
||||||
|
}
|
||||||
|
|
||||||
|
// MockCache_Flush_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Flush'
|
||||||
|
type MockCache_Flush_Call struct {
|
||||||
|
*mock.Call
|
||||||
|
}
|
||||||
|
|
||||||
|
// Flush is a helper method to define mock.On call
|
||||||
|
func (_e *MockCache_Expecter) Flush() *MockCache_Flush_Call {
|
||||||
|
return &MockCache_Flush_Call{Call: _e.mock.On("Flush")}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_c *MockCache_Flush_Call) Run(run func()) *MockCache_Flush_Call {
|
||||||
|
_c.Call.Run(func(args mock.Arguments) {
|
||||||
|
run()
|
||||||
|
})
|
||||||
|
return _c
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_c *MockCache_Flush_Call) Return(err error) *MockCache_Flush_Call {
|
||||||
|
_c.Call.Return(err)
|
||||||
|
return _c
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_c *MockCache_Flush_Call) RunAndReturn(run func() error) *MockCache_Flush_Call {
|
||||||
|
_c.Call.Return(run)
|
||||||
|
return _c
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get provides a mock function for the type MockCache
|
||||||
|
func (_mock *MockCache) Get(key string) any {
|
||||||
|
ret := _mock.Called(key)
|
||||||
|
|
||||||
|
if len(ret) == 0 {
|
||||||
|
panic("no return value specified for Get")
|
||||||
|
}
|
||||||
|
|
||||||
|
var r0 any
|
||||||
|
if returnFunc, ok := ret.Get(0).(func(string) any); ok {
|
||||||
|
r0 = returnFunc(key)
|
||||||
|
} else {
|
||||||
|
if ret.Get(0) != nil {
|
||||||
|
r0 = ret.Get(0).(any)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return r0
|
||||||
|
}
|
||||||
|
|
||||||
|
// MockCache_Get_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Get'
|
||||||
|
type MockCache_Get_Call struct {
|
||||||
|
*mock.Call
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get is a helper method to define mock.On call
|
||||||
|
// - key string
|
||||||
|
func (_e *MockCache_Expecter) Get(key any) *MockCache_Get_Call {
|
||||||
|
return &MockCache_Get_Call{Call: _e.mock.On("Get", key)}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_c *MockCache_Get_Call) Run(run func(key string)) *MockCache_Get_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 *MockCache_Get_Call) Return(v any) *MockCache_Get_Call {
|
||||||
|
_c.Call.Return(v)
|
||||||
|
return _c
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_c *MockCache_Get_Call) RunAndReturn(run func(key string) any) *MockCache_Get_Call {
|
||||||
|
_c.Call.Return(run)
|
||||||
|
return _c
|
||||||
|
}
|
||||||
|
|
||||||
|
// Incr provides a mock function for the type MockCache
|
||||||
|
func (_mock *MockCache) Incr(key string) error {
|
||||||
|
ret := _mock.Called(key)
|
||||||
|
|
||||||
|
if len(ret) == 0 {
|
||||||
|
panic("no return value specified for Incr")
|
||||||
|
}
|
||||||
|
|
||||||
|
var r0 error
|
||||||
|
if returnFunc, ok := ret.Get(0).(func(string) error); ok {
|
||||||
|
r0 = returnFunc(key)
|
||||||
|
} else {
|
||||||
|
r0 = ret.Error(0)
|
||||||
|
}
|
||||||
|
return r0
|
||||||
|
}
|
||||||
|
|
||||||
|
// MockCache_Incr_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Incr'
|
||||||
|
type MockCache_Incr_Call struct {
|
||||||
|
*mock.Call
|
||||||
|
}
|
||||||
|
|
||||||
|
// Incr is a helper method to define mock.On call
|
||||||
|
// - key string
|
||||||
|
func (_e *MockCache_Expecter) Incr(key any) *MockCache_Incr_Call {
|
||||||
|
return &MockCache_Incr_Call{Call: _e.mock.On("Incr", key)}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_c *MockCache_Incr_Call) Run(run func(key string)) *MockCache_Incr_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 *MockCache_Incr_Call) Return(err error) *MockCache_Incr_Call {
|
||||||
|
_c.Call.Return(err)
|
||||||
|
return _c
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_c *MockCache_Incr_Call) RunAndReturn(run func(key string) error) *MockCache_Incr_Call {
|
||||||
|
_c.Call.Return(run)
|
||||||
|
return _c
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsExist provides a mock function for the type MockCache
|
||||||
|
func (_mock *MockCache) IsExist(key string) bool {
|
||||||
|
ret := _mock.Called(key)
|
||||||
|
|
||||||
|
if len(ret) == 0 {
|
||||||
|
panic("no return value specified for IsExist")
|
||||||
|
}
|
||||||
|
|
||||||
|
var r0 bool
|
||||||
|
if returnFunc, ok := ret.Get(0).(func(string) bool); ok {
|
||||||
|
r0 = returnFunc(key)
|
||||||
|
} else {
|
||||||
|
r0 = ret.Get(0).(bool)
|
||||||
|
}
|
||||||
|
return r0
|
||||||
|
}
|
||||||
|
|
||||||
|
// MockCache_IsExist_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'IsExist'
|
||||||
|
type MockCache_IsExist_Call struct {
|
||||||
|
*mock.Call
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsExist is a helper method to define mock.On call
|
||||||
|
// - key string
|
||||||
|
func (_e *MockCache_Expecter) IsExist(key any) *MockCache_IsExist_Call {
|
||||||
|
return &MockCache_IsExist_Call{Call: _e.mock.On("IsExist", key)}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_c *MockCache_IsExist_Call) Run(run func(key string)) *MockCache_IsExist_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 *MockCache_IsExist_Call) Return(b bool) *MockCache_IsExist_Call {
|
||||||
|
_c.Call.Return(b)
|
||||||
|
return _c
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_c *MockCache_IsExist_Call) RunAndReturn(run func(key string) bool) *MockCache_IsExist_Call {
|
||||||
|
_c.Call.Return(run)
|
||||||
|
return _c
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ping provides a mock function for the type MockCache
|
||||||
|
func (_mock *MockCache) Ping() error {
|
||||||
|
ret := _mock.Called()
|
||||||
|
|
||||||
|
if len(ret) == 0 {
|
||||||
|
panic("no return value specified for Ping")
|
||||||
|
}
|
||||||
|
|
||||||
|
var r0 error
|
||||||
|
if returnFunc, ok := ret.Get(0).(func() error); ok {
|
||||||
|
r0 = returnFunc()
|
||||||
|
} else {
|
||||||
|
r0 = ret.Error(0)
|
||||||
|
}
|
||||||
|
return r0
|
||||||
|
}
|
||||||
|
|
||||||
|
// MockCache_Ping_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Ping'
|
||||||
|
type MockCache_Ping_Call struct {
|
||||||
|
*mock.Call
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ping is a helper method to define mock.On call
|
||||||
|
func (_e *MockCache_Expecter) Ping() *MockCache_Ping_Call {
|
||||||
|
return &MockCache_Ping_Call{Call: _e.mock.On("Ping")}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_c *MockCache_Ping_Call) Run(run func()) *MockCache_Ping_Call {
|
||||||
|
_c.Call.Run(func(args mock.Arguments) {
|
||||||
|
run()
|
||||||
|
})
|
||||||
|
return _c
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_c *MockCache_Ping_Call) Return(err error) *MockCache_Ping_Call {
|
||||||
|
_c.Call.Return(err)
|
||||||
|
return _c
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_c *MockCache_Ping_Call) RunAndReturn(run func() error) *MockCache_Ping_Call {
|
||||||
|
_c.Call.Return(run)
|
||||||
|
return _c
|
||||||
|
}
|
||||||
|
|
||||||
|
// Put provides a mock function for the type MockCache
|
||||||
|
func (_mock *MockCache) Put(key string, val any, timeout int64) error {
|
||||||
|
ret := _mock.Called(key, val, timeout)
|
||||||
|
|
||||||
|
if len(ret) == 0 {
|
||||||
|
panic("no return value specified for Put")
|
||||||
|
}
|
||||||
|
|
||||||
|
var r0 error
|
||||||
|
if returnFunc, ok := ret.Get(0).(func(string, any, int64) error); ok {
|
||||||
|
r0 = returnFunc(key, val, timeout)
|
||||||
|
} else {
|
||||||
|
r0 = ret.Error(0)
|
||||||
|
}
|
||||||
|
return r0
|
||||||
|
}
|
||||||
|
|
||||||
|
// MockCache_Put_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Put'
|
||||||
|
type MockCache_Put_Call struct {
|
||||||
|
*mock.Call
|
||||||
|
}
|
||||||
|
|
||||||
|
// Put is a helper method to define mock.On call
|
||||||
|
// - key string
|
||||||
|
// - val any
|
||||||
|
// - timeout int64
|
||||||
|
func (_e *MockCache_Expecter) Put(key, val, timeout any) *MockCache_Put_Call {
|
||||||
|
return &MockCache_Put_Call{Call: _e.mock.On("Put", key, val, timeout)}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_c *MockCache_Put_Call) Run(run func(key string, val any, timeout int64)) *MockCache_Put_Call {
|
||||||
|
_c.Call.Run(func(args mock.Arguments) {
|
||||||
|
var arg0 string
|
||||||
|
if args[0] != nil {
|
||||||
|
arg0 = args[0].(string)
|
||||||
|
}
|
||||||
|
var arg1 any
|
||||||
|
if args[1] != nil {
|
||||||
|
arg1 = args[1].(any)
|
||||||
|
}
|
||||||
|
var arg2 int64
|
||||||
|
if args[2] != nil {
|
||||||
|
arg2 = args[2].(int64)
|
||||||
|
}
|
||||||
|
run(
|
||||||
|
arg0,
|
||||||
|
arg1,
|
||||||
|
arg2,
|
||||||
|
)
|
||||||
|
})
|
||||||
|
return _c
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_c *MockCache_Put_Call) Return(err error) *MockCache_Put_Call {
|
||||||
|
_c.Call.Return(err)
|
||||||
|
return _c
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_c *MockCache_Put_Call) RunAndReturn(run func(key string, val any, timeout int64) error) *MockCache_Put_Call {
|
||||||
|
_c.Call.Return(run)
|
||||||
|
return _c
|
||||||
|
}
|
||||||
|
|
||||||
|
// StartAndGC provides a mock function for the type MockCache
|
||||||
|
func (_mock *MockCache) StartAndGC(opt cache.Options) error {
|
||||||
|
ret := _mock.Called(opt)
|
||||||
|
|
||||||
|
if len(ret) == 0 {
|
||||||
|
panic("no return value specified for StartAndGC")
|
||||||
|
}
|
||||||
|
|
||||||
|
var r0 error
|
||||||
|
if returnFunc, ok := ret.Get(0).(func(cache.Options) error); ok {
|
||||||
|
r0 = returnFunc(opt)
|
||||||
|
} else {
|
||||||
|
r0 = ret.Error(0)
|
||||||
|
}
|
||||||
|
return r0
|
||||||
|
}
|
||||||
|
|
||||||
|
// MockCache_StartAndGC_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'StartAndGC'
|
||||||
|
type MockCache_StartAndGC_Call struct {
|
||||||
|
*mock.Call
|
||||||
|
}
|
||||||
|
|
||||||
|
// StartAndGC is a helper method to define mock.On call
|
||||||
|
// - opt cache.Options
|
||||||
|
func (_e *MockCache_Expecter) StartAndGC(opt any) *MockCache_StartAndGC_Call {
|
||||||
|
return &MockCache_StartAndGC_Call{Call: _e.mock.On("StartAndGC", opt)}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_c *MockCache_StartAndGC_Call) Run(run func(opt cache.Options)) *MockCache_StartAndGC_Call {
|
||||||
|
_c.Call.Run(func(args mock.Arguments) {
|
||||||
|
var arg0 cache.Options
|
||||||
|
if args[0] != nil {
|
||||||
|
arg0 = args[0].(cache.Options)
|
||||||
|
}
|
||||||
|
run(
|
||||||
|
arg0,
|
||||||
|
)
|
||||||
|
})
|
||||||
|
return _c
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_c *MockCache_StartAndGC_Call) Return(err error) *MockCache_StartAndGC_Call {
|
||||||
|
_c.Call.Return(err)
|
||||||
|
return _c
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_c *MockCache_StartAndGC_Call) RunAndReturn(run func(opt cache.Options) error) *MockCache_StartAndGC_Call {
|
||||||
|
_c.Call.Return(run)
|
||||||
|
return _c
|
||||||
|
}
|
||||||
|
|
@ -30,6 +30,8 @@ type Manager struct {
|
||||||
// RedisClient is a subset of redis.UniversalClient, it exposes less methods
|
// RedisClient is a subset of redis.UniversalClient, it exposes less methods
|
||||||
// to avoid generating machine code for unused methods. New method definitions
|
// to avoid generating machine code for unused methods. New method definitions
|
||||||
// should be copied from the definitions in the Redis library github.com/redis/go-redis.
|
// should be copied from the definitions in the Redis library github.com/redis/go-redis.
|
||||||
|
//
|
||||||
|
//mockery:generate: true
|
||||||
type RedisClient interface {
|
type RedisClient interface {
|
||||||
// redis.GenericCmdable
|
// redis.GenericCmdable
|
||||||
Del(ctx context.Context, keys ...string) *redis.IntCmd
|
Del(ctx context.Context, keys ...string) *redis.IntCmd
|
||||||
|
|
|
||||||
1240
modules/nosql/mocks.go
Normal file
1240
modules/nosql/mocks.go
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -7,18 +7,17 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"forgejo.org/modules/queue/mock"
|
"forgejo.org/modules/nosql"
|
||||||
|
queue_mock "forgejo.org/modules/queue/mock"
|
||||||
"forgejo.org/modules/setting"
|
"forgejo.org/modules/setting"
|
||||||
|
|
||||||
"github.com/redis/go-redis/v9"
|
"github.com/redis/go-redis/v9"
|
||||||
|
"github.com/stretchr/testify/mock"
|
||||||
"github.com/stretchr/testify/suite"
|
"github.com/stretchr/testify/suite"
|
||||||
"go.uber.org/mock/gomock"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type baseRedisUnitTestSuite struct {
|
type baseRedisUnitTestSuite struct {
|
||||||
suite.Suite
|
suite.Suite
|
||||||
|
|
||||||
mockController *gomock.Controller
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestBaseRedis(t *testing.T) {
|
func TestBaseRedis(t *testing.T) {
|
||||||
|
|
@ -26,7 +25,6 @@ func TestBaseRedis(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (suite *baseRedisUnitTestSuite) SetupSuite() {
|
func (suite *baseRedisUnitTestSuite) SetupSuite() {
|
||||||
suite.mockController = gomock.NewController(suite.T())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (suite *baseRedisUnitTestSuite) TestBasic() {
|
func (suite *baseRedisUnitTestSuite) TestBasic() {
|
||||||
|
|
@ -71,39 +69,47 @@ func (suite *baseRedisUnitTestSuite) TestBasic() {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Configure expectations.
|
// Configure expectations.
|
||||||
mockRedisStore := mock.NewInMemoryMockRedis()
|
mockRedisStore := queue_mock.NewInMemoryMockRedis()
|
||||||
redisClient := mock.NewMockRedisClient(suite.mockController)
|
redisClient := nosql.NewMockRedisClient(suite.T())
|
||||||
|
|
||||||
redisClient.EXPECT().
|
redisClient.EXPECT().
|
||||||
Ping(gomock.Any()).
|
Ping(mock.Anything).
|
||||||
Times(1).
|
Return(&redis.StatusCmd{}).
|
||||||
Return(&redis.StatusCmd{})
|
Times(1)
|
||||||
redisClient.EXPECT().
|
redisClient.EXPECT().
|
||||||
LLen(gomock.Any(), testCase.QueueName).
|
LLen(mock.Anything, testCase.QueueName).
|
||||||
Times(1).
|
RunAndReturn(mockRedisStore.LLen).
|
||||||
DoAndReturn(mockRedisStore.LLen)
|
Times(1)
|
||||||
redisClient.EXPECT().
|
redisClient.EXPECT().
|
||||||
LPop(gomock.Any(), testCase.QueueName).
|
LPop(mock.Anything, testCase.QueueName).
|
||||||
Times(1).
|
RunAndReturn(mockRedisStore.LPop).
|
||||||
DoAndReturn(mockRedisStore.LPop)
|
Times(1)
|
||||||
redisClient.EXPECT().
|
redisClient.EXPECT().
|
||||||
RPush(gomock.Any(), testCase.QueueName, gomock.Any()).
|
RPush(mock.Anything, testCase.QueueName, mock.Anything).
|
||||||
Times(1).
|
RunAndReturn(func(ctx context.Context, key string, values ...any) *redis.IntCmd {
|
||||||
DoAndReturn(mockRedisStore.RPush)
|
return mockRedisStore.RPush(ctx, key, values[0].([]byte))
|
||||||
|
}).
|
||||||
|
Times(1)
|
||||||
|
|
||||||
if testCase.Unique {
|
if testCase.Unique {
|
||||||
redisClient.EXPECT().
|
redisClient.EXPECT().
|
||||||
SAdd(gomock.Any(), testCase.QueueName+"_unique", gomock.Any()).
|
SAdd(mock.Anything, testCase.QueueName+"_unique", mock.Anything).
|
||||||
Times(1).
|
RunAndReturn(func(ctx context.Context, key string, members ...any) *redis.IntCmd {
|
||||||
DoAndReturn(mockRedisStore.SAdd)
|
return mockRedisStore.SAdd(ctx, key, members[0].([]byte))
|
||||||
|
}).
|
||||||
|
Times(1)
|
||||||
redisClient.EXPECT().
|
redisClient.EXPECT().
|
||||||
SRem(gomock.Any(), testCase.QueueName+"_unique", gomock.Any()).
|
SRem(mock.Anything, testCase.QueueName+"_unique", mock.Anything).
|
||||||
Times(1).
|
RunAndReturn(func(ctx context.Context, key string, members ...any) *redis.IntCmd {
|
||||||
DoAndReturn(mockRedisStore.SRem)
|
return mockRedisStore.SRem(ctx, key, members[0].([]byte))
|
||||||
|
}).
|
||||||
|
Times(1)
|
||||||
redisClient.EXPECT().
|
redisClient.EXPECT().
|
||||||
SIsMember(gomock.Any(), testCase.QueueName+"_unique", gomock.Any()).
|
SIsMember(mock.Anything, testCase.QueueName+"_unique", mock.Anything).
|
||||||
Times(2).
|
RunAndReturn(func(ctx context.Context, key string, member any) *redis.BoolCmd {
|
||||||
DoAndReturn(mockRedisStore.SIsMember)
|
return mockRedisStore.SIsMember(ctx, key, member.([]byte))
|
||||||
|
}).
|
||||||
|
Times(2)
|
||||||
}
|
}
|
||||||
|
|
||||||
client, err := newBaseRedisGeneric(
|
client, err := newBaseRedisGeneric(
|
||||||
|
|
|
||||||
|
|
@ -1,344 +0,0 @@
|
||||||
// Code generated by MockGen. DO NOT EDIT.
|
|
||||||
// Source: forgejo.org/modules/nosql (interfaces: RedisClient)
|
|
||||||
//
|
|
||||||
// Generated by this command:
|
|
||||||
//
|
|
||||||
// mockgen -package mock -destination ./modules/queue/mock/redisuniversalclient.go forgejo.org/modules/nosql RedisClient
|
|
||||||
//
|
|
||||||
|
|
||||||
// Package mock is a generated GoMock package.
|
|
||||||
package mock
|
|
||||||
|
|
||||||
import (
|
|
||||||
context "context"
|
|
||||||
reflect "reflect"
|
|
||||||
time "time"
|
|
||||||
|
|
||||||
redis "github.com/redis/go-redis/v9"
|
|
||||||
gomock "go.uber.org/mock/gomock"
|
|
||||||
)
|
|
||||||
|
|
||||||
// MockRedisClient is a mock of RedisClient interface.
|
|
||||||
type MockRedisClient struct {
|
|
||||||
ctrl *gomock.Controller
|
|
||||||
recorder *MockRedisClientMockRecorder
|
|
||||||
isgomock struct{}
|
|
||||||
}
|
|
||||||
|
|
||||||
// MockRedisClientMockRecorder is the mock recorder for MockRedisClient.
|
|
||||||
type MockRedisClientMockRecorder struct {
|
|
||||||
mock *MockRedisClient
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewMockRedisClient creates a new mock instance.
|
|
||||||
func NewMockRedisClient(ctrl *gomock.Controller) *MockRedisClient {
|
|
||||||
mock := &MockRedisClient{ctrl: ctrl}
|
|
||||||
mock.recorder = &MockRedisClientMockRecorder{mock}
|
|
||||||
return mock
|
|
||||||
}
|
|
||||||
|
|
||||||
// EXPECT returns an object that allows the caller to indicate expected use.
|
|
||||||
func (m *MockRedisClient) EXPECT() *MockRedisClientMockRecorder {
|
|
||||||
return m.recorder
|
|
||||||
}
|
|
||||||
|
|
||||||
// Close mocks base method.
|
|
||||||
func (m *MockRedisClient) Close() error {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
ret := m.ctrl.Call(m, "Close")
|
|
||||||
ret0, _ := ret[0].(error)
|
|
||||||
return ret0
|
|
||||||
}
|
|
||||||
|
|
||||||
// Close indicates an expected call of Close.
|
|
||||||
func (mr *MockRedisClientMockRecorder) Close() *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Close", reflect.TypeOf((*MockRedisClient)(nil).Close))
|
|
||||||
}
|
|
||||||
|
|
||||||
// DBSize mocks base method.
|
|
||||||
func (m *MockRedisClient) DBSize(ctx context.Context) *redis.IntCmd {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
ret := m.ctrl.Call(m, "DBSize", ctx)
|
|
||||||
ret0, _ := ret[0].(*redis.IntCmd)
|
|
||||||
return ret0
|
|
||||||
}
|
|
||||||
|
|
||||||
// DBSize indicates an expected call of DBSize.
|
|
||||||
func (mr *MockRedisClientMockRecorder) DBSize(ctx any) *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DBSize", reflect.TypeOf((*MockRedisClient)(nil).DBSize), ctx)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Decr mocks base method.
|
|
||||||
func (m *MockRedisClient) Decr(ctx context.Context, key string) *redis.IntCmd {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
ret := m.ctrl.Call(m, "Decr", ctx, key)
|
|
||||||
ret0, _ := ret[0].(*redis.IntCmd)
|
|
||||||
return ret0
|
|
||||||
}
|
|
||||||
|
|
||||||
// Decr indicates an expected call of Decr.
|
|
||||||
func (mr *MockRedisClientMockRecorder) Decr(ctx, key any) *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Decr", reflect.TypeOf((*MockRedisClient)(nil).Decr), ctx, key)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Del mocks base method.
|
|
||||||
func (m *MockRedisClient) Del(ctx context.Context, keys ...string) *redis.IntCmd {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
varargs := []any{ctx}
|
|
||||||
for _, a := range keys {
|
|
||||||
varargs = append(varargs, a)
|
|
||||||
}
|
|
||||||
ret := m.ctrl.Call(m, "Del", varargs...)
|
|
||||||
ret0, _ := ret[0].(*redis.IntCmd)
|
|
||||||
return ret0
|
|
||||||
}
|
|
||||||
|
|
||||||
// Del indicates an expected call of Del.
|
|
||||||
func (mr *MockRedisClientMockRecorder) Del(ctx any, keys ...any) *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
varargs := append([]any{ctx}, keys...)
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Del", reflect.TypeOf((*MockRedisClient)(nil).Del), varargs...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Exists mocks base method.
|
|
||||||
func (m *MockRedisClient) Exists(ctx context.Context, keys ...string) *redis.IntCmd {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
varargs := []any{ctx}
|
|
||||||
for _, a := range keys {
|
|
||||||
varargs = append(varargs, a)
|
|
||||||
}
|
|
||||||
ret := m.ctrl.Call(m, "Exists", varargs...)
|
|
||||||
ret0, _ := ret[0].(*redis.IntCmd)
|
|
||||||
return ret0
|
|
||||||
}
|
|
||||||
|
|
||||||
// Exists indicates an expected call of Exists.
|
|
||||||
func (mr *MockRedisClientMockRecorder) Exists(ctx any, keys ...any) *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
varargs := append([]any{ctx}, keys...)
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Exists", reflect.TypeOf((*MockRedisClient)(nil).Exists), varargs...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// FlushDB mocks base method.
|
|
||||||
func (m *MockRedisClient) FlushDB(ctx context.Context) *redis.StatusCmd {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
ret := m.ctrl.Call(m, "FlushDB", ctx)
|
|
||||||
ret0, _ := ret[0].(*redis.StatusCmd)
|
|
||||||
return ret0
|
|
||||||
}
|
|
||||||
|
|
||||||
// FlushDB indicates an expected call of FlushDB.
|
|
||||||
func (mr *MockRedisClientMockRecorder) FlushDB(ctx any) *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "FlushDB", reflect.TypeOf((*MockRedisClient)(nil).FlushDB), ctx)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get mocks base method.
|
|
||||||
func (m *MockRedisClient) Get(ctx context.Context, key string) *redis.StringCmd {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
ret := m.ctrl.Call(m, "Get", ctx, key)
|
|
||||||
ret0, _ := ret[0].(*redis.StringCmd)
|
|
||||||
return ret0
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get indicates an expected call of Get.
|
|
||||||
func (mr *MockRedisClientMockRecorder) Get(ctx, key any) *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Get", reflect.TypeOf((*MockRedisClient)(nil).Get), ctx, key)
|
|
||||||
}
|
|
||||||
|
|
||||||
// HDel mocks base method.
|
|
||||||
func (m *MockRedisClient) HDel(ctx context.Context, key string, fields ...string) *redis.IntCmd {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
varargs := []any{ctx, key}
|
|
||||||
for _, a := range fields {
|
|
||||||
varargs = append(varargs, a)
|
|
||||||
}
|
|
||||||
ret := m.ctrl.Call(m, "HDel", varargs...)
|
|
||||||
ret0, _ := ret[0].(*redis.IntCmd)
|
|
||||||
return ret0
|
|
||||||
}
|
|
||||||
|
|
||||||
// HDel indicates an expected call of HDel.
|
|
||||||
func (mr *MockRedisClientMockRecorder) HDel(ctx, key any, fields ...any) *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
varargs := append([]any{ctx, key}, fields...)
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "HDel", reflect.TypeOf((*MockRedisClient)(nil).HDel), varargs...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// HKeys mocks base method.
|
|
||||||
func (m *MockRedisClient) HKeys(ctx context.Context, key string) *redis.StringSliceCmd {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
ret := m.ctrl.Call(m, "HKeys", ctx, key)
|
|
||||||
ret0, _ := ret[0].(*redis.StringSliceCmd)
|
|
||||||
return ret0
|
|
||||||
}
|
|
||||||
|
|
||||||
// HKeys indicates an expected call of HKeys.
|
|
||||||
func (mr *MockRedisClientMockRecorder) HKeys(ctx, key any) *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "HKeys", reflect.TypeOf((*MockRedisClient)(nil).HKeys), ctx, key)
|
|
||||||
}
|
|
||||||
|
|
||||||
// HSet mocks base method.
|
|
||||||
func (m *MockRedisClient) HSet(ctx context.Context, key string, values ...any) *redis.IntCmd {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
varargs := []any{ctx, key}
|
|
||||||
for _, a := range values {
|
|
||||||
varargs = append(varargs, a)
|
|
||||||
}
|
|
||||||
ret := m.ctrl.Call(m, "HSet", varargs...)
|
|
||||||
ret0, _ := ret[0].(*redis.IntCmd)
|
|
||||||
return ret0
|
|
||||||
}
|
|
||||||
|
|
||||||
// HSet indicates an expected call of HSet.
|
|
||||||
func (mr *MockRedisClientMockRecorder) HSet(ctx, key any, values ...any) *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
varargs := append([]any{ctx, key}, values...)
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "HSet", reflect.TypeOf((*MockRedisClient)(nil).HSet), varargs...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Incr mocks base method.
|
|
||||||
func (m *MockRedisClient) Incr(ctx context.Context, key string) *redis.IntCmd {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
ret := m.ctrl.Call(m, "Incr", ctx, key)
|
|
||||||
ret0, _ := ret[0].(*redis.IntCmd)
|
|
||||||
return ret0
|
|
||||||
}
|
|
||||||
|
|
||||||
// Incr indicates an expected call of Incr.
|
|
||||||
func (mr *MockRedisClientMockRecorder) Incr(ctx, key any) *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Incr", reflect.TypeOf((*MockRedisClient)(nil).Incr), ctx, key)
|
|
||||||
}
|
|
||||||
|
|
||||||
// LLen mocks base method.
|
|
||||||
func (m *MockRedisClient) LLen(ctx context.Context, key string) *redis.IntCmd {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
ret := m.ctrl.Call(m, "LLen", ctx, key)
|
|
||||||
ret0, _ := ret[0].(*redis.IntCmd)
|
|
||||||
return ret0
|
|
||||||
}
|
|
||||||
|
|
||||||
// LLen indicates an expected call of LLen.
|
|
||||||
func (mr *MockRedisClientMockRecorder) LLen(ctx, key any) *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "LLen", reflect.TypeOf((*MockRedisClient)(nil).LLen), ctx, key)
|
|
||||||
}
|
|
||||||
|
|
||||||
// LPop mocks base method.
|
|
||||||
func (m *MockRedisClient) LPop(ctx context.Context, key string) *redis.StringCmd {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
ret := m.ctrl.Call(m, "LPop", ctx, key)
|
|
||||||
ret0, _ := ret[0].(*redis.StringCmd)
|
|
||||||
return ret0
|
|
||||||
}
|
|
||||||
|
|
||||||
// LPop indicates an expected call of LPop.
|
|
||||||
func (mr *MockRedisClientMockRecorder) LPop(ctx, key any) *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "LPop", reflect.TypeOf((*MockRedisClient)(nil).LPop), ctx, key)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ping mocks base method.
|
|
||||||
func (m *MockRedisClient) Ping(ctx context.Context) *redis.StatusCmd {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
ret := m.ctrl.Call(m, "Ping", ctx)
|
|
||||||
ret0, _ := ret[0].(*redis.StatusCmd)
|
|
||||||
return ret0
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ping indicates an expected call of Ping.
|
|
||||||
func (mr *MockRedisClientMockRecorder) Ping(ctx any) *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Ping", reflect.TypeOf((*MockRedisClient)(nil).Ping), ctx)
|
|
||||||
}
|
|
||||||
|
|
||||||
// RPush mocks base method.
|
|
||||||
func (m *MockRedisClient) RPush(ctx context.Context, key string, values ...any) *redis.IntCmd {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
varargs := []any{ctx, key}
|
|
||||||
for _, a := range values {
|
|
||||||
varargs = append(varargs, a)
|
|
||||||
}
|
|
||||||
ret := m.ctrl.Call(m, "RPush", varargs...)
|
|
||||||
ret0, _ := ret[0].(*redis.IntCmd)
|
|
||||||
return ret0
|
|
||||||
}
|
|
||||||
|
|
||||||
// RPush indicates an expected call of RPush.
|
|
||||||
func (mr *MockRedisClientMockRecorder) RPush(ctx, key any, values ...any) *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
varargs := append([]any{ctx, key}, values...)
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RPush", reflect.TypeOf((*MockRedisClient)(nil).RPush), varargs...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// SAdd mocks base method.
|
|
||||||
func (m *MockRedisClient) SAdd(ctx context.Context, key string, members ...any) *redis.IntCmd {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
varargs := []any{ctx, key}
|
|
||||||
for _, a := range members {
|
|
||||||
varargs = append(varargs, a)
|
|
||||||
}
|
|
||||||
ret := m.ctrl.Call(m, "SAdd", varargs...)
|
|
||||||
ret0, _ := ret[0].(*redis.IntCmd)
|
|
||||||
return ret0
|
|
||||||
}
|
|
||||||
|
|
||||||
// SAdd indicates an expected call of SAdd.
|
|
||||||
func (mr *MockRedisClientMockRecorder) SAdd(ctx, key any, members ...any) *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
varargs := append([]any{ctx, key}, members...)
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SAdd", reflect.TypeOf((*MockRedisClient)(nil).SAdd), varargs...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// SIsMember mocks base method.
|
|
||||||
func (m *MockRedisClient) SIsMember(ctx context.Context, key string, member any) *redis.BoolCmd {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
ret := m.ctrl.Call(m, "SIsMember", ctx, key, member)
|
|
||||||
ret0, _ := ret[0].(*redis.BoolCmd)
|
|
||||||
return ret0
|
|
||||||
}
|
|
||||||
|
|
||||||
// SIsMember indicates an expected call of SIsMember.
|
|
||||||
func (mr *MockRedisClientMockRecorder) SIsMember(ctx, key, member any) *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SIsMember", reflect.TypeOf((*MockRedisClient)(nil).SIsMember), ctx, key, member)
|
|
||||||
}
|
|
||||||
|
|
||||||
// SRem mocks base method.
|
|
||||||
func (m *MockRedisClient) SRem(ctx context.Context, key string, members ...any) *redis.IntCmd {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
varargs := []any{ctx, key}
|
|
||||||
for _, a := range members {
|
|
||||||
varargs = append(varargs, a)
|
|
||||||
}
|
|
||||||
ret := m.ctrl.Call(m, "SRem", varargs...)
|
|
||||||
ret0, _ := ret[0].(*redis.IntCmd)
|
|
||||||
return ret0
|
|
||||||
}
|
|
||||||
|
|
||||||
// SRem indicates an expected call of SRem.
|
|
||||||
func (mr *MockRedisClientMockRecorder) SRem(ctx, key any, members ...any) *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
varargs := append([]any{ctx, key}, members...)
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SRem", reflect.TypeOf((*MockRedisClient)(nil).SRem), varargs...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set mocks base method.
|
|
||||||
func (m *MockRedisClient) Set(ctx context.Context, key string, value any, expiration time.Duration) *redis.StatusCmd {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
ret := m.ctrl.Call(m, "Set", ctx, key, value, expiration)
|
|
||||||
ret0, _ := ret[0].(*redis.StatusCmd)
|
|
||||||
return ret0
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set indicates an expected call of Set.
|
|
||||||
func (mr *MockRedisClientMockRecorder) Set(ctx, key, value, expiration any) *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Set", reflect.TypeOf((*MockRedisClient)(nil).Set), ctx, key, value, expiration)
|
|
||||||
}
|
|
||||||
|
|
@ -10,6 +10,7 @@ var AuthorizedIntegration = struct {
|
||||||
BlockedDomains string
|
BlockedDomains string
|
||||||
AllowLocalNetworks bool
|
AllowLocalNetworks bool
|
||||||
RequestTimeout time.Duration
|
RequestTimeout time.Duration
|
||||||
|
CacheTTL time.Duration
|
||||||
}{}
|
}{}
|
||||||
|
|
||||||
func loadAuthorizedIntegrationFrom(rootCfg ConfigProvider) {
|
func loadAuthorizedIntegrationFrom(rootCfg ConfigProvider) {
|
||||||
|
|
@ -18,4 +19,5 @@ func loadAuthorizedIntegrationFrom(rootCfg ConfigProvider) {
|
||||||
AuthorizedIntegration.BlockedDomains = sec.Key("BLOCKED_DOMAINS").MustString("")
|
AuthorizedIntegration.BlockedDomains = sec.Key("BLOCKED_DOMAINS").MustString("")
|
||||||
AuthorizedIntegration.AllowLocalNetworks = sec.Key("ALLOW_LOCALNETWORKS").MustBool(false)
|
AuthorizedIntegration.AllowLocalNetworks = sec.Key("ALLOW_LOCALNETWORKS").MustBool(false)
|
||||||
AuthorizedIntegration.RequestTimeout = sec.Key("REQUEST_TIMEOUT").MustDuration(10 * time.Second)
|
AuthorizedIntegration.RequestTimeout = sec.Key("REQUEST_TIMEOUT").MustDuration(10 * time.Second)
|
||||||
|
AuthorizedIntegration.CacheTTL = sec.Key("CACHE_TTL").MustDuration(10 * time.Minute)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,8 @@
|
||||||
package method
|
package method
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bufio"
|
||||||
|
"bytes"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
|
@ -15,6 +17,7 @@ import (
|
||||||
|
|
||||||
auth_model "forgejo.org/models/auth"
|
auth_model "forgejo.org/models/auth"
|
||||||
user_model "forgejo.org/models/user"
|
user_model "forgejo.org/models/user"
|
||||||
|
"forgejo.org/modules/cache"
|
||||||
"forgejo.org/modules/hostmatcher"
|
"forgejo.org/modules/hostmatcher"
|
||||||
"forgejo.org/modules/json"
|
"forgejo.org/modules/json"
|
||||||
"forgejo.org/modules/jwtx"
|
"forgejo.org/modules/jwtx"
|
||||||
|
|
@ -42,6 +45,7 @@ var (
|
||||||
initHTTPClient.Do(initAuthorizedIntegrationHTTPClient)
|
initHTTPClient.Do(initAuthorizedIntegrationHTTPClient)
|
||||||
return aiHTTPClient
|
return aiHTTPClient
|
||||||
}
|
}
|
||||||
|
getCache = cache.GetCache
|
||||||
)
|
)
|
||||||
|
|
||||||
// Restrict document size to prevent resource exhaustion attack with a malicious authorized integration; largest
|
// Restrict document size to prevent resource exhaustion attack with a malicious authorized integration; largest
|
||||||
|
|
@ -126,8 +130,7 @@ func (a *AuthorizedIntegration) Verify(req *http.Request, w http.ResponseWriter,
|
||||||
|
|
||||||
issuerOIDCURL := issuerURL.JoinPath(".well-known/openid-configuration")
|
issuerOIDCURL := issuerURL.JoinPath(".well-known/openid-configuration")
|
||||||
var oidcConfig openIDConfiguration
|
var oidcConfig openIDConfiguration
|
||||||
// TODO: cache external OIDC configuration, with a fixed timeout (not LRU/MRU)
|
if err := authorizedIntegrationFetchJSON(issuerOIDCURL.String(), &oidcConfig); err != nil {
|
||||||
if err := a.fetchJSON(issuerOIDCURL.String(), &oidcConfig); err != nil {
|
|
||||||
return nil, fmt.Errorf("error when fetching .well-known/openid-configuration from %s: %w", issuerOIDCURL, err)
|
return nil, fmt.Errorf("error when fetching .well-known/openid-configuration from %s: %w", issuerOIDCURL, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -149,8 +152,7 @@ func (a *AuthorizedIntegration) Verify(req *http.Request, w http.ResponseWriter,
|
||||||
return nil, fmt.Errorf("jwks_uri host mismatch: must be the same as issuer host %q, but was %q", issuerURL.Host, jwksURI.Host)
|
return nil, fmt.Errorf("jwks_uri host mismatch: must be the same as issuer host %q, but was %q", issuerURL.Host, jwksURI.Host)
|
||||||
}
|
}
|
||||||
var keys openIDKeys
|
var keys openIDKeys
|
||||||
// TODO: cache JWKS, with a fixed timeout (not LRU/MRU)
|
if err := authorizedIntegrationFetchJSON(oidcConfig.JwksURI, &keys); err != nil {
|
||||||
if err := a.fetchJSON(oidcConfig.JwksURI, &keys); err != nil {
|
|
||||||
return nil, fmt.Errorf("error when fetching JWKS from %s: %w", oidcConfig.JwksURI, err)
|
return nil, fmt.Errorf("error when fetching JWKS from %s: %w", oidcConfig.JwksURI, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -247,7 +249,56 @@ func initAuthorizedIntegrationHTTPClient() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *AuthorizedIntegration) fetchJSON(urlString string, v any) error {
|
func authorizedIntegrationCacheKey(urlString string) string {
|
||||||
|
return fmt.Sprintf("auth-int-remote:%s", urlString)
|
||||||
|
}
|
||||||
|
|
||||||
|
func authorizedIntegrationCacheGetJSON[K any](urlString string, v *K) bool {
|
||||||
|
conn := getCache()
|
||||||
|
if conn == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
cachedAny := conn.Get(authorizedIntegrationCacheKey(urlString))
|
||||||
|
if cachedAny == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
cachedBytes, ok := cachedAny.([]byte)
|
||||||
|
if !ok {
|
||||||
|
cachedString, ok := cachedAny.(string)
|
||||||
|
if !ok {
|
||||||
|
log.Error("cached content was not []byte or string, but was %T", cachedAny)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
cachedBytes = []byte(cachedString)
|
||||||
|
}
|
||||||
|
|
||||||
|
err := json.Unmarshal(cachedBytes, &v)
|
||||||
|
if err != nil {
|
||||||
|
// This error case shouldn't occur, as we only store data in the cache once we're sure we could unmarshal it.
|
||||||
|
// If it does occur, log and fallback to treating as uncached.
|
||||||
|
log.Error("failed to Unmarshal cached content: %s", err)
|
||||||
|
// Caller may reuse `v` in a future unmarshal/decode call, and failure here may have polluted it.
|
||||||
|
var zeroValue K
|
||||||
|
*v = zeroValue
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func authorizedIntegrationCacheSetJSON(urlString string, buf []byte) {
|
||||||
|
conn := getCache()
|
||||||
|
if conn == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err := conn.Put(authorizedIntegrationCacheKey(urlString), buf, int64(setting.AuthorizedIntegration.CacheTTL.Seconds()))
|
||||||
|
if err != nil {
|
||||||
|
log.Error("failed to put cache: %s", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func authorizedIntegrationFetchJSON[K any](urlString string, v *K) error {
|
||||||
parsedURL, err := url.Parse(urlString)
|
parsedURL, err := url.Parse(urlString)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed parsing URL %q: %w", urlString, err)
|
return fmt.Errorf("failed parsing URL %q: %w", urlString, err)
|
||||||
|
|
@ -259,6 +310,11 @@ func (a *AuthorizedIntegration) fetchJSON(urlString string, v any) error {
|
||||||
return fmt.Errorf("unsupported URL scheme: %q", parsedURL.String())
|
return fmt.Errorf("unsupported URL scheme: %q", parsedURL.String())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check our cache, save a remote HTTP interaction.
|
||||||
|
if authorizedIntegrationCacheGetJSON(urlString, v) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
resp, err := GetAuthorizedIntegrationHTTPClient().Get(parsedURL.String())
|
resp, err := GetAuthorizedIntegrationHTTPClient().Get(parsedURL.String())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
|
@ -269,15 +325,24 @@ func (a *AuthorizedIntegration) fetchJSON(urlString string, v any) error {
|
||||||
return fmt.Errorf("non-OK response code: %s", resp.Status)
|
return fmt.Errorf("non-OK response code: %s", resp.Status)
|
||||||
}
|
}
|
||||||
|
|
||||||
body := io.LimitReader(resp.Body, authorizedIntegrationRequestBodyLimit)
|
bodyReader := io.LimitReader(resp.Body, authorizedIntegrationRequestBodyLimit)
|
||||||
decoder := json.NewDecoder(body)
|
var buf bytes.Buffer
|
||||||
err = decoder.Decode(&v)
|
_, err = io.Copy(bufio.NewWriter(&buf), bodyReader)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("read from remote error: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = json.Unmarshal(buf.Bytes(), &v)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// If a decoding error is hit, decorate with information about the limited body size so that it doesn't look
|
// If a decoding error is hit, decorate with information about the limited body size so that it doesn't look
|
||||||
// like the remote server provided an incomplete response. err should be something like `io.UnexpectedEOF` in
|
// like the remote server provided an incomplete response. err should be something like `io.UnexpectedEOF` in
|
||||||
// this case, but it actually isn't, so don't bother trying to detect precisely.
|
// this case, but it actually isn't, so don't bother trying to detect precisely.
|
||||||
return fmt.Errorf("failed to decode (response body restricted to %d bytes): %w", authorizedIntegrationRequestBodyLimit, err)
|
return fmt.Errorf("failed to decode (response body restricted to %d bytes): %w", authorizedIntegrationRequestBodyLimit, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Successfully decoded the response -- cache the raw bytes for later access.
|
||||||
|
authorizedIntegrationCacheSetJSON(urlString, buf.Bytes())
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -14,14 +14,18 @@ import (
|
||||||
|
|
||||||
auth_model "forgejo.org/models/auth"
|
auth_model "forgejo.org/models/auth"
|
||||||
"forgejo.org/models/db"
|
"forgejo.org/models/db"
|
||||||
|
"forgejo.org/modules/cache"
|
||||||
"forgejo.org/modules/json"
|
"forgejo.org/modules/json"
|
||||||
"forgejo.org/modules/jwtx"
|
"forgejo.org/modules/jwtx"
|
||||||
|
"forgejo.org/modules/setting"
|
||||||
"forgejo.org/modules/test"
|
"forgejo.org/modules/test"
|
||||||
"forgejo.org/services/auth"
|
"forgejo.org/services/auth"
|
||||||
|
|
||||||
|
mc "code.forgejo.org/go-chi/cache"
|
||||||
"github.com/golang-jwt/jwt/v5"
|
"github.com/golang-jwt/jwt/v5"
|
||||||
gouuid "github.com/google/uuid"
|
gouuid "github.com/google/uuid"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/mock"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -557,6 +561,102 @@ func TestAuthorizedIntegration(t *testing.T) {
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
assert.False(t, writeAdmin, "write:admin")
|
assert.False(t, writeAdmin, "write:admin")
|
||||||
})
|
})
|
||||||
|
|
||||||
|
t.Run("cache", func(t *testing.T) {
|
||||||
|
t.Run("miss and store", func(t *testing.T) {
|
||||||
|
c := cache.NewMockCache(t)
|
||||||
|
defer test.MockVariableValue(&getCache, func() mc.Cache { return c })()
|
||||||
|
defer test.MockVariableValue(&setting.AuthorizedIntegration.CacheTTL, 10*time.Minute)()
|
||||||
|
|
||||||
|
var cacheKey string
|
||||||
|
c.On("Get", mock.AnythingOfType("string")).
|
||||||
|
Run(func(args mock.Arguments) {
|
||||||
|
key := args.Get(0).(string)
|
||||||
|
assert.True(t, strings.HasPrefix(key, "auth-int-remote:https://"), "key %s should have key prefix", key)
|
||||||
|
cacheKey = key
|
||||||
|
}).Return(nil)
|
||||||
|
c.On("Put", mock.Anything, mock.Anything, mock.Anything).
|
||||||
|
Once().
|
||||||
|
Run(func(args mock.Arguments) {
|
||||||
|
putKey := args.Get(0).(string)
|
||||||
|
assert.Equal(t, cacheKey, putKey)
|
||||||
|
putContents := args.Get(1).([]byte)
|
||||||
|
assert.Contains(t, string(putContents), "\"issuer\":")
|
||||||
|
assert.Contains(t, string(putContents), "\"jwks_uri\":")
|
||||||
|
assert.EqualValues(t, 600, args.Get(2))
|
||||||
|
}).Return(nil)
|
||||||
|
c.On("Put", mock.Anything, mock.Anything, mock.Anything).
|
||||||
|
Once().
|
||||||
|
Run(func(args mock.Arguments) {
|
||||||
|
putKey := args.Get(0).(string)
|
||||||
|
assert.Equal(t, cacheKey, putKey)
|
||||||
|
putContents := args.Get(1).([]byte)
|
||||||
|
assert.Contains(t, string(putContents), "\"alg\":\"RS256\"")
|
||||||
|
assert.Contains(t, string(putContents), "\"kty\":\"RSA\"")
|
||||||
|
assert.EqualValues(t, 600, args.Get(2))
|
||||||
|
}).Return(nil)
|
||||||
|
|
||||||
|
ait := newAITester(t)
|
||||||
|
defer ait.close()
|
||||||
|
output := ait.bearerRequest()
|
||||||
|
requireOutput[*auth.AuthenticationSuccess](t, output)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("hit", func(t *testing.T) {
|
||||||
|
var oidcMetadata []byte
|
||||||
|
var jwksData []byte
|
||||||
|
ait := newAITester(t,
|
||||||
|
openIDTweak(func(oi *openIDConfiguration, ait *AuthorizedIntegrationTester) {
|
||||||
|
var err error
|
||||||
|
oidcMetadata, err = json.Marshal(oi)
|
||||||
|
require.NoError(t, err)
|
||||||
|
}),
|
||||||
|
jwksTweak(func(oi *openIDKeys) {
|
||||||
|
var err error
|
||||||
|
jwksData, err = json.Marshal(oi)
|
||||||
|
require.NoError(t, err)
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
defer ait.close()
|
||||||
|
ait.bearerRequest() // populate oidcMetadata & jwksData by making a request
|
||||||
|
|
||||||
|
t.Run("cache returns []byte", func(t *testing.T) {
|
||||||
|
c := cache.NewMockCache(t)
|
||||||
|
defer test.MockVariableValue(&getCache, func() mc.Cache { return c })()
|
||||||
|
|
||||||
|
c.On("Get",
|
||||||
|
mock.MatchedBy(func(key string) bool {
|
||||||
|
return strings.Contains(key, ".well-known/openid-configuration")
|
||||||
|
})).
|
||||||
|
Return(oidcMetadata)
|
||||||
|
c.On("Get",
|
||||||
|
mock.MatchedBy(func(key string) bool {
|
||||||
|
return strings.Contains(key, ".keys")
|
||||||
|
})).
|
||||||
|
Return(jwksData)
|
||||||
|
|
||||||
|
ait.bearerRequest()
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("cache returns string", func(t *testing.T) {
|
||||||
|
c := cache.NewMockCache(t)
|
||||||
|
defer test.MockVariableValue(&getCache, func() mc.Cache { return c })()
|
||||||
|
|
||||||
|
c.On("Get",
|
||||||
|
mock.MatchedBy(func(key string) bool {
|
||||||
|
return strings.Contains(key, ".well-known/openid-configuration")
|
||||||
|
})).
|
||||||
|
Return(string(oidcMetadata))
|
||||||
|
c.On("Get",
|
||||||
|
mock.MatchedBy(func(key string) bool {
|
||||||
|
return strings.Contains(key, ".keys")
|
||||||
|
})).
|
||||||
|
Return(string(jwksData))
|
||||||
|
|
||||||
|
ait.bearerRequest()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
type AuthorizedIntegrationTester struct {
|
type AuthorizedIntegrationTester struct {
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,8 @@ import (
|
||||||
|
|
||||||
// Defines an API for reducing available permissions to specific resources. Typically associated with a fine-grained
|
// Defines an API for reducing available permissions to specific resources. Typically associated with a fine-grained
|
||||||
// access tokens and provides methods to reduce authorization that the access token provides down to specific resources.
|
// access tokens and provides methods to reduce authorization that the access token provides down to specific resources.
|
||||||
|
//
|
||||||
|
//mockery:generate: true
|
||||||
type AuthorizationReducer interface {
|
type AuthorizationReducer interface {
|
||||||
// Incorporate all the methods of [RepositoryAuthorizationReducer], which allows reducing permissions related to
|
// Incorporate all the methods of [RepositoryAuthorizationReducer], which allows reducing permissions related to
|
||||||
// repositories specifically.
|
// repositories specifically.
|
||||||
|
|
|
||||||
|
|
@ -1,86 +1,19 @@
|
||||||
// Code generated by mockery v2.53.5. DO NOT EDIT.
|
// Code generated by mockery; DO NOT EDIT.
|
||||||
|
// github.com/vektra/mockery
|
||||||
|
// template: testify
|
||||||
|
|
||||||
package authz
|
package authz
|
||||||
|
|
||||||
import (
|
import (
|
||||||
context "context"
|
"context"
|
||||||
|
|
||||||
"forgejo.org/models/perm"
|
"forgejo.org/models/perm"
|
||||||
repo_model "forgejo.org/models/repo"
|
"forgejo.org/models/repo"
|
||||||
|
|
||||||
mock "github.com/stretchr/testify/mock"
|
mock "github.com/stretchr/testify/mock"
|
||||||
"xorm.io/builder"
|
"xorm.io/builder"
|
||||||
)
|
)
|
||||||
|
|
||||||
// MockAuthorizationReducer is an autogenerated mock type for the AuthorizationReducer type
|
|
||||||
type MockAuthorizationReducer struct {
|
|
||||||
mock.Mock
|
|
||||||
}
|
|
||||||
|
|
||||||
// AllowAdminOverride provides a mock function with no fields
|
|
||||||
func (_m *MockAuthorizationReducer) AllowAdminOverride() bool {
|
|
||||||
ret := _m.Called()
|
|
||||||
|
|
||||||
if len(ret) == 0 {
|
|
||||||
panic("no return value specified for AllowAdminOverride")
|
|
||||||
}
|
|
||||||
|
|
||||||
var r0 bool
|
|
||||||
if rf, ok := ret.Get(0).(func() bool); ok {
|
|
||||||
r0 = rf()
|
|
||||||
} else {
|
|
||||||
r0 = ret.Get(0).(bool)
|
|
||||||
}
|
|
||||||
|
|
||||||
return r0
|
|
||||||
}
|
|
||||||
|
|
||||||
// ReduceRepoAccess provides a mock function with given fields: ctx, repo, accessMode
|
|
||||||
func (_m *MockAuthorizationReducer) ReduceRepoAccess(ctx context.Context, repo *repo_model.Repository, accessMode perm.AccessMode) (perm.AccessMode, error) {
|
|
||||||
ret := _m.Called(ctx, repo, accessMode)
|
|
||||||
|
|
||||||
if len(ret) == 0 {
|
|
||||||
panic("no return value specified for ReduceRepoAccess")
|
|
||||||
}
|
|
||||||
|
|
||||||
var r0 perm.AccessMode
|
|
||||||
var r1 error
|
|
||||||
if rf, ok := ret.Get(0).(func(context.Context, *repo_model.Repository, perm.AccessMode) (perm.AccessMode, error)); ok {
|
|
||||||
return rf(ctx, repo, accessMode)
|
|
||||||
}
|
|
||||||
if rf, ok := ret.Get(0).(func(context.Context, *repo_model.Repository, perm.AccessMode) perm.AccessMode); ok {
|
|
||||||
r0 = rf(ctx, repo, accessMode)
|
|
||||||
} else {
|
|
||||||
r0 = ret.Get(0).(perm.AccessMode)
|
|
||||||
}
|
|
||||||
|
|
||||||
if rf, ok := ret.Get(1).(func(context.Context, *repo_model.Repository, perm.AccessMode) error); ok {
|
|
||||||
r1 = rf(ctx, repo, accessMode)
|
|
||||||
} else {
|
|
||||||
r1 = ret.Error(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
return r0, r1
|
|
||||||
}
|
|
||||||
|
|
||||||
// RepoFilter provides a mock function with given fields: accessMode
|
|
||||||
func (_m *MockAuthorizationReducer) RepoReadAccessFilter() builder.Cond {
|
|
||||||
ret := _m.Called()
|
|
||||||
|
|
||||||
if len(ret) == 0 {
|
|
||||||
panic("no return value specified for AllowAdminOverride")
|
|
||||||
}
|
|
||||||
|
|
||||||
var r0 builder.Cond
|
|
||||||
if rf, ok := ret.Get(0).(func() builder.Cond); ok {
|
|
||||||
r0 = rf()
|
|
||||||
} else {
|
|
||||||
r0 = ret.Get(0).(builder.Cond)
|
|
||||||
}
|
|
||||||
|
|
||||||
return r0
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewMockAuthorizationReducer creates a new instance of MockAuthorizationReducer. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
|
// NewMockAuthorizationReducer creates a new instance of MockAuthorizationReducer. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
|
||||||
// The first argument is typically a *testing.T value.
|
// The first argument is typically a *testing.T value.
|
||||||
func NewMockAuthorizationReducer(t interface {
|
func NewMockAuthorizationReducer(t interface {
|
||||||
|
|
@ -95,3 +28,178 @@ func NewMockAuthorizationReducer(t interface {
|
||||||
|
|
||||||
return mock
|
return mock
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MockAuthorizationReducer is an autogenerated mock type for the AuthorizationReducer type
|
||||||
|
type MockAuthorizationReducer struct {
|
||||||
|
mock.Mock
|
||||||
|
}
|
||||||
|
|
||||||
|
type MockAuthorizationReducer_Expecter struct {
|
||||||
|
mock *mock.Mock
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_m *MockAuthorizationReducer) EXPECT() *MockAuthorizationReducer_Expecter {
|
||||||
|
return &MockAuthorizationReducer_Expecter{mock: &_m.Mock}
|
||||||
|
}
|
||||||
|
|
||||||
|
// AllowAdminOverride provides a mock function for the type MockAuthorizationReducer
|
||||||
|
func (_mock *MockAuthorizationReducer) AllowAdminOverride() bool {
|
||||||
|
ret := _mock.Called()
|
||||||
|
|
||||||
|
if len(ret) == 0 {
|
||||||
|
panic("no return value specified for AllowAdminOverride")
|
||||||
|
}
|
||||||
|
|
||||||
|
var r0 bool
|
||||||
|
if returnFunc, ok := ret.Get(0).(func() bool); ok {
|
||||||
|
r0 = returnFunc()
|
||||||
|
} else {
|
||||||
|
r0 = ret.Get(0).(bool)
|
||||||
|
}
|
||||||
|
return r0
|
||||||
|
}
|
||||||
|
|
||||||
|
// MockAuthorizationReducer_AllowAdminOverride_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'AllowAdminOverride'
|
||||||
|
type MockAuthorizationReducer_AllowAdminOverride_Call struct {
|
||||||
|
*mock.Call
|
||||||
|
}
|
||||||
|
|
||||||
|
// AllowAdminOverride is a helper method to define mock.On call
|
||||||
|
func (_e *MockAuthorizationReducer_Expecter) AllowAdminOverride() *MockAuthorizationReducer_AllowAdminOverride_Call {
|
||||||
|
return &MockAuthorizationReducer_AllowAdminOverride_Call{Call: _e.mock.On("AllowAdminOverride")}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_c *MockAuthorizationReducer_AllowAdminOverride_Call) Run(run func()) *MockAuthorizationReducer_AllowAdminOverride_Call {
|
||||||
|
_c.Call.Run(func(args mock.Arguments) {
|
||||||
|
run()
|
||||||
|
})
|
||||||
|
return _c
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_c *MockAuthorizationReducer_AllowAdminOverride_Call) Return(b bool) *MockAuthorizationReducer_AllowAdminOverride_Call {
|
||||||
|
_c.Call.Return(b)
|
||||||
|
return _c
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_c *MockAuthorizationReducer_AllowAdminOverride_Call) RunAndReturn(run func() bool) *MockAuthorizationReducer_AllowAdminOverride_Call {
|
||||||
|
_c.Call.Return(run)
|
||||||
|
return _c
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReduceRepoAccess provides a mock function for the type MockAuthorizationReducer
|
||||||
|
func (_mock *MockAuthorizationReducer) ReduceRepoAccess(ctx context.Context, repo1 *repo.Repository, accessMode perm.AccessMode) (perm.AccessMode, error) {
|
||||||
|
ret := _mock.Called(ctx, repo1, accessMode)
|
||||||
|
|
||||||
|
if len(ret) == 0 {
|
||||||
|
panic("no return value specified for ReduceRepoAccess")
|
||||||
|
}
|
||||||
|
|
||||||
|
var r0 perm.AccessMode
|
||||||
|
var r1 error
|
||||||
|
if returnFunc, ok := ret.Get(0).(func(context.Context, *repo.Repository, perm.AccessMode) (perm.AccessMode, error)); ok {
|
||||||
|
return returnFunc(ctx, repo1, accessMode)
|
||||||
|
}
|
||||||
|
if returnFunc, ok := ret.Get(0).(func(context.Context, *repo.Repository, perm.AccessMode) perm.AccessMode); ok {
|
||||||
|
r0 = returnFunc(ctx, repo1, accessMode)
|
||||||
|
} else {
|
||||||
|
r0 = ret.Get(0).(perm.AccessMode)
|
||||||
|
}
|
||||||
|
if returnFunc, ok := ret.Get(1).(func(context.Context, *repo.Repository, perm.AccessMode) error); ok {
|
||||||
|
r1 = returnFunc(ctx, repo1, accessMode)
|
||||||
|
} else {
|
||||||
|
r1 = ret.Error(1)
|
||||||
|
}
|
||||||
|
return r0, r1
|
||||||
|
}
|
||||||
|
|
||||||
|
// MockAuthorizationReducer_ReduceRepoAccess_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ReduceRepoAccess'
|
||||||
|
type MockAuthorizationReducer_ReduceRepoAccess_Call struct {
|
||||||
|
*mock.Call
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReduceRepoAccess is a helper method to define mock.On call
|
||||||
|
// - ctx context.Context
|
||||||
|
// - repo1 *repo.Repository
|
||||||
|
// - accessMode perm.AccessMode
|
||||||
|
func (_e *MockAuthorizationReducer_Expecter) ReduceRepoAccess(ctx, repo1, accessMode any) *MockAuthorizationReducer_ReduceRepoAccess_Call {
|
||||||
|
return &MockAuthorizationReducer_ReduceRepoAccess_Call{Call: _e.mock.On("ReduceRepoAccess", ctx, repo1, accessMode)}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_c *MockAuthorizationReducer_ReduceRepoAccess_Call) Run(run func(ctx context.Context, repo1 *repo.Repository, accessMode perm.AccessMode)) *MockAuthorizationReducer_ReduceRepoAccess_Call {
|
||||||
|
_c.Call.Run(func(args mock.Arguments) {
|
||||||
|
var arg0 context.Context
|
||||||
|
if args[0] != nil {
|
||||||
|
arg0 = args[0].(context.Context)
|
||||||
|
}
|
||||||
|
var arg1 *repo.Repository
|
||||||
|
if args[1] != nil {
|
||||||
|
arg1 = args[1].(*repo.Repository)
|
||||||
|
}
|
||||||
|
var arg2 perm.AccessMode
|
||||||
|
if args[2] != nil {
|
||||||
|
arg2 = args[2].(perm.AccessMode)
|
||||||
|
}
|
||||||
|
run(
|
||||||
|
arg0,
|
||||||
|
arg1,
|
||||||
|
arg2,
|
||||||
|
)
|
||||||
|
})
|
||||||
|
return _c
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_c *MockAuthorizationReducer_ReduceRepoAccess_Call) Return(accessMode1 perm.AccessMode, err error) *MockAuthorizationReducer_ReduceRepoAccess_Call {
|
||||||
|
_c.Call.Return(accessMode1, err)
|
||||||
|
return _c
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_c *MockAuthorizationReducer_ReduceRepoAccess_Call) RunAndReturn(run func(ctx context.Context, repo1 *repo.Repository, accessMode perm.AccessMode) (perm.AccessMode, error)) *MockAuthorizationReducer_ReduceRepoAccess_Call {
|
||||||
|
_c.Call.Return(run)
|
||||||
|
return _c
|
||||||
|
}
|
||||||
|
|
||||||
|
// RepoReadAccessFilter provides a mock function for the type MockAuthorizationReducer
|
||||||
|
func (_mock *MockAuthorizationReducer) RepoReadAccessFilter() builder.Cond {
|
||||||
|
ret := _mock.Called()
|
||||||
|
|
||||||
|
if len(ret) == 0 {
|
||||||
|
panic("no return value specified for RepoReadAccessFilter")
|
||||||
|
}
|
||||||
|
|
||||||
|
var r0 builder.Cond
|
||||||
|
if returnFunc, ok := ret.Get(0).(func() builder.Cond); ok {
|
||||||
|
r0 = returnFunc()
|
||||||
|
} else {
|
||||||
|
if ret.Get(0) != nil {
|
||||||
|
r0 = ret.Get(0).(builder.Cond)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return r0
|
||||||
|
}
|
||||||
|
|
||||||
|
// MockAuthorizationReducer_RepoReadAccessFilter_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'RepoReadAccessFilter'
|
||||||
|
type MockAuthorizationReducer_RepoReadAccessFilter_Call struct {
|
||||||
|
*mock.Call
|
||||||
|
}
|
||||||
|
|
||||||
|
// RepoReadAccessFilter is a helper method to define mock.On call
|
||||||
|
func (_e *MockAuthorizationReducer_Expecter) RepoReadAccessFilter() *MockAuthorizationReducer_RepoReadAccessFilter_Call {
|
||||||
|
return &MockAuthorizationReducer_RepoReadAccessFilter_Call{Call: _e.mock.On("RepoReadAccessFilter")}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_c *MockAuthorizationReducer_RepoReadAccessFilter_Call) Run(run func()) *MockAuthorizationReducer_RepoReadAccessFilter_Call {
|
||||||
|
_c.Call.Run(func(args mock.Arguments) {
|
||||||
|
run()
|
||||||
|
})
|
||||||
|
return _c
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_c *MockAuthorizationReducer_RepoReadAccessFilter_Call) Return(cond builder.Cond) *MockAuthorizationReducer_RepoReadAccessFilter_Call {
|
||||||
|
_c.Call.Return(cond)
|
||||||
|
return _c
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_c *MockAuthorizationReducer_RepoReadAccessFilter_Call) RunAndReturn(run func() builder.Cond) *MockAuthorizationReducer_RepoReadAccessFilter_Call {
|
||||||
|
_c.Call.Return(run)
|
||||||
|
return _c
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue