m = delegationRequest.getParameters();
m.put(OA2Constants.CLIENT_ID, delegationRequest.getClient().getIdentifierString());
m.put(OA2Constants.REDIRECT_URI, delegationRequest.getParameters().get(OA2Constants.REDIRECT_URI));
- URI authZUri = ((AGServer2)getAgServer()).getServiceClient().host();
+ URI authZUri = ((AGServer2) getAgServer()).getServiceClient().host();
URI redirectURI = URI.create(ServiceClient.convertToStringRequest(authZUri.toString(), m));
delResp.setParameters(m); //send them all back.
delResp.setRedirectUri(redirectURI);
return delResp;
-
}
/**
@@ -132,6 +140,7 @@ public URI createRedirectURL(DelegationRequest delegationAssetRequest, AGRespons
}
return URI.create(rc);
}
+
public RFC7662Server getRfc7662Server() {
return rfc7662Server;
}
diff --git a/oauth2/src/main/java/org/oa4mp/delegation/server/client/RFC6749_4_4Server.java b/oauth2/src/main/java/org/oa4mp/delegation/server/client/RFC6749_4_4Server.java
new file mode 100644
index 000000000..57938792a
--- /dev/null
+++ b/oauth2/src/main/java/org/oa4mp/delegation/server/client/RFC6749_4_4Server.java
@@ -0,0 +1,32 @@
+package org.oa4mp.delegation.server.client;
+
+import edu.uiuc.ncsa.security.servlet.ServiceClient;
+import net.sf.json.JSONObject;
+import org.oa4mp.delegation.client.request.RFC6749_4_4Request;
+import org.oa4mp.delegation.client.request.RFC6749_4_4_Response;
+import org.oa4mp.delegation.server.OA2Constants;
+
+import java.net.URI;
+import java.util.Map;
+
+public class RFC6749_4_4Server extends TokenAwareServer{
+ public RFC6749_4_4Server(ServiceClient serviceClient, URI issuer, String wellKnown, boolean serverOIDCEnabled) {
+ super(serviceClient, issuer, wellKnown, serverOIDCEnabled);
+ }
+ public RFC6749_4_4_Response processRFC6749_4_4Request(RFC6749_4_4Request request) {
+ Map parameters = request.getParameters();
+ String rawResponse;
+ RFC6749_4_4_Response response = new RFC6749_4_4_Response();
+ if(parameters.containsKey(OA2Constants.CLIENT_ID)) {
+ rawResponse = getServiceClient().doPost( parameters,
+ (String)parameters.get(OA2Constants.CLIENT_ID),
+ (String)parameters.get(OA2Constants.CLIENT_SECRET));
+ JSONObject jsonObject = JSONObject.fromObject(rawResponse);
+ response.setParameters(jsonObject);
+ }
+ // Note that the spec is very explicit that a refresh token is never returned
+ // in the initial exchange.
+ return response;
+ }
+
+}
diff --git a/oauth2/src/main/java/org/oa4mp/delegation/server/client/RFC7523Utils.java b/oauth2/src/main/java/org/oa4mp/delegation/server/client/RFC7523Utils.java
index 5743afdee..f19fc29ad 100644
--- a/oauth2/src/main/java/org/oa4mp/delegation/server/client/RFC7523Utils.java
+++ b/oauth2/src/main/java/org/oa4mp/delegation/server/client/RFC7523Utils.java
@@ -26,7 +26,10 @@
*/
public class RFC7523Utils implements RFC7523Constants {
/**
- * Does a POST to the endpoint using the client's key.
+ * Does a POST to the endpoint using the client's key. This fuilfills RFC 7523's section 2.2,
+ * authentication using a JWT. This returns a string (a JSON object) since there are
+ * various checks that can/should
+ * be done on the response, but not necessarily immediately.
*
* @param serviceClient
* @param oa2Client
@@ -149,7 +152,8 @@ protected static JSONWebKey findKey(Client client, String kid) {
}
/**
- * Creates an authorization grant for the client. Note that clients must have a previous
+ * Creates an authorization grant for the client as per RFC 7523 section 2.1.
+ * Note that clients must have a previous
* trust relationship to do this, or it will fail.
*
* @param serviceClient
diff --git a/oauth2/src/main/java/org/oa4mp/delegation/server/server/IDTokenResponse.java b/oauth2/src/main/java/org/oa4mp/delegation/server/server/IDTokenResponse.java
index c2ae1c38d..335408b88 100644
--- a/oauth2/src/main/java/org/oa4mp/delegation/server/server/IDTokenResponse.java
+++ b/oauth2/src/main/java/org/oa4mp/delegation/server/server/IDTokenResponse.java
@@ -171,14 +171,19 @@ public void write(HttpServletResponse response) throws IOException {
}
String ss = OA2Scopes.ScopeUtil.toString(allScopes);
- if(!StringUtils.isTrivial(ss)) {
+ if (!StringUtils.isTrivial(ss)) {
m.put(SCOPE, ss);
}
// We have to compute the user metadata no matter what, but only return it if the
- // client is OIDC.
+ // client is OIDC AND has requested it.
if (isOIDC() || serviceTransaction.getResponseTypes().contains(RESPONSE_TYPE_ID_TOKEN)) {
- DebugUtil.trace(this, "writing ID token response");
- m.put(ID_TOKEN, getIdToken().getToken());
+ if (st.getScopes().contains(OA2Scopes.SCOPE_OPENID)) {
+ // It is still possible that an OIDC client does not request its openid scope.
+ // There is nothing the spec that says it has to, just that it is not acting
+ // like an OIDC client, but a standard OAuth 2 client. It can happen.
+ DebugUtil.trace(this, "writing ID token response");
+ m.put(ID_TOKEN, getIdToken().getToken());
+ }
}
JSONObject json = JSONObject.fromObject(m);
diff --git a/proxy/src/main/java/org/oa4mp/server/proxy/OA2ATServlet.java b/proxy/src/main/java/org/oa4mp/server/proxy/OA2ATServlet.java
index 9987cde94..368dcf3f3 100644
--- a/proxy/src/main/java/org/oa4mp/server/proxy/OA2ATServlet.java
+++ b/proxy/src/main/java/org/oa4mp/server/proxy/OA2ATServlet.java
@@ -1,5 +1,6 @@
package org.oa4mp.server.proxy;
+import edu.uiuc.ncsa.security.servlet.HeaderUtils;
import org.oa4mp.server.loader.oauth2.OA2SE;
import org.oa4mp.server.loader.oauth2.claims.IDTokenHandler;
import org.oa4mp.server.loader.oauth2.loader.OA2ConfigurationLoader;
@@ -66,6 +67,7 @@
import static org.oa4mp.delegation.server.OA2Constants.STATE;
import static org.oa4mp.delegation.server.server.RFC8693Constants.*;
import static org.oa4mp.delegation.server.server.claims.OA2Claims.JWT_ID;
+import static org.oa4mp.delegation.server.server.claims.OA2Claims.SUBJECT;
/**
* Created by Jeff Gaynor
@@ -161,12 +163,11 @@ protected boolean executeByGrant(String grantType,
doRFC7523(request, response, oa2Client);
return true;
}
- OA2Client client = (OA2Client) getClient(request);
+ OA2Client client = getClient(request);
// In all other cases, the client credentials must be sent.
if (client == null) {
warn("executeByGrant encountered a null client");
throw new OA2ATException(OA2Errors.INVALID_REQUEST, "no such client");
-
}
MetaDebugUtil debugger = MyProxyDelegationServlet.createDebugger(client);
debugger.trace(this, "starting execute by grant, grant = \"" + grantType + "\"");
@@ -175,9 +176,24 @@ protected boolean executeByGrant(String grantType,
verifyClient(resolvedClient, request, true);
//verifyClientSecret(resolvedClient, getClientSecret(request));
}
+ if (grantType.equals(GRANT_TYPE_CLIENT_CREDENTIALS)) {
+ if (client.isPublicClient()) {
+ warn("public client " + client.getIdentifierString() + " requested a client credential flow but is not allowed on this server.");
+ throw new OA2ATException(OA2Errors.REQUEST_NOT_SUPPORTED,
+ "client credential flow not supported for public clients");
+ }
+ if (!client.isServiceClient()) {
+ warn("Client " + client.getIdentifierString() + " requested a client credential flow but is not allowed on this server.");
+ throw new OA2ATException(OA2Errors.REQUEST_NOT_SUPPORTED,
+ "client credential flow not supported for this client");
+ }
+ doRFC6749_4_4(request, response, resolvedClient);
+ debugger.trace(this, "client credential flow completed, returning... ");
+ return true;
+ }
if (grantType.equals(GRANT_TYPE_TOKEN_EXCHANGE)) {
if (!oa2SE.isRfc8693Enabled()) {
- warn("Client " + client.getIdentifierString() + " requested a token exchange but token exchange is not enabled onthis server.");
+ warn("Client " + client.getIdentifierString() + " requested a token exchange but token exchange is not enabled on this server.");
throw new OA2ATException(OA2Errors.REQUEST_NOT_SUPPORTED,
"token exchange not supported on this server ");
}
@@ -210,6 +226,137 @@ protected boolean executeByGrant(String grantType,
return false;
}
+ /**
+ * Does client credential flow
+ *
+ * @param request
+ * @param response
+ * @param client
+ * @throws Throwable
+ */
+ protected void doRFC6749_4_4(HttpServletRequest request, HttpServletResponse response, OA2Client client) throws Throwable {
+ ServletDebugUtil.printAllParameters(getClass(), request, true);
+ String state = getFirstParameterValue(request, STATE);
+ String nonce = getFirstParameterValue(request, NONCE);
+ OA2ServiceTransaction serviceTransaction = (OA2ServiceTransaction) getOA2SE().getTransactionStore().create();
+ String uri = serviceTransaction.getIdentifier().getUri().toString();
+ if (-1 == uri.indexOf("/rfc6749_4_4")) {
+ uri = uri.substring(0, uri.indexOf("/")) + "/rfc6749_4_4" + uri.substring(uri.indexOf("/"));
+ serviceTransaction.setIdentifier(BasicIdentifier.newID(uri));
+ }
+ serviceTransaction.setRequestState(state);
+ serviceTransaction.setNonce(nonce);
+ serviceTransaction.setClient(client);
+ Date now = new Date();
+ serviceTransaction.setAuthTime(now); // auth time is now.
+ client.setLastAccessed(now);
+ /*
+ exp
+ jti
+ */
+ JSONObject claims = new JSONObject();
+ List audience = HeaderUtils.getParameters(request, AUDIENCE, " ");
+ serviceTransaction.setAudience(audience);
+ List resource = HeaderUtils.getParameters(request, RESOURCE, " ");
+ serviceTransaction.setResource(resource);
+ if (null != request.getParameter(OA2Claims.ISSUER)) {
+ List issuers = HeaderUtils.getParameters(request, OA2Claims.ISSUER, null);
+ // the contract is that the issuers must match the client id if present
+ for (String issuer : issuers) {
+ if (!client.getIdentifierString().equals(issuer)) {
+ throw new OA2ATException(OA2Errors.INVALID_SCOPE,
+ "unable to determine scopes",
+ HttpStatus.SC_BAD_REQUEST,
+ state, client);
+
+ }
+ }
+ }
+ serviceTransaction.setUserMetaData(claims); // set this so it exists for later.
+ List scopes = OA2HeaderUtils.getParameters(request, SCOPE, " ");
+ // scopes are optional.
+ if (!scopes.isEmpty()) {
+ try {
+ serviceTransaction.setScopes(ClientUtils.resolveScopes(
+ request,
+ serviceTransaction,
+ client,
+ scopes,
+ true, false, true));
+ } catch (OA2RedirectableError redirectableError) {
+ throw new OA2ATException(OA2Errors.INVALID_SCOPE,
+ "unable to determine scopes",
+ HttpStatus.SC_BAD_REQUEST,
+ state, client);
+ }
+ }
+ String subject = getFirstParameterValue(request, SUBJECT);
+ if (subject == null) {
+ if (scopes.contains(OA2Scopes.SCOPE_OPENID)) {
+ // case here is that they are requesting an id token, but there is no subject
+ // for the ID token.
+ throw new OA2ATException(OA2Errors.INVALID_REQUEST,
+ "no subject for ID token set",
+ HttpStatus.SC_BAD_REQUEST,
+ state, client);
+
+ }
+ } else {
+ serviceTransaction.setUsername(subject);
+ claims.put(OA2Claims.SUBJECT, serviceTransaction.getUsername());
+ }
+ if (request.getParameter(OA2Constants.ACCESS_TOKEN_LIFETIME) != null) {
+ String rawATLifetime = getFirstParameterValue(request, OA2Constants.ACCESS_TOKEN_LIFETIME);
+ try {
+ long at = XMLConfigUtil.getValueSecsOrMillis(rawATLifetime);
+ serviceTransaction.setRequestedATLifetime(at);
+ } catch (Throwable t) {
+ getServiceEnvironment().info("Could not set requested access token lifetime to \"" + rawATLifetime
+ + "\" for client " + client.getIdentifierString());
+ // do nothing.
+ }
+ }
+ // serviceTransaction.setAccessTokenLifetime(ClientUtils.computeATLifetime(serviceTransaction, client, getOA2SE()));
+
+ if (request.getParameter(OA2Constants.REFRESH_LIFETIME) != null) {
+ String rawATLifetime = getFirstParameterValue(request, OA2Constants.REFRESH_LIFETIME);
+ try {
+ long rt = XMLConfigUtil.getValueSecsOrMillis(rawATLifetime);
+ serviceTransaction.setRequestedRTLifetime(rt);
+ } catch (Throwable t) {
+ getServiceEnvironment().info("Could not set requested refresh token lifetime to \"" + rawATLifetime
+ + "\" for client " + client.getIdentifierString());
+ // do nothing.
+ }
+ }
+/* if (client.isRTLifetimeEnabled()) {
+ long lifetime = ClientUtils.computeRefreshLifetime(serviceTransaction, client, getOA2SE());
+ serviceTransaction.setRefreshTokenLifetime(ClientUtils.computeRefreshLifetime(serviceTransaction, client, getOA2SE()));
+ serviceTransaction.setRefreshTokenExpiresAt(System.currentTimeMillis() + lifetime);
+ } else {
+ serviceTransaction.setRefreshTokenLifetime(0L);
+ }*/
+ if (request.getParameter(OA2Constants.ID_TOKEN_LIFETIME) != null) {
+ String rawATLifetime = getFirstParameterValue(request, OA2Constants.ID_TOKEN_LIFETIME);
+ try {
+ long idt = XMLConfigUtil.getValueSecsOrMillis(rawATLifetime);
+ serviceTransaction.setRequestedIDTLifetime(idt);
+ } catch (Throwable t) {
+ getServiceEnvironment().info("Could not set requested ID token lifetime to \"" + rawATLifetime
+ + "\" for client " + client.getIdentifierString());
+ // do nothing.
+ }
+ }
+ // serviceTransaction.setIDTokenLifetime(ClientUtils.computeIDTLifetime(serviceTransaction, client, getOA2SE()));
+
+
+ OA2ServletUtils.processXAs(request, serviceTransaction, client);
+
+ // ****** End of setup for request, setup for access token request
+ processServiceClientRequest(request, response, client, serviceTransaction, true);
+
+ }
+
/**
* Processes a request from a service client. This allows for getting tokens from a trusted
* client directly from the token endpoint by sending in the authorization grant request
@@ -305,7 +452,7 @@ protected void doRFC7523(HttpServletRequest request, HttpServletResponse respons
serviceTransaction,
client,
scopes,
- true, false));
+ true, false, false));
} catch (OA2RedirectableError redirectableError) {
throw new OA2ATException(OA2Errors.INVALID_SCOPE,
"unable to determine scopes",
@@ -319,44 +466,44 @@ protected void doRFC7523(HttpServletRequest request, HttpServletResponse respons
// long at = Long.parseLong(rawATLifetime);
serviceTransaction.setRequestedATLifetime(at);
} catch (Throwable t) {
- getServiceEnvironment().info("Could not set request access token lifetime to \"" + rawATLifetime
+ getServiceEnvironment().info("Could not set requested access token lifetime to \"" + rawATLifetime
+ "\" for client " + client.getIdentifierString());
// do nothing.
}
}
- serviceTransaction.setAccessTokenLifetime(ClientUtils.computeATLifetime(serviceTransaction, client, getOA2SE()));
+ // serviceTransaction.setAccessTokenLifetime(ClientUtils.computeATLifetime(serviceTransaction, client, getOA2SE()));
if (jsonRequest.containsKey(OA2Constants.REFRESH_LIFETIME)) {
String rawRTLifetime = jsonRequest.getString(OA2Constants.REFRESH_LIFETIME);
try {
long at = XMLConfigUtil.getValueSecsOrMillis(rawRTLifetime);
serviceTransaction.setRequestedRTLifetime(at);
} catch (Throwable t) {
- getServiceEnvironment().info("Could not set request refresh token lifetime to \"" + rawRTLifetime
+ getServiceEnvironment().info("Could not set requested refresh token lifetime to \"" + rawRTLifetime
+ "\" for client " + client.getIdentifierString());
// do nothing.
}
}
- if (client.isRTLifetimeEnabled()) {
+ /* if (client.isRTLifetimeEnabled()) {
long lifetime = ClientUtils.computeRefreshLifetime(serviceTransaction, client, getOA2SE());
serviceTransaction.setRefreshTokenLifetime(ClientUtils.computeRefreshLifetime(serviceTransaction, client, getOA2SE()));
serviceTransaction.setRefreshTokenExpiresAt(System.currentTimeMillis() + lifetime);
} else {
serviceTransaction.setRefreshTokenLifetime(0L);
- }
+ }*/
if (jsonRequest.containsKey(OA2Constants.ID_TOKEN_LIFETIME)) {
String rawLifetime = jsonRequest.getString(OA2Constants.ID_TOKEN_LIFETIME);
try {
long at = XMLConfigUtil.getValueSecsOrMillis(rawLifetime);
serviceTransaction.setRequestedIDTLifetime(at);
} catch (Throwable t) {
- getServiceEnvironment().info("Could not set request ID token lifetime to \"" + rawLifetime
+ getServiceEnvironment().info("Could not set requested ID token lifetime to \"" + rawLifetime
+ "\" for client " + client.getIdentifierString());
// do nothing.
}
}
- serviceTransaction.setIDTokenLifetime(ClientUtils.computeIDTLifetime(serviceTransaction, client, getOA2SE()));
+ // serviceTransaction.setIDTokenLifetime(ClientUtils.computeIDTLifetime(serviceTransaction, client, getOA2SE()));
try {
String[] rawResource = extractArray(jsonRequest, RESOURCE);
String[] rawAudience = extractArray(jsonRequest, AUDIENCE);
@@ -364,6 +511,33 @@ protected void doRFC7523(HttpServletRequest request, HttpServletResponse respons
} catch (OA2GeneralError ge) {
throw new OA2ATException(ge.getError(), ge.getDescription(), ge.getHttpStatus(), state, client);
}
+ processServiceClientRequest(request, response, client, serviceTransaction, false);
+ }
+
+ /**
+ * Both RFC7523 and credential flow clients operate the same once the parameters have
+ * been processed. This is code common to both for that.
+ *
+ * @param request
+ * @param response
+ * @param client
+ * @param serviceTransaction
+ * @throws Throwable
+ */
+ private void processServiceClientRequest(HttpServletRequest request,
+ HttpServletResponse response,
+ OA2Client client,
+ OA2ServiceTransaction serviceTransaction,
+ boolean isRFC6749_4_4) throws Throwable {
+ serviceTransaction.setAccessTokenLifetime(ClientUtils.computeATLifetime(serviceTransaction, client, getOA2SE()));
+ serviceTransaction.setIDTokenLifetime(ClientUtils.computeIDTLifetime(serviceTransaction, client, getOA2SE()));
+ if (client.isRTLifetimeEnabled()) {
+ long lifetime = ClientUtils.computeRefreshLifetime(serviceTransaction, client, getOA2SE());
+ serviceTransaction.setRefreshTokenLifetime(ClientUtils.computeRefreshLifetime(serviceTransaction, client, getOA2SE()));
+ serviceTransaction.setRefreshTokenExpiresAt(System.currentTimeMillis() + lifetime);
+ } else {
+ serviceTransaction.setRefreshTokenLifetime(0L);
+ }
ATRequest atRequest = getATRequest(request, serviceTransaction, client);
ATIResponse2 atiResponse2 = (ATIResponse2) getATI().process(atRequest);
serviceTransaction.setAccessToken(atiResponse2.getAccessToken());
@@ -403,6 +577,16 @@ protected void doRFC7523(HttpServletRequest request, HttpServletResponse respons
}
ATIResponse2 atResponse = (ATIResponse2) issuerTransactionState.getIssuerResponse();
atResponse.setJsonWebKey(key);
+ if(isRFC6749_4_4){
+ // Only return a refresh token if they explicitly request it with the offline_access
+ // scope AND they are allowed to get
+ if(!serviceTransaction.getScopes().contains(OA2Scopes.SCOPE_OFFLINE_ACCESS)){
+ ((ATIResponse2) issuerTransactionState.getIssuerResponse()).setRefreshToken(null);
+ }
+ if(!serviceTransaction.getScopes().contains(OA2Scopes.SCOPE_OPENID)){
+ ((ATIResponse2) issuerTransactionState.getIssuerResponse()).setIdToken(null);
+ }
+ }
writeATResponse(response, issuerTransactionState);
}
@@ -896,6 +1080,13 @@ Topography of the store. Auth grants (called temp_token for historical reasons)
debugger.trace(this, "resolving ersatz client");
try {
Permission p = getOA2SE().getPermissionStore().getErsatzChain(t.getProvisioningAdminID(), t.getProvisioningClientID(), client.getIdentifier());
+ if(p == null){
+ throw new OA2ATException(OA2Errors.UNAUTHORIZED_CLIENT,
+ "permissions not found for admin '" + t.getProvisioningAdminID()+
+ "' + and provisioner '" + t.getProvisioningClientID()+ "'", HttpStatus.SC_UNAUTHORIZED,
+ t.getRequestState(),
+ client);
+ }
client = createErsatz(t.getProvisioningClientID(), client, p.getErsatzChain());
debugger = MyProxyDelegationServlet.createDebugger(client);
t.setClient(client);
@@ -1150,7 +1341,7 @@ private void doRFC8693Fork(RFC8693Thingie rfc8693Thingie,
}
if (returnRTOnly) {
rfcClaims.put(OA2Constants.ACCESS_TOKEN, rtiResponse.getRefreshToken().getToken()); // Required.
- rfcClaims.put(EXPIRES_IN, rtiResponse.getRefreshToken().getLifetime()/1000);
+ rfcClaims.put(EXPIRES_IN, rtiResponse.getRefreshToken().getLifetime() / 1000);
if (client.isRTLifetimeEnabled() && rtiResponse.hasRefreshToken()) {
t.setRTData(rtiResponse.getRefreshToken().getPayload());
@@ -1164,7 +1355,7 @@ private void doRFC8693Fork(RFC8693Thingie rfc8693Thingie,
} else {
rfcClaims.put(OA2Constants.ACCESS_TOKEN, rtiResponse.getAccessToken().encodeToken()); // Required.
}
- rfcClaims.put(EXPIRES_IN, rtiResponse.getAccessToken().getLifetime()/1000);
+ rfcClaims.put(EXPIRES_IN, rtiResponse.getAccessToken().getLifetime() / 1000);
rfcClaims.put(OA2Constants.ID_TOKEN, rtiResponse.getIdToken().getToken());
@@ -1348,7 +1539,7 @@ private void doRFC8693Exchange(RFC8693Thingie rfc8693Thingie,
rfcClaims.put(OA2Constants.ACCESS_TOKEN, rtiResponse.getAccessToken().encodeToken()); // Required.
}
// https://jira.ncsa.illinois.edu/browse/CIL-2019
- rfcClaims.put(EXPIRES_IN, rtiResponse.getAccessToken().getLifetime()/1000);
+ rfcClaims.put(EXPIRES_IN, rtiResponse.getAccessToken().getLifetime() / 1000);
// create scope string Remember that these may have been changed by a script,
// so here is the right place to set it.
rfcClaims.put(OA2Constants.SCOPE, listToString(newATTX.getScopes()));
@@ -1363,7 +1554,7 @@ private void doRFC8693Exchange(RFC8693Thingie rfc8693Thingie,
rfcClaims.put(OA2Constants.REFRESH_TOKEN, rtiResponse.getRefreshToken().encodeToken()); // Optional
}
// https://jira.ncsa.illinois.edu/browse/CIL-2019
- rfcClaims.put(EXPIRES_IN, rtiResponse.getRefreshToken().getLifetime()/1000);
+ rfcClaims.put(EXPIRES_IN, rtiResponse.getRefreshToken().getLifetime() / 1000);
long gracePeriod = ClientUtils.computeRTGracePeriod(client, oa2se);
long expiresAt = System.currentTimeMillis() + gracePeriod;
@@ -1384,7 +1575,7 @@ private void doRFC8693Exchange(RFC8693Thingie rfc8693Thingie,
debugger.trace(this, "Processed id token return type");
rfcClaims.put(OA2Constants.ACCESS_TOKEN, rtiResponse.getIdToken().getToken());
// https://jira.ncsa.illinois.edu/browse/CIL-2019
- rfcClaims.put(EXPIRES_IN, rtiResponse.getIdToken().getLifetime()/1000);
+ rfcClaims.put(EXPIRES_IN, rtiResponse.getIdToken().getLifetime() / 1000);
}
debugger.trace(this, "rfc claims returned:" + rfcClaims.toString(1));
diff --git a/proxy/src/main/java/org/oa4mp/server/proxy/ProxyUtils.java b/proxy/src/main/java/org/oa4mp/server/proxy/ProxyUtils.java
index 7b1d9ef4c..da95410ca 100644
--- a/proxy/src/main/java/org/oa4mp/server/proxy/ProxyUtils.java
+++ b/proxy/src/main/java/org/oa4mp/server/proxy/ProxyUtils.java
@@ -333,7 +333,7 @@ protected static void setClaimsFromProxy(OA2ServiceTransaction t, JSONObject pro
}
/**
- * Attempt to do a refresh of the claims from the proxy server. This is not sued yet since there are a
+ * Attempt to do a refresh of the claims from the proxy server. This is not used yet since there are a
* lot of policy type decisions to make. For instance, what if the lifetimes of tokens on the proxy
* are much shorter than on the server? Then there has to be some way to communicate that no updates
* to the claims are possible.
@@ -344,8 +344,10 @@ protected static void setClaimsFromProxy(OA2ServiceTransaction t, JSONObject pro
*/
protected static void doProxyClaimsRefresh(OA2SE oa2SE, OA2ServiceTransaction t) throws Throwable {
OA2CLCCommands clcCommands = getCLC(oa2SE, t);
- clcCommands.refresh(new InputLine("user_info "));
- if (!clcCommands.hadException()) {
+ try {
+ //clcCommands.refresh(new InputLine("user_info "));
+ clcCommands.refresh();
+ }catch(Throwable throwable){
setClaimsFromProxy(t, clcCommands.getIdToken().getPayload(), MyProxyDelegationServlet.createDebugger(t.getOA2Client()));
}
t.setProxyState(clcCommands.toJSON());
diff --git a/qdl/buildNumber.properties b/qdl/buildNumber.properties
index cfeda4d05..6e717b700 100644
--- a/qdl/buildNumber.properties
+++ b/qdl/buildNumber.properties
@@ -1,3 +1,3 @@
#maven.buildNumber.plugin properties file
-#Wed Oct 02 16:49:27 CDT 2024
-buildNumber\\d*=13289
+#Tue Oct 15 05:53:56 CDT 2024
+buildNumber\\d*=13339
diff --git a/qdl/src/main/docs/qdl_clc_ini.odt b/qdl/src/main/docs/qdl_clc_ini.odt
index a077ce8bf..84b978dda 100644
Binary files a/qdl/src/main/docs/qdl_clc_ini.odt and b/qdl/src/main/docs/qdl_clc_ini.odt differ
diff --git a/qdl/src/main/java/org/oa4mp/server/qdl/CLC.java b/qdl/src/main/java/org/oa4mp/server/qdl/CLC.java
index 16ad68816..c44bfdc6b 100644
--- a/qdl/src/main/java/org/oa4mp/server/qdl/CLC.java
+++ b/qdl/src/main/java/org/oa4mp/server/qdl/CLC.java
@@ -1,5 +1,6 @@
package org.oa4mp.server.qdl;
+import edu.uiuc.ncsa.security.core.util.StringUtils;
import org.oa4mp.server.admin.myproxy.oauth2.tools.OA2CLCCommands;
import org.oa4mp.server.admin.myproxy.oauth2.tools.OA2CommandLineClient;
import org.oa4mp.server.qdl.clc.QDLCLC;
@@ -216,25 +217,53 @@ public int[] getArgCount() {
public Object evaluate(Object[] objects, State state) throws Throwable {
checkInit();
String args = DUMMY_ARG;
+ boolean verify = false;
+ boolean rawResponse = false;
if (objects.length == 1) {
- if (objects[0] instanceof Boolean) {
- if (!(Boolean) objects[0]) {
- args = args + " " + clcCommands.NO_VERIFY_JWT;
+ if (objects[0] instanceof QDLStem) {
+ QDLStem input = (QDLStem) objects[0];
+ if (input.containsKey("verify")) {
+ verify = input.getBoolean("verify");
+ }
+ if (input.containsKey("raw_response")) {
+ rawResponse = input.getBoolean("raw_response");
}
- } else {
- throw new IllegalArgumentException(getName() + " requires a boolean argument");
}
}
+ if (!verify) {
+ args = args + " " + clcCommands.NO_VERIFY_JWT;
+ }
+
clcCommands.access(new InputLine(args));
+ if (rawResponse) {
+ QDLStem out = new QDLStem();
+ try {
+ JSONObject jsonObject = JSONObject.fromObject(clcCommands.getCurrentATResponse().getRawResponse());
+ out.fromJSON(jsonObject);
+ return out;
+ } catch (Throwable t) {
+ }
+ return clcCommands.getCurrentATResponse().getRawResponse();
+ }
return getTokens();
}
@Override
public List getDocumentation(int argCount) {
List doxx = new ArrayList<>();
- doxx.add(getName() + "([verify_jwts]) get the access token.");
- doxx.add("verify_jwts - if true (default) verify any JWTs. If false, do not verify them.");
+ switch (argCount) {
+ case 0:
+ doxx.add(getName() + "() get the access token, verifying the response. This returns the tokens.");
+ break;
+ case 1:
+ doxx.add(getName() + "(arg.) get the access token, using the stem entries to construct the response.");
+ String bb = StringUtils.getBlanks(getName().length() + 1);
+ doxx.add("\nThe elements of the arg. stem are:\n");
+ doxx.add(" verify (boolean) - if true (default, verify the tokens");
+ doxx.add("raw_response (boolean) - if false, return the raw response. If true (default) return the actual tokens.");
+ break;
+ }
doxx.add(checkInitMessage);
return doxx;
}
@@ -353,7 +382,8 @@ public int[] getArgCount() {
@Override
public Object evaluate(Object[] objects, State state) throws Throwable {
checkInit();
- clcCommands.refresh(argsToInputLine(getName(), objects));
+ //clcCommands.refresh(argsToInputLine(getName(), objects));
+ clcCommands.refresh();
return getTokens();
}
@@ -365,7 +395,9 @@ public List getDocumentation(int argCount) {
return doxx;
}
}
-protected String ECHO_HTTP_RESPONSE = "echo_http_response";
+
+ protected String ECHO_HTTP_RESPONSE = "echo_http_response";
+
public class EchoHttpResponse implements QDLFunction {
@Override
public String getName() {
@@ -374,15 +406,15 @@ public String getName() {
@Override
public int[] getArgCount() {
- return new int[]{0,1};
+ return new int[]{0, 1};
}
@Override
public Object evaluate(Object[] objects, State state) throws Throwable {
- if(objects.length == 0){
+ if (objects.length == 0) {
return ServiceClient.ECHO_RESPONSE;
}
- if(!(objects[0] instanceof Boolean)){
+ if (!(objects[0] instanceof Boolean)) {
throw new IllegalArgumentException(getName() + " requires a boolean argument");
}
@@ -409,6 +441,7 @@ public List getDocumentation(int argCount) {
return doxx;
}
}
+
protected String ECHO_HTTP_REQUEST = "echo_http_request";
public class EchoHTTPRequest implements QDLFunction {
@@ -424,10 +457,10 @@ public int[] getArgCount() {
@Override
public Object evaluate(Object[] objects, State state) throws Throwable {
- if(objects.length == 0){
+ if (objects.length == 0) {
return ServiceClient.ECHO_REQUEST;
}
- if(!(objects[0] instanceof Boolean)){
+ if (!(objects[0] instanceof Boolean)) {
throw new IllegalArgumentException(getName() + " requires a boolean argument");
}
@@ -458,6 +491,7 @@ public List getDocumentation(int argCount) {
}
protected String EXCHANGE_NAME = "exchange";
+ protected String EXCHANGE_RAW_RESPONSE = "raw_response";
public class Exchange implements QDLFunction {
@Override
@@ -467,13 +501,16 @@ public String getName() {
@Override
public int[] getArgCount() {
- return new int[]{0, 1, 2, 3, 4, 5, 6, 7}; // just ion case we need to pass lots
+ return new int[]{0, 1, 2, 3, 4, 5, 6, 7}; // just in case we need to pass lots
}
@Override
public Object evaluate(Object[] objects, State state) throws Throwable {
checkInit();
- clcCommands.exchange(argsToInputLine(getName(), objects));
+ InputLine inputLine = argsToInputLine(getName(), objects);
+ boolean rawResponse = inputLine.hasArg(EXCHANGE_RAW_RESPONSE);
+ inputLine.removeSwitch(EXCHANGE_RAW_RESPONSE);
+ clcCommands.exchange(inputLine);
if (Arrays.asList(objects).contains("-id")) {
// if they request an id token, return it.
QDLStem x = new QDLStem();
@@ -486,16 +523,22 @@ public Object evaluate(Object[] objects, State state) throws Throwable {
x.put("refresh_token", tokenToStem(clcCommands.getDummyAsset().getRefreshToken()));
return x;
}
+ if (rawResponse) {
+ QDLStem out = new QDLStem();
+ out.fromJSON(clcCommands.getExchangeResponse());
+ return out;
+ }
return getTokens();
}
@Override
public List getDocumentation(int argCount) {
List doxx = new ArrayList<>();
- doxx.add(getName() + "([-rt | -at | -none] [-subject at|rt|id] Do the token exchange.");
+ doxx.add(getName() + "([-rt | -at | -none] [-subject at|rt|id] [" + EXCHANGE_RAW_RESPONSE+"] Do the token exchange.");
doxx.add("returns: Both tokens, but the requested token is updated.");
doxx.add("Arguments:");
doxx.add("(None) = exchange the access token using the access token as the bearer token. Make sure it has not expired.");
+ doxx.add(EXCHANGE_RAW_RESPONSE + " = return the raw response from the server, not just the tokens.");
doxx.add("-at = explicitly request an access token");
doxx.add("-rt = exchange refresh token, using the refresh token as the bearer token");
doxx.add("-none = do not request the return type, let the server use its default");
@@ -591,7 +634,8 @@ public Object evaluate(Object[] objects, State state) throws Throwable {
@Override
public List getDocumentation(int argCount) {
List doxx = new ArrayList<>();
- doxx.add(getName() + " initiate the device flow. If possible, the user code is copied to the clipboard.");
+ doxx.add(getName() + "() - initiate the device flow. If possible, the user code is copied to the clipboard.");
+ doxx.add("This returns the raw response from the server");
doxx.add(checkInitMessage);
return doxx;
}
@@ -1246,33 +1290,7 @@ public int[] getArgCount() {
@Override
public Object evaluate(Object[] objects, State state) throws Throwable {
checkInit();
- Map parameters = new HashMap();
- if (objects.length == 1) {
- if (objects[0] instanceof String) {
- parameters.put(OA2Claims.SUBJECT, objects[0]);
- } else {
- if (objects[0] instanceof QDLStem) {
- QDLStem stem = (QDLStem) objects[0];
- for (Object key : stem.keySet()) {
- Object value = stem.get(key);
- if (value instanceof QDLStem) {
- QDLStem qdlStem = (QDLStem) value;
- if (qdlStem.isList()) {
- JSONArray array = new JSONArray();
- array.addAll(qdlStem.getQDLList());
- parameters.put(key, array);
- } else {
- throw new IllegalArgumentException("General stems are not supported as values, just lists");
- }
- } else {
- parameters.put(key, value);
- }
- }
- } else {
- throw new IllegalArgumentException("unknown argument type for " + getName());
- }
- }
- }
+ Map parameters = argToMap(objects, getName());
clcCommands.rfc7523(parameters);
return getTokens();
@@ -1296,7 +1314,7 @@ public List getDocumentation(int argCount) {
dd.add("Sends request with the user name (as the subject of the request token). This is used on the");
dd.add("service as if the user logged in with the given name, so all e.g. QDL scripts will run against that name.");
dd.add("\nE.g.");
- dd.add(getName() + "('sub':'bob@bigstate.edu','lifetime':1000000)");
+ dd.add(getName() + "({'sub':'bob@bigstate.edu','lifetime':1000000})");
dd.add("sends the request with the given user name and the parameter (in this case, requesting a certificate lifetime).");
break;
}
@@ -1305,6 +1323,37 @@ public List getDocumentation(int argCount) {
}
}
+ private static Map argToMap(Object[] objects, String name) {
+ Map parameters = new HashMap();
+ if (objects.length == 1) {
+ if (objects[0] instanceof String) {
+ parameters.put(OA2Claims.SUBJECT, objects[0]);
+ } else {
+ if (objects[0] instanceof QDLStem) {
+ QDLStem stem = (QDLStem) objects[0];
+ for (Object key : stem.keySet()) {
+ Object value = stem.get(key);
+ if (value instanceof QDLStem) {
+ QDLStem qdlStem = (QDLStem) value;
+ if (qdlStem.isList()) {
+ JSONArray array = new JSONArray();
+ array.addAll(qdlStem.getQDLList());
+ parameters.put(key, array);
+ } else {
+ throw new IllegalArgumentException("General stems are not supported as values, just lists");
+ }
+ } else {
+ parameters.put(key, value);
+ }
+ }
+ } else {
+ throw new IllegalArgumentException("unknown argument type for " + name);
+ }
+ }
+ }
+ return parameters;
+ }
+
public static String VERBOSE_ON = "verbose_on";
public class VerboseOn implements QDLFunction {
@@ -1396,6 +1445,62 @@ public List getDocumentation(int argCount) {
}
}
+ public static String CLIENT_CREDENTIALS_FLOW = "ccf";
+ public static String CLIENT_CREDENTIALS_RFC7523 = "rfc7523";
+
+ public class ClientCredentialsFlow implements QDLFunction {
+ @Override
+ public String getName() {
+ return CLIENT_CREDENTIALS_FLOW;
+ }
+
+ @Override
+ public int[] getArgCount() {
+ return new int[]{0, 1};
+ }
+
+ @Override
+ public Object evaluate(Object[] objects, State state) throws Throwable {
+ checkInit();
+ Boolean useRFC7523 = Boolean.FALSE;
+ if (0 < objects.length && (objects[0] instanceof QDLStem)) {
+ useRFC7523 = ((QDLStem) objects[0]).containsKey("rfc7523");
+ if (useRFC7523) {
+ useRFC7523 = ((QDLStem) objects[0]).getBoolean("rfc7523");
+ }
+ }
+ Map parameters = argToMap(objects, getName());
+ clcCommands.ccf(parameters, useRFC7523);
+ QDLStem QDLStem = new QDLStem();
+ if (clcCommands.getCcfResponse() != null) {
+ QDLStem.fromJSON(clcCommands.getCcfResponse());
+ }
+ return QDLStem;
+ }
+
+ @Override
+ public List getDocumentation(int argCount) {
+ List doxx = new ArrayList<>();
+ switch (argCount) {
+ case 0:
+ doxx.add(getName() + "() - initiate basic client credentials flow.");
+ break;
+ case 1:
+ String bb = StringUtils.getBlanks(getName().length());
+ doxx.add(getName() + "(username | arg.) - initiate basic client credentials flow, using a subject or arguments.");
+ doxx.add(bb + "If the subject is supplied, it will be used as the subject of the");
+ doxx.add(bb + "ID token.");
+ doxx.add(bb + "If you supply the key " + CLIENT_CREDENTIALS_RFC7523 + " with a true value, then");
+ doxx.add(bb + "RFC7523 credentials are used. If false (default) or omitted, then the standard id + secret is used.");
+ break;
+ }
+ doxx.add("This returns the raw response as a JSON object. To get the tokens or claims, use the");
+ doxx.add("API calls, e.g. clc#tokens()");
+ doxx.add(checkInitMessage);
+ return doxx;
+ }
+ }
+
@Override
public JSONObject serializeToJSON() {
if (clcCommands == null) {
diff --git a/qdl/src/main/java/org/oa4mp/server/qdl/CLCModule.java b/qdl/src/main/java/org/oa4mp/server/qdl/CLCModule.java
index f7e985058..e190c0f2d 100644
--- a/qdl/src/main/java/org/oa4mp/server/qdl/CLCModule.java
+++ b/qdl/src/main/java/org/oa4mp/server/qdl/CLCModule.java
@@ -34,6 +34,7 @@ public Module newInstance(State state) {
funcs.add(clc.new AccessAT());
funcs.add(clc.new AccessRT());
funcs.add(clc.new ClearParam());
+ funcs.add(clc.new ClientCredentialsFlow());
funcs.add(clc.new CreateURI());
funcs.add(clc.new GetCurrentURI());
funcs.add(clc.new DeviceFlow());
diff --git a/qdl/src/main/java/org/oa4mp/server/qdl/OA4MPQDLWorkspace.java b/qdl/src/main/java/org/oa4mp/server/qdl/OA4MPQDLWorkspace.java
index ba3bd761f..9603cd9c6 100644
--- a/qdl/src/main/java/org/oa4mp/server/qdl/OA4MPQDLWorkspace.java
+++ b/qdl/src/main/java/org/oa4mp/server/qdl/OA4MPQDLWorkspace.java
@@ -16,12 +16,13 @@ public OA4MPQDLWorkspace(WorkspaceCommands workspaceCommands) {
public static void main(String[] args) throws Throwable {
WorkspaceProvider workspaceProvider = new QDLOA4MPWorkspaceprovider();
QDLWorkspace.setWorkspaceProvider(workspaceProvider);
+ WorkspaceCommands.setWorkspaceCommandsProvider(new QDLOA4MPWorkspaceCommandsProvider());
OA4MPQDLWorkspace workspace = (OA4MPQDLWorkspace) init(args);
- // Fix https://github.com/ncsa/oa4mp/issues/207
- OA2LibLoader2 oa2LibLoader2 = new OA2LibLoader2();
- oa2LibLoader2.add(workspace.getWorkspaceCommands().getState());
if (workspace != null) {
workspace.mainLoop();
}
}
+
+
+
}
diff --git a/qdl/src/main/java/org/oa4mp/server/qdl/OA4MPQDLWorkspaceCommands.java b/qdl/src/main/java/org/oa4mp/server/qdl/OA4MPQDLWorkspaceCommands.java
index 8fbdc8664..7ef2971b8 100644
--- a/qdl/src/main/java/org/oa4mp/server/qdl/OA4MPQDLWorkspaceCommands.java
+++ b/qdl/src/main/java/org/oa4mp/server/qdl/OA4MPQDLWorkspaceCommands.java
@@ -4,6 +4,7 @@
import org.oa4mp.server.loader.oauth2.loader.OA2ConfigurationLoader;
import org.oa4mp.server.api.OA4MPConfigTags;
import org.qdl_lang.config.QDLConfigurationConstants;
+import org.qdl_lang.state.LibLoader;
import org.qdl_lang.workspace.WorkspaceCommands;
import edu.uiuc.ncsa.security.core.exceptions.MyConfigurationException;
import edu.uiuc.ncsa.security.util.cli.IOInterface;
@@ -11,6 +12,9 @@
import edu.uiuc.ncsa.security.util.configuration.XMLConfigUtil;
import org.apache.commons.configuration.tree.ConfigurationNode;
+import java.util.ArrayList;
+import java.util.List;
+
/**
* Created by Jeff Gaynor
* on 6/21/24 at 11:20 AM
@@ -28,12 +32,12 @@ public void loadQE(InputLine inputLine, String cfgName) throws Throwable {
try {
super.loadQE(inputLine, cfgName);
} catch (MyConfigurationException mcx) {
- // try to process it as a server config
+ // try to process it as a server config
// https://github.com/ncsa/oa4mp/issues/196
ConfigurationNode node = XMLConfigUtil.findConfiguration(inputLine.getNextArgFor(QDLConfigurationConstants.CONFIG_FILE_FLAG), cfgName, OA4MPConfigTags.COMPONENT);
OA2ConfigurationLoader sourceLoader = new OA2ConfigurationLoader<>(node);
OA2SE sourceSE = (OA2SE) sourceLoader.load();
- setQdlEnvironment(sourceSE.getQDLEnvironment());
+ setQdlEnvironment(sourceSE.getQDLEnvironment());
}
}
@@ -46,4 +50,16 @@ public WorkspaceCommands newInstance() {
public WorkspaceCommands newInstance(IOInterface ioInterface) {
return new OA4MPQDLWorkspaceCommands(ioInterface);
}
+
+ protected List loaders;
+
+ @Override
+ public List getLibLoaders() {
+ if (loaders == null) {
+ loaders = new ArrayList<>();
+ // Fix https://github.com/ncsa/oa4mp/issues/207
+ loaders.add(new OA2LibLoader2());
+ }
+ return loaders;
+ }
}
diff --git a/qdl/src/main/java/org/oa4mp/server/qdl/QDLOA4MPWorkspaceCommandsProvider.java b/qdl/src/main/java/org/oa4mp/server/qdl/QDLOA4MPWorkspaceCommandsProvider.java
new file mode 100644
index 000000000..70b49b0d5
--- /dev/null
+++ b/qdl/src/main/java/org/oa4mp/server/qdl/QDLOA4MPWorkspaceCommandsProvider.java
@@ -0,0 +1,17 @@
+package org.oa4mp.server.qdl;
+
+import edu.uiuc.ncsa.security.util.cli.IOInterface;
+import org.qdl_lang.workspace.WorkspaceCommands;
+import org.qdl_lang.workspace.WorkspaceCommandsProvider;
+
+public class QDLOA4MPWorkspaceCommandsProvider extends WorkspaceCommandsProvider {
+ @Override
+ public WorkspaceCommands get() {
+ return new OA4MPQDLWorkspaceCommands();
+ }
+
+ @Override
+ public WorkspaceCommands get(IOInterface ioInterface) {
+ return new OA4MPQDLWorkspaceCommands(ioInterface);
+ }
+}
diff --git a/qdl/src/main/java/org/oa4mp/server/qdl/clc/QDLConfigLoader.java b/qdl/src/main/java/org/oa4mp/server/qdl/clc/QDLConfigLoader.java
index bb43bc6b5..6a99d291c 100644
--- a/qdl/src/main/java/org/oa4mp/server/qdl/clc/QDLConfigLoader.java
+++ b/qdl/src/main/java/org/oa4mp/server/qdl/clc/QDLConfigLoader.java
@@ -62,8 +62,51 @@ public void setFullConfig(QDLStem fullConfig) {
QDLStem fullConfig;
- protected void initialize(QDLStem s, String configName) {
- fullConfig = s;
+ /**
+ * Resolve the extension property for a client. If the client extends another one
+ * track that down and its extensions. Otherwise, do nothing.
+ *
+ * @param all
+ * @param target
+ * @return
+ */
+ protected QDLStem resolveExtends(QDLStem all, QDLStem target) {
+ // Fix https://github.com/ncsa/oa4mp/issues/210
+ if (!target.containsKey(EXTENDS)) {
+ return target; // This extends nothing
+ }
+ // Next figures out if the extends entry is a string or a list of strings
+ Object obj = target.getByMultiIndex(EXTENDS); // reuse obj;
+ QDLStem ext;
+ if (obj instanceof QDLStem) {
+ ext = (QDLStem) obj; //ext is the list of extensions
+ } else {
+ if (obj instanceof String) {
+ ext = new QDLStem();
+ ext.put(0L, obj);
+ } else {
+ throw new IllegalArgumentException("The extends list must contain only strings");
+ }
+ }
+ if (!ext.isList()) {
+ throw new IllegalArgumentException("The extends object must be a list ");
+ }
+ // So ext is now a list of antecessors.
+ // Spec says to do resolutions in order, so in [a0, a1, a2, ...] a0 is overridden by a1, etc.
+ for (Object value : ext.getQDLList().values()) {
+ String name = (String) value;
+ QDLStem y = resolveExtends(all, (QDLStem) all.getByMultiIndex(name));
+ target = target.union(y);
+ }
+ target.remove(EXTENDS); // So this is resolved and won't be redone later
+ return target;
+ }
+
+ protected QDLStem initialize(QDLStem s, String configName) {
+ return NEWinitialize(s, configName);
+ }
+
+ protected QDLStem NEWinitialize(QDLStem s, String configName) {
Object obj = null;
try {
obj = s.getByMultiIndex(configName); // because the constructor
@@ -77,53 +120,15 @@ protected void initialize(QDLStem s, String configName) {
if (!(obj instanceof QDLStem)) {
throw new IllegalArgumentException(configName + " must be a stem, but was a " + obj.getClass().getSimpleName());
}
- QDLStem base = (QDLStem) obj; // base is the original stem with all the configurations.
- if (base.containsKey(EXTENDS)) {
- obj = base.getByMultiIndex(EXTENDS); // reuse obj;
- QDLStem ext;
- if (obj instanceof QDLStem) {
- ext = (QDLStem) obj; //ext is the list of extensions
- } else {
- if (obj instanceof String) {
- ext = new QDLStem();
- ext.put(0L, obj);
- } else {
- throw new IllegalArgumentException("The extends list must contain only strings");
- }
- }
- if (!ext.isList()) {
- throw new IllegalArgumentException("The extends object must be a list ");
- }
- QDLStem arg = null;
- // so if ext is the current configuration and extends = [a,b,c,d] this is
- // a~b~c~d~ext
- // so much easier in QDL...
- for (Object x : ext.getQDLList().values()) {
- boolean firstPass = true;
- if (!(x instanceof String)) {
- throw new IllegalArgumentException("All the elements in the extends list must be names of configuration, i.e., strings.");
- }
- if (s.containsKey((String) x)) {
- QDLStem y = s.getStem((String) x);
- if (firstPass) {
- arg = y;
- firstPass = false;
- } else {
- arg = arg.union(y);
- }
- }
- }
- if (arg != null) {
- base = arg.union(base); // argument overrides values in arg.
- }
- }
- config = base;
+ return resolveExtends(s, (QDLStem) obj);
}
QDLStem config;
public QDLConfigLoader(QDLStem stem, String configName) {
- initialize(stem, configName); // sets config
+ fullConfig = stem;
+ config = initialize(stem, configName); // sets config
+
setConfigName(configName);
}
@@ -171,11 +176,14 @@ public Collection getScopes() {
public JSONWebKeys getKeys() {
if (jsonWebKeys == null) {
String path = getConfig().getString(JWKS);
- JWKUtil2 jwkUtil2 = new JWKUtil2();
- try {
- jsonWebKeys = jwkUtil2.fromJSON(new File(path));
- } catch (IOException e) {
- throw new IllegalArgumentException("the file '" + path + "' could not be loaded:" + e.getMessage());
+ if (path != null) {
+
+ JWKUtil2 jwkUtil2 = new JWKUtil2();
+ try {
+ jsonWebKeys = jwkUtil2.fromJSON(new File(path));
+ } catch (IOException e) {
+ throw new IllegalArgumentException("the file '" + path + "' could not be loaded:" + e.getMessage());
+ }
}
}
return jsonWebKeys;
@@ -703,7 +711,8 @@ public MetaDebugUtil getDebugger() {
public static void main(String[] args) throws Throwable {
String clientFile = "/home/ncsa/dev/csd/config/auto-test/clients.ini";
- String cfgName = "commandline2";
+ //String cfgName = "commandline2";
+ String cfgName = "oauth.conf.basic";
IniParserDriver iniParserDriver = new IniParserDriver();
FileReader fileReader = new FileReader(clientFile);
QDLStem out = iniParserDriver.parse(fileReader, true);
diff --git a/server-admin/src/main/java/org/oa4mp/server/admin/myproxy/oauth2/tools/OA2AdminClientCommands.java b/server-admin/src/main/java/org/oa4mp/server/admin/myproxy/oauth2/tools/OA2AdminClientCommands.java
index c8693378d..e6e8fe790 100644
--- a/server-admin/src/main/java/org/oa4mp/server/admin/myproxy/oauth2/tools/OA2AdminClientCommands.java
+++ b/server-admin/src/main/java/org/oa4mp/server/admin/myproxy/oauth2/tools/OA2AdminClientCommands.java
@@ -447,9 +447,9 @@ public void list_ersatz(InputLine inputLine) throws Exception {
for (Permission p : ersatzClients) {
count++;
if (p.getErsatzChain().size() == 1) {
- say(p.getErsatzChain().get(0).toString());
+ say(p.getErsatzChain().get(0).toString()); // the unique element is a singleton
} else {
- say(p.getErsatzChain().toString());
+ say(p.getErsatzChain().toString()); // whole thing if multiples
}
}
say(count + " total ersatz clients for " + clientID);
diff --git a/server-admin/src/main/java/org/oa4mp/server/admin/myproxy/oauth2/tools/OA2CLCCommands.java b/server-admin/src/main/java/org/oa4mp/server/admin/myproxy/oauth2/tools/OA2CLCCommands.java
index 30cbc950b..96e0c3a66 100644
--- a/server-admin/src/main/java/org/oa4mp/server/admin/myproxy/oauth2/tools/OA2CLCCommands.java
+++ b/server-admin/src/main/java/org/oa4mp/server/admin/myproxy/oauth2/tools/OA2CLCCommands.java
@@ -1,28 +1,8 @@
package org.oa4mp.server.admin.myproxy.oauth2.tools;
-import org.oa4mp.client.api.AssetResponse;
-import org.oa4mp.client.api.OA4MPResponse;
-import org.oa4mp.client.api.storage.AssetStoreUtil;
-import org.oa4mp.server.loader.oauth2.loader.OA2ConfigurationLoader;
-import org.oa4mp.server.loader.oauth2.servlet.RFC8628Constants2;
-import org.oa4mp.delegation.common.token.impl.*;
-import org.oa4mp.delegation.client.request.RTResponse;
-import org.oa4mp.delegation.common.token.Token;
-import org.oa4mp.delegation.server.JWTUtil;
-import org.oa4mp.delegation.server.NonceHerder;
-import org.oa4mp.delegation.server.OA2Constants;
-import org.oa4mp.delegation.server.UserInfo;
-import org.oa4mp.delegation.server.client.ATResponse2;
-import org.oa4mp.delegation.server.client.RFC7523Utils;
-import org.oa4mp.delegation.server.jwt.MyOtherJWTUtil2;
-import org.oa4mp.delegation.server.server.claims.OA2Claims;
-import org.oa4mp.client.loader.OA2Asset;
-import org.oa4mp.client.loader.OA2ClientEnvironment;
-import org.oa4mp.client.loader.OA2MPService;
import edu.uiuc.ncsa.security.core.Identifier;
import edu.uiuc.ncsa.security.core.exceptions.ConnectionException;
import edu.uiuc.ncsa.security.core.exceptions.MyConfigurationException;
-import edu.uiuc.ncsa.security.core.util.DateUtils;
import edu.uiuc.ncsa.security.core.util.MetaDebugUtil;
import edu.uiuc.ncsa.security.core.util.MyLoggingFacade;
import edu.uiuc.ncsa.security.core.util.StringUtils;
@@ -37,6 +17,25 @@
import net.sf.json.JSONArray;
import net.sf.json.JSONObject;
import org.apache.commons.codec.binary.Base64;
+import org.oa4mp.client.api.AssetResponse;
+import org.oa4mp.client.api.OA4MPResponse;
+import org.oa4mp.client.api.storage.AssetStoreUtil;
+import org.oa4mp.client.loader.OA2Asset;
+import org.oa4mp.client.loader.OA2ClientEnvironment;
+import org.oa4mp.client.loader.OA2MPService;
+import org.oa4mp.delegation.client.request.RTResponse;
+import org.oa4mp.delegation.common.token.Token;
+import org.oa4mp.delegation.common.token.impl.*;
+import org.oa4mp.delegation.server.JWTUtil;
+import org.oa4mp.delegation.server.NonceHerder;
+import org.oa4mp.delegation.server.OA2Constants;
+import org.oa4mp.delegation.server.UserInfo;
+import org.oa4mp.delegation.server.client.ATResponse2;
+import org.oa4mp.delegation.server.client.RFC7523Utils;
+import org.oa4mp.delegation.server.jwt.MyOtherJWTUtil2;
+import org.oa4mp.delegation.server.server.claims.OA2Claims;
+import org.oa4mp.server.loader.oauth2.loader.OA2ConfigurationLoader;
+import org.oa4mp.server.loader.oauth2.servlet.RFC8628Constants2;
import java.awt.*;
import java.awt.datatransfer.Clipboard;
@@ -54,10 +53,10 @@
import java.util.List;
import java.util.*;
+import static edu.uiuc.ncsa.security.core.util.StringUtils.isTrivial;
import static org.oa4mp.delegation.server.OA2Constants.*;
import static org.oa4mp.delegation.server.jwt.MyOtherJWTUtil2.PAYLOAD_INDEX;
import static org.oa4mp.delegation.server.server.RFC8628Constants.*;
-import static edu.uiuc.ncsa.security.core.util.StringUtils.isTrivial;
/**
* A command line client. Invoke help as needed, but the basic operation is to create the initial
@@ -128,14 +127,14 @@ public OA2CLCCommands(MyLoggingFacade logger,
super(logger);
try {
if (oa2CommandLineClient.getLoader() == null) {
- if(isBatch()){
+ if (isBatch()) {
throw new MyConfigurationException("No loader found");
- }else{
+ } else {
// probably should not issue this on startup since they may start without
// a configuration then load one.
- // say("warning: no loader found");
+ // say("warning: no loader found");
}
- }else{
+ } else {
setCe((OA2ClientEnvironment) oa2CommandLineClient.getEnvironment());
}
} catch (Throwable t) {
@@ -976,14 +975,7 @@ private void processATResponse(InputLine inputLine) {
if (getDummyAsset().hasRefreshToken() && getDummyAsset().getRefreshToken().isOldVersion() && getDummyAsset().getRefreshToken().getLifetime() < 0) {
getDummyAsset().getRefreshToken().setLifetime(OA2ConfigurationLoader.MAX_REFRESH_TOKEN_LIFETIME_DEFAULT);
}
-/*
- Object rawIdToken = currentATResponse.getParameters().get(RAW_ID_TOKEN);
- if (rawIdToken == null) {
- idToken = null; // something is probably wrong
- } else {
- idToken = TokenFactory.createIDT((String) rawIdToken);
- }
-*/
+
if (inputLine.hasArg(CLAIMS_FLAG)) {
if (getIdToken() != null && getIdToken().getPayload().isEmpty()) {
say("(no claims found)");
@@ -996,29 +988,12 @@ private void processATResponse(InputLine inputLine) {
}
}
- ATResponse2 currentATResponse;
-
-/* public String getAT() {
- if (currentATResponse == null) return "";
- return currentATResponse.getAccessToken().getToken();
- }
-
- public String getRT() {
- if (currentATResponse == null) return "";
- return currentATResponse.getRefreshToken().getToken();
+ public ATResponse2 getCurrentATResponse() {
+ return currentATResponse;
}
- public void setAT(AccessTokenImpl at) {
- if (currentATResponse != null) {
- currentATResponse.setAccessToken(at);
- }
- }
+ ATResponse2 currentATResponse;
- public void setRT(RefreshTokenImpl rt) {
- if (currentATResponse != null) {
- currentATResponse.setRefreshToken(rt);
- }
- }*/
protected void getCertHelp() {
say("get_cert");
@@ -1113,6 +1088,7 @@ public JSONObject resolveFromToken(Token token, boolean noVerify) {
JSONObject json = JWTUtil.verifyAndReadJWT(token.getToken(), keys);
return json;
} catch (Throwable t) {
+ t.printStackTrace();
// do nothing.
}
return null;
@@ -1184,55 +1160,6 @@ public void printToken(TokenImpl accessToken, boolean noVerify, boolean printRaw
NEWprintToken(accessToken, noVerify, printRaw);
}
- protected void OLDprintToken(AccessTokenImpl accessToken, boolean noVerify, boolean printRaw) {
-
- if (accessToken != null) {
- JSONObject token = null;
- // If the access token is a jwt
- try {
- token = resolveFromToken(accessToken, noVerify);
- } catch (Throwable t) {
- say("service is unreachable -- cannot verify token.");
- return;
- }
- if (token == null) {
- say("access token = " + accessToken.getToken());
- if (TokenUtils.isBase32(accessToken.getToken())) {
- // Or we over-write the access token and lose base 64 encoding.
- AccessTokenImpl accessToken2 = new AccessTokenImpl(null);
-
- accessToken2.decodeToken(accessToken.getToken());
- accessToken = accessToken2;
- say(" decoded token:" + accessToken.getToken());
- }
- Date startDate = DateUtils.getDate(accessToken.getToken());
- startDate.setTime(startDate.getTime() + accessToken.getLifetime());
- if (startDate.getTime() < System.currentTimeMillis()) {
- say(" token expired \n");
- } else {
- say(" expires in = " + accessToken.getLifetime() + " ms.");
- say(" valid until " + startDate + "\n");
- }
- } else {
- sayi("JWT access token:" + token.toString(1));
- AccessTokenImpl at = (AccessTokenImpl) accessToken;
- if (printRaw) {
- sayi("raw token=" + at.getToken());
- }
- if (token.containsKey(OA2Claims.EXPIRATION)) {
- Date d = new Date();
- d.setTime(token.getLong(OA2Claims.EXPIRATION) * 1000L);
-
- at.setLifetime(d.getTime() - System.currentTimeMillis());
- if (at.getLifetime() <= 0) {
- say(" token expired at " + d + "\n");
- } else {
- say(" expires in = " + at.getLifetime() + " ms.\n");
- }
- }
- }
- }
- }
protected void NEWprintToken(TokenImpl tokenImpl, boolean noVerify, boolean printRaw) {
if (tokenImpl == null) {
@@ -1280,53 +1207,6 @@ protected void NEWprintToken(TokenImpl tokenImpl, boolean noVerify, boolean prin
}
}
-
- protected void OLDprintToken(RefreshTokenImpl refreshToken, boolean noVerify, boolean printRaw) {
- if (refreshToken != null) {
- JSONObject token = null;
- try {
- token = resolveFromToken(refreshToken, noVerify);
- } catch (Throwable t) {
- say("service is unreachable -- cannot verify token.");
- return;
- }
- if (token == null) {
- say("refresh token = " + refreshToken.getToken());
- // if (TokenUtils.isBase32(refreshToken.getToken())) {
- // RefreshTokenImpl refreshToken2 = new RefreshTokenImpl(null);
-
- // refreshToken2.decodeToken(refreshToken.getToken());
- // refreshToken = refreshToken2;
- say(" decoded token:" + refreshToken.getToken());
- //}
- Date startDate = DateUtils.getDate(refreshToken.getToken());
- startDate.setTime(startDate.getTime() + refreshToken.getLifetime());
- if (startDate.getTime() <= System.currentTimeMillis()) {
- say(" token expired " + startDate + "\n");
- } else {
- say(" expires in = " + refreshToken.getLifetime() + " ms.");
- say(" valid until " + startDate + "\n");
- }
-
- } else {
- say("JWT refresh token = " + token.toString(1));
- sayi("raw token=" + refreshToken.getToken());
-
- if (token.containsKey(OA2Claims.EXPIRATION)) {
- Date d = new Date();
- d.setTime(token.getLong(OA2Claims.EXPIRATION) * 1000L);
-
- refreshToken.setLifetime(d.getTime() - System.currentTimeMillis());
- if (refreshToken.getLifetime() <= 0) {
- say(" token expired\n");
- } else {
- say(" expires in = " + refreshToken.getLifetime() + " ms.");
- }
- }
- }
- }
- }
-
protected void printTokens(boolean noVerify, boolean printRaw) {
// It is possible that the service is down in which case the tokens can't be verified.
if (isVerbose() && currentURI != null) {
@@ -1350,8 +1230,34 @@ public void refresh(InputLine inputLine) throws Exception {
say("Oops! No configuration has been loaded.");
return;
}
+ try {
+ refresh();
+ }catch(Throwable t){
+ say(t.getMessage());
+ return;
+ }
+ if (inputLine.hasArg(CLAIMS_FLAG)) {
+ if (getIdToken().getPayload().isEmpty()) {
+ say("(no claims found)");
+ } else {
+ say(getIdToken().getPayload().toString(2));
+ }
+ }
+ if (isPrintOuput()) {
+ printTokens(inputLine.hasArg(NO_VERIFY_JWT), false);
+ }
+
+ }
+ public void refresh() throws Exception {
lastException = null;
try {
+ if (getCe() == null) {
+ throw new IllegalStateException( "no configuration has been loaded.");
+ }
+
+ if(!dummyAsset.hasRefreshToken()){
+ throw new IllegalStateException("no refresh token");
+ }
RTResponse rtResponse = getService().refresh(dummyAsset.getIdentifier().toString(), refreshParameters);
OA2Asset z = (OA2Asset) getCe().getAssetStore().get(dummyAsset.getIdentifier().toString());
if (z != null && dummyAsset.getIssuedAt().getTime() < z.getIssuedAt().getTime()) {
@@ -1360,16 +1266,6 @@ public void refresh(InputLine inputLine) throws Exception {
// Have to update the AT reponse here every time or no token state is preserved.
currentATResponse = new ATResponse2(dummyAsset.getAccessToken(), dummyAsset.getRefreshToken(), dummyAsset.getIdToken());
currentATResponse.setParameters(rtResponse.getParameters());
- if (inputLine.hasArg(CLAIMS_FLAG)) {
- if (getIdToken().getPayload().isEmpty()) {
- say("(no claims found)");
- } else {
- say(getIdToken().getPayload().toString(2));
- }
- }
- if (isPrintOuput()) {
- printTokens(inputLine.hasArg(NO_VERIFY_JWT), false);
- }
} catch (Throwable t) {
lastException = t;
throw t;
@@ -1425,8 +1321,6 @@ protected void exchangeHelp() {
say("See also: access, refresh, set_param -x to set additional parameters (like specific scopes or the audience");
}
- JSONObject sciToken = null;
-
/*
Testing for exchange:
load localhost:p1
@@ -1529,7 +1423,7 @@ public void exchange(InputLine inputLine) throws Exception {
// This fixes it, but this code should be moved there, along with the resolveFromToken method
// Since it only really affects the CLC, it has a low priority though.
- getService().exchangeRefreshToken(getDummyAsset(),
+ exchangeResponse = getService().exchangeRefreshToken(getDummyAsset(),
subjectToken,
exchangeParameters,
requestedTokenType,
@@ -1565,6 +1459,15 @@ public void exchange(InputLine inputLine) throws Exception {
}
}
+ public JSONObject getExchangeResponse() {
+ return exchangeResponse;
+ }
+
+ public void setExchangeResponse(JSONObject exchangeResponse) {
+ this.exchangeResponse = exchangeResponse;
+ }
+
+ JSONObject exchangeResponse;
protected String ASSET_KEY = "asset";
protected String AT_RESPONSE_KEY = "at_response";
protected String AUTHZ_GRANT_KEY = "authz_grant";
@@ -2294,4 +2197,68 @@ public void rfc7523(InputLine inputLine) throws Exception {
}
+ public static final String CCF_RFC7523 = "-rfc7523";
+ public static final String CCF_SUB = "-sub";
+
+ public void ccf(InputLine inputLine) throws Exception {
+ if (showHelp(inputLine)) {
+ say("ccf [" + CCF_SUB + " subject | " + CCF_RFC7523 + "] - client credential flow with a given subject");
+ say(CCF_RFC7523 + " if present forces using that. The default is to use a standard client_id");
+ say(" and secret. Including this when there are no keys raises an error.");
+ say(CCF_SUB + " subject - sets the subject for the request. If this is configured to return an ID token");
+ say(" with the openid scopes, this will be used as the subject of that token");
+ return;
+ }
+ boolean useRFC7523 = inputLine.hasArg(CCF_RFC7523);
+ inputLine.removeSwitch(CCF_RFC7523);
+
+ String subject = null;
+ if (inputLine.hasArg(CCF_SUB)) {
+ subject = inputLine.getNextArgFor(CCF_SUB);
+ inputLine.removeSwitchAndValue(CCF_SUB);
+ }
+ Map parameters = new HashMap();
+ if (subject != null) {
+ parameters.put(OA2Claims.SUBJECT, subject);
+ }
+
+ say(getCcfResponse().toString(1));
+ }
+
+ public JSONObject ccf(Map parameters, boolean useRFC7523) throws Exception {
+ dummyAsset = (OA2Asset) getCe().getAssetStore().create();
+
+
+ if (!parameters.containsKey(SCOPE)) {
+ JSONArray array = new JSONArray();
+ array.addAll(getCe().getScopes());
+ parameters.put(SCOPE, array);
+ }
+ if (!parameters.containsKey(NONCE)) {
+ parameters.put(NONCE, NonceHerder.createNonce());
+ }
+ if (!parameters.containsKey(STATE)) {
+ parameters.put(STATE, NonceHerder.createNonce()); // random state is ok
+ }
+ if (!parameters.containsKey(OA2Claims.SUBJECT)) {
+ parameters.put(OA2Claims.SUBJECT, getCe().getClient().getIdentifierString());
+ }
+ parameters.put(GRANT_TYPE, GRANT_TYPE_CLIENT_CREDENTIALS);
+ JSONObject jsonObject = getService().rfc6749_4_4(getDummyAsset(), parameters, useRFC7523);
+ setCcfResponse(jsonObject);
+ return jsonObject;
+ }
+
+ public JSONObject getCcfResponse() {
+ return ccfResponse;
+ }
+
+ public void setCcfResponse(JSONObject ccfResponse) {
+ this.ccfResponse = ccfResponse;
+ }
+
+ JSONObject ccfResponse;
}
+/*
+load localhost:test/rfc9068 /home/ncsa/dev/csd/config/client-oa2.xml
+ */
\ No newline at end of file
diff --git a/server-admin/src/main/java/org/oa4mp/server/admin/myproxy/oauth2/tools/OA2ClientCommands.java b/server-admin/src/main/java/org/oa4mp/server/admin/myproxy/oauth2/tools/OA2ClientCommands.java
index 4897d0cc5..e96e0cc19 100644
--- a/server-admin/src/main/java/org/oa4mp/server/admin/myproxy/oauth2/tools/OA2ClientCommands.java
+++ b/server-admin/src/main/java/org/oa4mp/server/admin/myproxy/oauth2/tools/OA2ClientCommands.java
@@ -485,7 +485,7 @@ protected List processCommaSeparatedList(String key, String moniker, Str
}
/**
- * Prompt for a comma separated list, parse it an return it. The moniker identifies what goes on the list,
+ * Prompt for a comma separated list, parse it and return it. The moniker identifies what goes on the list,
* the legalValues (if present) restrict the values to what is on that list.
*
* @param legalValues
@@ -497,6 +497,9 @@ protected List processCommaSeparatedList(String key, String moniker, Str
protected List processCommaSeparatedList(String key, List legalValues, String moniker, String defaultValue) throws IOException {
String rawValues = getPropertyHelp(key, "enter a comma separated list of " + moniker + ".", defaultValue);
LinkedList list = new LinkedList<>();
+ if(StringUtils.isTrivial(rawValues)){
+ return list; // nix to do and don't return a list of blanks or some such.
+ }
if (rawValues.equals(defaultValue)) {
list.add(defaultValue);
return list;
diff --git a/server-admin/src/main/resources/help/client_help.xml b/server-admin/src/main/resources/help/client_help.xml
index 4cc329b73..ea9013926 100644
--- a/server-admin/src/main/resources/help/client_help.xml
+++ b/server-admin/src/main/resources/help/client_help.xml
@@ -566,7 +566,7 @@ be used for signature verification.
Tip: To set this property in the CLI, get the keys and place them in a file, then
use the -file parameter for the update command, e.g.
-oa4mp>update >jwks -file /home/root/temp/new-keys.json]]>
+clients>update >jwks -file /home/root/temp/new-keys.json]]>