-
Notifications
You must be signed in to change notification settings - Fork 21
refactor: prepare for ContainerAuthenticator impl #139
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -31,32 +31,19 @@ | |
* an access token from the IAM token service via the "POST /identity/token" operation. | ||
* When the access token expires, a new access token will be fetched. | ||
*/ | ||
public class IamAuthenticator extends TokenRequestBasedAuthenticator<IamToken, IamToken> implements Authenticator { | ||
public class IamAuthenticator extends IamRequestBasedAuthenticator implements Authenticator { | ||
private static final String DEFAULT_IAM_URL = "https://iam.cloud.ibm.com"; | ||
private static final String OPERATION_PATH = "/identity/token"; | ||
private static final String GRANT_TYPE = "grant_type"; | ||
private static final String REQUEST_GRANT_TYPE = "urn:ibm:params:oauth:grant-type:apikey"; | ||
private static final String API_KEY = "apikey"; | ||
private static final String RESPONSE_TYPE = "response_type"; | ||
private static final String CLOUD_IAM = "cloud_iam"; | ||
private static final String SCOPE = "scope"; | ||
|
||
// Properties specific to an IAM authenticator. | ||
private String url; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Most of the rest of the code deletions you see in this class are due to this code being moved to the new base class. |
||
private String apikey; | ||
private String scope; | ||
private String clientId; | ||
private String clientSecret; | ||
|
||
// This is the value of the Authorization header we'll use when interacting with the token server. | ||
private String cachedAuthorizationHeader = null; | ||
|
||
/** | ||
* This Builder class is used to construct IamAuthenticator instances. | ||
*/ | ||
public static class Builder { | ||
private String url; | ||
private String apikey; | ||
private String url; | ||
private String scope; | ||
private String clientId; | ||
private String clientSecret; | ||
|
@@ -70,12 +57,12 @@ public Builder() { } | |
|
||
// Builder ctor which copies config from an existing authenticator instance. | ||
private Builder(IamAuthenticator obj) { | ||
this.url = obj.url; | ||
this.apikey = obj.apikey; | ||
this.scope = obj.scope; | ||
this.clientId = obj.clientId; | ||
this.clientSecret = obj.clientSecret; | ||
|
||
this.url = obj.getURL(); | ||
this.scope = obj.getScope(); | ||
this.clientId = obj.getClientId(); | ||
this.clientSecret = obj.getClientSecret(); | ||
this.disableSSLVerification = obj.getDisableSSLVerification(); | ||
this.headers = obj.getHeaders(); | ||
this.proxy = obj.getProxy(); | ||
|
@@ -91,6 +78,16 @@ public IamAuthenticator build() { | |
return new IamAuthenticator(this); | ||
} | ||
|
||
/** | ||
* Sets the apikey property. | ||
* @param apikey the apikey to use when retrieving an access token | ||
* @return the Builder | ||
*/ | ||
public Builder apikey(String apikey) { | ||
this.apikey = apikey; | ||
return this; | ||
} | ||
|
||
/** | ||
* Sets the url property. | ||
* @param url the base url to use with the IAM token service | ||
|
@@ -121,16 +118,6 @@ public Builder clientSecret(String clientSecret) { | |
return this; | ||
} | ||
|
||
/** | ||
* Sets the apikey property. | ||
* @param apikey the apikey to use when retrieving an access token | ||
* @return the Builder | ||
*/ | ||
public Builder apikey(String apikey) { | ||
this.apikey = apikey; | ||
return this; | ||
} | ||
|
||
/** | ||
* Sets the scope property. | ||
* @param scope the scope to use when retrieving an access token | ||
|
@@ -195,12 +182,11 @@ protected IamAuthenticator() { | |
* @param builder the Builder instance containing the configuration to be used | ||
*/ | ||
protected IamAuthenticator(Builder builder) { | ||
this.url = builder.url; | ||
this.apikey = builder.apikey; | ||
this.scope = builder.scope; | ||
this.clientId = builder.clientId; | ||
this.clientSecret = builder.clientSecret; | ||
|
||
setURL(builder.url); | ||
setScope(builder.scope); | ||
setClientIdAndSecret(builder.clientId, builder.clientSecret); | ||
setDisableSSLVerification(builder.disableSSLVerification); | ||
setHeaders(builder.headers); | ||
setProxy(builder.proxy); | ||
|
@@ -319,8 +305,8 @@ public static IamAuthenticator fromConfiguration(Map<String, String> config) { | |
} | ||
|
||
return new Builder() | ||
.url(config.get(PROPNAME_URL)) | ||
.apikey(apikey) | ||
.url(config.get(PROPNAME_URL)) | ||
.scope(config.get(PROPNAME_SCOPE)) | ||
.clientId(config.get(PROPNAME_CLIENT_ID)) | ||
.clientSecret(config.get(PROPNAME_CLIENT_SECRET)) | ||
|
@@ -350,7 +336,8 @@ public static IamAuthenticator fromConfiguration(Map<String, String> config) { | |
protected void init(String apikey, String url, String clientId, String clientSecret, | ||
boolean disableSSLVerification, Map<String, String> headers, String scope) { | ||
this.apikey = apikey; | ||
this.url = url; | ||
|
||
setURL(url); | ||
setClientIdAndSecret(clientId, clientSecret); | ||
setScope(scope); | ||
this.validate(); | ||
|
@@ -361,13 +348,14 @@ protected void init(String apikey, String url, String clientId, String clientSec | |
|
||
@Override | ||
public void validate() { | ||
super.validate(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The new base class has a validate function that validates the common fields contained in that class. |
||
|
||
if (StringUtils.isEmpty(this.url)) { | ||
if (StringUtils.isEmpty(this.getURL())) { | ||
// If no base URL was configured, then use the default IAM base URL. | ||
this.url = DEFAULT_IAM_URL; | ||
} else if (this.url.endsWith(OPERATION_PATH)) { | ||
this.setURL(DEFAULT_IAM_URL); | ||
} else { | ||
// Canonicalize the URL by removing the operation path from it if present. | ||
this.url = this.url.substring(0, this.url.length() - OPERATION_PATH.length()); | ||
this.setURL(StringUtils.removeEnd(this.getURL(), OPERATION_PATH)); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Note: StringUtils.removeEnd() will only remove OPERATION_PATH from the end of the string if the string in fact ends with OPERATION_PATH :) |
||
} | ||
|
||
if (StringUtils.isEmpty(this.apikey)) { | ||
|
@@ -377,21 +365,6 @@ public void validate() { | |
if (CredentialUtils.hasBadStartOrEndChar(this.apikey)) { | ||
throw new IllegalArgumentException(String.format(ERRORMSG_PROP_INVALID, "apikey")); | ||
} | ||
|
||
if (StringUtils.isEmpty(getUsername()) && StringUtils.isEmpty(getPassword())) { | ||
// both empty is ok. | ||
} else { | ||
if (StringUtils.isEmpty(getUsername())) { | ||
throw new IllegalArgumentException(String.format(ERRORMSG_PROP_MISSING, "clientId")); | ||
} | ||
if (StringUtils.isEmpty(getPassword())) { | ||
throw new IllegalArgumentException(String.format(ERRORMSG_PROP_MISSING, "clientSecret")); | ||
} | ||
} | ||
|
||
// Assuming everything validates clean, let's cache the basic auth header if | ||
// the clientId/clientSecret properties are configured. | ||
this.cachedAuthorizationHeader = constructBasicAuthHeader(this.clientId, this.clientSecret); | ||
} | ||
|
||
@Override | ||
|
@@ -406,38 +379,6 @@ public String getApiKey() { | |
return this.apikey; | ||
} | ||
|
||
/** | ||
* @return the URL configured on this Authenticator. | ||
*/ | ||
public String getURL() { | ||
return this.url; | ||
} | ||
|
||
/** | ||
* Sets the URL on this Authenticator. | ||
* @param url the URL representing the IAM token server endpoint | ||
*/ | ||
public void setURL(String url) { | ||
if (StringUtils.isEmpty(url)) { | ||
url = DEFAULT_IAM_URL; | ||
} | ||
this.url = url; | ||
} | ||
|
||
/** | ||
* @return the clientId configured on this Authenticator. | ||
*/ | ||
public String getClientId() { | ||
return this.clientId; | ||
} | ||
|
||
/** | ||
* @return the clientSecret configured on this Authenticator. | ||
*/ | ||
public String getClientSecret() { | ||
return this.clientSecret; | ||
} | ||
|
||
/** | ||
* @return the basic auth username (clientId) configured for this Authenticator. | ||
* | ||
|
@@ -470,32 +411,6 @@ public void setBasicAuthInfo(String clientId, String clientSecret) { | |
setClientIdAndSecret(clientId, clientSecret); | ||
} | ||
|
||
/** | ||
* Sets the clientId and clientSecret on this Authenticator. | ||
* @param clientId the clientId to use in interactions with the token server | ||
* @param clientSecret the clientSecret to use in interactions with the token server | ||
*/ | ||
public void setClientIdAndSecret(String clientId, String clientSecret) { | ||
this.clientId = clientId; | ||
this.clientSecret = clientSecret; | ||
this.validate(); | ||
} | ||
|
||
/** | ||
* @return the scope parameter | ||
*/ | ||
public String getScope() { | ||
return this.scope; | ||
} | ||
|
||
/** | ||
* Sets the "scope" parameter to use when fetching the bearer token from the IAM token server. | ||
* @param value a space seperated string that makes up the scope parameter. | ||
*/ | ||
public void setScope(String value) { | ||
this.scope = value; | ||
} | ||
|
||
/** | ||
* Fetches an IAM access token for the apikey using the configured URL. | ||
* | ||
|
@@ -504,23 +419,22 @@ public void setScope(String value) { | |
@Override | ||
public IamToken requestToken() { | ||
// Form a POST request to retrieve the access token. | ||
RequestBuilder builder = RequestBuilder.post(RequestBuilder.resolveRequestUrl(this.url, OPERATION_PATH)); | ||
RequestBuilder builder = RequestBuilder.post(RequestBuilder.resolveRequestUrl(this.getURL(), OPERATION_PATH)); | ||
|
||
// Now add the Content-Type and (optionally) the Authorization header to the token server request. | ||
builder.header(HttpHeaders.CONTENT_TYPE, HttpMediaType.APPLICATION_FORM_URLENCODED); | ||
if (StringUtils.isNotEmpty(this.cachedAuthorizationHeader)) { | ||
builder.header(HttpHeaders.AUTHORIZATION, this.cachedAuthorizationHeader); | ||
} | ||
addAuthorizationHeader(builder); | ||
|
||
// Build the form request body. | ||
FormBody formBody; | ||
final FormBody.Builder formBodyBuilder = new FormBody.Builder() | ||
.add(GRANT_TYPE, REQUEST_GRANT_TYPE) | ||
.add(API_KEY, apikey) | ||
.add(RESPONSE_TYPE, CLOUD_IAM); | ||
// Add the scope param if it's not empty | ||
.add("grant_type", "urn:ibm:params:oauth:grant-type:apikey") | ||
.add("apikey", getApiKey()) | ||
.add("response_type", "cloud_iam"); | ||
|
||
// Add the scope param if it's not empty. | ||
if (!StringUtils.isEmpty(getScope())) { | ||
formBodyBuilder.add(SCOPE, getScope()); | ||
formBodyBuilder.add("scope", getScope()); | ||
} | ||
formBody = formBodyBuilder.build(); | ||
builder.body(formBody); | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,127 @@ | ||
/** | ||
* (C) Copyright IBM Corp. 2015, 2021. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is the new base class. I'll leave the initial copyright year (2015) here since this code was copied from the other file. |
||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with | ||
* the License. You may obtain a copy of the License at | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on | ||
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the | ||
* specific language governing permissions and limitations under the License. | ||
*/ | ||
|
||
package com.ibm.cloud.sdk.core.security; | ||
|
||
import org.apache.commons.lang3.StringUtils; | ||
|
||
import com.ibm.cloud.sdk.core.http.HttpHeaders; | ||
import com.ibm.cloud.sdk.core.http.RequestBuilder; | ||
|
||
/** | ||
* This class contains code that is common to all authenticators that need to | ||
* interact with the IAM tokens service to obtain an access token. | ||
*/ | ||
public abstract class IamRequestBasedAuthenticator | ||
extends TokenRequestBasedAuthenticator<IamToken, IamToken> | ||
implements Authenticator { | ||
|
||
private static final String DEFAULT_IAM_URL = "https://iam.cloud.ibm.com"; | ||
|
||
// Properties common to IAM-based authenticators. | ||
private String url; | ||
private String scope; | ||
private String clientId; | ||
private String clientSecret; | ||
|
||
// This is the value of the Authorization header we'll use when interacting with the token server. | ||
protected String cachedAuthorizationHeader = null; | ||
|
||
|
||
@Override | ||
public void validate() { | ||
if (StringUtils.isEmpty(getClientId()) && StringUtils.isEmpty(getClientSecret())) { | ||
// both empty is ok. | ||
} else { | ||
if (StringUtils.isEmpty(getClientId())) { | ||
throw new IllegalArgumentException(String.format(ERRORMSG_PROP_MISSING, "clientId")); | ||
} | ||
if (StringUtils.isEmpty(getClientSecret())) { | ||
throw new IllegalArgumentException(String.format(ERRORMSG_PROP_MISSING, "clientSecret")); | ||
} | ||
} | ||
|
||
// Assuming everything validates clean, let's cache the basic auth header if | ||
// the clientId/clientSecret properties are configured. | ||
this.cachedAuthorizationHeader = constructBasicAuthHeader(this.clientId, this.clientSecret); | ||
} | ||
|
||
/** | ||
* @return the URL configured on this Authenticator. | ||
*/ | ||
public String getURL() { | ||
return this.url; | ||
} | ||
|
||
/** | ||
* Sets the URL on this Authenticator. | ||
* @param url the URL representing the IAM token server endpoint | ||
*/ | ||
public void setURL(String url) { | ||
if (StringUtils.isEmpty(url)) { | ||
url = DEFAULT_IAM_URL; | ||
} | ||
this.url = url; | ||
} | ||
|
||
/** | ||
* @return the clientId configured on this Authenticator. | ||
*/ | ||
public String getClientId() { | ||
return this.clientId; | ||
} | ||
|
||
/** | ||
* @return the clientSecret configured on this Authenticator. | ||
*/ | ||
public String getClientSecret() { | ||
return this.clientSecret; | ||
} | ||
|
||
/** | ||
* Sets the clientId and clientSecret on this Authenticator. | ||
* @param clientId the clientId to use in interactions with the token server | ||
* @param clientSecret the clientSecret to use in interactions with the token server | ||
*/ | ||
public void setClientIdAndSecret(String clientId, String clientSecret) { | ||
this.clientId = clientId; | ||
this.clientSecret = clientSecret; | ||
this.validate(); | ||
} | ||
|
||
/** | ||
* @return the scope parameter | ||
*/ | ||
public String getScope() { | ||
return this.scope; | ||
} | ||
|
||
/** | ||
* Sets the "scope" parameter to use when fetching the bearer token from the IAM token server. | ||
* @param value a space seperated string that makes up the scope parameter. | ||
*/ | ||
public void setScope(String value) { | ||
this.scope = value; | ||
} | ||
|
||
/** | ||
* If a basic auth Authorization header is cached in "this", then add it to | ||
* the specified request builder. | ||
* @param builder the request builder | ||
*/ | ||
protected void addAuthorizationHeader(RequestBuilder builder) { | ||
if (StringUtils.isNotEmpty(this.cachedAuthorizationHeader)) { | ||
builder.header(HttpHeaders.AUTHORIZATION, this.cachedAuthorizationHeader); | ||
} | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I removed these constants because they are used only in the requestToken() function and I actually think the code is more readable and understandable WITHOUT using these contants.