Skip to content

Commit a44720b

Browse files
authored
refactor: prepare for ContainerAuthenticator impl (#139)
This commit introduces the IamRequestBasedAuthenticator class which will serve as a common base class for any authenticator impl that interacts with the IAM token service to obtain an access token. Common code was moved from the IamAuthenticator class to the new IamRequestBasedAuthenticator base class.
1 parent f35b552 commit a44720b

File tree

2 files changed

+162
-121
lines changed

2 files changed

+162
-121
lines changed

src/main/java/com/ibm/cloud/sdk/core/security/IamAuthenticator.java

Lines changed: 35 additions & 121 deletions
Original file line numberDiff line numberDiff line change
@@ -31,32 +31,19 @@
3131
* an access token from the IAM token service via the "POST /identity/token" operation.
3232
* When the access token expires, a new access token will be fetched.
3333
*/
34-
public class IamAuthenticator extends TokenRequestBasedAuthenticator<IamToken, IamToken> implements Authenticator {
34+
public class IamAuthenticator extends IamRequestBasedAuthenticator implements Authenticator {
3535
private static final String DEFAULT_IAM_URL = "https://iam.cloud.ibm.com";
3636
private static final String OPERATION_PATH = "/identity/token";
37-
private static final String GRANT_TYPE = "grant_type";
38-
private static final String REQUEST_GRANT_TYPE = "urn:ibm:params:oauth:grant-type:apikey";
39-
private static final String API_KEY = "apikey";
40-
private static final String RESPONSE_TYPE = "response_type";
41-
private static final String CLOUD_IAM = "cloud_iam";
42-
private static final String SCOPE = "scope";
4337

4438
// Properties specific to an IAM authenticator.
45-
private String url;
4639
private String apikey;
47-
private String scope;
48-
private String clientId;
49-
private String clientSecret;
50-
51-
// This is the value of the Authorization header we'll use when interacting with the token server.
52-
private String cachedAuthorizationHeader = null;
5340

5441
/**
5542
* This Builder class is used to construct IamAuthenticator instances.
5643
*/
5744
public static class Builder {
58-
private String url;
5945
private String apikey;
46+
private String url;
6047
private String scope;
6148
private String clientId;
6249
private String clientSecret;
@@ -70,12 +57,12 @@ public Builder() { }
7057

7158
// Builder ctor which copies config from an existing authenticator instance.
7259
private Builder(IamAuthenticator obj) {
73-
this.url = obj.url;
7460
this.apikey = obj.apikey;
75-
this.scope = obj.scope;
76-
this.clientId = obj.clientId;
77-
this.clientSecret = obj.clientSecret;
7861

62+
this.url = obj.getURL();
63+
this.scope = obj.getScope();
64+
this.clientId = obj.getClientId();
65+
this.clientSecret = obj.getClientSecret();
7966
this.disableSSLVerification = obj.getDisableSSLVerification();
8067
this.headers = obj.getHeaders();
8168
this.proxy = obj.getProxy();
@@ -91,6 +78,16 @@ public IamAuthenticator build() {
9178
return new IamAuthenticator(this);
9279
}
9380

81+
/**
82+
* Sets the apikey property.
83+
* @param apikey the apikey to use when retrieving an access token
84+
* @return the Builder
85+
*/
86+
public Builder apikey(String apikey) {
87+
this.apikey = apikey;
88+
return this;
89+
}
90+
9491
/**
9592
* Sets the url property.
9693
* @param url the base url to use with the IAM token service
@@ -121,16 +118,6 @@ public Builder clientSecret(String clientSecret) {
121118
return this;
122119
}
123120

124-
/**
125-
* Sets the apikey property.
126-
* @param apikey the apikey to use when retrieving an access token
127-
* @return the Builder
128-
*/
129-
public Builder apikey(String apikey) {
130-
this.apikey = apikey;
131-
return this;
132-
}
133-
134121
/**
135122
* Sets the scope property.
136123
* @param scope the scope to use when retrieving an access token
@@ -195,12 +182,11 @@ protected IamAuthenticator() {
195182
* @param builder the Builder instance containing the configuration to be used
196183
*/
197184
protected IamAuthenticator(Builder builder) {
198-
this.url = builder.url;
199185
this.apikey = builder.apikey;
200-
this.scope = builder.scope;
201-
this.clientId = builder.clientId;
202-
this.clientSecret = builder.clientSecret;
203186

187+
setURL(builder.url);
188+
setScope(builder.scope);
189+
setClientIdAndSecret(builder.clientId, builder.clientSecret);
204190
setDisableSSLVerification(builder.disableSSLVerification);
205191
setHeaders(builder.headers);
206192
setProxy(builder.proxy);
@@ -319,8 +305,8 @@ public static IamAuthenticator fromConfiguration(Map<String, String> config) {
319305
}
320306

321307
return new Builder()
322-
.url(config.get(PROPNAME_URL))
323308
.apikey(apikey)
309+
.url(config.get(PROPNAME_URL))
324310
.scope(config.get(PROPNAME_SCOPE))
325311
.clientId(config.get(PROPNAME_CLIENT_ID))
326312
.clientSecret(config.get(PROPNAME_CLIENT_SECRET))
@@ -350,7 +336,8 @@ public static IamAuthenticator fromConfiguration(Map<String, String> config) {
350336
protected void init(String apikey, String url, String clientId, String clientSecret,
351337
boolean disableSSLVerification, Map<String, String> headers, String scope) {
352338
this.apikey = apikey;
353-
this.url = url;
339+
340+
setURL(url);
354341
setClientIdAndSecret(clientId, clientSecret);
355342
setScope(scope);
356343
this.validate();
@@ -361,13 +348,14 @@ protected void init(String apikey, String url, String clientId, String clientSec
361348

362349
@Override
363350
public void validate() {
351+
super.validate();
364352

365-
if (StringUtils.isEmpty(this.url)) {
353+
if (StringUtils.isEmpty(this.getURL())) {
366354
// If no base URL was configured, then use the default IAM base URL.
367-
this.url = DEFAULT_IAM_URL;
368-
} else if (this.url.endsWith(OPERATION_PATH)) {
355+
this.setURL(DEFAULT_IAM_URL);
356+
} else {
369357
// Canonicalize the URL by removing the operation path from it if present.
370-
this.url = this.url.substring(0, this.url.length() - OPERATION_PATH.length());
358+
this.setURL(StringUtils.removeEnd(this.getURL(), OPERATION_PATH));
371359
}
372360

373361
if (StringUtils.isEmpty(this.apikey)) {
@@ -377,21 +365,6 @@ public void validate() {
377365
if (CredentialUtils.hasBadStartOrEndChar(this.apikey)) {
378366
throw new IllegalArgumentException(String.format(ERRORMSG_PROP_INVALID, "apikey"));
379367
}
380-
381-
if (StringUtils.isEmpty(getUsername()) && StringUtils.isEmpty(getPassword())) {
382-
// both empty is ok.
383-
} else {
384-
if (StringUtils.isEmpty(getUsername())) {
385-
throw new IllegalArgumentException(String.format(ERRORMSG_PROP_MISSING, "clientId"));
386-
}
387-
if (StringUtils.isEmpty(getPassword())) {
388-
throw new IllegalArgumentException(String.format(ERRORMSG_PROP_MISSING, "clientSecret"));
389-
}
390-
}
391-
392-
// Assuming everything validates clean, let's cache the basic auth header if
393-
// the clientId/clientSecret properties are configured.
394-
this.cachedAuthorizationHeader = constructBasicAuthHeader(this.clientId, this.clientSecret);
395368
}
396369

397370
@Override
@@ -406,38 +379,6 @@ public String getApiKey() {
406379
return this.apikey;
407380
}
408381

409-
/**
410-
* @return the URL configured on this Authenticator.
411-
*/
412-
public String getURL() {
413-
return this.url;
414-
}
415-
416-
/**
417-
* Sets the URL on this Authenticator.
418-
* @param url the URL representing the IAM token server endpoint
419-
*/
420-
public void setURL(String url) {
421-
if (StringUtils.isEmpty(url)) {
422-
url = DEFAULT_IAM_URL;
423-
}
424-
this.url = url;
425-
}
426-
427-
/**
428-
* @return the clientId configured on this Authenticator.
429-
*/
430-
public String getClientId() {
431-
return this.clientId;
432-
}
433-
434-
/**
435-
* @return the clientSecret configured on this Authenticator.
436-
*/
437-
public String getClientSecret() {
438-
return this.clientSecret;
439-
}
440-
441382
/**
442383
* @return the basic auth username (clientId) configured for this Authenticator.
443384
*
@@ -470,32 +411,6 @@ public void setBasicAuthInfo(String clientId, String clientSecret) {
470411
setClientIdAndSecret(clientId, clientSecret);
471412
}
472413

473-
/**
474-
* Sets the clientId and clientSecret on this Authenticator.
475-
* @param clientId the clientId to use in interactions with the token server
476-
* @param clientSecret the clientSecret to use in interactions with the token server
477-
*/
478-
public void setClientIdAndSecret(String clientId, String clientSecret) {
479-
this.clientId = clientId;
480-
this.clientSecret = clientSecret;
481-
this.validate();
482-
}
483-
484-
/**
485-
* @return the scope parameter
486-
*/
487-
public String getScope() {
488-
return this.scope;
489-
}
490-
491-
/**
492-
* Sets the "scope" parameter to use when fetching the bearer token from the IAM token server.
493-
* @param value a space seperated string that makes up the scope parameter.
494-
*/
495-
public void setScope(String value) {
496-
this.scope = value;
497-
}
498-
499414
/**
500415
* Fetches an IAM access token for the apikey using the configured URL.
501416
*
@@ -504,23 +419,22 @@ public void setScope(String value) {
504419
@Override
505420
public IamToken requestToken() {
506421
// Form a POST request to retrieve the access token.
507-
RequestBuilder builder = RequestBuilder.post(RequestBuilder.resolveRequestUrl(this.url, OPERATION_PATH));
422+
RequestBuilder builder = RequestBuilder.post(RequestBuilder.resolveRequestUrl(this.getURL(), OPERATION_PATH));
508423

509424
// Now add the Content-Type and (optionally) the Authorization header to the token server request.
510425
builder.header(HttpHeaders.CONTENT_TYPE, HttpMediaType.APPLICATION_FORM_URLENCODED);
511-
if (StringUtils.isNotEmpty(this.cachedAuthorizationHeader)) {
512-
builder.header(HttpHeaders.AUTHORIZATION, this.cachedAuthorizationHeader);
513-
}
426+
addAuthorizationHeader(builder);
514427

515428
// Build the form request body.
516429
FormBody formBody;
517430
final FormBody.Builder formBodyBuilder = new FormBody.Builder()
518-
.add(GRANT_TYPE, REQUEST_GRANT_TYPE)
519-
.add(API_KEY, apikey)
520-
.add(RESPONSE_TYPE, CLOUD_IAM);
521-
// Add the scope param if it's not empty
431+
.add("grant_type", "urn:ibm:params:oauth:grant-type:apikey")
432+
.add("apikey", getApiKey())
433+
.add("response_type", "cloud_iam");
434+
435+
// Add the scope param if it's not empty.
522436
if (!StringUtils.isEmpty(getScope())) {
523-
formBodyBuilder.add(SCOPE, getScope());
437+
formBodyBuilder.add("scope", getScope());
524438
}
525439
formBody = formBodyBuilder.build();
526440
builder.body(formBody);
Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
/**
2+
* (C) Copyright IBM Corp. 2015, 2021.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
5+
* the License. You may obtain a copy of the License at
6+
*
7+
* http://www.apache.org/licenses/LICENSE-2.0
8+
*
9+
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
10+
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
11+
* specific language governing permissions and limitations under the License.
12+
*/
13+
14+
package com.ibm.cloud.sdk.core.security;
15+
16+
import org.apache.commons.lang3.StringUtils;
17+
18+
import com.ibm.cloud.sdk.core.http.HttpHeaders;
19+
import com.ibm.cloud.sdk.core.http.RequestBuilder;
20+
21+
/**
22+
* This class contains code that is common to all authenticators that need to
23+
* interact with the IAM tokens service to obtain an access token.
24+
*/
25+
public abstract class IamRequestBasedAuthenticator
26+
extends TokenRequestBasedAuthenticator<IamToken, IamToken>
27+
implements Authenticator {
28+
29+
private static final String DEFAULT_IAM_URL = "https://iam.cloud.ibm.com";
30+
31+
// Properties common to IAM-based authenticators.
32+
private String url;
33+
private String scope;
34+
private String clientId;
35+
private String clientSecret;
36+
37+
// This is the value of the Authorization header we'll use when interacting with the token server.
38+
protected String cachedAuthorizationHeader = null;
39+
40+
41+
@Override
42+
public void validate() {
43+
if (StringUtils.isEmpty(getClientId()) && StringUtils.isEmpty(getClientSecret())) {
44+
// both empty is ok.
45+
} else {
46+
if (StringUtils.isEmpty(getClientId())) {
47+
throw new IllegalArgumentException(String.format(ERRORMSG_PROP_MISSING, "clientId"));
48+
}
49+
if (StringUtils.isEmpty(getClientSecret())) {
50+
throw new IllegalArgumentException(String.format(ERRORMSG_PROP_MISSING, "clientSecret"));
51+
}
52+
}
53+
54+
// Assuming everything validates clean, let's cache the basic auth header if
55+
// the clientId/clientSecret properties are configured.
56+
this.cachedAuthorizationHeader = constructBasicAuthHeader(this.clientId, this.clientSecret);
57+
}
58+
59+
/**
60+
* @return the URL configured on this Authenticator.
61+
*/
62+
public String getURL() {
63+
return this.url;
64+
}
65+
66+
/**
67+
* Sets the URL on this Authenticator.
68+
* @param url the URL representing the IAM token server endpoint
69+
*/
70+
public void setURL(String url) {
71+
if (StringUtils.isEmpty(url)) {
72+
url = DEFAULT_IAM_URL;
73+
}
74+
this.url = url;
75+
}
76+
77+
/**
78+
* @return the clientId configured on this Authenticator.
79+
*/
80+
public String getClientId() {
81+
return this.clientId;
82+
}
83+
84+
/**
85+
* @return the clientSecret configured on this Authenticator.
86+
*/
87+
public String getClientSecret() {
88+
return this.clientSecret;
89+
}
90+
91+
/**
92+
* Sets the clientId and clientSecret on this Authenticator.
93+
* @param clientId the clientId to use in interactions with the token server
94+
* @param clientSecret the clientSecret to use in interactions with the token server
95+
*/
96+
public void setClientIdAndSecret(String clientId, String clientSecret) {
97+
this.clientId = clientId;
98+
this.clientSecret = clientSecret;
99+
this.validate();
100+
}
101+
102+
/**
103+
* @return the scope parameter
104+
*/
105+
public String getScope() {
106+
return this.scope;
107+
}
108+
109+
/**
110+
* Sets the "scope" parameter to use when fetching the bearer token from the IAM token server.
111+
* @param value a space seperated string that makes up the scope parameter.
112+
*/
113+
public void setScope(String value) {
114+
this.scope = value;
115+
}
116+
117+
/**
118+
* If a basic auth Authorization header is cached in "this", then add it to
119+
* the specified request builder.
120+
* @param builder the request builder
121+
*/
122+
protected void addAuthorizationHeader(RequestBuilder builder) {
123+
if (StringUtils.isNotEmpty(this.cachedAuthorizationHeader)) {
124+
builder.header(HttpHeaders.AUTHORIZATION, this.cachedAuthorizationHeader);
125+
}
126+
}
127+
}

0 commit comments

Comments
 (0)