Skip to content

Commit fcfe402

Browse files
committed
Change: Refactor KeycloakJWTClient
1 parent 4c7c66e commit fcfe402

File tree

5 files changed

+154
-7
lines changed

5 files changed

+154
-7
lines changed

auth/keycloakJWTClient.go renamed to client/keycloakJWTClient.go

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
package auth
1+
package client
22

33
import (
44
"context"
@@ -26,16 +26,17 @@ func NewKeycloakJWTCacheInMemory(keycloakJWTClient ITokenReceiver) *KeycloakJWTC
2626
}
2727
}
2828

29-
func (k *KeycloakJWTCacheInMemory) isTokenValid() bool {
30-
if k.cachedToken == nil {
29+
func isTokenValid(token *gocloak.JWT) bool {
30+
if token == nil {
3131
return false
3232
}
3333

3434
parser := jwt.NewParser()
3535
claims := &jwt.MapClaims{}
3636

37-
_, _, err := parser.ParseUnverified(k.cachedToken.AccessToken, claims)
37+
_, _, err := parser.ParseUnverified(token.AccessToken, claims)
3838
if err != nil {
39+
log.Error().Msgf("couldn't parse JWT access token: %v", err)
3940
return false
4041
}
4142

@@ -49,7 +50,7 @@ func (k *KeycloakJWTCacheInMemory) isTokenValid() bool {
4950
}
5051

5152
func (k *KeycloakJWTCacheInMemory) getToken() (*gocloak.JWT, error) {
52-
if k.cachedToken == nil || !k.isTokenValid() {
53+
if k.cachedToken == nil || !isTokenValid(k.cachedToken) {
5354
token, err := k.keycloakJWTClient.getToken()
5455
if err != nil {
5556
return nil, err
@@ -74,7 +75,6 @@ func (k *KeycloakJWTCacheInMemory) GetAccessToken() (string, error) {
7475

7576
type KeycloakJWTClient struct {
7677
client *gocloak.GoCloak
77-
basePath string
7878
clientName string
7979
clientSecret string
8080
realm string
@@ -85,7 +85,6 @@ var _ ITokenReceiver = &KeycloakJWTClient{}
8585
func NewKeycloakJWTClient(basePath, clientName, clientSecret, realm string) *KeycloakJWTClient {
8686
return &KeycloakJWTClient{
8787
client: gocloak.NewClient(basePath),
88-
basePath: basePath,
8988
clientName: clientName,
9089
clientSecret: clientSecret,
9190
realm: realm,

client/keycloakJWTClient_test.go

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
package client
2+
3+
import (
4+
"testing"
5+
6+
"github.com/Nerzal/gocloak/v12"
7+
"github.com/stretchr/testify/assert"
8+
"github.com/stretchr/testify/mock"
9+
)
10+
11+
type MockTokenReceiver struct {
12+
mock.Mock
13+
}
14+
15+
func (m *MockTokenReceiver) GetAccessToken() (string, error) {
16+
args := m.Called()
17+
return args.String(0), args.Error(1)
18+
}
19+
20+
func (m *MockTokenReceiver) getToken() (*gocloak.JWT, error) {
21+
args := m.Called()
22+
return args.Get(0).(*gocloak.JWT), args.Error(1)
23+
}
24+
25+
func TestKeycloakJWTCacheInMemory_GetToken(t *testing.T) {
26+
27+
tests := []struct {
28+
name string
29+
cachedToken *gocloak.JWT
30+
mockToken *gocloak.JWT
31+
mockError error
32+
expectedToken *gocloak.JWT
33+
expectedError error
34+
shouldFetchToken bool
35+
}{
36+
{
37+
name: "No cached token",
38+
cachedToken: nil,
39+
mockToken: &gocloak.JWT{
40+
AccessToken: "test_token",
41+
},
42+
expectedToken: &gocloak.JWT{
43+
AccessToken: "test_token",
44+
},
45+
expectedError: nil,
46+
shouldFetchToken: true,
47+
},
48+
{
49+
name: "Expired cached token",
50+
cachedToken: &gocloak.JWT{
51+
AccessToken: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyLCJleHAiOjE1MTYyMzEwMjJ9.hsfQPY3ZVrVIV-bzI54NRoTDG6wWzORVp68lxGa3D08", // todo add actual expired token -> create one on jwt.io
52+
},
53+
mockToken: &gocloak.JWT{
54+
AccessToken: "test_token",
55+
},
56+
expectedToken: &gocloak.JWT{
57+
AccessToken: "test_token",
58+
},
59+
expectedError: nil,
60+
shouldFetchToken: true,
61+
},
62+
{
63+
name: "Valid cached token",
64+
cachedToken: &gocloak.JWT{
65+
AccessToken: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c",
66+
},
67+
mockToken: &gocloak.JWT{
68+
AccessToken: "test_token",
69+
},
70+
expectedToken: &gocloak.JWT{
71+
AccessToken: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c",
72+
},
73+
expectedError: nil,
74+
shouldFetchToken: false,
75+
},
76+
}
77+
78+
for _, tt := range tests {
79+
t.Run(tt.name, func(t *testing.T) {
80+
81+
mockTokenReceiver := new(MockTokenReceiver)
82+
cache := NewKeycloakJWTCacheInMemory(mockTokenReceiver)
83+
84+
cache.cachedToken = tt.cachedToken
85+
86+
mockTokenReceiver.On("getToken").Return(tt.mockToken, tt.mockError)
87+
88+
token, err := cache.getToken()
89+
90+
if tt.expectedError != nil {
91+
assert.ErrorIs(t, err, tt.expectedError)
92+
} else {
93+
assert.NoError(t, err)
94+
}
95+
96+
assert.Equal(t, tt.expectedToken, token)
97+
if tt.shouldFetchToken {
98+
mockTokenReceiver.AssertCalled(t, "getToken")
99+
}
100+
})
101+
}
102+
}
103+
104+
func TestKeycloakJWTCacheInMemory_GetAccessToken(t *testing.T) {
105+
mockTokenReceiver := new(MockTokenReceiver)
106+
mockToken := &gocloak.JWT{
107+
AccessToken: "test_token",
108+
ExpiresIn: 3600,
109+
}
110+
111+
mockTokenReceiver.On("getToken").Return(mockToken, nil)
112+
113+
cache := NewKeycloakJWTCacheInMemory(mockTokenReceiver)
114+
115+
accessToken, err := cache.GetAccessToken()
116+
117+
assert.NoError(t, err)
118+
assert.Equal(t, "test_token", accessToken)
119+
mockTokenReceiver.AssertCalled(t, "getToken")
120+
}

client/keycloakRepository.go

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
package client
2+
3+
import (
4+
"context"
5+
"github.com/Nerzal/gocloak/v12"
6+
)
7+
8+
type IKeycloakRepository interface {
9+
GetClientAccessToken(clientName, clientSecret, realm string) (*gocloak.JWT, error)
10+
}
11+
12+
type KeycloakRepository struct {
13+
client *gocloak.GoCloak
14+
}
15+
16+
var _ IKeycloakRepository = &KeycloakRepository{}
17+
18+
func NewKeycloakRepository(basePath string) *KeycloakRepository {
19+
return &KeycloakRepository{
20+
client: gocloak.NewClient(basePath),
21+
}
22+
}
23+
24+
func (r *KeycloakRepository) GetClientAccessToken(clientName, clientSecret, realm string) (*gocloak.JWT, error) {
25+
return r.client.LoginClient(context.Background(), clientName, clientSecret, realm)
26+
}

go.mod

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ require (
3838
github.com/pkg/errors v0.9.1 // indirect
3939
github.com/pmezard/go-difflib v1.0.0 // indirect
4040
github.com/segmentio/ksuid v1.0.4 // indirect
41+
github.com/stretchr/objx v0.5.2 // indirect
4142
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
4243
github.com/ugorji/go/codec v1.2.12 // indirect
4344
golang.org/x/arch v0.8.0 // indirect

go.sum

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@ github.com/segmentio/ksuid v1.0.4/go.mod h1:/XUiZBD3kVx5SmUOl55voK5yeAbBNNIed+2O
7777
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
7878
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
7979
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
80+
github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
8081
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
8182
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
8283
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=

0 commit comments

Comments
 (0)