-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge branch 'dev' into 30948_ANTLR4_filter_grammar
- Loading branch information
Showing
30 changed files
with
611 additions
and
21 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
# DINA Client | ||
|
||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
<?xml version="1.0" encoding="UTF-8"?> | ||
|
||
<project xmlns="http://maven.apache.org/POM/4.0.0" | ||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> | ||
|
||
<modelVersion>4.0.0</modelVersion> | ||
|
||
<parent> | ||
<groupId>io.github.aafc-bicoe</groupId> | ||
<artifactId>dina-base-parent</artifactId> | ||
<version>0.118-SNAPSHOT</version> | ||
</parent> | ||
|
||
<artifactId>dina-client</artifactId> | ||
<name>dina-client</name> | ||
<description>DINA client</description> | ||
<url>https://github.com/AAFC-BICoE/dina-base-api</url> | ||
|
||
<properties> | ||
<okhttp.version>4.9.0</okhttp.version> | ||
<retrofit.version>2.9.0</retrofit.version> | ||
</properties> | ||
|
||
<dependencies> | ||
<dependency> | ||
<groupId>com.squareup.okhttp3</groupId> | ||
<artifactId>okhttp</artifactId> | ||
<version>${okhttp.version}</version> | ||
</dependency> | ||
|
||
<dependency> | ||
<groupId>com.squareup.retrofit2</groupId> | ||
<artifactId>retrofit</artifactId> | ||
<version>${retrofit.version}</version> | ||
</dependency> | ||
<dependency> | ||
<groupId>com.squareup.retrofit2</groupId> | ||
<artifactId>converter-jackson</artifactId> | ||
<version>${retrofit.version}</version> | ||
</dependency> | ||
</dependencies> | ||
|
||
<licenses> | ||
<license> | ||
<name>MIT License</name> | ||
<url>https://opensource.org/licenses/mit-license</url> | ||
<distribution>repo</distribution> | ||
</license> | ||
</licenses> | ||
|
||
</project> |
49 changes: 49 additions & 0 deletions
49
dina-client/src/main/java/ca/gc/aafc/dina/client/AccessTokenAuthenticator.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
package ca.gc.aafc.dina.client; | ||
|
||
import java.io.IOException; | ||
import okhttp3.Authenticator; | ||
import okhttp3.Request; | ||
import okhttp3.Response; | ||
import okhttp3.Route; | ||
|
||
import ca.gc.aafc.dina.client.token.AccessTokenManager; | ||
|
||
/** | ||
* OkHttp3 Authenticator that supports OpenID Connect access token. | ||
* | ||
* Usage: | ||
* OkHttpClient client = new OkHttpClient.Builder() | ||
* .authenticator(new AccessTokenAuthenticator(new AccessTokenManager(openIdConfig))) | ||
* .build(); | ||
* | ||
*/ | ||
public class AccessTokenAuthenticator implements Authenticator { | ||
|
||
private final AccessTokenManager accessTokenManager; | ||
|
||
public AccessTokenAuthenticator(AccessTokenManager accessTokenManager) { | ||
this.accessTokenManager = accessTokenManager; | ||
} | ||
|
||
@Override | ||
public Request authenticate(Route route, Response response) { | ||
final String accessToken; | ||
try { | ||
accessToken = accessTokenManager.getAccessToken(); | ||
} catch (IOException e) { | ||
throw new RuntimeException(e); | ||
} | ||
|
||
if (accessToken == null) { | ||
return null; | ||
} | ||
|
||
return newRequestWithBearerToken(response.request(), accessToken); | ||
} | ||
|
||
private Request newRequestWithBearerToken(Request request, String accessToken) { | ||
return request.newBuilder() | ||
.header("Authorization", "Bearer " + accessToken) | ||
.build(); | ||
} | ||
} |
24 changes: 24 additions & 0 deletions
24
dina-client/src/main/java/ca/gc/aafc/dina/client/TokenBasedRequestBuilder.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
package ca.gc.aafc.dina.client; | ||
|
||
import java.io.IOException; | ||
import okhttp3.Request; | ||
|
||
import ca.gc.aafc.dina.client.token.AccessTokenManager; | ||
|
||
/** | ||
* OkHttp Request builder that will use a AccessTokenManager to create a Request | ||
* with the Authorization header properly set. | ||
*/ | ||
public class TokenBasedRequestBuilder { | ||
|
||
private final AccessTokenManager accessTokenManager; | ||
|
||
public TokenBasedRequestBuilder(AccessTokenManager accessTokenManager) { | ||
this.accessTokenManager = accessTokenManager; | ||
} | ||
|
||
public Request.Builder newBuilder() throws IOException { | ||
return new Request.Builder().header("Authorization", "Bearer " + accessTokenManager.getAccessToken()); | ||
} | ||
|
||
} |
17 changes: 17 additions & 0 deletions
17
dina-client/src/main/java/ca/gc/aafc/dina/client/config/OpenIdConnectConfig.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
package ca.gc.aafc.dina.client.config; | ||
|
||
import lombok.Data; | ||
|
||
@Data | ||
public class OpenIdConnectConfig { | ||
|
||
/** | ||
* for Keycloak it should look like .../realms/dina/protocol/openid-connect/ | ||
*/ | ||
private String openIdConnectBaseUrl; | ||
|
||
private String clientId; | ||
private String username; | ||
private String password; | ||
|
||
} |
24 changes: 24 additions & 0 deletions
24
dina-client/src/main/java/ca/gc/aafc/dina/client/token/AccessToken.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
package ca.gc.aafc.dina.client.token; | ||
|
||
import lombok.Data; | ||
|
||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties; | ||
import com.fasterxml.jackson.databind.PropertyNamingStrategies; | ||
import com.fasterxml.jackson.databind.annotation.JsonNaming; | ||
|
||
/** | ||
* Represents an access token response from an OpenId Connect endpoint. | ||
*/ | ||
@JsonIgnoreProperties(ignoreUnknown = true) | ||
@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class) | ||
@Data | ||
public class AccessToken { | ||
|
||
private String clientId; | ||
private String tokenType; | ||
private String accessToken; | ||
|
||
private String refreshToken; | ||
private int expiresIn; | ||
|
||
} |
18 changes: 18 additions & 0 deletions
18
dina-client/src/main/java/ca/gc/aafc/dina/client/token/AccessTokenApiCall.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
package ca.gc.aafc.dina.client.token; | ||
|
||
import java.util.Map; | ||
import retrofit2.Call; | ||
import retrofit2.http.FieldMap; | ||
import retrofit2.http.FormUrlEncoded; | ||
import retrofit2.http.POST; | ||
|
||
/** | ||
* Retrofit based API call to an OpenId Connect endpoint to get or refresh tokens. | ||
*/ | ||
public interface AccessTokenApiCall { | ||
|
||
@FormUrlEncoded | ||
@POST("token") | ||
Call<AccessToken> callAccessTokenEndpoint(@FieldMap Map<String, Object> accessTokenRequest); | ||
|
||
} |
100 changes: 100 additions & 0 deletions
100
dina-client/src/main/java/ca/gc/aafc/dina/client/token/AccessTokenManager.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,100 @@ | ||
package ca.gc.aafc.dina.client.token; | ||
|
||
import ca.gc.aafc.dina.client.config.OpenIdConnectConfig; | ||
|
||
import java.io.IOException; | ||
import java.time.Instant; | ||
import lombok.extern.log4j.Log4j2; | ||
import retrofit2.Call; | ||
import retrofit2.Response; | ||
import retrofit2.Retrofit; | ||
import retrofit2.converter.jackson.JacksonConverterFactory; | ||
|
||
/** | ||
* Encapsulate the logic to acquire and refresh tokens. | ||
*/ | ||
@Log4j2 | ||
public class AccessTokenManager { | ||
|
||
private static final int BUFFER_IN_SEC = 10; | ||
|
||
private final AccessTokenApiCall accessTokenApiCall; | ||
private final OpenIdConnectConfig config; | ||
|
||
private String accessToken; | ||
// in seconds | ||
private int expiresIn; | ||
private String refreshToken; | ||
private Instant tokenInstant; | ||
|
||
public AccessTokenManager(OpenIdConnectConfig openIdConnectConfig) { | ||
Retrofit retrofit = new Retrofit.Builder() | ||
.baseUrl(openIdConnectConfig.getOpenIdConnectBaseUrl()) | ||
.addConverterFactory(JacksonConverterFactory.create()) | ||
.build(); | ||
|
||
accessTokenApiCall = retrofit.create(AccessTokenApiCall.class); | ||
this.config = openIdConnectConfig; | ||
} | ||
|
||
public synchronized String getAccessToken() throws IOException { | ||
|
||
// check if we already have a token | ||
if (accessToken == null) { | ||
acquireAccessToken(); | ||
return accessToken; | ||
} | ||
|
||
boolean isAlmostExpired = Instant.now().isAfter(tokenInstant.plusSeconds(expiresIn - BUFFER_IN_SEC)); | ||
if(!isAlmostExpired) { | ||
return accessToken; | ||
} else { | ||
if(!refreshAccessToken()) { | ||
acquireAccessToken(); | ||
} | ||
} | ||
|
||
return accessToken; | ||
} | ||
|
||
private boolean acquireAccessToken() throws IOException { | ||
log.debug("Acquire token"); | ||
Call<AccessToken> accessTokenCall = accessTokenApiCall.callAccessTokenEndpoint( | ||
AccessTokenRequest.newPasswordBased(config).toFieldMap()); | ||
Response<AccessToken> accessTokenResponse = accessTokenCall.execute(); | ||
|
||
if(!accessTokenResponse.isSuccessful()) { | ||
accessToken = null; | ||
return false; | ||
} | ||
|
||
AccessToken token = accessTokenResponse.body(); | ||
accessToken = token.getAccessToken(); | ||
expiresIn = token.getExpiresIn(); | ||
refreshToken = token.getRefreshToken(); | ||
tokenInstant = Instant.now(); | ||
return true; | ||
} | ||
|
||
private boolean refreshAccessToken() throws IOException { | ||
log.debug("Refresh token"); | ||
|
||
Call<AccessToken> accessTokenCall = accessTokenApiCall.callAccessTokenEndpoint( | ||
AccessTokenRequest.newRefreshTokenBased(config.getClientId(), refreshToken) | ||
.toFieldMap()); | ||
|
||
Response<AccessToken> accessTokenResponse = accessTokenCall.execute(); | ||
log.debug("Refreshing token successful: {}", accessTokenResponse::isSuccessful); | ||
|
||
if(!accessTokenResponse.isSuccessful()) { | ||
return false; | ||
} | ||
|
||
AccessToken token = accessTokenResponse.body(); | ||
accessToken = token.getAccessToken(); | ||
expiresIn = token.getExpiresIn(); | ||
refreshToken = token.getRefreshToken(); | ||
tokenInstant = Instant.now(); | ||
return true; | ||
} | ||
} |
Oops, something went wrong.