1
1
package cloud .stackit .sdk .core ;
2
2
3
3
import cloud .stackit .sdk .core .config .CoreConfiguration ;
4
+ import cloud .stackit .sdk .core .config .EnvironmentVariables ;
4
5
import cloud .stackit .sdk .core .exception .ApiException ;
5
6
import cloud .stackit .sdk .core .model .ServiceAccountKey ;
7
+ import cloud .stackit .sdk .core .utils .Utils ;
6
8
import com .auth0 .jwt .JWT ;
7
9
import com .auth0 .jwt .algorithms .Algorithm ;
8
10
import com .google .gson .Gson ;
@@ -36,7 +38,7 @@ public class KeyFlowAuthenticator {
36
38
private final String tokenUrl ;
37
39
private long tokenLeewayInSeconds = DEFAULT_TOKEN_LEEWAY ;
38
40
39
- private static class KeyFlowTokenResponse {
41
+ protected static class KeyFlowTokenResponse {
40
42
@ SerializedName ("access_token" )
41
43
private String accessToken ;
42
44
@@ -52,27 +54,42 @@ private static class KeyFlowTokenResponse {
52
54
@ SerializedName ("token_type" )
53
55
private String tokenType ;
54
56
55
- public boolean isExpired () {
57
+ public KeyFlowTokenResponse (
58
+ String accessToken ,
59
+ String refreshToken ,
60
+ long expiresIn ,
61
+ String scope ,
62
+ String tokenType ) {
63
+ this .accessToken = accessToken ;
64
+ this .refreshToken = refreshToken ;
65
+ this .expiresIn = expiresIn ;
66
+ this .scope = scope ;
67
+ this .tokenType = tokenType ;
68
+ }
69
+
70
+ protected boolean isExpired () {
56
71
return expiresIn < new Date ().toInstant ().getEpochSecond ();
57
72
}
58
73
59
- public String getAccessToken () {
74
+ protected String getAccessToken () {
60
75
return accessToken ;
61
76
}
62
77
}
63
78
79
+ public KeyFlowAuthenticator (CoreConfiguration cfg , ServiceAccountKey saKey ) {
80
+ this (cfg , saKey , null );
81
+ }
82
+
64
83
/**
65
84
* Creates the initial service account and refreshes expired access token.
66
85
*
67
86
* @param cfg Configuration to set a custom token endpoint and the token expiration leeway.
68
87
* @param saKey Service Account Key, which should be used for the authentication
69
- * @throws InvalidKeySpecException thrown when the private key in the service account can not be
70
- * parsed
71
- * @throws IOException thrown on unexpected responses from the key flow
72
- * @throws ApiException thrown on unexpected responses from the key flow
73
88
*/
74
- public KeyFlowAuthenticator (CoreConfiguration cfg , ServiceAccountKey saKey )
75
- throws InvalidKeySpecException , IOException , ApiException {
89
+ public KeyFlowAuthenticator (
90
+ CoreConfiguration cfg ,
91
+ ServiceAccountKey saKey ,
92
+ EnvironmentVariables environmentVariables ) {
76
93
this .saKey = saKey ;
77
94
this .gson = new Gson ();
78
95
this .httpClient =
@@ -81,26 +98,36 @@ public KeyFlowAuthenticator(CoreConfiguration cfg, ServiceAccountKey saKey)
81
98
.writeTimeout (10 , TimeUnit .SECONDS )
82
99
.readTimeout (30 , TimeUnit .SECONDS )
83
100
.build ();
84
- if (cfg .getTokenCustomUrl () != null && !cfg .getTokenCustomUrl ().trim ().isEmpty ()) {
101
+
102
+ if (environmentVariables == null ) {
103
+ environmentVariables = new EnvironmentVariables ();
104
+ }
105
+
106
+ if (Utils .isStringSet (cfg .getTokenCustomUrl ())) {
85
107
this .tokenUrl = cfg .getTokenCustomUrl ();
108
+ } else if (Utils .isStringSet (environmentVariables .getStackitTokenBaseurl ())) {
109
+ this .tokenUrl = environmentVariables .getStackitTokenBaseurl ();
86
110
} else {
87
111
this .tokenUrl = DEFAULT_TOKEN_ENDPOINT ;
88
112
}
89
113
if (cfg .getTokenExpirationLeeway () != null && cfg .getTokenExpirationLeeway () > 0 ) {
90
114
this .tokenLeewayInSeconds = cfg .getTokenExpirationLeeway ();
91
115
}
92
-
93
- createAccessToken ();
94
116
}
95
117
96
118
/**
97
119
* Returns access token. If the token is expired it creates a new token.
98
120
*
121
+ * @throws InvalidKeySpecException thrown when the private key in the service account can not be
122
+ * parsed
99
123
* @throws IOException request for new access token failed
100
124
* @throws ApiException response for new access token with bad status code
101
125
*/
102
- public synchronized String getAccessToken () throws IOException , ApiException {
103
- if (token == null || token .isExpired ()) {
126
+ public synchronized String getAccessToken ()
127
+ throws IOException , ApiException , InvalidKeySpecException {
128
+ if (token == null ) {
129
+ createAccessToken ();
130
+ } else if (token .isExpired ()) {
104
131
createAccessTokenWithRefreshToken ();
105
132
}
106
133
return token .getAccessToken ();
@@ -114,7 +141,7 @@ public synchronized String getAccessToken() throws IOException, ApiException {
114
141
* @throws ApiException response for new access token with bad status code
115
142
* @throws JsonSyntaxException parsing of the created access token failed
116
143
*/
117
- private void createAccessToken ()
144
+ protected void createAccessToken ()
118
145
throws InvalidKeySpecException , IOException , JsonSyntaxException , ApiException {
119
146
String grant = "urn:ietf:params:oauth:grant-type:jwt-bearer" ;
120
147
String assertion ;
@@ -137,7 +164,7 @@ private void createAccessToken()
137
164
* @throws ApiException response for new access token with bad status code
138
165
* @throws JsonSyntaxException can not parse new access token
139
166
*/
140
- private synchronized void createAccessTokenWithRefreshToken ()
167
+ protected synchronized void createAccessTokenWithRefreshToken ()
141
168
throws IOException , JsonSyntaxException , ApiException {
142
169
String refreshToken = token .refreshToken ;
143
170
Response response = requestToken (REFRESH_TOKEN , refreshToken ).execute ();
@@ -146,7 +173,7 @@ private synchronized void createAccessTokenWithRefreshToken()
146
173
}
147
174
148
175
private synchronized void parseTokenResponse (Response response )
149
- throws ApiException , JsonSyntaxException {
176
+ throws ApiException , JsonSyntaxException , IOException {
150
177
if (response .code () != HttpURLConnection .HTTP_OK ) {
151
178
String body = null ;
152
179
if (response .body () != null ) {
@@ -156,20 +183,15 @@ private synchronized void parseTokenResponse(Response response)
156
183
throw new ApiException (
157
184
response .message (), response .code (), response .headers ().toMultimap (), body );
158
185
}
159
- if (response .body () == null ) {
186
+ if (response .body () == null || response . body (). contentLength () == 0 ) {
160
187
throw new JsonSyntaxException ("body from token creation is null" );
161
188
}
162
189
163
- token =
190
+ KeyFlowTokenResponse keyFlowTokenResponse =
164
191
gson .fromJson (
165
192
new InputStreamReader (response .body ().byteStream (), StandardCharsets .UTF_8 ),
166
193
KeyFlowTokenResponse .class );
167
- token .expiresIn =
168
- JWT .decode (token .accessToken )
169
- .getExpiresAt ()
170
- .toInstant ()
171
- .minusSeconds (tokenLeewayInSeconds )
172
- .getEpochSecond ();
194
+ setToken (keyFlowTokenResponse );
173
195
response .body ().close ();
174
196
}
175
197
@@ -189,6 +211,16 @@ private Call requestToken(String grant, String assertionValue) throws IOExceptio
189
211
return httpClient .newCall (request );
190
212
}
191
213
214
+ protected void setToken (KeyFlowTokenResponse response ) {
215
+ token = response ;
216
+ token .expiresIn =
217
+ JWT .decode (response .accessToken )
218
+ .getExpiresAt ()
219
+ .toInstant ()
220
+ .minusSeconds (tokenLeewayInSeconds )
221
+ .getEpochSecond ();
222
+ }
223
+
192
224
private String generateSelfSignedJWT ()
193
225
throws InvalidKeySpecException , NoSuchAlgorithmException {
194
226
RSAPrivateKey prvKey ;
0 commit comments