-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Dynamic Media with Open API: Optional IMS Authentication for metadata…
… requests to get full asset metadata (#73)
- Loading branch information
1 parent
3e461d7
commit 4073ce4
Showing
13 changed files
with
524 additions
and
44 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
44 changes: 44 additions & 0 deletions
44
src/main/java/io/wcm/handler/mediasource/ngdm/impl/metadata/AccessTokenResponse.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,44 @@ | ||
/* | ||
* #%L | ||
* wcm.io | ||
* %% | ||
* Copyright (C) 2024 wcm.io | ||
* %% | ||
* 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. | ||
* #L% | ||
*/ | ||
package io.wcm.handler.mediasource.ngdm.impl.metadata; | ||
|
||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties; | ||
import com.fasterxml.jackson.annotation.JsonProperty; | ||
|
||
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; | ||
|
||
/** | ||
* Used for Jackson Object mapping of JSON response from IMS Token v3 API. | ||
*/ | ||
@SuppressWarnings({ "checkstyle:VisibilityModifierCheck", "java:S1104" }) | ||
@SuppressFBWarnings("UUF_UNUSED_PUBLIC_OR_PROTECTED_FIELD") | ||
@JsonIgnoreProperties(ignoreUnknown = true) | ||
final class AccessTokenResponse { | ||
|
||
@JsonProperty("access_token") | ||
public String accessToken; | ||
|
||
@JsonProperty("token_type") | ||
public String tokenType; | ||
|
||
@JsonProperty("expires_in") | ||
public long expiresInSec; | ||
|
||
} |
135 changes: 135 additions & 0 deletions
135
src/main/java/io/wcm/handler/mediasource/ngdm/impl/metadata/ImsAccessTokenCache.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,135 @@ | ||
/* | ||
* #%L | ||
* wcm.io | ||
* %% | ||
* Copyright (C) 2024 wcm.io | ||
* %% | ||
* 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. | ||
* #L% | ||
*/ | ||
package io.wcm.handler.mediasource.ngdm.impl.metadata; | ||
|
||
import java.io.IOException; | ||
import java.nio.charset.StandardCharsets; | ||
import java.util.ArrayList; | ||
import java.util.List; | ||
import java.util.concurrent.TimeUnit; | ||
|
||
import org.apache.http.HttpStatus; | ||
import org.apache.http.NameValuePair; | ||
import org.apache.http.client.entity.UrlEncodedFormEntity; | ||
import org.apache.http.client.methods.CloseableHttpResponse; | ||
import org.apache.http.client.methods.HttpPost; | ||
import org.apache.http.impl.client.CloseableHttpClient; | ||
import org.apache.http.message.BasicNameValuePair; | ||
import org.apache.http.util.EntityUtils; | ||
import org.jetbrains.annotations.NotNull; | ||
import org.jetbrains.annotations.Nullable; | ||
import org.slf4j.Logger; | ||
import org.slf4j.LoggerFactory; | ||
|
||
import com.fasterxml.jackson.databind.json.JsonMapper; | ||
import com.github.benmanes.caffeine.cache.Cache; | ||
import com.github.benmanes.caffeine.cache.Caffeine; | ||
import com.github.benmanes.caffeine.cache.Expiry; | ||
|
||
/** | ||
* Manages IMS access tokens with expiration handling. | ||
*/ | ||
class ImsAccessTokenCache { | ||
|
||
private static final long EXPERIATION_BUFFER_SEC = 5; | ||
|
||
// cache IMS access tokens until they expire | ||
private final Cache<String, AccessTokenResponse> tokenCache = Caffeine.newBuilder() | ||
.expireAfter(new Expiry<String, AccessTokenResponse>() { | ||
@Override | ||
public long expireAfterCreate(String key, AccessTokenResponse value, long currentTime) { | ||
// substract a few secs from expiration time to be on the safe side | ||
return TimeUnit.SECONDS.toNanos(value.expiresInSec - EXPERIATION_BUFFER_SEC); | ||
} | ||
@Override | ||
public long expireAfterUpdate(String key, AccessTokenResponse value, long currentTime, long currentDuration) { | ||
// not used | ||
return Long.MAX_VALUE; | ||
} | ||
@Override | ||
public long expireAfterRead(String key, AccessTokenResponse value, long currentTime, long currentDuration) { | ||
// not used | ||
return Long.MAX_VALUE; | ||
} | ||
}) | ||
.build(); | ||
|
||
private static final JsonMapper OBJECT_MAPPER = new JsonMapper(); | ||
private static final Logger log = LoggerFactory.getLogger(ImsAccessTokenCache.class); | ||
|
||
private final CloseableHttpClient httpClient; | ||
private final String imsTokenApiUrl; | ||
|
||
ImsAccessTokenCache(@NotNull CloseableHttpClient httpClient, @NotNull String imsTokenApiUrl) { | ||
this.httpClient = httpClient; | ||
this.imsTokenApiUrl = imsTokenApiUrl; | ||
} | ||
|
||
/** | ||
* Get IMS OAuth access token | ||
* @param clientId Client ID | ||
* @param clientSecret Client Secret | ||
* @param scope Scope | ||
* @return Access token or null if access token could not be obtained | ||
*/ | ||
public @Nullable String getAccessToken(@NotNull String clientId, @NotNull String clientSecret, @NotNull String scope) { | ||
String key = clientId + "::" + scope; | ||
AccessTokenResponse accessTokenResponse = tokenCache.get(key, k -> createAccessToken(clientId, clientSecret, scope)); | ||
if (accessTokenResponse != null) { | ||
return accessTokenResponse.accessToken; | ||
} | ||
return null; | ||
} | ||
|
||
private @Nullable AccessTokenResponse createAccessToken(@NotNull String clientId, @NotNull String clientSecret, @NotNull String scope) { | ||
List<NameValuePair> formData = new ArrayList<>(); | ||
formData.add(new BasicNameValuePair("grant_type", "client_credentials")); | ||
formData.add(new BasicNameValuePair("client_id", clientId)); | ||
formData.add(new BasicNameValuePair("client_secret", clientSecret)); | ||
formData.add(new BasicNameValuePair("scope", scope)); | ||
|
||
HttpPost httpPost = new HttpPost(imsTokenApiUrl); | ||
httpPost.setEntity(new UrlEncodedFormEntity(formData, StandardCharsets.UTF_8)); | ||
|
||
try (CloseableHttpResponse response = httpClient.execute(httpPost)) { | ||
return processResponse(response); | ||
} | ||
catch (IOException ex) { | ||
log.warn("Unable to obtain access token from URL {}", imsTokenApiUrl, ex); | ||
return null; | ||
} | ||
} | ||
|
||
@SuppressWarnings("null") | ||
private @Nullable AccessTokenResponse processResponse(@NotNull CloseableHttpResponse response) throws IOException { | ||
if (response.getStatusLine().getStatusCode() == HttpStatus.SC_OK) { | ||
String jsonResponse = EntityUtils.toString(response.getEntity()); | ||
AccessTokenResponse accessTokenResponse = OBJECT_MAPPER.readValue(jsonResponse, AccessTokenResponse.class); | ||
log.trace("HTTP response for access token reqeust from {} returned a response, expires in {} sec", | ||
imsTokenApiUrl, accessTokenResponse.expiresInSec); | ||
return accessTokenResponse; | ||
} | ||
else { | ||
log.warn("Unexpected HTTP response for access token request from {}: {}", imsTokenApiUrl, response.getStatusLine()); | ||
return null; | ||
} | ||
} | ||
|
||
} |
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
Oops, something went wrong.