-
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
- Loading branch information
1 parent
3e461d7
commit 0d3aca8
Showing
3 changed files
with
224 additions
and
0 deletions.
There are no files selected for viewing
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; | ||
|
||
} |
136 changes: 136 additions & 0 deletions
136
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,136 @@ | ||
/* | ||
* #%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.checkerframework.checker.index.qual.NonNegative; | ||
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, @NonNegative long currentDuration) { | ||
// not used | ||
return Long.MAX_VALUE; | ||
} | ||
@Override | ||
public long expireAfterRead(String key, AccessTokenResponse value, long currentTime, @NonNegative 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; | ||
return tokenCache.get(key, k -> createAccessToken(clientId, clientSecret, scope)).accessToken; | ||
} | ||
|
||
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 { | ||
switch (response.getStatusLine().getStatusCode()) { | ||
case 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; | ||
case HttpStatus.SC_NOT_FOUND: | ||
log.trace("HTTP response for access token request from {} returns HTTP 404.", imsTokenApiUrl); | ||
break; | ||
default: | ||
log.warn("Unexpected HTTP response for access token request from {}: {}", imsTokenApiUrl, response.getStatusLine()); | ||
break; | ||
} | ||
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