Skip to content

Commit 3baa14a

Browse files
feat(multiple-auth): add support for multiple auth (#79)
adds support for multiple auth i.e. authentication using And/Or combination. Now core library uses the auth builder to validate and apply the provided auth combinations. It also uses the same design for a single authentication scheme.
1 parent 0940f71 commit 3baa14a

13 files changed

Lines changed: 1033 additions & 85 deletions

File tree

README.md

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,13 +17,19 @@ Core lib's Maven group ID is `io.apimatic`, and its artifact ID is `core`.
1717
|-------------------------------------------------------------------------|--------------------------------------------------------------------|
1818
| [`ApiCall`](./src/main/java/io/apimatic/core/ApiCall.java) | An API call, or API request, is a message sent to a server asking an API to provide a service or information |
1919
| [`Parameter`](./src/main/java/io/apimatic/core/Parameter.java) | HTTP parameters consist of a type, a name, and a value. These parameters appear in the header and body of an HTTP request. |
20-
| [`ErrorCase`](./src/main/java/io/apimatic/core/ErrorCase.java) | A class is responsible to generate the SDK Exception |
20+
| [`ErrorCase`](./src/main/java/io/apimatic/core/ErrorCase.java) | A class which is responsible to generate the SDK Exception |
2121
| [`GlobalConfiguration`](./src/main/java/io/apimatic/core/GlobalConfiguration.java) | A class which hold the global configuration properties to make a successful Api Call |
2222
| [`HttpRequest`](./src/main/java/io/apimatic/core/HttpRequest.java) | An HTTP request is made by a client, to a named host, which is located on a server |
2323
| [`ResponseHandler`](./src/main/java/io/apimatic/core/ResponseHandler.java) | Handler that encapsulates the process of generating a response object from a Response |
2424
| [`HttpLogger`](./src/main/java/io/apimatic/core/logger/HttpLogger.java) | A class to log the Http events. |
25+
| [`AuthBuilder`](./src/main/java/io/apimatic/core/authentication/AuthBuilder.java) | A class to build and validate provided combination of auth schemes. |
26+
| [`AuthCredential`](./src/main/java/io/apimatic/core/authentication/AuthCredential.java) | A parent class of [`HeaderAuth`](./src/main/java/io/apimatic/core/authentication/HeaderAuth.java) and [`QueryAuth`](./src/main/java/io/apimatic/core/authentication/QueryAuth.java) to hold the common implementation for header and query parameters |
2527
| [`HeaderAuth`](./src/main/java/io/apimatic/core/authentication/HeaderAuth.java) | A class supports HTTP authentication through HTTP Headers |
2628
| [`QueryAuth`](./src/main/java/io/apimatic/core/authentication/QueryAuth.java) | A class supports HTTP authentication through query parameters |
29+
| [`AuthGroup`](./src/main/java/io/apimatic/core/authentication/multiple/AuthGroup.java) | A parent class of [`And`](./src/main/java/io/apimatic/core/authentication/multiple/And.java) and [`Or`](./src/main/java/io/apimatic/core/authentication/multiple/Or.java) to hold the common functionality of multiple auth |
30+
| [`And`](./src/main/java/io/apimatic/core/authentication/multiple/And.java) | A class to hold the algorithm for `And` combination of auth schemes|
31+
| [`Or`](./src/main/java/io/apimatic/core/authentication/multiple/Or.java) | A class to hold the algorithm for `Or` combination of auth schemes |
32+
| [`Single`](./src/main/java/io/apimatic/core/authentication/multiple/Single.java) | A class to hold the logic for single auth scheme, it is used as leaf node for auth combination or it could be used directly to apply one auth only to the http request |
2733
| [`CoreHttpClientConfiguration`](./src/main/java/io/apimatic/core/configurations/http/client/CoreHttpClientConfiguration.java) | To hold HTTP Client Configuration |
2834
| [`ApiLoggingConfiguration`](./src/main/java/io/apimatic/core/configurations/http/client/ApiLoggingConfiguration.java) | To hold logging configuration |
2935
| [`EndpointConfiguration`](./src/main/java/io/apimatic/core/configurations/http/request/EndpointConfiguration.java) | The configuration for an endpoint |

src/main/java/io/apimatic/core/HttpRequest.java

Lines changed: 49 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@
99
import java.util.Map;
1010
import java.util.Set;
1111
import java.util.function.Consumer;
12+
import io.apimatic.core.authentication.AuthBuilder;
13+
import io.apimatic.core.exceptions.AuthValidationException;
1214
import io.apimatic.core.types.http.request.MultipartFileWrapper;
1315
import io.apimatic.core.types.http.request.MultipartWrapper;
1416
import io.apimatic.core.utilities.CoreHelper;
@@ -55,7 +57,7 @@ public final class HttpRequest {
5557
* @param server
5658
* @param path
5759
* @param httpMethod
58-
* @param authenticationKey
60+
* @param authentication
5961
* @param queryParams
6062
* @param templateParams
6163
* @param headerParams
@@ -65,16 +67,18 @@ public final class HttpRequest {
6567
* @param bodySerializer
6668
* @param bodyParameters
6769
* @param arraySerializationFormat
70+
* @param isSingleAuth
6871
* @throws IOException
6972
*/
7073
private HttpRequest(final GlobalConfiguration coreConfig, final String server,
71-
final String path, final Method httpMethod, final String authenticationKey,
74+
final String path, final Method httpMethod, final Authentication authentication,
7275
final Map<String, Object> queryParams,
7376
final Map<String, SimpleEntry<Object, Boolean>> templateParams,
7477
final Map<String, List<String>> headerParams, final Set<Parameter> formParams,
7578
final Map<String, Object> formParameters, final Object body,
7679
final Serializer bodySerializer, final Map<String, Object> bodyParameters,
77-
final ArraySerializationFormat arraySerializationFormat) throws IOException {
80+
final ArraySerializationFormat arraySerializationFormat,
81+
final boolean isSingleAuth) throws IOException {
7882
this.coreConfig = coreConfig;
7983
this.compatibilityFactory = coreConfig.getCompatibilityFactory();
8084
urlBuilder = getStringBuilder(server, path);
@@ -86,7 +90,7 @@ private HttpRequest(final GlobalConfiguration coreConfig, final String server,
8690
coreHttpRequest =
8791
buildRequest(httpMethod, bodyValue, addHeaders(headerParams), queryParams,
8892
formFields, arraySerializationFormat);
89-
applyAuthentication(authenticationKey);
93+
applyAuthentication(authentication, isSingleAuth);
9094
}
9195

9296
/**
@@ -96,21 +100,6 @@ public Request getCoreHttpRequest() {
96100
return coreHttpRequest;
97101
}
98102

99-
private void applyAuthentication(String authenticationKey) {
100-
if (authenticationKey == null) {
101-
return;
102-
}
103-
104-
Map<String, Authentication> authentications = coreConfig.getAuthentications();
105-
if (authentications != null) {
106-
Authentication authManager = authentications.get(authenticationKey);
107-
if (authManager != null) {
108-
authManager.validate();
109-
authManager.apply(coreHttpRequest);
110-
}
111-
}
112-
}
113-
114103
private Request buildRequest(
115104
Method httpMethod, Object body, HttpHeaders headerParams,
116105
Map<String, Object> queryParams, List<SimpleEntry<String, Object>> formFields,
@@ -124,6 +113,22 @@ private Request buildRequest(
124113
queryParams, formFields);
125114
}
126115

116+
private void applyAuthentication(Authentication authentication, boolean isSingleAuth) {
117+
if (authentication != null) {
118+
authentication.validate();
119+
if (!authentication.isValid() && !isSingleAuth) {
120+
throw new AuthValidationException(authentication.getErrorMessage());
121+
}
122+
123+
// The following block should be removed with the next major version release.
124+
if (isSingleAuth && authentication.getErrorMessage() != null) {
125+
throw new AuthValidationException(authentication.getErrorMessage());
126+
}
127+
128+
authentication.apply(coreHttpRequest);
129+
}
130+
}
131+
127132
/**
128133
* @param formParams
129134
* @param optionalFormParamaters
@@ -239,9 +244,15 @@ public static class Builder {
239244
private Method httpMethod;
240245

241246
/**
242-
* A authentication key string.
247+
* An auth builder for the request.
248+
*/
249+
private AuthBuilder authBuilder = new AuthBuilder();
250+
251+
/**
252+
* Flag to use for backward compatibility.
253+
* It should be removed with the next major version release.
243254
*/
244-
private String authenticationKey;
255+
private boolean isSingleAuth = false;
245256

246257
/**
247258
* A map of query parameters.
@@ -325,12 +336,23 @@ public Builder httpMethod(Method httpMethod) {
325336
}
326337

327338
/**
328-
* Setter for requiresAuth.
339+
* Setter for authentication key.
329340
* @param authenticationKey string value for authenticationKey.
330341
* @return Builder.
331342
*/
332343
public Builder authenticationKey(String authenticationKey) {
333-
this.authenticationKey = authenticationKey;
344+
authBuilder = authBuilder.add(authenticationKey);
345+
isSingleAuth = true;
346+
return this;
347+
}
348+
349+
/**
350+
* Setter for Authentication Builder, used for authenticating the request.
351+
* @param consumer the builder consumer for authentication.
352+
* @return Builder.
353+
*/
354+
public Builder withAuth(Consumer<AuthBuilder> consumer) {
355+
consumer.accept(authBuilder);
334356
return this;
335357
}
336358

@@ -472,10 +494,12 @@ public Builder arraySerializationFormat(ArraySerializationFormat arraySerializat
472494
* @throws IOException Signals that an I/O exception of some sort has occurred.
473495
*/
474496
public Request build(GlobalConfiguration coreConfig) throws IOException {
497+
Authentication authentication = authBuilder.build(coreConfig.getAuthentications());
475498
HttpRequest coreRequest =
476-
new HttpRequest(coreConfig, server, path, httpMethod, authenticationKey,
499+
new HttpRequest(coreConfig, server, path, httpMethod, authentication,
477500
queryParams, templateParams, headerParams, formParams, formParamaters,
478-
body, bodySerializer, bodyParameters, arraySerializationFormat);
501+
body, bodySerializer, bodyParameters, arraySerializationFormat,
502+
isSingleAuth);
479503
Request coreHttpRequest = coreRequest.getCoreHttpRequest();
480504

481505
if (coreConfig.getHttpCallback() != null) {
Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
package io.apimatic.core.authentication;
2+
3+
import java.util.ArrayList;
4+
import java.util.HashMap;
5+
import java.util.List;
6+
import java.util.Map;
7+
import java.util.function.Consumer;
8+
import io.apimatic.core.authentication.multiple.And;
9+
import io.apimatic.core.authentication.multiple.Or;
10+
import io.apimatic.core.authentication.multiple.Single;
11+
import io.apimatic.coreinterfaces.authentication.Authentication;
12+
13+
/**
14+
* A builder for authentication.
15+
*/
16+
public class AuthBuilder {
17+
18+
/**
19+
* Constant for AND group identifier.
20+
*/
21+
private static final String AND = "AND";
22+
23+
/**
24+
* Constant for OR group identifier.
25+
*/
26+
private static final String OR = "OR";
27+
28+
/**
29+
* Holds nested combination of authentication.
30+
*/
31+
private Map<String, List<AuthBuilder>> authBuilders;
32+
33+
/**
34+
* Holds the authentication keys, must belong to the provided authentication managers.
35+
*/
36+
private List<String> authKeys;
37+
38+
/**
39+
* Default constructor.
40+
*/
41+
public AuthBuilder() {
42+
authBuilders = new HashMap<String, List<AuthBuilder>>();
43+
authBuilders.put(AND, new ArrayList<AuthBuilder>());
44+
authBuilders.put(OR, new ArrayList<AuthBuilder>());
45+
authKeys = new ArrayList<String>();
46+
}
47+
48+
/**
49+
* Registers the authentication key to the builder.
50+
* @param authKey A key pointing to some authentication in the provided auth managers.
51+
* @return {@link AuthBuilder} The instance of the current builder.
52+
*/
53+
public AuthBuilder add(String authKey) {
54+
authKeys.add(authKey);
55+
return this;
56+
}
57+
58+
/**
59+
* Registers the and group for authentication.
60+
* @param action A consumer for the nested builder.
61+
* @return {@link AuthBuilder} The instance of the current builder.
62+
*/
63+
public AuthBuilder and(Consumer<AuthBuilder> action) {
64+
AuthBuilder authBuilder = new AuthBuilder();
65+
action.accept(authBuilder);
66+
authBuilders.get(AND).add(authBuilder);
67+
return this;
68+
}
69+
70+
/**
71+
* Registers the or group for authentication.
72+
* @param action A consumer for the nested builder.
73+
* @return {@link AuthBuilder} The instance of the current builder.
74+
*/
75+
public AuthBuilder or(Consumer<AuthBuilder> action) {
76+
AuthBuilder authBuilder = new AuthBuilder();
77+
action.accept(authBuilder);
78+
authBuilders.get(OR).add(authBuilder);
79+
return this;
80+
}
81+
82+
/**
83+
* Builds and validates the authentication using registered authentication keys.
84+
* @param authManagers The map of authentication managers.
85+
* @return {@link Authentication} The validated instance of authentication.
86+
*/
87+
public Authentication build(Map<String, Authentication> authManagers) {
88+
if (authManagers == null || authManagers.isEmpty()) {
89+
return null;
90+
}
91+
92+
if (authBuilders.get(AND).isEmpty() && authBuilders.get(OR).isEmpty()
93+
&& authKeys.isEmpty()) {
94+
return null;
95+
}
96+
97+
Authentication mappedAuth = null;
98+
if (authBuilders.get(AND).isEmpty() && authBuilders.get(OR).isEmpty()
99+
&& !authKeys.isEmpty()) {
100+
mappedAuth = new Single(authManagers.get(authKeys.get(0)));
101+
return mappedAuth;
102+
}
103+
104+
for (AuthBuilder authBuilder : authBuilders.get(AND)) {
105+
mappedAuth = new And(authBuilder.buildAuthGroup(authManagers));
106+
}
107+
108+
for (AuthBuilder authBuilder : authBuilders.get(OR)) {
109+
mappedAuth = new Or(authBuilder.buildAuthGroup(authManagers));
110+
}
111+
112+
return mappedAuth;
113+
}
114+
115+
/**
116+
* Builds the nested authentication groups.
117+
* @param authManagers The map of authentication managers.
118+
* @return List<{@link Authentication}> The converted instance of nested authentications.
119+
*/
120+
private List<Authentication> buildAuthGroup(Map<String, Authentication> authManagers) {
121+
List<Authentication> auths = new ArrayList<Authentication>();
122+
123+
authKeys.forEach(authKey -> {
124+
if (authManagers.containsKey(authKey)) {
125+
auths.add(new Single(authManagers.get(authKey)));
126+
}
127+
});
128+
129+
authBuilders.get(AND).forEach(authBuilder -> {
130+
auths.add(new And(authBuilder.buildAuthGroup(authManagers)));
131+
});
132+
133+
authBuilders.get(OR).forEach(authBuilder -> {
134+
auths.add(new Or(authBuilder.buildAuthGroup(authManagers)));
135+
});
136+
137+
return auths;
138+
}
139+
}
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
package io.apimatic.core.authentication;
2+
3+
import java.util.HashMap;
4+
import java.util.Map;
5+
import io.apimatic.coreinterfaces.authentication.Authentication;
6+
7+
/**
8+
* Handles and validates the Authentication parameters.
9+
*/
10+
public abstract class AuthCredential extends Authentication {
11+
12+
/**
13+
* A map of authentication parameters.
14+
*/
15+
private Map<String, String> authParams = new HashMap<>();
16+
17+
/**
18+
* @param authParams Map of authentication parameters.
19+
*/
20+
public AuthCredential(final Map<String, String> authParams) {
21+
this.authParams = authParams;
22+
}
23+
24+
/**
25+
* Getter for the map of authentication parameters.
26+
* @return Map&lt;String, String&gt; The map of authentication parameters.
27+
*/
28+
public Map<String, String> getAuthParams() {
29+
return authParams;
30+
}
31+
32+
/**
33+
* Validates the credentials for authentication.
34+
*/
35+
public void validate() {
36+
// Check for null keys or values
37+
boolean hasNullKeyOrValue = authParams.entrySet().stream()
38+
.anyMatch(entry -> entry.getKey() == null
39+
|| entry.getKey() == ""
40+
|| entry.getValue() == null
41+
|| entry.getValue() == "");
42+
43+
if (hasNullKeyOrValue) {
44+
setErrorMessage("[Auth key and value cannot be null]");
45+
setValidity(false);
46+
return;
47+
}
48+
49+
setValidity(true);
50+
}
51+
52+
}

0 commit comments

Comments
 (0)