diff --git a/checkstyle-suppressions.xml b/checkstyle-suppressions.xml
index d7d0b3c1124..03447fc1034 100644
--- a/checkstyle-suppressions.xml
+++ b/checkstyle-suppressions.xml
@@ -29,4 +29,10 @@
+
+
\ No newline at end of file
diff --git a/clients/go/zts/client.go b/clients/go/zts/client.go
index 1bce616e894..9694016f011 100644
--- a/clients/go/zts/client.go
+++ b/clients/go/zts/client.go
@@ -1156,17 +1156,22 @@ func (client ZTSClient) PostAccessTokenRequest(request AccessTokenRequest) (*Acc
}
}
-func (client ZTSClient) GetOIDCResponse(responseType string, clientId ServiceName, redirectUri string, scope string, state EntityName, nonce EntityName, keyType SimpleName, fullArn *bool, expiryTime *int32) (*OIDCResponse, string, error) {
+func (client ZTSClient) GetOIDCResponse(responseType string, clientId ServiceName, redirectUri string, scope string, state EntityName, nonce EntityName, keyType SimpleName, fullArn *bool, expiryTime *int32, output SimpleName) (*OIDCResponse, string, error) {
var data *OIDCResponse
- url := client.URL + "/oauth2/auth" + encodeParams(encodeStringParam("response_type", string(responseType), ""), encodeStringParam("client_id", string(clientId), ""), encodeStringParam("redirect_uri", string(redirectUri), ""), encodeStringParam("scope", string(scope), ""), encodeStringParam("state", string(state), ""), encodeStringParam("nonce", string(nonce), ""), encodeStringParam("keyType", string(keyType), ""), encodeOptionalBoolParam("fullArn", fullArn), encodeOptionalInt32Param("expiryTime", expiryTime))
+ url := client.URL + "/oauth2/auth" + encodeParams(encodeStringParam("response_type", string(responseType), ""), encodeStringParam("client_id", string(clientId), ""), encodeStringParam("redirect_uri", string(redirectUri), ""), encodeStringParam("scope", string(scope), ""), encodeStringParam("state", string(state), ""), encodeStringParam("nonce", string(nonce), ""), encodeStringParam("keyType", string(keyType), ""), encodeOptionalBoolParam("fullArn", fullArn), encodeOptionalInt32Param("expiryTime", expiryTime), encodeStringParam("output", string(output), ""))
resp, err := client.httpGet(url, nil)
if err != nil {
return nil, "", err
}
defer resp.Body.Close()
switch resp.StatusCode {
- case 302:
- data = nil
+ case 200, 302:
+ if 302 != resp.StatusCode {
+ err = json.NewDecoder(resp.Body).Decode(&data)
+ if err != nil {
+ return nil, "", err
+ }
+ }
location := resp.Header.Get(rdl.FoldHttpHeaderName("Location"))
return data, location, nil
default:
diff --git a/clients/go/zts/model.go b/clients/go/zts/model.go
index 6f778992c00..797fd9073be 100644
--- a/clients/go/zts/model.go
+++ b/clients/go/zts/model.go
@@ -2596,7 +2596,31 @@ type AccessTokenRequest string
// OIDCResponse -
type OIDCResponse struct {
- Location string `json:"location"`
+
+ //
+ // version number
+ //
+ Version int32 `json:"version"`
+
+ //
+ // id token
+ //
+ Id_token string `json:"id_token"`
+
+ //
+ // token type e.g. urn:ietf:params:oauth:token-type:id_token
+ //
+ Token_type string `json:"token_type"`
+
+ //
+ // response status
+ //
+ Success bool `json:"success"`
+
+ //
+ // expiration time in UTC
+ //
+ Expiration_time int64 `json:"expiration_time"`
}
// NewOIDCResponse - creates an initialized OIDCResponse instance, returns a pointer to it
@@ -2626,12 +2650,20 @@ func (self *OIDCResponse) UnmarshalJSON(b []byte) error {
// Validate - checks for missing required fields, etc
func (self *OIDCResponse) Validate() error {
- if self.Location == "" {
- return fmt.Errorf("OIDCResponse.location is missing but is a required field")
+ if self.Id_token == "" {
+ return fmt.Errorf("OIDCResponse.id_token is missing but is a required field")
+ } else {
+ val := rdl.Validate(ZTSSchema(), "String", self.Id_token)
+ if !val.Valid {
+ return fmt.Errorf("OIDCResponse.id_token does not contain a valid String (%v)", val.Error)
+ }
+ }
+ if self.Token_type == "" {
+ return fmt.Errorf("OIDCResponse.token_type is missing but is a required field")
} else {
- val := rdl.Validate(ZTSSchema(), "String", self.Location)
+ val := rdl.Validate(ZTSSchema(), "String", self.Token_type)
if !val.Valid {
- return fmt.Errorf("OIDCResponse.location does not contain a valid String (%v)", val.Error)
+ return fmt.Errorf("OIDCResponse.token_type does not contain a valid String (%v)", val.Error)
}
}
return nil
diff --git a/clients/go/zts/zts_schema.go b/clients/go/zts/zts_schema.go
index cd628bdd896..2fbbd02d249 100644
--- a/clients/go/zts/zts_schema.go
+++ b/clients/go/zts/zts_schema.go
@@ -354,7 +354,11 @@ func init() {
sb.AddType(tAccessTokenRequest.Build())
tOIDCResponse := rdl.NewStructTypeBuilder("Struct", "OIDCResponse")
- tOIDCResponse.Field("location", "String", false, nil, "")
+ tOIDCResponse.Field("version", "Int32", false, nil, "version number")
+ tOIDCResponse.Field("id_token", "String", false, nil, "id token")
+ tOIDCResponse.Field("token_type", "String", false, nil, "token type e.g. urn:ietf:params:oauth:token-type:id_token")
+ tOIDCResponse.Field("success", "Bool", false, nil, "response status")
+ tOIDCResponse.Field("expiration_time", "Int64", false, nil, "expiration time in UTC")
sb.AddType(tOIDCResponse.Build())
tInstanceRegisterInformation := rdl.NewStructTypeBuilder("Struct", "InstanceRegisterInformation")
@@ -1038,9 +1042,9 @@ func init() {
mGetOIDCResponse.Input("keyType", "SimpleName", false, "keyType", "", true, nil, "optional signing key type - RSA or EC. Might be ignored if server doesn't have the requested type configured")
mGetOIDCResponse.Input("fullArn", "Bool", false, "fullArn", "", true, false, "flag to indicate to use full arn in group claim (e.g. sports:role.deployer instead of deployer)")
mGetOIDCResponse.Input("expiryTime", "Int32", false, "expiryTime", "", true, nil, "optional expiry period specified in seconds")
+ mGetOIDCResponse.Input("output", "SimpleName", false, "output", "", true, nil, "optional output format of json")
mGetOIDCResponse.Output("location", "String", "Location", false, "return location header with id token")
mGetOIDCResponse.Auth("", "", true, "")
- mGetOIDCResponse.Expected("FOUND")
mGetOIDCResponse.Exception("BAD_REQUEST", "ResourceError", "")
mGetOIDCResponse.Exception("FORBIDDEN", "ResourceError", "")
mGetOIDCResponse.Exception("NOT_FOUND", "ResourceError", "")
diff --git a/clients/java/zts/examples/tls-support/pom.xml b/clients/java/zts/examples/tls-support/pom.xml
index 26c875e1d23..ddf25777f4b 100644
--- a/clients/java/zts/examples/tls-support/pom.xml
+++ b/clients/java/zts/examples/tls-support/pom.xml
@@ -110,4 +110,4 @@
-
+
\ No newline at end of file
diff --git a/clients/java/zts/src/main/java/com/yahoo/athenz/zts/ZTSClient.java b/clients/java/zts/src/main/java/com/yahoo/athenz/zts/ZTSClient.java
index af5742bd22c..1d33cb5b673 100644
--- a/clients/java/zts/src/main/java/com/yahoo/athenz/zts/ZTSClient.java
+++ b/clients/java/zts/src/main/java/com/yahoo/athenz/zts/ZTSClient.java
@@ -26,7 +26,6 @@
import java.nio.file.Path;
import java.nio.file.Paths;
import java.security.PrivateKey;
-import java.security.PublicKey;
import java.security.cert.Certificate;
import java.security.cert.CertificateParsingException;
import java.security.cert.X509Certificate;
@@ -46,7 +45,6 @@
import com.oath.auth.KeyRefresherException;
import com.oath.auth.KeyRefresherListener;
import com.oath.auth.Utils;
-import com.yahoo.athenz.auth.token.IdToken;
import com.yahoo.athenz.auth.token.jwts.JwtsSigningKeyResolver;
import org.apache.http.HttpHost;
import org.apache.http.client.config.RequestConfig;
@@ -169,7 +167,7 @@ public class ZTSClient implements Closeable {
final static ConcurrentHashMap ROLE_TOKEN_CACHE = new ConcurrentHashMap<>();
final static ConcurrentHashMap ACCESS_TOKEN_CACHE = new ConcurrentHashMap<>();
final static ConcurrentHashMap AWS_CREDS_CACHE = new ConcurrentHashMap<>();
- final static ConcurrentHashMap ID_TOKEN_CACHE = new ConcurrentHashMap<>();
+ final static ConcurrentHashMap ID_TOKEN_CACHE = new ConcurrentHashMap<>();
private static final Queue PREFETCH_SCHEDULED_ITEMS = new ConcurrentLinkedQueue<>();
private static Timer FETCH_TIMER;
@@ -1752,13 +1750,13 @@ static void processPrefetchTask(PrefetchTokenScheduledItem item, ZTSClient itemZ
case ID:
- String idToken = itemZtsClient.getIDToken(item.responseType, item.idTokenServiceName,
+ OIDCResponse oidcResponse = itemZtsClient.getIDToken(item.responseType, item.idTokenServiceName,
item.redirectUri, item.scope, item.state, item.keyType, item.fullArn,
item.maxDuration, true);
- if (!idToken.isEmpty()) {
- item.setExpiresAtUTC(extractIdTokenExpiry(idToken));
- }
+ // update the expiry time
+
+ item.setExpiresAtUTC(oidcResponse.getExpiration_time());
}
// update the fetch/fail times
@@ -1926,28 +1924,16 @@ public boolean prefetchAccessToken(String domainName, List roleNames,
public boolean prefetchIdToken(String responseType, String clientId, String redirectUri, String scope,
String state, String keyType, Boolean fullArn, Integer expiryTime) {
- String idToken = getIDToken(responseType, clientId, redirectUri, scope, state,
+ OIDCResponse oidcResponse = getIDToken(responseType, clientId, redirectUri, scope, state,
keyType, fullArn, expiryTime, true);
- if (isEmpty(idToken)) {
+ if (oidcResponse == null) {
LOG.error("PrefetchToken: No id token fetchable for client id={} and scope={}", clientId, scope);
return false;
}
return prefetchToken(null, null, null, null, expiryTime, null, null, clientId, null,
responseType, redirectUri, scope, state, keyType, fullArn,
- extractIdTokenExpiry(idToken), TokenType.ID);
- }
-
- protected static long extractIdTokenExpiry(final String idToken) {
-
- // strip out the signature part
- int idx = idToken.lastIndexOf('.');
- if (idx == -1) {
- return 0;
- }
-
- IdToken token = new IdToken(idToken.substring(0, idx + 1), (PublicKey) null);
- return token.getExpiryTime();
+ oidcResponse.getExpiration_time(), TokenType.ID);
}
boolean prefetchToken(String domainName, String roleName, List roleNames,
@@ -3094,7 +3080,7 @@ public Info getInfo() {
* server default timeout.
* @return ZTS generated ID Token String. ZTSClientException will be thrown in case of failure
*/
- public String getIDToken(String domainName, String roleName, String clientId, String redirectUriSuffix,
+ public OIDCResponse getIDToken(String domainName, String roleName, String clientId, String redirectUriSuffix,
boolean fullArn, Integer expiryTime) {
return getIDToken(domainName, Collections.singletonList(roleName), clientId,
redirectUriSuffix, fullArn, expiryTime);
@@ -3116,7 +3102,7 @@ public String getIDToken(String domainName, String roleName, String clientId, St
* server default timeout.
* @return ZTS generated ID Token String. ZTSClientException will be thrown in case of failure
*/
- public String getIDToken(String domainName, List roleNames, String clientId, String redirectUriSuffix,
+ public OIDCResponse getIDToken(String domainName, List roleNames, String clientId, String redirectUriSuffix,
boolean fullArn, Integer expiryTime) {
final String redirectUri = generateRedirectUri(clientId, redirectUriSuffix);
final String scope = generateIdTokenScope(domainName, roleNames);
@@ -3140,7 +3126,7 @@ public String getIDToken(String domainName, List roleNames, String clien
* server default timeout.
* @return ZTS generated ID Token String. ZTSClientException will be thrown in case of failure
*/
- public String getIDToken(String responseType, String clientId, String redirectUri, String scope, String state,
+ public OIDCResponse getIDToken(String responseType, String clientId, String redirectUri, String scope, String state,
String keyType, Boolean fullArn, Integer expiryTime, boolean ignoreCache) {
// check for required attributes
@@ -3149,7 +3135,7 @@ public String getIDToken(String responseType, String clientId, String redirectUr
throw new ZTSClientException(ResourceException.BAD_REQUEST, "missing required attribute(s)");
}
- String idToken;
+ OIDCResponse oidcResponse = null;
// first lookup in our cache to see if it can be satisfied
// only if we're not asked to ignore the cache
@@ -3158,18 +3144,18 @@ public String getIDToken(String responseType, String clientId, String redirectUr
if (!cacheDisabled) {
cacheKey = getIdTokenCacheKey(responseType, clientId, redirectUri, scope, state, keyType, fullArn);
if (cacheKey != null && !ignoreCache) {
- idToken = lookupIdTokenResponseInCache(cacheKey, expiryTime);
- if (idToken != null) {
- return idToken;
+ oidcResponse = lookupIdTokenResponseInCache(cacheKey, expiryTime);
+ if (oidcResponse != null) {
+ return oidcResponse;
}
// start prefetch for this token if prefetch is enabled
if (enablePrefetch && prefetchAutoEnable) {
if (prefetchIdToken(responseType, clientId, redirectUri, scope,
state, keyType, fullArn, expiryTime)) {
- idToken = lookupIdTokenResponseInCache(cacheKey, expiryTime);
+ oidcResponse = lookupIdTokenResponseInCache(cacheKey, expiryTime);
}
- if (idToken != null) {
- return idToken;
+ if (oidcResponse != null) {
+ return oidcResponse;
}
LOG.error("GetIdToken: cache prefetch and lookup error");
}
@@ -3181,10 +3167,8 @@ public String getIDToken(String responseType, String clientId, String redirectUr
updateServicePrincipal();
try {
Map> responseHeaders = new HashMap<>();
- ztsClient.getOIDCResponse(responseType, clientId, redirectUri, scope,
- state, Crypto.randomSalt(), keyType, fullArn, expiryTime, responseHeaders);
-
- idToken = extractIdTokenFromLocation(responseHeaders, redirectUri, state);
+ oidcResponse = ztsClient.getOIDCResponse(responseType, clientId, redirectUri, scope,
+ state, Crypto.randomSalt(), keyType, fullArn, expiryTime, "json", responseHeaders);
} catch (ResourceException ex) {
@@ -3193,9 +3177,9 @@ public String getIDToken(String responseType, String clientId, String redirectUr
// if we have an entry in our cache then we'll return that
// instead of returning failure
- idToken = lookupIdTokenResponseInCache(cacheKey, -1);
- if (idToken != null) {
- return idToken;
+ oidcResponse = lookupIdTokenResponseInCache(cacheKey, -1);
+ if (oidcResponse != null) {
+ return oidcResponse;
}
}
throw new ZTSClientException(ex.getCode(), ex.getData());
@@ -3206,9 +3190,9 @@ public String getIDToken(String responseType, String clientId, String redirectUr
// instead of returning failure
if (cacheKey != null && !ignoreCache) {
- idToken = lookupIdTokenResponseInCache(cacheKey, -1);
- if (idToken != null) {
- return idToken;
+ oidcResponse = lookupIdTokenResponseInCache(cacheKey, -1);
+ if (oidcResponse != null) {
+ return oidcResponse;
}
}
throw new ZTSClientException(ResourceException.BAD_REQUEST, ex.getMessage());
@@ -3223,27 +3207,37 @@ public String getIDToken(String responseType, String clientId, String redirectUr
keyType, fullArn);
}
if (cacheKey != null) {
- ID_TOKEN_CACHE.put(cacheKey, new IdTokenCacheEntry(idToken, extractIdTokenExpiry(idToken)));
+ ID_TOKEN_CACHE.put(cacheKey, oidcResponse);
}
}
- return idToken;
+ return oidcResponse;
}
- String lookupIdTokenResponseInCache(String cacheKey, Integer expiryTime) {
+ OIDCResponse lookupIdTokenResponseInCache(String cacheKey, Integer expirySeconds) {
- IdTokenCacheEntry idTokenCacheEntry = ID_TOKEN_CACHE.get(cacheKey);
- if (idTokenCacheEntry == null) {
+ OIDCResponse oidcResponse = ID_TOKEN_CACHE.get(cacheKey);
+ if (oidcResponse == null) {
if (LOG.isInfoEnabled()) {
LOG.info("LookupIdTokenResponseInCache: cache-lookup key: {} result: not found", cacheKey);
}
return null;
}
+ long now = System.currentTimeMillis() / 1000;
+ long tokenExpiryTime = oidcResponse.getExpiration_time();
+
// default timeout for id tokens is 1 hour
- if (expiryTime == null) {
- expiryTime = 60 * 60;
+ if (expirySeconds == null) {
+ expirySeconds = 60 * 60;
+ }
+
+ // if our expiry seconds is -1 then we should return
+ // our cached object as long as it's not expired
+
+ if (expirySeconds == -1 && tokenExpiryTime > now) {
+ return oidcResponse;
}
// before returning our cache hit we need to make sure it
@@ -3251,37 +3245,17 @@ String lookupIdTokenResponseInCache(String cacheKey, Integer expiryTime) {
// if the expiryTime is -1 then we return the token as
// long as it's not expired
- if (idTokenCacheEntry.isExpired(expiryTime)) {
- if (idTokenCacheEntry.isExpired(-1)) {
+ if (tokenExpiryTime < now + expirySeconds / 4) {
+
+ // if the token is completely expired then we'll remove it from the cache
+
+ if (tokenExpiryTime <= now) {
ID_TOKEN_CACHE.remove(cacheKey);
}
return null;
}
- return idTokenCacheEntry.getIdToken();
- }
-
- String extractIdTokenFromLocation(Map> responseHeaders, final String redirectUri,
- final String state) {
-
- //the format of the location header is #id_token=&state=
- List locationValues = responseHeaders.get("location");
- if (isEmpty(locationValues)) {
- return "";
- }
- final String location = locationValues.get(0);
- final String prefix = redirectUri + "#id_token=";
- if (!location.startsWith(prefix)) {
- return "";
- }
- if (isEmpty(state)) {
- return location.substring(prefix.length());
- }
- final String suffix = "&state=" + state;
- if (!location.endsWith(suffix)) {
- return "";
- }
- return location.substring(prefix.length(), prefix.length() + location.length() - prefix.length() - suffix.length());
+ return oidcResponse;
}
String generateIdTokenScope(final String domainName, List roleNames) {
diff --git a/clients/java/zts/src/main/java/com/yahoo/athenz/zts/ZTSRDLGeneratedClient.java b/clients/java/zts/src/main/java/com/yahoo/athenz/zts/ZTSRDLGeneratedClient.java
index c2ae56aab2f..e92142f5f6b 100644
--- a/clients/java/zts/src/main/java/com/yahoo/athenz/zts/ZTSRDLGeneratedClient.java
+++ b/clients/java/zts/src/main/java/com/yahoo/athenz/zts/ZTSRDLGeneratedClient.java
@@ -930,7 +930,7 @@ public AccessTokenResponse postAccessTokenRequest(String request) throws URISynt
}
}
- public OIDCResponse getOIDCResponse(String responseType, String clientId, String redirectUri, String scope, String state, String nonce, String keyType, Boolean fullArn, Integer expiryTime, java.util.Map> headers) throws URISyntaxException, IOException {
+ public OIDCResponse getOIDCResponse(String responseType, String clientId, String redirectUri, String scope, String state, String nonce, String keyType, Boolean fullArn, Integer expiryTime, String output, java.util.Map> headers) throws URISyntaxException, IOException {
UriTemplateBuilder uriTemplateBuilder = new UriTemplateBuilder(baseUrl, "/oauth2/auth");
URIBuilder uriBuilder = new URIBuilder(uriTemplateBuilder.getUri());
if (responseType != null) {
@@ -960,6 +960,9 @@ public OIDCResponse getOIDCResponse(String responseType, String clientId, String
if (expiryTime != null) {
uriBuilder.setParameter("expiryTime", String.valueOf(expiryTime));
}
+ if (output != null) {
+ uriBuilder.setParameter("output", output);
+ }
HttpUriRequest httpUriRequest = RequestBuilder.get()
.setUri(uriBuilder.build())
.build();
@@ -971,11 +974,15 @@ public OIDCResponse getOIDCResponse(String responseType, String clientId, String
int code = httpResponse.getStatusLine().getStatusCode();
httpResponseEntity = httpResponse.getEntity();
switch (code) {
+ case 200:
case 302:
if (headers != null) {
headers.put("location", List.of(httpResponse.getFirstHeader("Location").getValue()));
}
- return null;
+ if (code == 302) {
+ return null;
+ }
+ return jsonMapper.readValue(httpResponseEntity.getContent(), OIDCResponse.class);
default:
final String errorData = (httpResponseEntity == null) ? null : EntityUtils.toString(httpResponseEntity);
throw (errorData != null && !errorData.isEmpty())
diff --git a/clients/java/zts/src/test/java/com/yahoo/athenz/zts/ZTSClientTest.java b/clients/java/zts/src/test/java/com/yahoo/athenz/zts/ZTSClientTest.java
index bfd9c0971be..f49062a3df3 100644
--- a/clients/java/zts/src/test/java/com/yahoo/athenz/zts/ZTSClientTest.java
+++ b/clients/java/zts/src/test/java/com/yahoo/athenz/zts/ZTSClientTest.java
@@ -3682,38 +3682,6 @@ public void testEncodeAWSRoleName() {
client.close();
}
- @Test
- public void testExtractIdTokenFromLocation() {
-
- System.setProperty(ZTSClient.ZTS_CLIENT_PROP_ATHENZ_CONF, "src/test/resources/athenz.conf");
- ZTSClient.initConfigValues();
- Principal principal = SimplePrincipal.create("user_domain", "user",
- "v=S1;d=user_domain;n=user;s=sig", PRINCIPAL_AUTHORITY);
- ZTSClient client = new ZTSClient(null, principal);
-
- Map> responseHeaders = new HashMap<>();
- responseHeaders.put("agent", Collections.singletonList("my-agent"));
-
- assertEquals(client.extractIdTokenFromLocation(responseHeaders, "https://athenz.io", null), "");
-
- List locationValues = new ArrayList<>();
- responseHeaders.put("location", locationValues);
- assertEquals(client.extractIdTokenFromLocation(responseHeaders, "https://athenz.io", null), "");
-
- locationValues.add("https://athenz.io#id_token=token-value");
- assertEquals(client.extractIdTokenFromLocation(responseHeaders, "https://api.athenz.io", null), "");
- assertEquals(client.extractIdTokenFromLocation(responseHeaders, "https://api.athenz.io", ""), "");
- assertEquals(client.extractIdTokenFromLocation(responseHeaders, "https://athenz.io", null), "token-value");
-
- locationValues.clear();
- locationValues.add("https://athenz.io#id_token=token-value&state=abc123");
- assertEquals(client.extractIdTokenFromLocation(responseHeaders, "https://athenz.io", "test"), "");
- assertEquals(client.extractIdTokenFromLocation(responseHeaders, "https://athenz.io", "abc123"), "token-value");
-
- System.clearProperty(ZTSClient.ZTS_CLIENT_PROP_ATHENZ_CONF);
- client.close();
- }
-
@Test
public void testGenerateIdTokenScope() {
@@ -3754,18 +3722,6 @@ public void testGenerateRedirectUri() {
client.close();
}
- @Test
- public void testExtractIdTokenExpiry() {
- String token = AccessTokenTestFileHelper.getSignedAccessToken(3600);
- assertTrue(ZTSClient.extractIdTokenExpiry(token) != 0);
- assertEquals(ZTSClient.extractIdTokenExpiry("invalid"), 0);
- try {
- ZTSClient.extractIdTokenExpiry("invalid.token.signature");
- fail();
- } catch (Exception ignored) {
- }
- }
-
@Test
public void testGetIdTokenCacheKey() {
System.setProperty(ZTSClient.ZTS_CLIENT_PROP_ATHENZ_CONF, "src/test/resources/athenz.conf");
@@ -3803,7 +3759,9 @@ public void testLookupIdTokenResponseInCache() throws InterruptedException {
assertNull(client.lookupIdTokenResponseInCache(cacheKey, 3600));
- ZTSClient.ID_TOKEN_CACHE.put(cacheKey, new IdTokenCacheEntry("token", System.currentTimeMillis() / 1000 + 3600));
+ ZTSClient.ID_TOKEN_CACHE.put(cacheKey,
+ new OIDCResponse().setId_token("token")
+ .setExpiration_time(System.currentTimeMillis() / 1000 + 3600));
// with standard 1 hour check, our entry is not expired
@@ -3821,8 +3779,9 @@ public void testLookupIdTokenResponseInCache() throws InterruptedException {
// add a second entry with 1 second timeout
- ZTSClient.ID_TOKEN_CACHE.put(cacheKey, new IdTokenCacheEntry("token", System.currentTimeMillis() / 1000 + 1));
-
+ ZTSClient.ID_TOKEN_CACHE.put(cacheKey,
+ new OIDCResponse().setId_token("token")
+ .setExpiration_time(System.currentTimeMillis() / 1000 + 1));
// sleep a second and then ask for a cache entry
Thread.sleep(1000);
@@ -3847,39 +3806,39 @@ public void testGetIdToken() {
ZTSClient.setPrefetchAutoEnable(true);
client.setEnablePrefetch(true);
- String idToken = client.getIDToken("sports", "readers", "sys.auth.gcp",
+ OIDCResponse oidcResponse = client.getIDToken("sports", "readers", "sys.auth.gcp",
"gcp.athenz.io", true, null);
- assertNotNull(idToken);
+ assertNotNull(oidcResponse);
// passing the role name as a list should give us the same token back
// as we should be caching our results
- String idToken2 = client.getIDToken("sports", Collections.singletonList("readers"), "sys.auth.gcp",
+ OIDCResponse oidcResponse2 = client.getIDToken("sports", Collections.singletonList("readers"), "sys.auth.gcp",
"gcp.athenz.io", true, null);
- assertNotNull(idToken2);
- assertEquals(idToken, idToken2);
+ assertNotNull(oidcResponse2);
+ assertEquals(oidcResponse, oidcResponse2);
// now let's pass with the expiry time of 1 hour, and we still should get
// back the same token from the cache
- idToken2 = client.getIDToken("sports", Collections.singletonList("readers"), "sys.auth.gcp",
+ oidcResponse2 = client.getIDToken("sports", Collections.singletonList("readers"), "sys.auth.gcp",
"gcp.athenz.io", true, 3600);
- assertNotNull(idToken2);
- assertEquals(idToken, idToken2);
+ assertNotNull(oidcResponse2);
+ assertEquals(oidcResponse, oidcResponse2);
// now let's try with the full api and ignore cache disabled
- idToken2 = client.getIDToken("id_token", "sys.auth.gcp", "https://gcp.sys-auth.gcp.athenz.io",
+ oidcResponse2 = client.getIDToken("id_token", "sys.auth.gcp", "https://gcp.sys-auth.gcp.athenz.io",
"openid sports:role.readers", null, "EC", true, 3600, false);
- assertNotNull(idToken2);
- assertEquals(idToken, idToken2);
+ assertNotNull(oidcResponse2);
+ assertEquals(oidcResponse, oidcResponse2);
// finally let's try with cached disabled, and we should get a new token
- idToken2 = client.getIDToken("id_token", "sys.auth.gcp", "https://gcp.sys-auth.gcp.athenz.io",
+ oidcResponse2 = client.getIDToken("id_token", "sys.auth.gcp", "https://gcp.sys-auth.gcp.athenz.io",
"openid sports:role.readers", null, "EC", true, 3600, true);
- assertNotNull(idToken2);
- assertNotEquals(idToken, idToken2);
+ assertNotNull(oidcResponse2);
+ assertNotEquals(oidcResponse, oidcResponse2);
client.close();
}
@@ -4022,19 +3981,19 @@ public void testPrefetchIdTokenShouldNotCallServer() throws Exception {
int scheduledItemsSize2 = client.getScheduledItemsSize();
assertEquals(scheduledItemsSize, scheduledItemsSize2);
- String idToken = client.getIDToken("id_token", "sys.auth.gcp", "https://gcp.sys-auth.gcp.athenz.io",
+ OIDCResponse oidcResponse = client.getIDToken("id_token", "sys.auth.gcp", "https://gcp.sys-auth.gcp.athenz.io",
"openid sports:role.readers", null, "EC", true, 8, false);
- assertNotNull(idToken);
- assertFalse(idToken.isEmpty());
+ assertNotNull(oidcResponse);
+ assertFalse(oidcResponse.getId_token().isEmpty());
client.prefetchIdToken("id_token", "sys.auth.gcp", "https://gcp.sys-auth.gcp.athenz.io",
"openid sports:role.writers", null, "EC", true, 8);
assertEquals(client.getScheduledItemsSize(), scheduledItemsSize + 1);
- String idToken2 = client.getIDToken("id_token", "sys.auth.gcp", "https://gcp.sys-auth.gcp.athenz.io",
+ OIDCResponse oidcResponse2 = client.getIDToken("id_token", "sys.auth.gcp", "https://gcp.sys-auth.gcp.athenz.io",
"openid sports:role.writers", null, "EC", true, 8, false);
- long rt2Expiry = ZTSClient.extractIdTokenExpiry(idToken2);
- assertNotNull(idToken2);
+ assertNotNull(oidcResponse2);
+ long rt2Expiry = oidcResponse2.getExpiration_time();
System.out.println("testPrefetchIdTokenShouldNotCallServer: sleep Secs=5");
Thread.sleep(5000);
@@ -4045,9 +4004,9 @@ public void testPrefetchIdTokenShouldNotCallServer() throws Exception {
long lastTokenFetchedTime1 = ztsClientMock.getLastIdTokenFetchedTime("openid sports:role.readers");
assertTrue(lastTokenFetchedTime1 > 0);
- idToken2 = client.getIDToken("id_token", "sys.auth.gcp", "https://gcp.sys-auth.gcp.athenz.io",
+ oidcResponse2 = client.getIDToken("id_token", "sys.auth.gcp", "https://gcp.sys-auth.gcp.athenz.io",
"openid sports:role.writers", null, "EC", true, 8, false);
- long rt2Expiry2 = ZTSClient.extractIdTokenExpiry(idToken2);
+ long rt2Expiry2 = oidcResponse2.getExpiration_time();
assertTrue(rt2Expiry2 > rt2Expiry); // this token was refreshed
@@ -4056,9 +4015,9 @@ public void testPrefetchIdTokenShouldNotCallServer() throws Exception {
Thread.sleep(5000);
System.out.println("testPrefetchIdTokenShouldNotCallServer: again nap over so what happened");
- idToken2 = client.getIDToken("id_token", "sys.auth.gcp", "https://gcp.sys-auth.gcp.athenz.io",
+ oidcResponse2 = client.getIDToken("id_token", "sys.auth.gcp", "https://gcp.sys-auth.gcp.athenz.io",
"openid sports:role.writers", null, "EC", true, 8, false);
- long rt2Expiry3 = ZTSClient.extractIdTokenExpiry(idToken2);
+ long rt2Expiry3 = oidcResponse2.getExpiration_time();
assertTrue(rt2Expiry3 > rt2Expiry2); // this token was refreshed
ZTSClient.cancelPrefetch();
diff --git a/clients/java/zts/src/test/java/com/yahoo/athenz/zts/ZTSRDLClientMock.java b/clients/java/zts/src/test/java/com/yahoo/athenz/zts/ZTSRDLClientMock.java
index a5bc5c9c3fa..f7572b72845 100644
--- a/clients/java/zts/src/test/java/com/yahoo/athenz/zts/ZTSRDLClientMock.java
+++ b/clients/java/zts/src/test/java/com/yahoo/athenz/zts/ZTSRDLClientMock.java
@@ -241,7 +241,7 @@ public AccessTokenResponse postAccessTokenRequest(String request) {
@Override
public OIDCResponse getOIDCResponse(String responseType, String clientId, String redirectUri, String scope,
String state, String nonce, String keyType, Boolean fullArn, Integer expiryTime,
- Map> headers) throws URISyntaxException, IOException {
+ String output, Map> headers) throws URISyntaxException, IOException {
// some exception test cases based on the state value
if (state != null) {
@@ -255,17 +255,19 @@ public OIDCResponse getOIDCResponse(String responseType, String clientId, String
// process our request, generate a token and return
- if (headers != null) {
- //the format of the location header is #id_token=&state=
- String token = AccessTokenTestFileHelper.getSignedAccessToken(expiryTime == null ? 3600 : expiryTime);
- String location = redirectUri + "#id_token=" + token;
- if (state != null) {
- location = location.concat("&state=" + state);
- }
- headers.put("location", List.of(location));
- lastIdTokenFetchedTime.put(scope, System.currentTimeMillis());
+ if (expiryTime == null) {
+ expiryTime = 3600;
}
- return null;
+ String token = AccessTokenTestFileHelper.getSignedAccessToken(expiryTime);
+ OIDCResponse oidcResponse = new OIDCResponse()
+ .setExpiration_time(System.currentTimeMillis() / 1000 + expiryTime)
+ .setVersion(1)
+ .setSuccess(true)
+ .setToken_type("urn:ietf:params:oauth:token-type:id_token")
+ .setId_token(token);
+ lastIdTokenFetchedTime.put(scope, System.currentTimeMillis());
+
+ return oidcResponse;
}
@Override
diff --git a/core/zts/src/main/java/com/yahoo/athenz/zts/OIDCResponse.java b/core/zts/src/main/java/com/yahoo/athenz/zts/OIDCResponse.java
index 9c59b57d40c..5281ef1dda3 100644
--- a/core/zts/src/main/java/com/yahoo/athenz/zts/OIDCResponse.java
+++ b/core/zts/src/main/java/com/yahoo/athenz/zts/OIDCResponse.java
@@ -11,14 +11,46 @@
//
@JsonIgnoreProperties(ignoreUnknown = true)
public class OIDCResponse {
- public String location;
+ public int version;
+ public String id_token;
+ public String token_type;
+ public boolean success;
+ public long expiration_time;
- public OIDCResponse setLocation(String location) {
- this.location = location;
+ public OIDCResponse setVersion(int version) {
+ this.version = version;
return this;
}
- public String getLocation() {
- return location;
+ public int getVersion() {
+ return version;
+ }
+ public OIDCResponse setId_token(String id_token) {
+ this.id_token = id_token;
+ return this;
+ }
+ public String getId_token() {
+ return id_token;
+ }
+ public OIDCResponse setToken_type(String token_type) {
+ this.token_type = token_type;
+ return this;
+ }
+ public String getToken_type() {
+ return token_type;
+ }
+ public OIDCResponse setSuccess(boolean success) {
+ this.success = success;
+ return this;
+ }
+ public boolean getSuccess() {
+ return success;
+ }
+ public OIDCResponse setExpiration_time(long expiration_time) {
+ this.expiration_time = expiration_time;
+ return this;
+ }
+ public long getExpiration_time() {
+ return expiration_time;
}
@Override
@@ -28,7 +60,19 @@ public boolean equals(Object another) {
return false;
}
OIDCResponse a = (OIDCResponse) another;
- if (location == null ? a.location != null : !location.equals(a.location)) {
+ if (version != a.version) {
+ return false;
+ }
+ if (id_token == null ? a.id_token != null : !id_token.equals(a.id_token)) {
+ return false;
+ }
+ if (token_type == null ? a.token_type != null : !token_type.equals(a.token_type)) {
+ return false;
+ }
+ if (success != a.success) {
+ return false;
+ }
+ if (expiration_time != a.expiration_time) {
return false;
}
}
diff --git a/core/zts/src/main/java/com/yahoo/athenz/zts/ZTSSchema.java b/core/zts/src/main/java/com/yahoo/athenz/zts/ZTSSchema.java
index bd7b7097b58..e3e79d866bb 100644
--- a/core/zts/src/main/java/com/yahoo/athenz/zts/ZTSSchema.java
+++ b/core/zts/src/main/java/com/yahoo/athenz/zts/ZTSSchema.java
@@ -303,7 +303,11 @@ private static Schema build() {
sb.structType("OIDCResponse")
- .field("location", "String", false, "");
+ .field("version", "Int32", false, "version number")
+ .field("id_token", "String", false, "id token")
+ .field("token_type", "String", false, "token type e.g. urn:ietf:params:oauth:token-type:id_token")
+ .field("success", "Bool", false, "response status")
+ .field("expiration_time", "Int64", false, "expiration time in UTC");
sb.structType("InstanceRegisterInformation")
.field("provider", "ServiceName", false, "the provider service name (i.e. \"aws.us-west-2\", \"sys.openstack.cluster1\")")
@@ -1055,9 +1059,10 @@ private static Schema build() {
.queryParam("keyType", "keyType", "SimpleName", null, "optional signing key type - RSA or EC. Might be ignored if server doesn't have the requested type configured")
.queryParam("fullArn", "fullArn", "Bool", false, "flag to indicate to use full arn in group claim (e.g. sports:role.deployer instead of deployer)")
.queryParam("expiryTime", "expiryTime", "Int32", null, "optional expiry period specified in seconds")
+ .queryParam("output", "output", "SimpleName", null, "optional output format of json")
.output("Location", "location", "String", "return location header with id token")
.auth("", "", true)
- .expected("FOUND")
+ .expected("OK")
.exception("BAD_REQUEST", "ResourceError", "")
.exception("FORBIDDEN", "ResourceError", "")
diff --git a/core/zts/src/main/rdl/OAuth.rdli b/core/zts/src/main/rdl/OAuth.rdli
index e136d9d75c6..6424ee88171 100644
--- a/core/zts/src/main/rdl/OAuth.rdli
+++ b/core/zts/src/main/rdl/OAuth.rdli
@@ -44,7 +44,7 @@ resource AccessTokenResponse POST "/oauth2/token" {
}
// Fetch OAuth OpenID Connect ID Token
-resource OIDCResponse GET "/oauth2/auth?response_type={responseType}&client_id={clientId}&redirect_uri={redirectUri}&scope={scope}&state={state}&nonce={nonce}&keyType={keyType}&fullArn={fullArn}&expiryTime={expiryTime}" {
+resource OIDCResponse GET "/oauth2/auth?response_type={responseType}&client_id={clientId}&redirect_uri={redirectUri}&scope={scope}&state={state}&nonce={nonce}&keyType={keyType}&fullArn={fullArn}&expiryTime={expiryTime}&output={output}" {
String responseType; //response type - currently only supporting id tokens - id_token
ServiceName clientId; //client id - must be valid athenz service identity name
String redirectUri; //redirect uri for the response
@@ -54,9 +54,10 @@ resource OIDCResponse GET "/oauth2/auth?response_type={responseType}&client_id={
SimpleName keyType (optional); //optional signing key type - RSA or EC. Might be ignored if server doesn't have the requested type configured
Bool fullArn (optional, default=false); //flag to indicate to use full arn in group claim (e.g. sports:role.deployer instead of deployer)
Int32 expiryTime (optional); //optional expiry period specified in seconds
+ SimpleName output (optional); //optional output format of json
String location (header="Location", out); //return location header with id token
authenticate;
- expected FOUND;
+ expected OK, FOUND;
exceptions {
ResourceError BAD_REQUEST;
ResourceError FORBIDDEN;
diff --git a/core/zts/src/main/rdl/OAuth.tdl b/core/zts/src/main/rdl/OAuth.tdl
index b640de1e204..09f0b8fbe09 100644
--- a/core/zts/src/main/rdl/OAuth.tdl
+++ b/core/zts/src/main/rdl/OAuth.tdl
@@ -53,5 +53,9 @@ type JWKList Struct {
type AccessTokenRequest String;
type OIDCResponse Struct {
- String location;
-};
+ Int32 version; //version number
+ String id_token; //id token
+ String token_type; //token type e.g. urn:ietf:params:oauth:token-type:id_token
+ Bool success; //response status
+ Int64 expiration_time; //expiration time in UTC
+}
diff --git a/core/zts/src/test/java/com/yahoo/athenz/zts/OIDCResponseTest.java b/core/zts/src/test/java/com/yahoo/athenz/zts/OIDCResponseTest.java
index 7013ec31754..b2e320da9e7 100644
--- a/core/zts/src/test/java/com/yahoo/athenz/zts/OIDCResponseTest.java
+++ b/core/zts/src/test/java/com/yahoo/athenz/zts/OIDCResponseTest.java
@@ -18,8 +18,7 @@
import org.testng.annotations.Test;
-import static org.testng.Assert.assertEquals;
-import static org.testng.Assert.assertNotEquals;
+import static org.testng.Assert.*;
public class OIDCResponseTest {
@@ -29,21 +28,52 @@ public void testOIDCResponse() {
OIDCResponse resp1 = new OIDCResponse();
OIDCResponse resp2 = new OIDCResponse();
- resp1.setLocation("https://localhost:4443/zts");
- resp2.setLocation("https://localhost:4443/zts");
+ resp1.setId_token("idtoken1");
+ resp1.setSuccess(true);
+ resp1.setVersion(1);
+ resp1.setExpiration_time(1000);
+ resp1.setToken_type("id-token");
+
+ resp2.setId_token("idtoken1");
+ resp2.setSuccess(true);
+ resp2.setVersion(1);
+ resp2.setExpiration_time(1000);
+ resp2.setToken_type("id-token");
assertEquals(resp1, resp2);
assertEquals(resp1, resp1);
assertNotEquals(null, resp1);
assertNotEquals("oidcresponse", resp1);
- assertEquals("https://localhost:4443/zts", resp1.getLocation());
+ assertEquals("idtoken1", resp1.getId_token());
+ assertTrue(resp1.getSuccess());
+ assertEquals(1, resp1.getVersion());
+ assertEquals(1000, resp1.getExpiration_time());
+ assertEquals("id-token", resp1.getToken_type());
+
+ resp2.setId_token("idtoken2");
+ assertNotEquals(resp1, resp2);
+ resp2.setId_token(null);
+ assertNotEquals(resp1, resp2);
+ resp2.setId_token("idtoken1");
+
+ resp2.setToken_type("id-token2");
+ assertNotEquals(resp1, resp2);
+ resp2.setToken_type(null);
+ assertNotEquals(resp1, resp2);
+ resp2.setToken_type("id-token");
- resp2.setLocation("https://localhost:8443/zts");
+ resp2.setVersion(2);
assertNotEquals(resp1, resp2);
- resp2.setLocation(null);
+ resp2.setVersion(1);
+
+ resp2.setExpiration_time(1001);
+ assertNotEquals(resp1, resp2);
+ resp2.setExpiration_time(1000);
+
+ resp2.setSuccess(false);
assertNotEquals(resp1, resp2);
- resp2.setLocation("https://localhost:4443/zts");
+ resp2.setSuccess(true);
assertEquals(resp1, resp2);
}
diff --git a/libs/go/athenzutils/idtoken.go b/libs/go/athenzutils/idtoken.go
index 0c7d44125aa..4b41b0fae04 100644
--- a/libs/go/athenzutils/idtoken.go
+++ b/libs/go/athenzutils/idtoken.go
@@ -23,7 +23,7 @@ func FetchIdToken(ztsURL, svcKeyFile, svcCertFile, svcCACertFile, clientId, redi
client.DisableRedirect = true
// request an id token
- _, location, err := client.GetOIDCResponse("id_token", zts.ServiceName(clientId), redirectUri, scope, zts.EntityName(state), zts.EntityName(nonce), zts.SimpleName(keyType), fullArn, expireTime)
+ _, location, err := client.GetOIDCResponse("id_token", zts.ServiceName(clientId), redirectUri, scope, zts.EntityName(state), zts.EntityName(nonce), zts.SimpleName(keyType), fullArn, expireTime, "")
if err != nil {
return "", err
}
diff --git a/rdl/rdl-gen-athenz-go-client/main.go b/rdl/rdl-gen-athenz-go-client/main.go
index ca09cb2304d..46d93b5b669 100644
--- a/rdl/rdl-gen-athenz-go-client/main.go
+++ b/rdl/rdl-gen-athenz-go-client/main.go
@@ -653,7 +653,7 @@ func goMethodBody(reg rdl.TypeRegistry, r *rdl.Resource, precise bool) string {
expected = append(expected, rdl.StatusCode(e))
}
s += "\tcase " + strings.Join(expected, ", ") + ":\n"
- if couldBeNoContent || couldBeNotModified {
+ if couldBeNoContent || couldBeNotModified || couldBeRedirect {
if !noContent {
tmp := ""
if couldBeNoContent {
@@ -665,13 +665,17 @@ func goMethodBody(reg rdl.TypeRegistry, r *rdl.Resource, precise bool) string {
}
tmp += "304 != resp.StatusCode"
}
+ if couldBeRedirect {
+ if tmp != "" {
+ tmp += " || "
+ }
+ tmp += "302 != resp.StatusCode"
+ }
s += "\t\tif " + tmp + " {\n"
s += "\t\t\terr = json.NewDecoder(resp.Body).Decode(&data)\n"
s += "\t\t\tif err != nil {\n\t\t\t\t" + errorReturn + "\n\t\t\t}\n"
s += "\t\t}\n"
}
- } else if couldBeRedirect {
- s += "\t\tdata = nil\n"
} else {
s += "\t\terr = json.NewDecoder(resp.Body).Decode(&data)\n"
s += "\t\tif err != nil {\n\t\t\t" + errorReturn + "\n\t\t}\n"
diff --git a/rdl/rdl-gen-athenz-java-client/javaclient.go b/rdl/rdl-gen-athenz-java-client/javaclient.go
index 388d9071bb1..ffd5c8e4374 100644
--- a/rdl/rdl-gen-athenz-java-client/javaclient.go
+++ b/rdl/rdl-gen-athenz-java-client/javaclient.go
@@ -346,6 +346,9 @@ func (gen *javaClientGenerator) clientMethodBody(r *rdl.Resource) string {
if "NOT_MODIFIED" == e {
couldBeNotModified = true
}
+ if "FOUND" == e {
+ couldBeRedirect = true
+ }
expected = append(expected, rdl.StatusCode(e))
}
for _, expCode := range expected {
@@ -358,11 +361,11 @@ func (gen *javaClientGenerator) clientMethodBody(r *rdl.Resource) string {
}
s += " }\n"
}
- if noContent || couldBeRedirect {
+ if noContent {
s += " return null;\n"
} else {
- if couldBeNoContent || couldBeNotModified {
- s += " if (" + gen.responseCondition(couldBeNoContent, couldBeNotModified) + ") {\n"
+ if couldBeNoContent || couldBeNotModified || couldBeRedirect {
+ s += " if (" + gen.responseCondition(couldBeNoContent, couldBeNotModified, couldBeRedirect) + ") {\n"
s += " return null;\n"
s += " }\n"
}
@@ -386,15 +389,23 @@ func (gen *javaClientGenerator) clientMethodBody(r *rdl.Resource) string {
return s
}
-func (gen *javaClientGenerator) responseCondition(noContent, notModified bool) string {
+func (gen *javaClientGenerator) responseCondition(noContent, notModified, redirect bool) string {
var s string
- if noContent && notModified {
- s += "code == " + rdl.StatusCode("NO_CONTENT") + " || code == " + rdl.StatusCode("NOT_MODIFIED")
- } else if noContent {
+ if noContent {
s += "code == " + rdl.StatusCode("NO_CONTENT")
- } else {
+ }
+ if notModified {
+ if s != "" {
+ s += " || "
+ }
s += "code == " + rdl.StatusCode("NOT_MODIFIED")
}
+ if redirect {
+ if s != "" {
+ s += " || "
+ }
+ s += "code == " + rdl.StatusCode("FOUND")
+ }
return s
}
diff --git a/servers/zts/src/main/java/com/yahoo/athenz/zts/ZTSConsts.java b/servers/zts/src/main/java/com/yahoo/athenz/zts/ZTSConsts.java
index 9d7af7a00b1..917db520c88 100644
--- a/servers/zts/src/main/java/com/yahoo/athenz/zts/ZTSConsts.java
+++ b/servers/zts/src/main/java/com/yahoo/athenz/zts/ZTSConsts.java
@@ -201,6 +201,7 @@ public final class ZTSConsts {
public static final String RSA = "RSA";
public static final String EC = "EC";
public static final String ECDSA = "ECDSA";
+ public static final String JSON = "json";
public static final String ZTS_PROP_METRIC_FACTORY_CLASS = "athenz.zts.metric_factory_class";
public static final String ZTS_PROP_CERT_SIGNER_FACTORY_CLASS = "athenz.zts.cert_signer_factory_class";
@@ -242,9 +243,9 @@ public final class ZTSConsts {
public static final String ZTS_PROP_CERT_PRIORITY_MAX_PERCENT_HIGH_PRIORITY = "athenz.zts.cert_priority_max_percent_high_priority";
public static final String ZTS_CERT_PRIORITY_MAX_PERCENT_HIGH_PRIORITY_DEFAULT = "25";
+ public static final String ZTS_OPENID_RESPONSE_TOKEN_TYPE = "urn:ietf:params:oauth:token-type:";
public static final String ZTS_OPENID_RESPONSE_AT_ONLY = "token";
public static final String ZTS_OPENID_RESPONSE_IT_ONLY = "id_token";
public static final String ZTS_OPENID_RESPONSE_BOTH_IT_AT = "id_token token";
public static final String ZTS_OPENID_SUBJECT_TYPE_PUBLIC = "public";
-
}
diff --git a/servers/zts/src/main/java/com/yahoo/athenz/zts/ZTSHandler.java b/servers/zts/src/main/java/com/yahoo/athenz/zts/ZTSHandler.java
index 46fdc6131fc..a47c66889ff 100644
--- a/servers/zts/src/main/java/com/yahoo/athenz/zts/ZTSHandler.java
+++ b/servers/zts/src/main/java/com/yahoo/athenz/zts/ZTSHandler.java
@@ -39,7 +39,7 @@ public interface ZTSHandler {
OAuthConfig getOAuthConfig(ResourceContext context);
JWKList getJWKList(ResourceContext context, Boolean rfc);
AccessTokenResponse postAccessTokenRequest(ResourceContext context, String request);
- Response getOIDCResponse(ResourceContext context, String responseType, String clientId, String redirectUri, String scope, String state, String nonce, String keyType, Boolean fullArn, Integer expiryTime);
+ Response getOIDCResponse(ResourceContext context, String responseType, String clientId, String redirectUri, String scope, String state, String nonce, String keyType, Boolean fullArn, Integer expiryTime, String output);
RoleCertificate postRoleCertificateRequestExt(ResourceContext context, RoleCertificateRequest req);
RoleAccess getRolesRequireRoleCert(ResourceContext context, String principal);
Workloads getWorkloadsByService(ResourceContext context, String domainName, String serviceName);
diff --git a/servers/zts/src/main/java/com/yahoo/athenz/zts/ZTSImpl.java b/servers/zts/src/main/java/com/yahoo/athenz/zts/ZTSImpl.java
index 1677dc8652d..a2bf7bd3de8 100644
--- a/servers/zts/src/main/java/com/yahoo/athenz/zts/ZTSImpl.java
+++ b/servers/zts/src/main/java/com/yahoo/athenz/zts/ZTSImpl.java
@@ -1932,7 +1932,7 @@ String getQueryLogData(final String request) {
@Override
public Response getOIDCResponse(ResourceContext ctx, String responseType, String clientId, String redirectUri,
String scope, String state, String nonce, String keyType, Boolean fullArn,
- Integer timeout) {
+ Integer timeout, String output) {
final String caller = ctx.getApiName();
@@ -1980,14 +1980,14 @@ public Response getOIDCResponse(ResourceContext ctx, String responseType, String
// of oidc and return id tokens without access tokens
if (!ZTSConsts.ZTS_OPENID_RESPONSE_IT_ONLY.equals(responseType)) {
- return oidcError("invalid response type", redirectUri, state);
+ throw requestError("invalid response type", caller, principal.getDomain(), principalDomain);
}
// we must have scope provided, so we know what access
// the client is looking for
if (StringUtil.isEmpty(scope)) {
- return oidcError("no scope provided", redirectUri, state);
+ throw requestError("no scope provided", caller, principal.getDomain(), principalDomain);
}
// our scopes are space separated list of values. Any groups
@@ -2037,16 +2037,30 @@ public Response getOIDCResponse(ResourceContext ctx, String responseType, String
// service principals 12 hours as the max timeout, unless the client
// is explicitly asking for something smaller.
- idToken.setExpiryTime(iat + determineOIDCIdTokenTimeout(principalDomain, timeout));
+ long expiryTime = iat + determineOIDCIdTokenTimeout(principalDomain, timeout);
+ idToken.setExpiryTime(expiryTime);
ServerPrivateKey signPrivateKey = getSignPrivateKey(keyType);
- String location = redirectUri + "#id_token=" +
- idToken.getSignedToken(signPrivateKey.getKey(), signPrivateKey.getId(), signPrivateKey.getAlgorithm());
+ final String signedIdToken = idToken.getSignedToken(signPrivateKey.getKey(), signPrivateKey.getId(), signPrivateKey.getAlgorithm());
+ String location = redirectUri + "#id_token=" + signedIdToken;
if (!StringUtil.isEmpty(state)) {
location += "&state=" + state;
}
- return Response.status(ResourceException.FOUND).header("Location", location).build();
+ // based on the output argument we'll just return 200 with response object
+ // or redirect with the location header set in both cases
+
+ if (ZTSConsts.JSON.equalsIgnoreCase(output)) {
+ OIDCResponse oidcResponse = new OIDCResponse()
+ .setId_token(signedIdToken)
+ .setSuccess(true)
+ .setVersion(1)
+ .setToken_type(ZTSConsts.ZTS_OPENID_RESPONSE_TOKEN_TYPE + responseType)
+ .setExpiration_time(expiryTime);
+ return Response.status(ResourceException.OK).entity(oidcResponse).header("Location", location).build();
+ } else {
+ return Response.status(ResourceException.FOUND).header("Location", location).build();
+ }
}
List processIdTokenGroups(final String principalName, IdTokenRequest tokenRequest, final String clientIdDomainName,
@@ -2274,20 +2288,6 @@ ServerPrivateKey getSignPrivateKey(final String keyType) {
return serverPrivateKey;
}
- Response oidcError(final String errorMessage, final String redirectUri, final String state) {
- String location;
- try {
- location = redirectUri + "?error=invalid_request&error_description=" +
- URLEncoder.encode(errorMessage, StandardCharsets.UTF_8);
- if (!StringUtil.isEmpty(state)) {
- location += "&state=" + state;
- }
- } catch (Exception ex) {
- location = redirectUri + "?error=invalid_request";
- }
- return Response.status(ResourceException.FOUND).header("Location", location).build();
- }
-
@Override
public AccessTokenResponse postAccessTokenRequest(ResourceContext ctx, String request) {
diff --git a/servers/zts/src/main/java/com/yahoo/athenz/zts/ZTSResources.java b/servers/zts/src/main/java/com/yahoo/athenz/zts/ZTSResources.java
index 8b8f0c99d63..c73fffee4d6 100644
--- a/servers/zts/src/main/java/com/yahoo/athenz/zts/ZTSResources.java
+++ b/servers/zts/src/main/java/com/yahoo/athenz/zts/ZTSResources.java
@@ -934,13 +934,14 @@ public Response getOIDCResponse(
@Parameter(description = "nonce claim included in the id token", required = true) @QueryParam("nonce") String nonce,
@Parameter(description = "optional signing key type - RSA or EC. Might be ignored if server doesn't have the requested type configured", required = false) @QueryParam("keyType") String keyType,
@Parameter(description = "flag to indicate to use full arn in group claim (e.g. sports:role.deployer instead of deployer)", required = false) @QueryParam("fullArn") @DefaultValue("false") Boolean fullArn,
- @Parameter(description = "optional expiry period specified in seconds", required = false) @QueryParam("expiryTime") Integer expiryTime) {
+ @Parameter(description = "optional expiry period specified in seconds", required = false) @QueryParam("expiryTime") Integer expiryTime,
+ @Parameter(description = "optional output format of json", required = false) @QueryParam("output") String output) {
int code = ResourceException.OK;
ResourceContext context = null;
try {
context = this.delegate.newResourceContext(this.servletContext, this.request, this.response, "getOIDCResponse");
context.authenticate();
- return this.delegate.getOIDCResponse(context, responseType, clientId, redirectUri, scope, state, nonce, keyType, fullArn, expiryTime);
+ return this.delegate.getOIDCResponse(context, responseType, clientId, redirectUri, scope, state, nonce, keyType, fullArn, expiryTime, output);
} catch (ResourceException e) {
code = e.getCode();
switch (code) {
diff --git a/servers/zts/src/test/java/com/yahoo/athenz/zts/ZTSImplTest.java b/servers/zts/src/test/java/com/yahoo/athenz/zts/ZTSImplTest.java
index 84cf6822ab8..0543bd3c543 100644
--- a/servers/zts/src/test/java/com/yahoo/athenz/zts/ZTSImplTest.java
+++ b/servers/zts/src/test/java/com/yahoo/athenz/zts/ZTSImplTest.java
@@ -87,6 +87,7 @@
import java.io.*;
import java.net.*;
+import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
@@ -2267,10 +2268,7 @@ public void testAthenzJWKConfChangedNoModifyTime() {
zts.jwkConfig = new AthenzJWKConfig().setModified(Timestamp.fromMillis(100));
Timestamp zmsModified = Timestamp.fromMillis(99);
- Timestamp ztsModified = null;
-
- assertFalse(zts.hasNewJWKConfig(zmsModified, ztsModified));
-
+ assertFalse(zts.hasNewJWKConfig(zmsModified, null));
}
@Test
@@ -2341,7 +2339,6 @@ public void testFillAthenzJWKConfig() {
@Test
public void testLoadAthenzJWK() {
-
SignedDomain providerDomain = signedAuthorizedProviderDomain();
store.processSignedDomain(providerDomain, false);
providerDomain.getDomain().setServices(
@@ -2353,7 +2350,6 @@ public void testLoadAthenzJWK() {
ZTSImpl newZts = new ZTSImpl(cloudStore, store);
SimplePrincipal principal = (SimplePrincipal) SimplePrincipal.create("hockey", "kings",
"v=S1,d=hockey;n=kings;s=sig", 0, new PrincipalAuthority());
- ResourceContext context = createResourceContext(principal);
assertNotNull(newZts.jwkConfig);
assertNotNull(newZts.jwkConfig.zts);
@@ -9683,7 +9679,7 @@ public void testPostAccessTokenRequest() throws UnsupportedEncodingException {
"v=U1;d=user_domain;n=user;s=signature", 0, null);
ResourceContext context = createResourceContext(principal);
- final String scope = URLEncoder.encode("coretech:domain", "UTF-8");
+ final String scope = URLEncoder.encode("coretech:domain", StandardCharsets.UTF_8);
AccessTokenResponse resp = ztsImpl.postAccessTokenRequest(context,
"grant_type=client_credentials&scope=" + scope);
assertNotNull(resp);
@@ -9917,7 +9913,7 @@ public void testPostAccessTokenRequestOpenIdScope() throws UnsupportedEncodingEx
"v=U1;d=user_domain;n=user;s=signature", 0, null);
ResourceContext context = createResourceContext(principal);
- final String scope = URLEncoder.encode("coretech:domain openid coretech:service.api", "UTF-8");
+ final String scope = URLEncoder.encode("coretech:domain openid coretech:service.api", StandardCharsets.UTF_8);
AccessTokenResponse resp = ztsImpl.postAccessTokenRequest(context,
"grant_type=client_credentials&scope=" + scope + "&expires_in=240");
assertNotNull(resp);
@@ -9964,7 +9960,7 @@ public void testPostAccessTokenRequestOpenIdScopeOAuthIssuer() throws Unsupporte
System.clearProperty(ZTSConsts.ZTS_PROP_OPENID_ISSUER);
}
- private void testPostAccessTokenRequestOpenIdScope(final String issuer, final String reqComp) throws UnsupportedEncodingException {
+ private void testPostAccessTokenRequestOpenIdScope(final String issuer, final String reqComp) {
System.setProperty(FilePrivateKeyStore.ATHENZ_PROP_PRIVATE_KEY, "src/test/resources/unit_test_zts_at_private.pem");
@@ -9981,7 +9977,7 @@ private void testPostAccessTokenRequestOpenIdScope(final String issuer, final St
"v=U1;d=user_domain;n=user;s=signature", 0, null);
ResourceContext context = createResourceContext(principal);
- final String scope = URLEncoder.encode("coretech:domain openid coretech:service.api", "UTF-8");
+ final String scope = URLEncoder.encode("coretech:domain openid coretech:service.api", StandardCharsets.UTF_8);
AccessTokenResponse resp = ztsImpl.postAccessTokenRequest(context,
"grant_type=client_credentials&scope=" + scope + "&expires_in=240" + reqComp);
assertNotNull(resp);
@@ -10034,7 +10030,7 @@ public void testPostAccessTokenRequestOpenIdScopeMaxTimeout() throws Unsupported
// default max timeout is 12 hours so we'll pick a value
// bigger than that
- final String scope = URLEncoder.encode("coretech:domain openid coretech:service.api", "UTF-8");
+ final String scope = URLEncoder.encode("coretech:domain openid coretech:service.api", StandardCharsets.UTF_8);
AccessTokenResponse resp = ztsImpl.postAccessTokenRequest(context,
"grant_type=client_credentials&scope=" + scope + "&expires_in=57600");
assertNotNull(resp);
@@ -10072,7 +10068,7 @@ public void testPostAccessTokenRequestOpenIdScopeOnly() throws UnsupportedEncodi
// we should only get back openid scope
try {
- final String scope = URLEncoder.encode("coretech:role.role999 openid coretech:service.api", "UTF-8");
+ final String scope = URLEncoder.encode("coretech:role.role999 openid coretech:service.api", StandardCharsets.UTF_8);
zts.postAccessTokenRequest(context, "grant_type=client_credentials&scope=" + scope);
fail();
} catch (ResourceException ex) {
@@ -10102,7 +10098,7 @@ public void testPostAccessTokenRequestOpenIdScopeOnlyDisabled() throws Unsupport
// no role access and no openid - we should get back 403
try {
- final String scope = URLEncoder.encode("coretech:role.role999 openid coretech:service.api", "UTF-8");
+ final String scope = URLEncoder.encode("coretech:role.role999 openid coretech:service.api", StandardCharsets.UTF_8);
ztsImpl.postAccessTokenRequest(context, "grant_type=client_credentials&scope=" + scope);
fail();
} catch (ResourceException ex) {
@@ -10380,7 +10376,8 @@ public void testPostAccessTokenRequestProxyUserOpenidScope() throws UnsupportedE
ResourceContext context = createResourceContext(principal);
try {
- final String scope = URLEncoder.encode("openid coretech-proxy4:domain coretech-proxy4:service.api", "UTF-8");
+ final String scope = URLEncoder.encode("openid coretech-proxy4:domain coretech-proxy4:service.api",
+ StandardCharsets.UTF_8);
zts.postAccessTokenRequest(context, "grant_type=client_credentials&scope=" + scope +
"&proxy_for_principal=user_domain.jane");
fail();
@@ -11297,9 +11294,7 @@ public void testGetInstanceRegisterQueryLog() {
// 978 chars and then pass some more in the api
StringBuilder hostnameBuilder = new StringBuilder(978);
- for (int i = 0; i < 163; i++) {
- hostnameBuilder.append("123456");
- }
+ hostnameBuilder.append("123456".repeat(163));
final String check = "provider=aws&certReqInstanceId=id001&hostname=" + hostnameBuilder;
assertEquals(check, zts.getInstanceRegisterQueryLog("aws", "id001", hostnameBuilder + "01234"));
@@ -11313,12 +11308,7 @@ public void testGetQueryLogData() {
// generate a string with 1024 length
- StringBuilder longRequest = new StringBuilder(1024);
- for (int i = 0; i < 64; i++) {
- longRequest.append("0123456789012345");
- }
- request = longRequest.toString();
-
+ request = "0123456789012345".repeat(64);
assertEquals(zts.getQueryLogData(request + "abcd"), request);
}
@@ -13329,26 +13319,6 @@ public void testExtractServiceEndpoint() {
zts.redirectUriSuffix = savedUriSuffix;
}
- @Test
- public void testOidcError() {
-
- Response response = zts.oidcError("bad request", "https://localhost", null);
- assertEquals(response.getStatus(), ResourceException.FOUND);
- assertEquals(response.getHeaderString("Location"), "https://localhost?error=invalid_request&error_description=bad+request");
-
- response = zts.oidcError("bad request", "https://localhost", "");
- assertEquals(response.getStatus(), ResourceException.FOUND);
- assertEquals(response.getHeaderString("Location"), "https://localhost?error=invalid_request&error_description=bad+request");
-
- response = zts.oidcError("bad request", "https://localhost", "state");
- assertEquals(response.getStatus(), ResourceException.FOUND);
- assertEquals(response.getHeaderString("Location"), "https://localhost?error=invalid_request&error_description=bad+request&state=state");
-
- response = zts.oidcError(null, "https://localhost", "state");
- assertEquals(response.getStatus(), ResourceException.FOUND);
- assertEquals(response.getHeaderString("Location"), "https://localhost?error=invalid_request");
- }
-
@Test
public void testGetOIDCResponseFailures() {
@@ -13360,7 +13330,7 @@ public void testGetOIDCResponseFailures() {
try {
zts.getOIDCResponse(context, "id_token", "coretech", "https://localhost:4443", "openid",
- null, "nonce", "RSA", null, null);
+ null, "nonce", "RSA", null, null, null);
fail();
} catch (ResourceException ex) {
assertEquals(ex.getCode(), ResourceException.BAD_REQUEST);
@@ -13371,7 +13341,7 @@ public void testGetOIDCResponseFailures() {
try {
zts.getOIDCResponse(context, "id_token", "unknown-domain.api", "https://localhost:4443",
- "openid", null, "nonce", "EC", null, null);
+ "openid", null, "nonce", "EC", null, null, null);
fail();
} catch (ResourceException ex) {
assertEquals(ex.getCode(), ResourceException.NOT_FOUND);
@@ -13387,7 +13357,7 @@ public void testGetOIDCResponseFailures() {
try {
zts.getOIDCResponse(context, "id_token", "coretech.backup", "https://localhost:4443/zts",
- "openid", null, "nonce", "RSA", null, null);
+ "openid", null, "nonce", "RSA", null, null, null);
fail();
} catch (ResourceException ex) {
assertEquals(ex.getCode(), ResourceException.BAD_REQUEST);
@@ -13397,7 +13367,7 @@ public void testGetOIDCResponseFailures() {
try {
zts.getOIDCResponse(context, "id_token", "coretech.api", "https://localhost:4443",
- "openid", "state", "nonce", null, null, null);
+ "openid", "state", "nonce", null, null, null, null);
fail();
} catch (ResourceException ex) {
assertEquals(ex.getCode(), ResourceException.BAD_REQUEST);
@@ -13405,25 +13375,34 @@ public void testGetOIDCResponseFailures() {
// invalid response type
- Response response = zts.getOIDCResponse(context, "token", "coretech.api", "https://localhost:4443/zts",
- "openid", null, "nonce", "", null, null);
- assertEquals(response.getStatus(), ResourceException.FOUND);
- assertEquals(response.getHeaderString("Location"),
- "https://localhost:4443/zts?error=invalid_request&error_description=invalid+response+type");
+ try {
+ zts.getOIDCResponse(context, "token", "coretech.api", "https://localhost:4443/zts",
+ "openid", null, "nonce", "", null, null, null);
+ fail();
+ } catch (ResourceException ex) {
+ assertEquals(ex.getCode(), ResourceException.BAD_REQUEST);
+ assertTrue(ex.getMessage().contains("invalid response type"));
+ }
// empty scope
- response = zts.getOIDCResponse(context, "id_token", "coretech.api", "https://localhost:4443/zts",
- "", null, "nonce", "rsa", null, null);
- assertEquals(response.getStatus(), ResourceException.FOUND);
- assertEquals(response.getHeaderString("Location"),
- "https://localhost:4443/zts?error=invalid_request&error_description=no+scope+provided");
+ try {
+ zts.getOIDCResponse(context, "id_token", "coretech.api", "https://localhost:4443/zts",
+ "", null, "nonce", "rsa", null, null, null);
+ fail();
+ } catch (ResourceException ex) {
+ assertEquals(ex.getCode(), ResourceException.BAD_REQUEST);
+ assertTrue(ex.getMessage().contains("no scope provided"));
+ }
- response = zts.getOIDCResponse(context, "id_token", "coretech.api", "https://localhost:4443/zts",
- null, null, "nonce", "unknown", Boolean.FALSE, null);
- assertEquals(response.getStatus(), ResourceException.FOUND);
- assertEquals(response.getHeaderString("Location"),
- "https://localhost:4443/zts?error=invalid_request&error_description=no+scope+provided");
+ try {
+ zts.getOIDCResponse(context, "id_token", "coretech.api", "https://localhost:4443/zts",
+ null, null, "nonce", "unknown", Boolean.FALSE, null, null);
+ fail();
+ } catch (ResourceException ex) {
+ assertEquals(ex.getCode(), ResourceException.BAD_REQUEST);
+ assertTrue(ex.getMessage().contains("no scope provided"));
+ }
}
@Test
@@ -13445,8 +13424,8 @@ public void testGetOIDCResponseNoRulesGroups() {
store.processSignedDomain(signedDomain, false);
Response response = ztsImpl.getOIDCResponse(context, "id_token", "coretech.api", "https://localhost:4443/zts",
- "openid", null, "nonce", "RSA", Boolean.FALSE, null);
- Jws claims = getClaimsFromResponse(response, ztsImpl.privateKey.getKey());
+ "openid", null, "nonce", "RSA", Boolean.FALSE, null, null);
+ Jws claims = getClaimsFromResponse(response, ztsImpl.privateKey.getKey(), null);
assertNotNull(claims);
assertEquals("user_domain.user", claims.getBody().getSubject());
assertEquals("coretech.api", claims.getBody().getAudience());
@@ -13483,8 +13462,8 @@ public void testGetOIDCResponseGroups() {
// get all the groups
Response response = ztsImpl.getOIDCResponse(context, "id_token", "coretech.api", "https://localhost:4443/zts",
- "openid groups", null, "nonce", "EC", null, null);
- Jws claims = getClaimsFromResponse(response, ztsImpl.privateKey.getKey());
+ "openid groups", null, "nonce", "EC", null, null, null);
+ Jws claims = getClaimsFromResponse(response, ztsImpl.privateKey.getKey(), null);
assertNotNull(claims);
assertEquals("user_domain.user", claims.getBody().getSubject());
assertEquals("coretech.api", claims.getBody().getAudience());
@@ -13499,7 +13478,7 @@ public void testGetOIDCResponseGroups() {
// get only one of the groups and include state
response = ztsImpl.getOIDCResponse(context, "id_token", "coretech.api", "https://localhost:4443/zts",
- "openid coretech:group.dev-team", "valid-state", "nonce", "RSA", null, null);
+ "openid coretech:group.dev-team", "valid-state", "nonce", "RSA", null, null, null);
assertEquals(response.getStatus(), ResourceException.FOUND);
String location = response.getHeaderString("Location");
final String stateComp = "&state=valid-state";
@@ -13527,7 +13506,7 @@ public void testGetOIDCResponseGroups() {
try {
ztsImpl.getOIDCResponse(context, "id_token", "coretech.api", "https://localhost:4443/zts",
- "openid coretech:group.eng-team", null, "nonce", null, Boolean.FALSE, null);
+ "openid coretech:group.eng-team", null, "nonce", null, Boolean.FALSE, null, null);
fail();
} catch (ResourceException ex) {
assertEquals(ex.getCode(), ResourceException.FORBIDDEN);
@@ -13566,8 +13545,8 @@ public void testGetOIDCResponseGroupsDifferentDomain() {
// get all the groups from the coretech domain
Response response = ztsImpl.getOIDCResponse(context, "id_token", "coretech.api", "https://localhost:4443/zts",
- "openid groups weather:domain", null, "nonce", "EC", null, null);
- Jws claims = getClaimsFromResponse(response, ztsImpl.privateKey.getKey());
+ "openid groups weather:domain", null, "nonce", "EC", null, null, null);
+ Jws claims = getClaimsFromResponse(response, ztsImpl.privateKey.getKey(), null);
assertNotNull(claims);
assertEquals("user_domain.user", claims.getBody().getSubject());
assertEquals("coretech.api", claims.getBody().getAudience());
@@ -13583,7 +13562,7 @@ public void testGetOIDCResponseGroupsDifferentDomain() {
try {
ztsImpl.getOIDCResponse(context, "id_token", "coretech.api", "https://localhost:4443/zts",
- "openid groups unknown-domain:domain", null, "nonce", "EC", null, null);
+ "openid groups unknown-domain:domain", null, "nonce", "EC", null, null, null);
fail();
} catch (ResourceException ex) {
assertEquals(ex.getCode(), ResourceException.NOT_FOUND);
@@ -13654,7 +13633,7 @@ public void testGetOIDCResponseGroupsMultipleDomains() {
// get all the groups
Response response = ztsImpl.getOIDCResponse(context, "id_token", "coretech.api", "https://localhost:4443/zts",
- "openid groups coretech:domain weather:domain homepage:domain", null, "nonce", "EC", null, null);
+ "openid groups coretech:domain weather:domain homepage:domain", null, "nonce", "EC", null, null, null);
assertEquals(response.getStatus(), ResourceException.FOUND);
String location = response.getHeaderString("Location");
@@ -13684,7 +13663,7 @@ public void testGetOIDCResponseGroupsMultipleDomains() {
// get only one of the groups and include state
response = ztsImpl.getOIDCResponse(context, "id_token", "coretech.api", "https://localhost:4443/zts",
- "openid coretech:group.dev-team weather:group.pe-team", "valid-state", "nonce", "RSA", null, null);
+ "openid coretech:group.dev-team weather:group.pe-team", "valid-state", "nonce", "RSA", null, null, null);
assertEquals(response.getStatus(), ResourceException.FOUND);
location = response.getHeaderString("Location");
String stateComp = "&state=valid-state";
@@ -13713,7 +13692,7 @@ public void testGetOIDCResponseGroupsMultipleDomains() {
try {
ztsImpl.getOIDCResponse(context, "id_token", "coretech.api", "https://localhost:4443/zts",
- "openid coretech:group.eng-team weather:group.eng-team", null, "nonce", null, Boolean.FALSE, null);
+ "openid coretech:group.eng-team weather:group.eng-team", null, "nonce", null, Boolean.FALSE, null, null);
fail();
} catch (ResourceException ex) {
assertEquals(ex.getCode(), ResourceException.FORBIDDEN);
@@ -13724,7 +13703,7 @@ public void testGetOIDCResponseGroupsMultipleDomains() {
try {
ztsImpl.getOIDCResponse(context, "id_token", "coretech.api", "https://localhost:4443/zts",
- "openid coretech:group.eng finance:group.eng", null, "nonce", "EC", Boolean.FALSE, null);
+ "openid coretech:group.eng finance:group.eng", null, "nonce", "EC", Boolean.FALSE, null, null);
fail();
} catch (ResourceException ex) {
assertEquals(ex.getCode(), ResourceException.NOT_FOUND);
@@ -13734,7 +13713,7 @@ public void testGetOIDCResponseGroupsMultipleDomains() {
// requests from domains where the user is not part of any groups
response = ztsImpl.getOIDCResponse(context, "id_token", "coretech.api", "https://localhost:4443/zts",
- "openid groups homepage:domain fantasy:domain", "valid-state", "nonce", "RSA", null, null);
+ "openid groups homepage:domain fantasy:domain", "valid-state", "nonce", "RSA", null, null, null);
assertEquals(response.getStatus(), ResourceException.FOUND);
location = response.getHeaderString("Location");
stateComp = "&state=valid-state";
@@ -13758,7 +13737,16 @@ public void testGetOIDCResponseGroupsMultipleDomains() {
}
@Test
- public void testGetOIDCResponseRoles() {
+ public void testGetOIDCResponseRolesWithJson() {
+ testGetOIDCResponseRoles("json");
+ }
+
+ @Test
+ public void testGetOIDCResponseRolesRFC() {
+ testGetOIDCResponseRoles(null);
+ }
+
+ private void testGetOIDCResponseRoles(final String output) {
System.setProperty(FilePrivateKeyStore.ATHENZ_PROP_PRIVATE_KEY, "src/test/resources/unit_test_zts_at_private.pem");
@@ -13780,8 +13768,8 @@ public void testGetOIDCResponseRoles() {
// get all the roles
Response response = ztsImpl.getOIDCResponse(context, "id_token", "coretech.api", "https://localhost:4443/zts",
- "openid roles", null, "nonce", "", null, null);
- Jws claims = getClaimsFromResponse(response, ztsImpl.privateKey.getKey());
+ "openid roles", null, "nonce", "", null, null, output);
+ Jws claims = getClaimsFromResponse(response, ztsImpl.privateKey.getKey(), output);
assertNotNull(claims);
assertEquals("user_domain.user", claims.getBody().getSubject());
assertEquals("coretech.api", claims.getBody().getAudience());
@@ -13796,8 +13784,8 @@ public void testGetOIDCResponseRoles() {
// which should be honored
response = ztsImpl.getOIDCResponse(context, "id_token", "coretech.api", "https://localhost:4443/zts",
- "openid coretech:role.writers", null, "nonce", "RSA", Boolean.FALSE, 30 * 60);
- claims = getClaimsFromResponse(response, ztsImpl.privateKey.getKey());
+ "openid coretech:role.writers", null, "nonce", "RSA", Boolean.FALSE, 30 * 60, output);
+ claims = getClaimsFromResponse(response, ztsImpl.privateKey.getKey(), output);
assertNotNull(claims);
assertEquals("user_domain.user", claims.getBody().getSubject());
assertEquals("coretech.api", claims.getBody().getAudience());
@@ -13812,8 +13800,8 @@ public void testGetOIDCResponseRoles() {
// expiry is still set to 1 hour
response = ztsImpl.getOIDCResponse(context, "id_token", "coretech.api", "https://localhost:4443/zts",
- "openid coretech:role.writers", null, "nonce", "RSA", Boolean.FALSE, 120 * 60);
- claims = getClaimsFromResponse(response, ztsImpl.privateKey.getKey());
+ "openid coretech:role.writers", null, "nonce", "RSA", Boolean.FALSE, 120 * 60, output);
+ claims = getClaimsFromResponse(response, ztsImpl.privateKey.getKey(), output);
assertNotNull(claims);
assertEquals(claims.getBody().getExpiration().getTime() - claims.getBody().getIssuedAt().getTime(), 60 * 60 * 1000);
@@ -13824,8 +13812,8 @@ public void testGetOIDCResponseRoles() {
ztsImpl.userDomain = "user-other-domain";
response = ztsImpl.getOIDCResponse(context, "id_token", "coretech.api", "https://localhost:4443/zts",
- "openid coretech:role.writers", null, "nonce", "RSA", Boolean.FALSE, 120 * 60);
- claims = getClaimsFromResponse(response, ztsImpl.privateKey.getKey());
+ "openid coretech:role.writers", null, "nonce", "RSA", Boolean.FALSE, 120 * 60, output);
+ claims = getClaimsFromResponse(response, ztsImpl.privateKey.getKey(), output);
assertNotNull(claims);
assertEquals(claims.getBody().getExpiration().getTime() - claims.getBody().getIssuedAt().getTime(), 120 * 60 * 1000);
@@ -13837,7 +13825,7 @@ public void testGetOIDCResponseRoles() {
try {
ztsImpl.getOIDCResponse(context, "id_token", "coretech.api", "https://localhost:4443/zts",
- "openid coretech:role.eng-team", null, "nonce", "EC", Boolean.FALSE, null);
+ "openid coretech:role.eng-team", null, "nonce", "EC", Boolean.FALSE, null, output);
fail();
} catch (ResourceException ex) {
assertEquals(ex.getCode(), ResourceException.FORBIDDEN);
@@ -13845,12 +13833,20 @@ public void testGetOIDCResponseRoles() {
}
}
- private Jws getClaimsFromResponse(Response response, PrivateKey privateKey) {
- assertEquals(response.getStatus(), ResourceException.FOUND);
- String location = response.getHeaderString("Location");
+ private Jws getClaimsFromResponse(Response response, PrivateKey privateKey, final String output) {
- int idx = location.indexOf("#id_token=");
- String idToken = location.substring(idx + 10);
+ String idToken;
+ if ("json".equalsIgnoreCase(output)) {
+ assertEquals(response.getStatus(), ResourceException.OK);
+ OIDCResponse oidcResponse = (OIDCResponse) response.getEntity();
+ idToken = oidcResponse.getId_token();
+ } else {
+ assertEquals(response.getStatus(), ResourceException.FOUND);
+ String location = response.getHeaderString("Location");
+
+ int idx = location.indexOf("#id_token=");
+ idToken = location.substring(idx + 10);
+ }
try {
return Jwts.parserBuilder().setSigningKey(Crypto.extractPublicKey(privateKey))
@@ -13884,8 +13880,8 @@ public void testGetOIDCResponseRolesDifferentDomain() {
// get all the roles
Response response = ztsImpl.getOIDCResponse(context, "id_token", "coretech.api", "https://localhost:4443/zts",
- "openid roles weather:domain", null, "nonce", "", null, null);
- Jws claims = getClaimsFromResponse(response, ztsImpl.privateKey.getKey());
+ "openid roles weather:domain", null, "nonce", "", null, null, null);
+ Jws claims = getClaimsFromResponse(response, ztsImpl.privateKey.getKey(), null);
assertNotNull(claims);
assertEquals("user_domain.user", claims.getBody().getSubject());
@@ -13901,7 +13897,7 @@ public void testGetOIDCResponseRolesDifferentDomain() {
try {
ztsImpl.getOIDCResponse(context, "id_token", "coretech.api", "https://localhost:4443/zts",
- "openid roles unknown-domain:domain", null, "nonce", "EC", null, null);
+ "openid roles unknown-domain:domain", null, "nonce", "EC", null, null, null);
fail();
} catch (ResourceException ex) {
assertEquals(ex.getCode(), ResourceException.NOT_FOUND);
@@ -13940,8 +13936,8 @@ public void testGetOIDCResponseRolesMultipleDomains() {
// get all the roles
Response response = ztsImpl.getOIDCResponse(context, "id_token", "coretech.api", "https://localhost:4443/zts",
- "openid roles coretech:domain weather:domain homepage:domain", null, "nonce", "", null, null);
- Jws claims = getClaimsFromResponse(response, ztsImpl.privateKey.getKey());
+ "openid roles coretech:domain weather:domain homepage:domain", null, "nonce", "", null, null, null);
+ Jws claims = getClaimsFromResponse(response, ztsImpl.privateKey.getKey(), null);
assertNotNull(claims);
assertEquals("user_domain.user", claims.getBody().getSubject());
@@ -13957,9 +13953,9 @@ public void testGetOIDCResponseRolesMultipleDomains() {
// specific the roles explicitly
response = ztsImpl.getOIDCResponse(context, "id_token", "coretech.api", "https://localhost:4443/zts",
- "openid coretech:role.writers weather:role.writers", null, "nonce", "RSA", Boolean.FALSE, null);
+ "openid coretech:role.writers weather:role.writers", null, "nonce", "RSA", Boolean.FALSE, null, null);
assertEquals(response.getStatus(), ResourceException.FOUND);
- claims = getClaimsFromResponse(response, ztsImpl.privateKey.getKey());
+ claims = getClaimsFromResponse(response, ztsImpl.privateKey.getKey(), null);
assertNotNull(claims);
assertEquals("user_domain.user", claims.getBody().getSubject());
@@ -13975,7 +13971,7 @@ public void testGetOIDCResponseRolesMultipleDomains() {
try {
ztsImpl.getOIDCResponse(context, "id_token", "coretech.api", "https://localhost:4443/zts",
- "openid coretech:role.eng weather:role.eng", null, "nonce", "EC", Boolean.FALSE, null);
+ "openid coretech:role.eng weather:role.eng", null, "nonce", "EC", Boolean.FALSE, null, null);
fail();
} catch (ResourceException ex) {
assertEquals(ex.getCode(), ResourceException.FORBIDDEN);
@@ -13986,7 +13982,7 @@ public void testGetOIDCResponseRolesMultipleDomains() {
try {
ztsImpl.getOIDCResponse(context, "id_token", "coretech.api", "https://localhost:4443/zts",
- "openid coretech:role.eng finance:role.eng", null, "nonce", "EC", Boolean.FALSE, null);
+ "openid coretech:role.eng finance:role.eng", null, "nonce", "EC", Boolean.FALSE, null, null);
fail();
} catch (ResourceException ex) {
assertEquals(ex.getCode(), ResourceException.NOT_FOUND);
@@ -13996,8 +13992,8 @@ public void testGetOIDCResponseRolesMultipleDomains() {
// requests from domains where the user is not part of any role
response = ztsImpl.getOIDCResponse(context, "id_token", "coretech.api", "https://localhost:4443/zts",
- "openid roles homepage:domain fantasy:domain", null, "nonce", "RSA", null, null);
- claims = getClaimsFromResponse(response, ztsImpl.privateKey.getKey());
+ "openid roles homepage:domain fantasy:domain", null, "nonce", "RSA", null, null, null);
+ claims = getClaimsFromResponse(response, ztsImpl.privateKey.getKey(), null);
assertNotNull(claims);
assertEquals("user_domain.user", claims.getBody().getSubject());