Skip to content

Commit 986840e

Browse files
committed
Added test coverage for custom token granter, enhancer, auth registry and tenancy loader.
1 parent d52156a commit 986840e

File tree

9 files changed

+358
-2
lines changed

9 files changed

+358
-2
lines changed

pkg/integrate/httpclient/client_test.go

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ import (
3434
"go.uber.org/fx"
3535
"net/http"
3636
"net/url"
37+
"path"
3738
"testing"
3839
"time"
3940
)
@@ -112,6 +113,7 @@ func TestWithMockedServer(t *testing.T) {
112113
test.GomegaSubTest(SubTestWithRetry(&di), "TestWithRetry"),
113114
test.GomegaSubTest(SubTestWithTimeout(&di), "TestWithTimeout"),
114115
test.GomegaSubTest(SubTestWithURLEncoded(&di), "TestWithURLEncoded"),
116+
test.GomegaSubTest(SubTestWithAbsoluteUrl(&di), "TestWithAbsoluteUrl"),
115117
)
116118
}
117119

@@ -390,6 +392,44 @@ func SubTestWithURLEncoded(di *TestDI) test.GomegaSubTestFunc {
390392
}
391393
}
392394

395+
func SubTestWithAbsoluteUrl(di *TestDI) test.GomegaSubTestFunc {
396+
return func(ctx context.Context, t *testing.T, g *gomega.WithT) {
397+
client, e := di.HttpClient.WithNoTargetResolver()
398+
g.Expect(e).To(Succeed(), "client without target resolver should be available")
399+
400+
random := utils.RandomString(20)
401+
now := time.Now().Format(time.RFC3339)
402+
reqBody := makeEchoRequestBody()
403+
opts := append([]httpclient.RequestOptions{
404+
httpclient.WithHeader("X-Data", random),
405+
httpclient.WithParam("time", now),
406+
httpclient.WithParam("data", random),
407+
httpclient.WithBody(reqBody),
408+
})
409+
410+
uri, e := url.Parse(fmt.Sprintf(`http://localhost:%d%s`, webtest.CurrentPort(ctx), webtest.CurrentContextPath(ctx)))
411+
g.Expect(e).ToNot(HaveOccurred())
412+
413+
uri.Path = path.Join(uri.Path, TestPath)
414+
req := httpclient.NewRequest(uri.String(), http.MethodPost, opts...)
415+
416+
resp, e := client.Execute(ctx, req, httpclient.JsonBody(&EchoResponse{}))
417+
g.Expect(e).To(Succeed(), "execute request shouldn't fail")
418+
419+
expected := EchoResponse{
420+
Headers: map[string]string{
421+
"X-Data": random,
422+
},
423+
Form: map[string]string{
424+
"time": now,
425+
"data": random,
426+
},
427+
ReqBody: reqBody,
428+
}
429+
assertResponse(t, g, resp, http.StatusOK, &expected)
430+
}
431+
}
432+
393433
/*************************
394434
Request/Response
395435
*************************/

pkg/security/config/integration_test.go

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ import (
3737
"github.com/cisco-open/go-lanai/pkg/security/idp/passwdidp"
3838
"github.com/cisco-open/go-lanai/pkg/security/logout"
3939
"github.com/cisco-open/go-lanai/pkg/security/oauth2"
40+
"github.com/cisco-open/go-lanai/pkg/security/oauth2/auth"
4041
"github.com/cisco-open/go-lanai/pkg/security/oauth2/auth/authorize"
4142
"github.com/cisco-open/go-lanai/pkg/security/oauth2/auth/clientauth"
4243
"github.com/cisco-open/go-lanai/pkg/security/oauth2/auth/token"
@@ -59,6 +60,7 @@ import (
5960
. "github.com/cisco-open/go-lanai/test/utils/gomega"
6061
"github.com/cisco-open/go-lanai/test/webtest"
6162
"github.com/crewjam/saml"
63+
"github.com/golang-jwt/jwt/v4"
6264
"github.com/google/uuid"
6365
"github.com/onsi/gomega"
6466
. "github.com/onsi/gomega"
@@ -157,6 +159,7 @@ type intDI struct {
157159
Mocking sectest.MockingProperties
158160
TokenReader oauth2.TokenStoreReader
159161
SessionStore session.Store
162+
AuthReg auth.AuthorizationRegistry `optional:"true"`
160163
}
161164

162165
func TestWithMockedServer(t *testing.T) {
@@ -189,6 +192,7 @@ func TestWithMockedServer(t *testing.T) {
189192
testdata.NewAuthServerConfigurer, //This configurer will set up mocked client store, mocked tenant store etc.
190193
testdata.NewResServerConfigurer,
191194
testdata.NewMockedApprovalStore,
195+
auth.NewLegacyTokenEnhancer,
192196
),
193197
),
194198
test.GomegaSubTest(SubTestOAuth2AuthorizeWithPasswdIDP(di), "TestOAuth2AuthorizeWithPasswdIDP"),
@@ -234,6 +238,7 @@ func TestWithMockedServerWithoutFinalizer(t *testing.T) {
234238
sectest.BindMockingProperties,
235239
testdata.NewAuthServerConfigurer,
236240
testdata.NewResServerConfigurer,
241+
auth.NewLegacyTokenEnhancer,
237242
),
238243
),
239244
// a user has access to two tenants, switch from one to the other
@@ -242,6 +247,39 @@ func TestWithMockedServerWithoutFinalizer(t *testing.T) {
242247
)
243248
}
244249

250+
func TestWithMockedServerWithCustomTokenGranter(t *testing.T) {
251+
di := &intDI{}
252+
test.RunTest(context.Background(), t,
253+
apptest.Bootstrap(),
254+
apptest.WithTimeout(2*time.Minute),
255+
webtest.WithMockedServer(),
256+
sectest.WithMockedMiddleware(sectest.MWEnableSession()),
257+
apptest.WithModules(
258+
authserver.Module, resserver.Module,
259+
passwdidp.Module, extsamlidp.Module, authorize.Module, samlidp.Module,
260+
passwd.Module, formlogin.Module, logout.Module,
261+
samlctx.Module, samlsp.Module,
262+
basicauth.Module, clientauth.Module,
263+
token.Module, access.Module, errorhandling.Module,
264+
request_cache.Module, csrf.Module, session.Module,
265+
redis.Module,
266+
),
267+
apptest.WithDI(di),
268+
apptest.WithFxOptions(
269+
fx.Provide(
270+
IntegrationTestMocksProvider(),
271+
sectest.BindMockingProperties,
272+
testdata.NewAuthServerConfigurer,
273+
testdata.NewResServerConfigurer,
274+
testdata.NewCustomTokenEnhancer,
275+
testdata.NewCustomAuthRegistry,
276+
testdata.NewCustomTokenGranter,
277+
),
278+
),
279+
test.GomegaSubTest(SubTestCustomTokenGranter(di), "TestCustomTokenGranter"),
280+
)
281+
}
282+
245283
/*************************
246284
Sub Tests
247285
*************************/
@@ -914,6 +952,31 @@ func SubTestOauth2SwitchTenant(
914952
}
915953
}
916954

955+
func SubTestCustomTokenGranter(
956+
di *intDI,
957+
) test.GomegaSubTestFunc {
958+
return func(ctx context.Context, t *testing.T, g *gomega.WithT) {
959+
req := webtest.NewRequest(ctx, http.MethodPost, "/v2/token", customGrantReqBody(), withClientAuth("custom-grant-client", TestClientSecret), tokenReqOptions())
960+
resp := webtest.MustExec(ctx, req)
961+
g.Expect(resp).ToNot(BeNil(), "response should not be nil")
962+
g.Expect(resp.Response.StatusCode).To(Equal(http.StatusOK), "response should have correct status code")
963+
964+
body, e := io.ReadAll(resp.Response.Body)
965+
g.Expect(e).To(Succeed(), `token response body should be readable`)
966+
g.Expect(body).To(HaveJsonPath("$.access_token"), "token response should have access_token")
967+
968+
accessToken := oauth2.NewDefaultAccessToken("")
969+
e = json.Unmarshal(body, accessToken)
970+
g.Expect(e).ToNot(HaveOccurred())
971+
972+
tk, _, e := jwt.NewParser().ParseUnverified(accessToken.Value(), jwt.MapClaims{})
973+
g.Expect(e).ToNot(HaveOccurred())
974+
g.Expect(tk.Claims.(jwt.MapClaims)["MyClaim"]).To(Equal("my_claim_value"))
975+
976+
g.Expect(di.AuthReg.(*testdata.CustomAuthRegistry).RegistrationCount).To(Equal(1))
977+
}
978+
}
979+
917980
/*************************
918981
Helpers
919982
*************************/
@@ -1103,6 +1166,12 @@ func passwordGrantReqBody(tenantId string, username string, password string) io.
11031166
return strings.NewReader(values.Encode())
11041167
}
11051168

1169+
func customGrantReqBody() io.Reader {
1170+
values := url.Values{}
1171+
values.Set(oauth2.ParameterGrantType, "custom_grant")
1172+
return strings.NewReader(values.Encode())
1173+
}
1174+
11061175
func tokenReqOptions() webtest.RequestOptions {
11071176
return func(req *http.Request) {
11081177
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")

pkg/security/config/testdata/application-test.yml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,11 @@ mocking:
9393
redirect-uris: ["localhost:*/**"]
9494
tenants: ["id-tenant-3"]
9595
scopes: "scope_a,scope_b"
96+
custom-grant-client:
97+
id: "custom-grant-client"
98+
secret: "test-secret"
99+
access-token-validity: 3600s
100+
grant-types: "custom_grant"
96101
accounts:
97102
system:
98103
username: "system"

pkg/security/config/testdata/authserver_configurer.go

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,9 @@ type authDI struct {
4949
Properties authserver.AuthServerProperties
5050
PasswdIDPProperties passwdidp.PwdAuthProperties
5151
SamlIDPProperties extsamlidp.SamlAuthProperties
52+
CustomTokenGranter auth.TokenGranter `optional:"true"`
53+
CustomTokenEnhancer auth.TokenEnhancer `optional:"true"`
54+
CustomAuthRegistry auth.AuthorizationRegistry `optional:"true"`
5255
}
5356

5457
func NewAuthServerConfigurer(di authDI) authserver.AuthorizationServerConfigurer {
@@ -71,7 +74,15 @@ func NewAuthServerConfigurer(di authDI) authserver.AuthorizationServerConfigurer
7174
config.ProviderStore = sectest.MockedProviderStore{}
7275
config.UserPasswordEncoder = di.PasswordEncoder
7376
config.SessionSettingService = StaticSessionSettingService(1)
74-
config.CustomTokenEnhancer = []auth.TokenEnhancer{auth.NewLegacyTokenEnhancer()}
77+
if di.CustomTokenEnhancer != nil {
78+
config.CustomTokenEnhancer = []auth.TokenEnhancer{di.CustomTokenEnhancer}
79+
}
80+
if di.CustomTokenGranter != nil {
81+
config.CustomTokenGranter = []auth.TokenGranter{di.CustomTokenGranter}
82+
}
83+
if di.CustomAuthRegistry != nil {
84+
config.CustomAuthRegistry = di.CustomAuthRegistry
85+
}
7586
}
7687
}
7788

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
package testdata
2+
3+
import (
4+
"context"
5+
"github.com/cisco-open/go-lanai/pkg/security/oauth2"
6+
"github.com/cisco-open/go-lanai/pkg/security/oauth2/auth"
7+
)
8+
9+
type CustomAuthRegistry struct {
10+
RegistrationCount int
11+
}
12+
13+
func NewCustomAuthRegistry() auth.AuthorizationRegistry {
14+
return &CustomAuthRegistry{}
15+
}
16+
17+
func (c *CustomAuthRegistry) RegisterRefreshToken(ctx context.Context, token oauth2.RefreshToken, oauth oauth2.Authentication) error {
18+
panic("implement me")
19+
}
20+
21+
func (c *CustomAuthRegistry) RegisterAccessToken(ctx context.Context, token oauth2.AccessToken, oauth oauth2.Authentication) error {
22+
c.RegistrationCount++
23+
return nil
24+
}
25+
26+
func (c *CustomAuthRegistry) ReadStoredAuthorization(ctx context.Context, token oauth2.RefreshToken) (oauth2.Authentication, error) {
27+
panic("implement me")
28+
}
29+
30+
func (c *CustomAuthRegistry) FindSessionId(ctx context.Context, token oauth2.Token) (string, error) {
31+
panic("implement me")
32+
}
33+
34+
func (c *CustomAuthRegistry) RevokeRefreshToken(ctx context.Context, token oauth2.RefreshToken) error {
35+
panic("implement me")
36+
}
37+
38+
func (c *CustomAuthRegistry) RevokeAccessToken(ctx context.Context, token oauth2.AccessToken) error {
39+
panic("implement me")
40+
}
41+
42+
func (c *CustomAuthRegistry) RevokeAllAccessTokens(ctx context.Context, token oauth2.RefreshToken) error {
43+
panic("implement me")
44+
}
45+
46+
func (c *CustomAuthRegistry) RevokeUserAccess(ctx context.Context, username string, revokeRefreshToken bool) error {
47+
panic("implement me")
48+
}
49+
50+
func (c *CustomAuthRegistry) RevokeClientAccess(ctx context.Context, clientId string, revokeRefreshToken bool) error {
51+
panic("implement me")
52+
}
53+
54+
func (c CustomAuthRegistry) RevokeSessionAccess(ctx context.Context, sessionId string, revokeRefreshToken bool) error {
55+
panic("implement me")
56+
}
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
package testdata
2+
3+
import (
4+
"context"
5+
"github.com/cisco-open/go-lanai/pkg/security/oauth2"
6+
"github.com/cisco-open/go-lanai/pkg/security/oauth2/auth"
7+
)
8+
9+
type CustomClaims struct {
10+
oauth2.FieldClaimsMapper
11+
oauth2.Claims
12+
MyClaim string `claim:"MyClaim"`
13+
}
14+
15+
func (c *CustomClaims) MarshalJSON() ([]byte, error) {
16+
return c.FieldClaimsMapper.DoMarshalJSON(c)
17+
}
18+
19+
func (c *CustomClaims) UnmarshalJSON(bytes []byte) error {
20+
return c.FieldClaimsMapper.DoUnmarshalJSON(c, bytes)
21+
}
22+
23+
func (c *CustomClaims) Get(claim string) interface{} {
24+
return c.FieldClaimsMapper.Get(c, claim)
25+
}
26+
27+
func (c *CustomClaims) Has(claim string) bool {
28+
return c.FieldClaimsMapper.Has(c, claim)
29+
}
30+
31+
func (c *CustomClaims) Set(claim string, value interface{}) {
32+
c.FieldClaimsMapper.Set(c, claim, value)
33+
}
34+
35+
func (c *CustomClaims) Values() map[string]interface{} {
36+
return c.FieldClaimsMapper.Values(c)
37+
}
38+
39+
type CustomTokenEnhancer struct{}
40+
41+
func NewCustomTokenEnhancer() auth.TokenEnhancer {
42+
return &CustomTokenEnhancer{}
43+
}
44+
45+
func (c *CustomTokenEnhancer) Enhance(ctx context.Context, token oauth2.AccessToken, oauth oauth2.Authentication) (oauth2.AccessToken, error) {
46+
t, ok := token.(*oauth2.DefaultAccessToken)
47+
if !ok {
48+
return nil, oauth2.NewInternalError("unsupported token implementation %T", t)
49+
}
50+
51+
if t.Claims() == nil {
52+
return nil, oauth2.NewInternalError("need to be placed after BasicClaimsEnhancer")
53+
}
54+
55+
customClaims := &CustomClaims{
56+
Claims: t.Claims(),
57+
}
58+
customClaims.MyClaim = "my_claim_value"
59+
60+
t.SetClaims(customClaims)
61+
return t, nil
62+
}

0 commit comments

Comments
 (0)