11package cloud .stackit .sdk .core ;
22
33import cloud .stackit .sdk .core .config .CoreConfiguration ;
4+ import cloud .stackit .sdk .core .config .EnvironmentVariables ;
45import cloud .stackit .sdk .core .exception .ApiException ;
56import cloud .stackit .sdk .core .model .ServiceAccountKey ;
7+ import cloud .stackit .sdk .core .utils .Utils ;
68import com .auth0 .jwt .JWT ;
79import com .auth0 .jwt .algorithms .Algorithm ;
810import com .google .gson .Gson ;
@@ -36,7 +38,7 @@ public class KeyFlowAuthenticator {
3638 private final String tokenUrl ;
3739 private long tokenLeewayInSeconds = DEFAULT_TOKEN_LEEWAY ;
3840
39- private static class KeyFlowTokenResponse {
41+ protected static class KeyFlowTokenResponse {
4042 @ SerializedName ("access_token" )
4143 private String accessToken ;
4244
@@ -52,27 +54,42 @@ private static class KeyFlowTokenResponse {
5254 @ SerializedName ("token_type" )
5355 private String tokenType ;
5456
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 () {
5671 return expiresIn < new Date ().toInstant ().getEpochSecond ();
5772 }
5873
59- public String getAccessToken () {
74+ protected String getAccessToken () {
6075 return accessToken ;
6176 }
6277 }
6378
79+ public KeyFlowAuthenticator (CoreConfiguration cfg , ServiceAccountKey saKey ) {
80+ this (cfg , saKey , null );
81+ }
82+
6483 /**
6584 * Creates the initial service account and refreshes expired access token.
6685 *
6786 * @param cfg Configuration to set a custom token endpoint and the token expiration leeway.
6887 * @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
7388 */
74- public KeyFlowAuthenticator (CoreConfiguration cfg , ServiceAccountKey saKey )
75- throws InvalidKeySpecException , IOException , ApiException {
89+ public KeyFlowAuthenticator (
90+ CoreConfiguration cfg ,
91+ ServiceAccountKey saKey ,
92+ EnvironmentVariables environmentVariables ) {
7693 this .saKey = saKey ;
7794 this .gson = new Gson ();
7895 this .httpClient =
@@ -81,26 +98,36 @@ public KeyFlowAuthenticator(CoreConfiguration cfg, ServiceAccountKey saKey)
8198 .writeTimeout (10 , TimeUnit .SECONDS )
8299 .readTimeout (30 , TimeUnit .SECONDS )
83100 .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 ())) {
85107 this .tokenUrl = cfg .getTokenCustomUrl ();
108+ } else if (Utils .isStringSet (environmentVariables .getStackitTokenBaseurl ())) {
109+ this .tokenUrl = environmentVariables .getStackitTokenBaseurl ();
86110 } else {
87111 this .tokenUrl = DEFAULT_TOKEN_ENDPOINT ;
88112 }
89113 if (cfg .getTokenExpirationLeeway () != null && cfg .getTokenExpirationLeeway () > 0 ) {
90114 this .tokenLeewayInSeconds = cfg .getTokenExpirationLeeway ();
91115 }
92-
93- createAccessToken ();
94116 }
95117
96118 /**
97119 * Returns access token. If the token is expired it creates a new token.
98120 *
121+ * @throws InvalidKeySpecException thrown when the private key in the service account can not be
122+ * parsed
99123 * @throws IOException request for new access token failed
100124 * @throws ApiException response for new access token with bad status code
101125 */
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 ()) {
104131 createAccessTokenWithRefreshToken ();
105132 }
106133 return token .getAccessToken ();
@@ -114,7 +141,7 @@ public synchronized String getAccessToken() throws IOException, ApiException {
114141 * @throws ApiException response for new access token with bad status code
115142 * @throws JsonSyntaxException parsing of the created access token failed
116143 */
117- private void createAccessToken ()
144+ protected void createAccessToken ()
118145 throws InvalidKeySpecException , IOException , JsonSyntaxException , ApiException {
119146 String grant = "urn:ietf:params:oauth:grant-type:jwt-bearer" ;
120147 String assertion ;
@@ -137,7 +164,7 @@ private void createAccessToken()
137164 * @throws ApiException response for new access token with bad status code
138165 * @throws JsonSyntaxException can not parse new access token
139166 */
140- private synchronized void createAccessTokenWithRefreshToken ()
167+ protected synchronized void createAccessTokenWithRefreshToken ()
141168 throws IOException , JsonSyntaxException , ApiException {
142169 String refreshToken = token .refreshToken ;
143170 Response response = requestToken (REFRESH_TOKEN , refreshToken ).execute ();
@@ -146,7 +173,7 @@ private synchronized void createAccessTokenWithRefreshToken()
146173 }
147174
148175 private synchronized void parseTokenResponse (Response response )
149- throws ApiException , JsonSyntaxException {
176+ throws ApiException , JsonSyntaxException , IOException {
150177 if (response .code () != HttpURLConnection .HTTP_OK ) {
151178 String body = null ;
152179 if (response .body () != null ) {
@@ -156,20 +183,15 @@ private synchronized void parseTokenResponse(Response response)
156183 throw new ApiException (
157184 response .message (), response .code (), response .headers ().toMultimap (), body );
158185 }
159- if (response .body () == null ) {
186+ if (response .body () == null || response . body (). contentLength () == 0 ) {
160187 throw new JsonSyntaxException ("body from token creation is null" );
161188 }
162189
163- token =
190+ KeyFlowTokenResponse keyFlowTokenResponse =
164191 gson .fromJson (
165192 new InputStreamReader (response .body ().byteStream (), StandardCharsets .UTF_8 ),
166193 KeyFlowTokenResponse .class );
167- token .expiresIn =
168- JWT .decode (token .accessToken )
169- .getExpiresAt ()
170- .toInstant ()
171- .minusSeconds (tokenLeewayInSeconds )
172- .getEpochSecond ();
194+ setToken (keyFlowTokenResponse );
173195 response .body ().close ();
174196 }
175197
@@ -189,6 +211,16 @@ private Call requestToken(String grant, String assertionValue) throws IOExceptio
189211 return httpClient .newCall (request );
190212 }
191213
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+
192224 private String generateSelfSignedJWT ()
193225 throws InvalidKeySpecException , NoSuchAlgorithmException {
194226 RSAPrivateKey prvKey ;
0 commit comments