diff --git a/README.md b/README.md index 487c06d..c341369 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,5 @@ # JWTs for Java + This is not an officially supported Google product [![CircleCI](https://img.shields.io/circleci/project/github/auth0/java-jwt.svg?style=flat-square)](https://circleci.com/gh/auth0/java-jwt/tree/master) diff --git a/lib/build.gradle b/lib/build.gradle index 2a7694c..dc05b99 100644 --- a/lib/build.gradle +++ b/lib/build.gradle @@ -37,11 +37,12 @@ dependencies { compile 'com.fasterxml.jackson.core:jackson-databind:2.9.2' compile 'commons-codec:commons-codec:1.11' compile 'com.google.code.gson:gson:2.8.2' + compile 'com.google.guava:guava:r05' testCompile 'org.bouncycastle:bcprov-jdk15on:1.58' - testCompile 'junit:junit:4.12' testCompile 'net.jodah:concurrentunit:0.4.3' testCompile 'org.hamcrest:java-hamcrest:2.0.0.0' testCompile 'org.mockito:mockito-core:2.11.0' + testCompile 'org.slf4j:slf4j-simple:1.6.1' } jacocoTestReport { diff --git a/lib/src/main/java/com/auth0/jwt/creators/Message.java b/lib/src/main/java/com/auth0/jwt/creators/Message.java index 45d252c..b581670 100644 --- a/lib/src/main/java/com/auth0/jwt/creators/Message.java +++ b/lib/src/main/java/com/auth0/jwt/creators/Message.java @@ -19,10 +19,10 @@ package com.auth0.jwt.creators; +import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.ObjectMapper; -import com.google.gson.Gson; - +import com.fasterxml.jackson.databind.ObjectWriter; import java.io.IOException; import java.io.UnsupportedEncodingException; import java.net.URLDecoder; @@ -40,8 +40,9 @@ public String toUrlDecoded(String urlEncoded) throws UnsupportedEncodingExceptio return URLDecoder.decode(urlEncoded, "UTF-8"); } - public String toJSON(HashMap hashMap) { - return new Gson().toJson(hashMap); + public String toJSON() throws JsonProcessingException { + ObjectWriter ow = new ObjectMapper().writer().withDefaultPrettyPrinter(); + return ow.writeValueAsString(this); } public HashMap fromJSON(String json) throws IOException { diff --git a/lib/src/main/java/com/auth0/jwt/jwts/ImplicitJWT.java b/lib/src/main/java/com/auth0/jwt/jwts/ImplicitJWT.java index 91eb070..f3f946c 100644 --- a/lib/src/main/java/com/auth0/jwt/jwts/ImplicitJWT.java +++ b/lib/src/main/java/com/auth0/jwt/jwts/ImplicitJWT.java @@ -34,9 +34,9 @@ public class ImplicitJWT extends JWT.BaseVerification implements Verification{ /** * Create Verification object for verification purposes - * @issuer scope * @param issuer * @param audience + * @param iatLeeway * @return */ public Verification createVerifierForImplicit(List issuer, diff --git a/lib/src/main/java/com/auth0/jwt/jwts/ScopedJWT.java b/lib/src/main/java/com/auth0/jwt/jwts/ScopedJWT.java index 0354502..6fbb790 100644 --- a/lib/src/main/java/com/auth0/jwt/jwts/ScopedJWT.java +++ b/lib/src/main/java/com/auth0/jwt/jwts/ScopedJWT.java @@ -34,9 +34,11 @@ public class ScopedJWT extends JWT.BaseVerification implements Verification{ /** * Create Verification object for verification purposes - * @issuer scope + * @param scope * @param issuer * @param audience + * @param expLeeway + * @param iatLeeway * @return */ public Verification createVerifierForScoped(String scope, List issuer, diff --git a/lib/src/main/java/oiccli/AES.java b/lib/src/main/java/oiccli/AES.java new file mode 100644 index 0000000..4f93502 --- /dev/null +++ b/lib/src/main/java/oiccli/AES.java @@ -0,0 +1,139 @@ +package oiccli; + +import java.util.Arrays; +import java.util.List; +import oiccli.exceptions.AESError; +import org.bouncycastle.util.encoders.Base64; +import org.junit.Assert; + +public class AES { + + private static final int BLOCK_SIZE = 16; + private static final String AES_ALGORITHM = "aes_128_cbc"; + private static final String PADDING_7 = "PKCS#7"; + private static final String PADDING_5 = "PKCS#5"; + private byte[] key; + private int mode; + private byte[] iv; + private AES kernel; + + public AES(byte[] key, byte[] iv, int mode) { + assert key instanceof byte[]; + assert iv instanceof byte[]; + this.key = key; + this.mode = mode; + this.iv = iv; + this.kernel = new AES(this.key, this.mode, this.iv); + } + + public static List buildCipher(byte[] key, byte[] iv, String alg) + throws AESError { + String[] algArr = alg.split("_"); + + if (iv == null) { + + } else { + Assert.assertTrue(key.length == BLOCK_SIZE); //AES.blockSize + } + + int bits = Integer.parseInt(algArr[1]); + if (bits != 128 && bits != 192 && bits != 256) { + throw new AESError("Unsupported key length"); + } + + try { + Assert.assertTrue(key.length == bits >> 3); + } catch (AssertionError error) { + throw new AESError("Wrong key length"); + } + + /* + CONVERT THIS TO JAVA + + try: + return AES.new(tobytes(key), POSTFIX_MODE[cmode], tobytes(iv)), iv + except KeyError: + raise AESError("Unsupported chaining mode")*/ + } + + public static List buildCipher(byte[] key, byte[] iv) throws AESError { + buildCipher(key, iv, AES_ALGORITHM); + return null; + } + + public static void encrypt(byte[] key, String msg, byte[] iv, String alg, String padding, + boolean shouldBase64Encrypt, int blockSize) { + + int blockSizeLocal; + if (padding.equals(PADDING_7)) { + blockSizeLocal = blockSize; + } else if (padding.equals(PADDING_5)) { + blockSizeLocal = 8; + } else { + blockSizeLocal = 0; + } + + if (blockSizeLocal != 0) { + int pLength = blockSizeLocal - (msg.length() % blockSizeLocal); + char character = (char) pLength; + msg += (character * pLength); + } + + try { + List buildCipher = buildCipher(key, iv, alg); + } catch (AESError aesError) { + aesError.printStackTrace(); + } + + if (shouldBase64Encrypt) { + Base64.encode(cmsg); + } else { + return cmsg; + } + } + + public static void encrypt(byte[] key, String msg) { + return encrypt(key, msg, null, AES_ALGORITHM, PADDING_7, true, BLOCK_SIZE); + } + + public static byte[] decrypt(byte[] key, String msg, byte[] iv, String padding, boolean shouldBase64Decrypt) throws AESError { + byte[] data; + if (shouldBase64Decrypt) { + data = Base64.decode(msg); + } else { + data = msg.getBytes(); + } + + byte[] ivByteArr = Arrays.copyOfRange(data, 0, BLOCK_SIZE); + if (iv != null) { + Assert.assertEquals(iv, ivByteArr); + } + List cipherList = buildCipher(key, iv); + //insert python code + byte[] decrpytArr = cipher.decrypt(data); + byte[] res = Arrays.copyOfRange(decrpytArr, BLOCK_SIZE, decrpytArr.length); + + if (padding.equals(PADDING_5) || padding.equals(PADDING_7)) { + res = Arrays.copyOfRange(res, 0, res[res.length - 1]); + } + + return Base64.decode(res); + } + + public static void decrypt(byte[] key, String msg, byte[] iv) { + return decrypt(key, msg, null, PADDING_7, true); + } + + public void addAssociatedData(String data) { + data = new String(Base64.encode(data.getBytes())); + this.kernel.update(data); + } + + public List encryptAndTag(byte[] clearData) { + return this.kernel.encryptAndDigest(clearData); + } + + public void decryptAndVerify(byte[] cypherData, byte[] tag) { + return this.kernel.decryptAndVerify(cypherData, tag); + } +} diff --git a/lib/src/main/java/oiccli/AuthorizationResponse.java b/lib/src/main/java/oiccli/AuthorizationResponse.java new file mode 100644 index 0000000..53add1e --- /dev/null +++ b/lib/src/main/java/oiccli/AuthorizationResponse.java @@ -0,0 +1,101 @@ +package oiccli; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; +import oiccli.exceptions.AtHashError; +import oiccli.exceptions.CHashError; +import oiccli.exceptions.MissingRequiredAttribute; + +public class AuthorizationResponse { + + private String code; + private String accessToken; + private String tokenType; + private String idToken; + + public boolean verify(Map args) throws MissingRequiredAttribute { + //super(AuthorizationResponse, self).verify(**kwargs) + + if (this.contains("aud")) { + if (args.containsKey("clientId")) { + if (!this.getAudience().contains(args.get("clientId"))) { + return false; + } + } + } + + if (this.contains("idToken")) { + Map argsTemp = new HashMap<>(); + for (String arg : Arrays.asList("key", "keyjar", "algs", "sender")) { + argsTemp.put(arg, args.get(arg)); + } + + /* + idt = IdToken().from_jwt(str(self["id_token"]), **args) + if not idt.verify(**kwargs): + raise VerificationError("Could not verify id_token", idt) + + _alg = idt.jws_header["alg"] + */ + + String hFunction = "HS" + algorithm.substring(0, algorithm.length() - 3); + if (this.getAccessToken() != null) { + if (this.idt.getAtHash() == null) { + throw new MissingRequiredAttribute("Missing at_hash property" + + idToken.toString()); + } + + if (idt.getAtHash() != jws.leftHash(this.getAccessToken(), hFunction)) { + throw new AtHashError( + "Failed to verify access_token hash " + idt.toString()); + } + } + + if (this.getCode() != null) { + if (this.idt.getCHash() == null) { + throw new MissingRequiredAttribute("Missing cHash property" + + idToken.toString()); + } + if (idt.getCHash() != jws.leftHash(this.getCode(), hFunction)) { + throw new CHashError("Failed to verify code hash " + idt.toString()); + } + } + + this.setVerifiedIdToken(idt); + } + return true; + } + + public String getCode() { + return code; + } + + public void setCode(String code) { + this.code = code; + } + + public String getAccessToken() { + return accessToken; + } + + public void setAccessToken(String accessToken) { + this.accessToken = accessToken; + } + + public String getTokenType() { + return tokenType; + } + + public void setTokenType(String tokenType) { + this.tokenType = tokenType; + } + + public String getIdToken() { + return idToken; + } + + public void setIdToken(String idToken) { + this.idToken = idToken; + } +} diff --git a/lib/src/main/java/oiccli/ErrorMessage.java b/lib/src/main/java/oiccli/ErrorMessage.java new file mode 100644 index 0000000..44f788e --- /dev/null +++ b/lib/src/main/java/oiccli/ErrorMessage.java @@ -0,0 +1,7 @@ +package oiccli; + +import com.auth0.jwt.creators.Message; + +public class ErrorMessage extends Message { + +} diff --git a/lib/src/main/java/oiccli/FakeResponse.java b/lib/src/main/java/oiccli/FakeResponse.java new file mode 100644 index 0000000..fcd5158 --- /dev/null +++ b/lib/src/main/java/oiccli/FakeResponse.java @@ -0,0 +1,17 @@ +package oiccli; + +import java.util.HashMap; +import java.util.Map; + +public class FakeResponse { + + private Map headers; + private String text; + + public FakeResponse(String header) { + Map headersTemp = new HashMap<>(); + headers.put("contentType", header); + this.headers = headersTemp; + this.text = "TEST_RESPONSE"; + } +} diff --git a/lib/src/main/java/oiccli/HTTP/CookieJar.java b/lib/src/main/java/oiccli/HTTP/CookieJar.java new file mode 100644 index 0000000..f143bed --- /dev/null +++ b/lib/src/main/java/oiccli/HTTP/CookieJar.java @@ -0,0 +1,4 @@ +package oiccli.HTTP; + +public class CookieJar { +} diff --git a/lib/src/main/java/oiccli/HTTP/FileCookieJar.java b/lib/src/main/java/oiccli/HTTP/FileCookieJar.java new file mode 100644 index 0000000..c8ba94f --- /dev/null +++ b/lib/src/main/java/oiccli/HTTP/FileCookieJar.java @@ -0,0 +1,4 @@ +package oiccli.HTTP; + +public class FileCookieJar { +} diff --git a/lib/src/main/java/oiccli/HTTP/Http.java b/lib/src/main/java/oiccli/HTTP/Http.java new file mode 100644 index 0000000..21d52e5 --- /dev/null +++ b/lib/src/main/java/oiccli/HTTP/Http.java @@ -0,0 +1,50 @@ +package oiccli.HTTP; + +import java.util.HashMap; +import java.util.Map; +import oiccli.exceptions.ValueError; + +public class Http { + + private String caCerts; + private Map requestArgs; + private KeyJar keyJar; + private FileCookieJar cookieJar; + private Object events; + private Object reqCallback; + + public Http(String caCerts, boolean shouldVerifySSL, KeyJar keyjar, String clientCert) throws ValueError { + this.caCerts = caCerts; + this.requestArgs = new HashMap() {{ + put("allowRedirects", false); + }}; + this.keyJar = keyjar; //or KeyJar(verify_ssl=verify_ssl) + this.cookiejar = FileCookieJar(); + if (caCerts != null) { + if (!shouldVerifySSL) { + throw new ValueError("conflict: ca_certs defined, but verify_ssl is False"); + } + this.requestArgs.put("verify", caCerts); + } else if (shouldVerifySSL) { + this.requestArgs.put("verify", true); + } else { + this.requestArgs.put("verify", false); + } + this.events = null; + this.reqCallback = null; + + if (clientCert != null) { + this.requestArgs.put("cert", clientCert); + } + } + + public Map getCookies() { + Map cookiesMap = new HashMap<>(); + for (cookieJar.getCookies().) + + + { + return cookiesMap; + } + } +} diff --git a/lib/src/main/java/oiccli/HTTP/Response.java b/lib/src/main/java/oiccli/HTTP/Response.java new file mode 100644 index 0000000..5402c71 --- /dev/null +++ b/lib/src/main/java/oiccli/HTTP/Response.java @@ -0,0 +1,109 @@ +package oiccli.HTTP; + +import com.auth0.jwt.creators.Message; +import com.google.common.base.Strings; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +public class Response { + + private static final List successfulCodes = + Arrays.asList(200, 201, 202, 203, 204, 205, 206); + private static Map corsHeaders = new + HashMap() {{ + put("Access-Control-Allow-Origin", "*"); + put("Access-Control-Allow-Methods", "GET"); + put("Access-Control-Allow-Headers", "Authorization"); + }}; + private String status = "200 OK"; + private String contentType = "text/html"; + private Object template; + private Object makoTemplate; + private Object makoLookup; + private Message message; + private List> headers; + + public Response(Message message, Map args) { + this.status = args.get("status"); + this.response = args.get("response"); + this.template = args.get("template"); + this.makoTemplate = args.get("makoTemplate"); + this.makoLookup = args.get("templateLookup"); + + this.message = message; + this.headers = new ArrayList<>(); + this.headers.add(args.get("headers"), new List<>()); + this.contentType = args.get("content"); + + } + + private List getResponse(String message, Map args) { + if (!Strings.isNullOrEmpty(message)) { + if (message.contains("", "</script>"); + } + } + + if (this.template != null) { + for (Map hMap : headers) { + if ("application/json".equals(hMap.get("Content-type"))) { + return Arrays.asList(message); + } else { + //return [str(self.template % message).encode("utf-8")] + } + } + } else if (this.makoLookup != null && this.makoTemplate != null) { + args.put("message", message); + Object mte = this.makoLookup.getTemplate(this.makoTemplate); + return Arrays.asList(mte.render(args)); + } else { + for (String type : this._c_types()) { + if (type.startsWith("image/") || type.equals("application/x-gzip")) { + return Arrays.asList(message); + } + } + } + + + } + + public Map info() { + Map hMap = new HashMap() {{ + put("status", this.status); + put("headers", this.headers); + put("message", this.message); + }}; + + return hMap; + } + + public void addHeader(Map value) { + this.headers.add(value); + } + + public Response reply(Map args) { + return this.response(message, args); + } + + public List cTypes() { + List cTypes = new ArrayList<>(); + Iterator it; + Map.Entry pair; + for (Map index : this.headers) { + it = index.entrySet().iterator(); + while (it.hasNext()) { + pair = (Map.Entry) it.next(); + if (((String) pair.getKey()).equals("Content-type")) { + cTypes.add((String) pair.getValue()); + } + } + } + } + + +} diff --git a/lib/src/main/java/oiccli/Service.java b/lib/src/main/java/oiccli/Service.java new file mode 100644 index 0000000..c448621 --- /dev/null +++ b/lib/src/main/java/oiccli/Service.java @@ -0,0 +1,481 @@ +package oiccli; + +import com.auth0.jwt.creators.Message; +import com.google.common.base.Strings; +import java.io.UnsupportedEncodingException; +import java.lang.reflect.Field; +import java.net.URI; +import java.net.URISyntaxException; +import java.text.ParseException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import javax.xml.ws.http.HTTPException; +import oiccli.HTTP.Response; +import oiccli.client_info.ClientInfo; +import oiccli.exceptions.MissingEndpoint; +import oiccli.exceptions.OicCliError; +import oiccli.exceptions.UnsupportedType; +import oiccli.exceptions.ValueError; +import oiccli.exceptions.WrongContentType; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class Service { + + private final static Logger logger = LoggerFactory.getLogger(Service.class); + private static final List successfulCodes = + Arrays.asList(200, 201, 202, 203, 204, 205, 206); + private static final List specialArgs = Arrays.asList("authenticationEndpoint", "algs"); + private static final Map attributes = new HashMap() {{ + put("version", null); + put("name", ""); + put("value", null); + put("port", null); + put("isPortSpecified", false); + put("domain", ""); + put("isDomainSpecified", false); + put("domainInitialDot", false); + put("path", ""); + put("isPathSpecified", false); + put("isSecure", false); + put("expires", null); + put("shouldDiscard", true); + put("comment", null); + put("commentUrl", null); + put("rest", ""); + put("rfc2109", true); + }}; + + private Message msgType; + private Message responseCls; + private ErrorResponse errorResponse; + private String endpointName; + private boolean isSynchronous = true; + private String request; + private String defaultAuthenticationMethod; + private String httpMethod; + private String bodyType; + private String responseBodyType; + private String endpoint; + private String httpLib; + private KeyJar keyJar; + private String clientAuthenticationMethod; + private List events; + private Map defaultRequestArgs; + private List preConstruct; + private List postConstruct; + private List postParseResponse; + private Map conf; + + + public Service(String httpLib, KeyJar keyJar, String clientAuthenticationMethod, Map conf, Map args) throws NoSuchFieldException, IllegalAccessException { + this.httpLib = httpLib; + this.keyJar = keyJar; + this.clientAuthenticationMethod = clientAuthenticationMethod; + this.events = new ArrayList<>(); + this.endpoint = ""; + this.defaultRequestArgs = new HashMap<>(); + + if(conf != null) { + this.conf = conf; + List params = Arrays.asList("msgType", "responseCls", "errorMsg", "defaultAuthenticationMethod", + "httpMethod", "bodyType", "responseBodyType"); + for(String param : params) { + if(conf.containsKey(param)) { + this.getClass().getField(param).set(this, conf.get("param")); + } + } + } else { + this.conf = new HashMap<>(); + } + + this.preConstruct = new ArrayList<>(); + this.postConstruct = new ArrayList<>(); + this.postParseResponse = new ArrayList<>(); + } + + public Map gatherRequestArgs(ClientInfo clientInfo, Map args) throws NoSuchFieldException { + Map arArgs = new HashMap<>(args); + Set properties = this.msgType.getCParam().keySet(); + + Field field; + for(String property : properties) { + if(!arArgs.containsKey(property)) { + field = clientInfo.getClass().getField(property); + if(field != null) { + arArgs.put(property, field); + } else { + field = this.conf.getRequestArgs().getProp(); + if(field != null) { + arArgs.put(property, field); + } else { + arArgs.put(property, this.defaultRequestArgs.get(property)); + } + } + } + } + + return arArgs; + } + + public static Map updateHttpArgs(Map httpArgs, Map> info) { + Map hArgs = info.get("hArgs"); + if (hArgs == null) { + hArgs = new HashMap<>(); + } + + if (httpArgs == null) { + httpArgs = hArgs; + } else { + httpArgs.update(info.get("hArgs")); + } + + final String headers = info.get("kwargs").get("headers"); + Map hMap = new HashMap() {{ + put("headers", headers); + }}; + httpArgs.update(hMap); + + info.put("httpArgs", httpArgs); + return info; + } + + public Map parseArgs(ClientInfo clientInfo, Map args) throws NoSuchFieldException, IllegalAccessException { + Map arArgs = new HashMap<>(args); + Object value; + for (String property : this.msgType.cParam.keySet()) { + if (!arArgs.containsKey(property)) { + value = clientInfo.getClass().getField(property).get(property); + if (value != null) { + arArgs.put(property, value); + } else { + arArgs.put(property, this.defaultRequestArgs.get(property)); + } + } + } + + return arArgs; + } + + public List> doPreConstruct(ClientInfo clientInfo, Map requestArgs, Map args) { + Map postArgs = new HashMap<>(); + for (Object method : this.preConstruct) { + /*request_args, _post_args = meth(cli_info, request_args, **kwargs) + post_args.update(_post_args)*/ + } + + return Arrays.asList(requestArgs, postArgs); + } + + public Message doPostConstruct(ClientInfo clientInfo, Map requestArgs, Map postArgs) { + + } + + public void doPostParseResponse(Message response, ClientInfo clientInfo, String state, Map args) { + /* + for meth in self.post_parse_response: + meth(resp, cli_info, state=state, **kwargs) + */ + } + + public Message constructMessage(ClientInfo clientInfo, Map requestArgs, Map args) throws NoSuchFieldException, IllegalAccessException { + + if (requestArgs == null) { + requestArgs = new HashMap<>(); + } + + List> returnedArgs = this.doPreConstruct(clientInfo, requestArgs, args); + + if (!this.msgType.getCParam().containsKey("state")) { + args.remove("state"); + } + + Map argsParam = this.gatherRequestArgs(clientInfo, requestArgs); + this.msgType(argsParam); + + return this.doPostConstruct(clientInfo, requestArgs, returnedArgs.get(1)); + } + + public String getEndpoint(Map args) throws MissingEndpoint { + String uri = args.get("endpoint"); + + if (uri != null) { + args.remove("endpoint"); + } else { + if (!Strings.isNullOrEmpty(endpoint)) { + uri = this.endpoint; + } else { + throw new MissingEndpoint("No endpoint specified"); + } + } + + return uri; + } + + public Map uriAndBody(Message cis, String method, Map args) throws UnsupportedEncodingException, UnsupportedType, MissingEndpoint { + String uri = this.getEndpoint(args); + Map response = Util.getOrPost(uri, method, cis, args); + response.put("cis", cis); + Map hMap = new HashMap<>(); + hMap.put("headers", response.get("headers")); + response.put("hArgs", hMap); + + return response; + } + + public Map initAuthenticationMethod(Message cis, ClientInfo clientInfo, String authenticationMethod, + Map httpArgs, Map args) { + return initAuthenticationMethod(cis, clientInfo, authenticationMethod, httpArgs, null, args); + } + + public Map initAuthenticationMethod(Message cis, ClientInfo clientInfo, String authenticationMethod, + Map requestArgs, Map httpArgs, Map args) { + if (httpArgs == null) { + httpArgs = new HashMap<>(); + } + + if (!Strings.isNullOrEmpty(authenticationMethod)) { + //return this.client_authn_method[authn_method]().construct( + // cis, cli_info, request_args, http_args, **kwargs); + } else { + return httpArgs; + } + } + + public Map requestInfo(ClientInfo clientInfo, String method, Map requestArgs, + String bodyType, String authenticationMethod, boolean lax, Map args) throws NoSuchFieldException, IllegalAccessException, MissingEndpoint, UnsupportedEncodingException, UnsupportedType { + if (method == null) { + method = this.httpMethod; + } + + if (requestArgs == null) { + requestArgs = new HashMap<>(); + } + + Map hMap = new HashMap<>(); + for (String key : args.keySet()) { + if (args.get(key) != null && !specialArgs.contains(key)) { + hMap.put(key, args.get(key)); + } + } + + Message cis = this.constructMessage(clientInfo, requestArgs, args); + + if (this.events != null) { + this.events.store("Protocol request", cis); + } + + cis.setLax(lax); + Map hArg = new HashMap<>(); + if (!Strings.isNullOrEmpty(authenticationMethod)) { + hArg = this.initAuthenticationMethod(cis, clientInfo, authenticationMethod, requestArgs, args); + } + + if (hArg != null) { + if (args.containsKey("headers")) { + args.get("headers").update(hArg.get("headers")); + } else { + args.put("headers", hArg.get("headers")); + } + } + + if (bodyType.equals("json")) { + args.put("contentType", "application/json"); + } + + return this.uriAndBody(cis, method, args); + } + + public Map updateHttpArgs(Map httpArgs, Map info) { + Map hArgs = info.get("hArgs"); + if (hArgs == null) { + hArgs = new HashMap<>(); + } + + if (httpArgs == null) { + httpArgs = hArgs; + } else { + httpArgs.update(info.get("h_args")); + } + + info.put("httpArgs", httpArgs); + return info; + } + + public Map doRequestInit(ClientInfo clientInfo, String bodyType, String method, String authenticationMethod, + Map requestArgs, Map httpArgs, Map args) throws NoSuchFieldException, IllegalAccessException, MissingEndpoint, UnsupportedEncodingException, UnsupportedType { + if (Strings.isNullOrEmpty(method)) { + method = this.httpMethod; + } + if (Strings.isNullOrEmpty(authenticationMethod)) { + authenticationMethod = this.defaultAuthenticationMethod; + } + if (Strings.isNullOrEmpty(bodyType)) { + bodyType = this.bodyType; + } + + Map info = this.requestInfo(clientInfo, method, requestArgs, bodyType, authenticationMethod, false, args); + + return this.updateHttpArgs(httpArgs, info); + } + + public String getUrlInfo(String info) throws URISyntaxException { + String query = null; + String fragment = null; + if (info.contains("?") || info.contains("#")) { + URI uri = new URI(info); + query = uri.getQuery(); + fragment = uri.getFragment(); + } + + if (!Strings.isNullOrEmpty(query)) { + return query; + } else { + return fragment; + } + } + + public Message parseResponse(String info, ClientInfo clientInfo, String sFormat, String state, + Map args) throws URISyntaxException, OicCliError { + logger.debug("Response format: " + sFormat); + + if (sFormat.equals("urlencoded")) { + info = this.getUrlInfo(info); + } + + if (this.events != null) { + this.events.store("Response", info); + } + + logger.debug("Response cls: " + this.responseCls.getClass()); + + Message response = this.responseCls.deserialize(info, sFormat, args); + + String message = "Initial response parsing => \"{}\""; + logger.debug(message.format(response.toDict())); + + if (this.events != null) { + this.events.store("Protocol Response", response); + } + + List errorMsgs; + if (response.containsKey("error") && !(response instanceof ErrorResponse)) { + response = null; + errorMsgs = Arrays.asList(this.errorResponse); + + + for (ErrorResponse errorMsg : errorMsgs) { + response = errorMsg.deserialize(info, sFormat); + response.verify(); + break; + } + + logger.debug("Error response: " + response); + } else { + args.put("clientId", clientInfo.getClientId()); + args.put("issuer", clientInfo.getIssuer()); + + if (!args.containsKey("key") && !args.containsKey("keyJar")) { + args.put("keyJar", keyJar); + } + + logger.debug("Verify response with " + args); + + boolean isVerificationSuccessful = response.verify(args); + if (!isVerificationSuccessful) { + logger.error("Verification of the response failed"); + throw new OicCliError("Verification of the response failed"); + } + + if (response.getType().equals("AuthorizationResponse") && response.getScope() == null) { + response.setScope(args.get("scope")); + } + } + + if (response == null) { + this.doPostParseResponse(response, clientInfo, state, args); + } + + return response; + } + + public ErrorResponse parseErrorMessage(Response response, String bodyType) { + String bodyTypeResult; + if (bodyType.equals("txt")) { + bodyTypeResult = "urlencoded"; + } else { + bodyTypeResult = bodyType; + } + + ErrorResponse errorResponse = this.errorResponse.deserialize(response.getText(), bodyTypeResult); + errorResponse.verify(); + + return errorResponse; + } + + public String getValueType(FakeResponse response, String bodyType) throws WrongContentType, ValueError { + if (!Strings.isNullOrEmpty(bodyType)) { + return Util.verifyHeader(response, bodyType); + } else { + return "urlencoded"; + } + } + + public static ErrorResponse parseRequestResponse(ErrorResponse response, ClientInfo clientInfo, String responseBodyType, + String state, Map args) { + int statusCode = response.getStatusCode(); + if (successfulCodes.contains(statusCode)) { + logger.debug("Response body type " + responseBodyType); + String valueType = this.getValueType(response, responseBodyType); + logger.debug("Successful response " + response.getText()); + return this.parseResponse(response.getText(), clientInfo, valueType, state, args); + } else if (statusCode == 302 || statusCode == 303) { + return response; + } else if (statusCode == 500) { + logger.error("(" + statusCode + ")" + response.getText()); + throw new ParseException("ERROR: Something went wrong " + response.getText()); + } else if (statusCode >= 400 && statusCode < 500) { + logger.error("Error response (" + statusCode + "): " + response.getText()); + String valueType = this.getValueType(response, responseBodyType); + return this.parseErrorMessage(response, valueType); + } else { + logger.error("Error response (" + statusCode + "):" + response.getText()); + throw new HTTPException("HTTP ERROR: " + response.getText() + "[" + statusCode + "]" + " on " + response.getUrl()); + } + } + + public static ErrorResponse serviceRequest(String url, String method, String body, String responseBodyType, Map httpArgs, + ClientInfo clientInfo, Map args) { + if (httpArgs == null) { + httpArgs = new HashMap<>(); + } + + logger.debug("Doing request with: URL: " + url + ", method: " + method + ", data: " + body + ", https_args: " + httpArgs); + Response response = this.httpLib(url, method, body, httpArgs); + + if (!args.containsKey("keyjar")) { + args.put("keyjar", this.keyJar); + } + if (responseBodyType == null) { + responseBodyType = this.responseBodyType; + } + + return parseRequestResponse(response, clientInfo, responseBodyType, "", args); + } + + public static ErrorResponse serviceRequest(String url, Map args) { + return serviceRequest(url, "GET", null, "", null, null, args); + } + + public static ErrorResponse serviceRequest(String url, String method, ClientInfo clientInfo, Map args) { + return serviceRequest(url, "GET", null, "", null, null, args); + } + + public static ErrorResponse serviceRequest(String url, String method, ClientInfo clientInfo) { + return serviceRequest(url, "GET", null, "", null, null, null); + } +} diff --git a/lib/src/main/java/oiccli/State.java b/lib/src/main/java/oiccli/State.java new file mode 100644 index 0000000..06287c1 --- /dev/null +++ b/lib/src/main/java/oiccli/State.java @@ -0,0 +1,194 @@ +package oiccli; + +import com.google.common.base.Strings; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; +import oiccli.exceptions.ExpiredToken; +import oiccli.exceptions.UnknownState; +import sun.swing.plaf.synth.DefaultSynthStyle; + +public class State { + + private String clientId; + private Database db; + private int lifetime; + + public State(String clientId, Database db, String dbName, int lifetime) { + this.clientId = clientId; + if (db == null) { + if (!Strings.isNullOrEmpty(dbName)) { + this.db = shelve.open(dbName, true); + } else { + this.db = new Database(); + } + } else { + this.db = db; + } + this.lifetime = lifetime; + } + + public State(String clientId, Database db, String dbName) { + this(clientId, db, dbName, 600); + } + + public String createState(final String receiver, Object request) { + String state = StringUtil.generateRandomString(24); + final long now = System.currentTimeMillis(); + Map info = new HashMap() {{ + put("clientId", clientId); + put("as", receiver); + put("iat", now); + }}; + switch (state) { + case "1": + this.db.set1(info); + break; + case "2": + this.db.set2(info); + break; + case "3": + this.db.set3(info); + break; + } + + return state; + } + + public Map> updateTokenInfo(Map> info, AuthorizationResponse authorizationResponse) { + Map hMap = info.get("token"); + if (hMap == null) { + hMap = new HashMap<>(); + } + + String token = authorizationResponse.getAccessToken(); + hMap.put("accessToken", token); + Integer expiresAtInteger = authorizationResponse.getExpiresIn(); + int expiresAt; + if (expiresAtInteger != null) { + expiresAt = expiresAtInteger.intValue(); + hMap.put("exp", System.currentTimeMillis() + expiresAt); + hMap.put("expiresIn", expiresAt); + } else { + hMap.put("exp", System.currentTimeMillis() + ((Long) hMap.get("expiresIn")).longValue()); + } + + hMap.put("tokenType", authorizationResponse.getTokenType()); + hMap.put("scope", authorizationResponse.getScope()); + + info.put("token", hMap); + + return info; + } + + public Map> addMessageInfo(AuthorizationResponse authorizationResponse, String state) { + if (state == null) { + state = authorizationResponse.getState(); + } + + //_info = self[state] what are all the available types so i can create a switch/case? + + info.put("code", authorizationResponse.getCode()); + this.updateTokenInfo(info, authorizationResponse); + + info.put("idToken", authorizationResponse.getIdToken()); + info.put("refreshToken", authorizationResponse.getRefreshToken()); + + switch (state) { + case "1": + this.db.set1(info); + break; + case "2": + this.db.set2(info); + break; + case "3": + this.db.set3(info); + break; + } + + return info; + } + + public Map addInfo(String state, Map args) { + Map info = this.get(state); + info.update(args); + + switch (state) { + case "1": + this.db.set1(info); + break; + case "2": + this.db.set2(info); + break; + case "3": + this.db.set3(info); + break; + } + + return info; + } + + public Map getTokenInfo(String state, long now) throws ExpiredToken { + //_tinfo = self[state]['token'] + //_exp = _tinfo['exp'] + if (now == 0) { + now = System.currentTimeMillis(); + } + if (now > _exp) { + throw new ExpiredToken("Passed best before"); + } + return _tinfo; + } + + public Map getTokenInfo(String state) throws ExpiredToken { + return getTokenInfo(state, 0); + } + + public Map getResponseArgs(String state, AccessTokenRequest request, int now) throws ExpiredToken { + Map info = state.getState(state); + Map responseArgs = new HashMap<>(); + for (String claim : request.c_param) { + if (claim.equals("accessToken")) { + Map tInfo = this.getTokenInfo(state, now); + if (tInfo == null) { + continue; + } + responseArgs.put(claim, tInfo.get("accessToken")); + } else { + responseArgs.put(claim, info.get(claim)); + } + } + + return responseArgs; + } + + public DefaultSynthStyle.StateInfo addResponse(AuthorizationResponse authorizationResponse, String state) throws UnknownState { + if (Strings.isNullOrEmpty(state)) { + state = authorizationResponse.getState(); + } + + DefaultSynthStyle.StateInfo stateInfo = this.getState(state); + if (stateInfo == null) { + throw new UnknownState(state); + } + + stateInfo.setCode(authorizationResponse.getCode()); + this.updateTokenInfo(stateInfo, authorizationResponse); + + for (String claim : Arrays.asList("idToken", "refreshToken")) { + stateInfo.setClaim(authorizationResponse.getClaim()); + } + + this.setState(stateInfo); + + return stateInfo; + } + + public String getIdToken(String state) { + return this.get(state).get("idToken"); + } + + public void setClientId(String clientId) { + this.clientId = clientId; + } +} diff --git a/lib/src/main/java/oiccli/StringUtil.java b/lib/src/main/java/oiccli/StringUtil.java new file mode 100644 index 0000000..d9a8f6b --- /dev/null +++ b/lib/src/main/java/oiccli/StringUtil.java @@ -0,0 +1,28 @@ +package oiccli; + +import org.apache.commons.text.CharacterPredicates; +import org.apache.commons.text.RandomStringGenerator; + +public class StringUtil { + + public static String generateRandomString(int length) { + return new RandomStringGenerator.Builder() + .withinRange('0', 'z') + .filteredBy(CharacterPredicates.LETTERS, CharacterPredicates.DIGITS) + .build().generate(length); + } + + public static String alg2keytype(String algorithm) { + if (algorithm == null || algorithm.toLowerCase().equals("none")) { + return "none"; + } else if (algorithm.startsWith("RS") || algorithm.startsWith("PS")) { + return "RSA"; + } else if (algorithm.startsWith("HS") || algorithm.startsWith("A")) { + return "oct"; + } else if (algorithm.startsWith("ES") || algorithm.startsWith("ECDH-ES")) { + return "EC"; + } else { + return null; + } + } +} diff --git a/lib/src/main/java/oiccli/Tuple.java b/lib/src/main/java/oiccli/Tuple.java new file mode 100644 index 0000000..10bf39f --- /dev/null +++ b/lib/src/main/java/oiccli/Tuple.java @@ -0,0 +1,20 @@ +package oiccli; + +public class Tuple { + + public final String a; + public final String b; + + public Tuple(String a, String b) { + this.a = a; + this.b = b; + } + + public String getA() { + return a; + } + + public String getB() { + return b; + } +} diff --git a/lib/src/main/java/oiccli/Util.java b/lib/src/main/java/oiccli/Util.java new file mode 100644 index 0000000..f6c968e --- /dev/null +++ b/lib/src/main/java/oiccli/Util.java @@ -0,0 +1,255 @@ +package oiccli; + +import com.auth0.jwt.creators.Message; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.google.common.base.Strings; +import java.io.UnsupportedEncodingException; +import java.text.DateFormat; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import oiccli.exceptions.UnsupportedType; +import oiccli.exceptions.ValueError; +import oiccli.exceptions.WrongContentType; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class Util { + + private static final String URL_ENCODED = "application/x-www-form-urlencoded"; + private static final String JSON_ENCODED = "application/json"; + private static final String JRD_JSON = "application/jrd+json"; + private static final String JWT_ENCODED = "application/jwt"; + private static final String PLAIN_TEXT = "text/plain"; + private static final String HTML_TEXT = "text/html"; + private static final String JWT = "jwt"; + private static final String JSON = "json"; + private static final String URLENCODED = "urlencoded"; + private static final String TEXT = "txt"; + private static final String DEFAULT_POST_CONTENT_TYPE = URL_ENCODED; + private static final Map pairs = new HashMap() {{ + put("port", "portSpecified"); + put("domain", "domainSpecified"); + put("path", "pathSpecified"); + }}; + private static final Map attributes = new HashMap() {{ + put("version", null); + put("name", ""); + put("value", null); + put("port", null); + put("portSpecified", false); + put("domain", ""); + put("domainSpecified", false); + put("domainInitialDot", false); + put("path", ""); + put("pathSpecified", false); + put("secure", false); + put("expires", null); + put("discard", true); + put("comment", null); + put("commentUrl", null); + put("rest", ""); + put("rfc2109", true); + }}; + private final static Logger logger = LoggerFactory.getLogger(Util.class); + private final static Map sortOrder = new HashMap() {{ + put("RS", 0); + put("ES", 1); + put("HS", 2); + put("PS", 3); + put("no", 4); + }}; + + public static Map getOrPost(String uri, String method, Message cis, Map args) throws UnsupportedEncodingException, UnsupportedType { + return getOrPost(uri, method, cis, DEFAULT_POST_CONTENT_TYPE, null, args); + } + + public static Map getOrPost(String uri, String method, Message request, String contentType, String accept, Map args) throws UnsupportedEncodingException, UnsupportedType, JsonProcessingException { + Map response = new HashMap<>(); + String urlEncoded; + if (method.equals("GET") || method.equals("DELETE")) { + /*urlEncoded = request.toUrlEncoded(request.toString()); + if (urlEncoded.contains("?")) { + response.put("uri", uri + '&' + urlEncoded); + } else { + response.put("uri", uri + '?' + urlEncoded); + }*/ + } else if (method.equals("POST") || method.equals("PUT")) { + response.put("uri", uri); + if (contentType.equals(URL_ENCODED)) { + response.put("body", request.toUrlEncoded(request.toString())); + } else if (contentType.equals(JSON_ENCODED)) { + response.put("body", request.toJSON()); + } else { + throw new UnsupportedType("Unsupported content type " + contentType); + } + + Map headers = new HashMap<>(); + headers.put("Content-Type", contentType); + + if (accept != null) { + headers = new HashMap<>(); + headers.put("Accept", accept); + } + if (args.containsKey("headers")) { + //kwargs["headers"].update(header_ext) + } else { + args.put("headers", headers); + } + response.put("args", args); + } else { + throw new UnsupportedType("Unsupported HTTP method " + method); + } + + return response; + } + + public static void setCookie(CookieJar cookieJar, Map cookieMap) { + Map attributesCopy; + String codedValue; + Morsel morsel; + String morselValue; + for (String cookieName : cookieMap.keySet()) { + attributesCopy = new HashMap<>(attributes); + attributesCopy.put("name", cookieName); + morsel = cookieMap.get(cookieName); + codedValue = morsel.getCodedValue(); + + if (codedValue.startsWith("\"") && codedValue.endsWith("\"")) { + attributesCopy.put("value", codedValue.substring(1, codedValue.length() - 1)); + } else { + attributesCopy.put("value", codedValue); + } + + attributesCopy.put("version", 0); + String failedAttribute = null; + try { + for (String attribute : morsel.keySet()) { + failedAttribute = attribute; + if (attributes.containsKey(attribute)) { + morselValue = morsel.get(attribute); + if (!Strings.isNullOrEmpty(morselValue)) { + if (attribute.equals("expires")) { + attributesCopy.put(attribute, dateToTime(morselValue)); + } else { + attributesCopy.put(attribute, morselValue); + } + } else if (attribute.equals("maxAge")) { + if (!Strings.isNullOrEmpty(morselValue)) { + attributesCopy.put("expires", dateToTime(morselValue)); + } + } + } + } + } catch (ParseException e) { + logger.info("Time format error on " + failedAttribute + " parameter in received cookie"); + continue; + } + + for (String attribute : pairs.keySet()) { + if (attributesCopy.get(attribute) != null) { + attributesCopy.put(pairs.get(attribute), true); + } + } + + if (attributesCopy.get("domain") instanceof String && !Strings.isNullOrEmpty((String) attributesCopy.get("domain")) && ((String) attributesCopy.get("domain")).startsWith(".")) { + attributesCopy.put("domainInitialDot", true); + } + + if (morsel.getMaxAge() == 0) { + cookieJar.clear(attributesCopy.getDomain(), attributesCopy.getPath(), attributesCopy.getName()); + } else { + if (attributesCopy.containsKey("version")) { + attributesCopy.put("version", ((String) attributesCopy.get("version")).split(",")[0]); + } + + Cookie newCookie = new Cookie(); + cookieJar.setCookie(newCookie); + } + } + } + + public static boolean matchTo(String value, List valuesList) { + for (String index : valuesList) { + if (index.startsWith(value)) { + return true; + } + } + return false; + } + + public static boolean matchTo(String value, String valueExpected) { + return valueExpected.startsWith(value); + } + + public static String verifyHeader(FakeResponse response, String bodyType) throws WrongContentType, ValueError { + logger.debug("Response headers: " + response.getHeaders().toString()); + logger.debug("Response txt: " + response.getText().toString()); + + String contentType = response.getHeaders().getContentType(); + if (Strings.isNullOrEmpty(contentType)) { + if (!Strings.isNullOrEmpty(bodyType)) { + return bodyType; + } else { + return "txt"; + } + } + + logger.debug("Expected body type: " + bodyType); + + if (bodyType.equals("")) { + if (matchTo(JSON_ENCODED, contentType) || matchTo(JRD_JSON, contentType)) { + bodyType = JSON; + } else if (matchTo(JWT_ENCODED, contentType)) { + bodyType = JWT; + } else if (matchTo(URL_ENCODED, contentType)) { + bodyType = URLENCODED; + } else { + bodyType = TEXT; + } + } else if (bodyType.equals(JSON)) { + if (matchTo(JWT_ENCODED, contentType)) { + bodyType = JWT; + } else if(!matchTo(JSON_ENCODED, contentType) || !matchTo(JRD_JSON, contentType)){ + throw new WrongContentType(contentType); + } + } else if (bodyType.equals(JWT)) { + if (!matchTo(JWT_ENCODED, contentType)) { + throw new WrongContentType(contentType); + } + } else if (bodyType.equals(URLENCODED)) { + if (!matchTo(DEFAULT_POST_CONTENT_TYPE, contentType) && + !matchTo(PLAIN_TEXT, contentType)) { + throw new WrongContentType(contentType); + } + } else if (bodyType.equals(TEXT)) { + if (!matchTo(PLAIN_TEXT, contentType) && !matchTo(HTML_TEXT, contentType)) { + throw new WrongContentType("Content type: " + contentType); + } + } else { + throw new ValueError("Unknown return format: " + bodyType); + } + + logger.debug("Got body type: " + bodyType); + return bodyType; + } + + public Integer sortSignAlgorithm(String algorithm1, String algorithm2) { + if (sortOrder.get(algorithm1.substring(0, 2)) < sortOrder.get(algorithm2.substring(0, 2))) { + return -1; + } else if (sortOrder.get(algorithm1.substring(0, 2)) > sortOrder.get(algorithm2.substring(0, 2))) { + return 1; + } else { + return algorithm1.compareTo(algorithm2); + } + } + + public static long dateToTime(String date) throws ParseException { + DateFormat inputFormat = new SimpleDateFormat("dd MMM yyy HH:mm:ss zz"); + Date d = inputFormat.parse(date); + return d.getTime(); + } +} diff --git a/lib/src/main/java/oiccli/Utils.java b/lib/src/main/java/oiccli/Utils.java new file mode 100644 index 0000000..7723948 --- /dev/null +++ b/lib/src/main/java/oiccli/Utils.java @@ -0,0 +1,80 @@ +package oiccli; + +import com.auth0.jwt.creators.Message; +import com.google.common.base.Strings; +import java.io.File; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.List; +import java.util.Map; +import oiccli.client_info.ClientInfo; +import oiccli.exceptions.MissingRequiredAttribute; + +public class Utils { + + public static Message requestObjectEncryption(Message message, ClientInfo clientInfo, Map args) throws MissingRequiredAttribute { + String encryptionAlg = (String) args.get("requestObjectEncryptionAlg"); + + if(Strings.isNullOrEmpty(encryptionAlg)) { + List listOfAlgs = clientInfo.getBehavior().get("requestObjectEncryptionAlg"); + if(listOfAlgs != null || !listOfAlgs.isEmpty()) { + encryptionAlg = listOfAlgs.get(0); + } + + if(encryptionAlg == null) { + return message; + } + } + + String encryptionEnc = (String) args.get("requestObjectEncryptionEnc"); + + if(Strings.isNullOrEmpty(encryptionEnc)) { + List listOfAlgs = clientInfo.getBehavior().get("requestObjectEncryptionEnc"); + if(listOfAlgs != null || !listOfAlgs.isEmpty()) { + encryptionEnc = listOfAlgs.get(0); + } + + if(encryptionEnc == null) { + throw new MissingRequiredAttribute("No requestObjectEncryptionEnc specified"); + } + } + + JWE jwe = new JWE(message, encryptionAlg, encryptionEnc); + String keyType = StringUtil.alg2keytype(encryptionAlg); + + String kid = (String) args.get("encKid"); + if(Strings.isNullOrEmpty(kid)) { + kid = ""; + } + + if(!args.containsKey("target")) { + throw new MissingRequiredAttribute("No target specified"); + } + + List keys; + if(!Strings.isNullOrEmpty(kid)) { + keys = clientInfo.getKeyJar().getEncryptKey(keyType, args.get("target"), kid); + jwe.setKid(kid); + } else { + keys = clientInfo.getKeyJar().getEncryptKey(keyType, args.get("target")); + } + + return jwe.encrypt(keys); + } + + public static Tuple constructRequestUri(String localDir, String basePath, Map args) { + File file = new File(localDir); + if(!file.isDirectory()) { + file.mkdirs(); + } + String name = StringUtil.generateRandomString(10) + ".jwt"; + File fileExists = Paths.get(localDir, name).toFile(); + while(fileExists.exists()) { + name = StringUtil.generateRandomString(10); + fileExists = Paths.get(localDir, name).toFile(); + } + + String webName = basePath + name; + return new Tuple(fileExists.toString(), webName); + } +} diff --git a/lib/src/main/java/oiccli/client_auth/BearerBody.java b/lib/src/main/java/oiccli/client_auth/BearerBody.java new file mode 100644 index 0000000..e8d23ef --- /dev/null +++ b/lib/src/main/java/oiccli/client_auth/BearerBody.java @@ -0,0 +1,50 @@ +package oiccli.client_auth; + +import com.auth0.jwt.jwts.JWT; +import java.security.Key; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import oiccli.State; +import oiccli.StringUtil; +import oiccli.client_info.ClientInfo; +import oiccli.exceptions.AuthenticationFailure; + +public class BearerBody { + + public Map> construct(Map cis, ClientInfo clientInfo, Map requestArgs, Map> httpArgs, + Map args) throws AuthenticationFailure { + if (requestArgs == null) { + requestArgs = new HashMap<>(); + } + + if (!cis.containsKey("accessToken")) { + String accessToken = requestArgs.get("accessToken"); + if (accessToken != null) { + cis.put("accessToken", accessToken); + } else { + if (args.get("state") == null) { + State state = clientInfo.getState(); + if (state == null) { + throw new AuthenticationFailure("Missing state specification"); + } + + args.put("state", state); + } + + cis.put("accessToken", clientInfo.getStateDb().getTokenInfo(args).get("accessToken")); + } + } + + return httpArgs; + } + + public static JWT assertionJWT(String clientId, List keys, List audience, + String algorithm, int lifeTime) { + long now = System.currentTimeMillis(); + + AuthenticationToken at = new AuthenticationToken(clientId, clientId, audience, StringUtil.generateRandomString(32), + now + lifeTime, now); + return at.toJwt(keys, algorithm); + } +} diff --git a/lib/src/main/java/oiccli/client_auth/BearerHeader.java b/lib/src/main/java/oiccli/client_auth/BearerHeader.java new file mode 100644 index 0000000..22d4163 --- /dev/null +++ b/lib/src/main/java/oiccli/client_auth/BearerHeader.java @@ -0,0 +1,48 @@ +package oiccli.client_auth; + +import java.util.HashMap; +import java.util.Map; +import oiccli.client_info.ClientInfo; +import oiccli.exceptions.MissingRequiredAttribute; + +public class BearerHeader { + + public Map> construct(Map cis, ClientInfo clientInfo, Map requestArgs, Map> httpArgs, + Map args) throws MissingRequiredAttribute { + String accessToken; + if (cis != null) { + if (cis.containsKey("accessToken")) { + accessToken = cis.get("accessToken"); + cis.remove(accessToken); + //cis.c_param["access_token"] = SINGLE_OPTIONAL_STRING + } else { + accessToken = requestArgs.get("accessToken"); + + if (accessToken == null) { + accessToken = clientInfo.getStateDb().getTokenInfo(args).get("accessToken"); + } + } + } else { + accessToken = args.get("accessToken"); + if (accessToken == null) { + throw new MissingRequiredAttribute("accessToken"); + } + } + + String bearer = "Bearer " + accessToken; + if (httpArgs == null) { + Map hMap = new HashMap<>(); + hMap.put("Authorization", bearer); + httpArgs.put("headers", hMap); + } else { + Map hMap = httpArgs.get("headers"); + if (hMap == null) { + hMap = new HashMap<>(); + } + hMap.put("Authorization", bearer); + httpArgs.put("headers", hMap); + } + + return httpArgs; + } +} diff --git a/lib/src/main/java/oiccli/client_auth/ClientSecretBasic.java b/lib/src/main/java/oiccli/client_auth/ClientSecretBasic.java new file mode 100644 index 0000000..c581154 --- /dev/null +++ b/lib/src/main/java/oiccli/client_auth/ClientSecretBasic.java @@ -0,0 +1,74 @@ +package oiccli.client_auth; + +import com.auth0.jwt.jwts.JWT; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import oiccli.StringUtil; +import oiccli.client_info.ClientInfo; +import org.apache.commons.codec.binary.Base64; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class ClientSecretBasic extends ClientAuthenticationMethod { + + private final static Logger logger = LoggerFactory.getLogger(ClientSecretBasic.class); + + public static JWT assertionJwt(String clientId, List keys, List audience, String algorithm, int lifetime) { + long now = System.currentTimeMillis(); + String jti = StringUtil.generateRandomString(32); + + AuthenticationToken token = new AuthenticationToken(clientId, clientId, audience, jti, now + lifetime, now); + logger.debug("AuthnToken " + token.toString()); + return token.toJWT(keys, algorithm); + } + + public Map> construct(AccessTokenRequest request, ClientInfo cliInfo, Map> httpArgs, + Map args) { + if (httpArgs == null) { + httpArgs = new HashMap<>(); + } + + if (!httpArgs.containsKey("headers")) { + httpArgs.put("headers", new HashMap()); + } + + String password = args.get("password"); + if (password == null) { + password = httpArgs.get("password").get("password"); + if (password == null) { + password = request.getClientSecret(); + if (password == null) { + password = cliInfo.getClientSecret(); + } + } + } + + String user = args.get("user"); + if (user == null) { + user = cliInfo.getClientId(); + } + + String credentials = user + ":" + password; + String authz = new String(Base64.encodeBase64(credentials.getBytes())); + Map hMap = new HashMap<>(); + hMap.put("Authorization", "Basic " + authz); + httpArgs.put("headers", hMap); + + request.setClientSecret(null); + + if (request.get("grantType").equals("authorizationCode")) { + if (request.getClientId() != null) { + request.setClientId(cliInfo.getClientId()); + } + } else { + boolean req = request.getCParam("clientId").get(VREQUIRED); + + if (!req) { + request.remove("clientId"); + } + } + + return httpArgs; + } +} diff --git a/lib/src/main/java/oiccli/client_auth/ClientSecretJwt.java b/lib/src/main/java/oiccli/client_auth/ClientSecretJwt.java new file mode 100644 index 0000000..099a3df --- /dev/null +++ b/lib/src/main/java/oiccli/client_auth/ClientSecretJwt.java @@ -0,0 +1,19 @@ +package oiccli.client_auth; + +import java.security.Key; +import java.util.Map; +import oiccli.StringUtil; +import oiccli.client_info.ClientInfo; +import oiccli.exceptions.AuthenticationFailure; + +public class ClientSecretJwt extends JWSAuthenticationMethod { + + public static String chooseAlgorithm(String entity, Map args) throws AuthenticationFailure { + return JWSAuthenticationMethod.chooseAlgorithm(entity, args); + } + + @Override + public Key getSigningKey(String algorithm, ClientInfo clientInfo) { + return clientInfo.getKeyJar().getSigningKey(StringUtil.alg2keytype(algorithm), algorithm); + } +} diff --git a/lib/src/main/java/oiccli/client_auth/ClientSecretPost.java b/lib/src/main/java/oiccli/client_auth/ClientSecretPost.java new file mode 100644 index 0000000..bb8dfce --- /dev/null +++ b/lib/src/main/java/oiccli/client_auth/ClientSecretPost.java @@ -0,0 +1,27 @@ +package oiccli.client_auth; + +import com.google.common.base.Strings; +import java.util.Map; +import oiccli.client_info.ClientInfo; + +public class ClientSecretPost extends ClientSecretBasic { + + public Map> construct(AccessTokenRequest request, ClientInfo clientInfo, Map> httpArgs, + Map args) { + if (Strings.isNullOrEmpty(request.getClientSecret())) { + Map clientSecret = httpArgs.get("clientSecret"); + if (clientSecret != null) { + request.setClientSecret(clientSecret.get("clientSecret")); + httpArgs.remove("clientSecret"); + } else { + if (!Strings.isNullOrEmpty(clientInfo.getClientSecret())) { + request.setClientSecret(clientInfo.getClientSecret()); + } + } + } + + request.setClientId(clientInfo.getClientId()); + + return httpArgs; + } +} diff --git a/lib/src/main/java/oiccli/client_auth/JWSAuthenticationMethod.java b/lib/src/main/java/oiccli/client_auth/JWSAuthenticationMethod.java new file mode 100644 index 0000000..bcf927d --- /dev/null +++ b/lib/src/main/java/oiccli/client_auth/JWSAuthenticationMethod.java @@ -0,0 +1,117 @@ +package oiccli.client_auth; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import oiccli.StringUtil; +import oiccli.client_info.ClientInfo; +import oiccli.exceptions.AuthenticationFailure; +import oiccli.exceptions.NoMatchingKey; +import org.junit.Assert; + +public class JWSAuthenticationMethod extends ClientAuthenticationMethod { + + private static final Map DEF_SIGN_ALG = new HashMap() {{ + put("id_token", "RS256"); + put("userinfo", "RS256"); + put("request_object", "RS256"); + put("client_secret_jwt", "HS256"); + put("private_key_jwt", "RS256"); + }}; + private static final String JWT_BEARER = "urn:ietf:params:oauth:client-assertion-type:jwt-bearer"; + private static final String V_Required = 1; + + public static String chooseAlgorithm(String entity, Map args) throws AuthenticationFailure { + String algorithm = args.get("algorithm"); + if (algorithm == null) { + algorithm = DEF_SIGN_ALG.get(entity); + if (algorithm == null) { + throw new AuthenticationFailure("Missing algorithm specification"); + } + } + + return algorithm; + } + + public String chooseAlgorithm(Map args) throws AuthenticationFailure { + return chooseAlgorithm(null, args); + } + + public Key getSigningKey(String algorithm, ClientInfo clientInfo) { + return clientInfo.getKeyJar().getSigningKey( + StringUtil.alg2keytype(algorithm), algorithm); + } + + public Key getKeyByKid(String kid, String algorithm, ClientInfo clientInfo) throws NoMatchingKey { + Key key = clientInfo.getKeyJar().getKeyByKid(kid); + String ktype; + if (key != null) { + ktype = StringUtil.alg2keytype(algorithm); + try { + Assert.assertTrue(key.getType().equals(ktype)); + return key; + } catch (AssertionError error) { + throw new NoMatchingKey("Wrong key type"); + } + } else { + throw new NoMatchingKey("No key with kid " + kid); + } + } + + public Map construct(AccessTokenRequest request, ClientInfo clientInfo, Map requestArgs, + Map httpArgs, Map args) throws AuthenticationFailure, NoMatchingKey { + String algorithm = null; + List audience = null; + if (args.containsKey("clientAssertion")) { + request.setClientAssertion(args.get("clientAssertion")); + if (args.containsKey("clientAssertionType")) { + request.setClientAssertionType(args.get("clientAssertionType")); + } else { + request.setClientAssertionType(JWT_BEARER); + } + } else if (request.getClientAssertion() != null) { + if (request.getClientAssertionType() == null) { + request.setClientAssertionType(JWT_BEARER); + } + } else { + if (args.get("authenticationEndpoint").equals("token") || args.get("authenticationEndpoint").equals("refresh")) { + algorithm = clientInfo.registrationInfo().get("tokenEndpointAuthSigningAlg"); + audience = clientInfo.getProviderInfo().get("tokenEndpoint"); + } else { + audience = clientInfo.getProviderInfo().get("issuer"); + } + } + + if (algorithm == null) { + algorithm = this.chooseAlgorithm(args); + } + + String ktype = StringUtil.alg2keytype(algorithm); + List signingKey = null; + if (args.containsKey("kid")) { + signingKey = Arrays.asList(this.getKeyByKid(args.get("kid"), algorithm, clientInfo)); + } else if (clientInfo.getKid().get("sig").containsKey(ktype)) { + Key key = this.getKeyByKid(clientInfo.getKid().get("sig").get("ktype"), algorithm, clientInfo); + if (key != null) { + signingKey = Arrays.asList(key); + } else { + signingKey = Arrays.asList(this.getSigningKey(algorithm, clientInfo)); + } + } + + Map hMap = new HashMap<>(); + hMap.put("lifetime", args.get("lifetime")); + + request.setClientAssertion(BearerBody.assertionJWT(clientInfo.getClientId(), signingKey, audience, algorithm, 600)); + request.setClientAssertionType(JWT_BEARER); + request.setClientSecret(null); + + if (request.getCParam.getClientId().get(V_Required) == null) { + request.setClientId(null); + } + + return new HashMap<>(); + } + +} diff --git a/lib/src/main/java/oiccli/client_auth/PrivateKeyJwt.java b/lib/src/main/java/oiccli/client_auth/PrivateKeyJwt.java new file mode 100644 index 0000000..34c546f --- /dev/null +++ b/lib/src/main/java/oiccli/client_auth/PrivateKeyJwt.java @@ -0,0 +1,40 @@ +package oiccli.client_auth; + +import java.security.Key; +import java.util.Map; +import oiccli.StringUtil; +import oiccli.client_info.ClientInfo; +import oiccli.exceptions.AuthenticationFailure; + +public class PrivateKeyJwt extends JWSAuthenticationMethod { + + public static String chooseAlgorithm(String entity, Map args) throws AuthenticationFailure { + return JWSAuthenticationMethod.chooseAlgorithm(entity, args); + } + + @Override + public String chooseAlgorithm(Map args) throws AuthenticationFailure { + return chooseAlgorithm("private_key_jwt", args); + } + + @Override + public Key getSigningKey(String algorithm, ClientInfo clientInfo) { + return clientInfo.getKeyJar().getSigningKey(StringUtil.alg2keytype(algorithm), "", algorithm); + } + + public static boolean validClientInfo(ClientInfo clientInfo, long when) { + long eta = clientInfo.getClientSecretExpiresAt(); + long now; + if(when != 0) { + now = when; + } else { + now = System.currentTimeMillis(); + } + + if(eta != 0 && eta < now) { + return false; + } + + return true; + } +} diff --git a/lib/src/main/java/oiccli/client_info/ClientInfo.java b/lib/src/main/java/oiccli/client_info/ClientInfo.java new file mode 100644 index 0000000..36070e7 --- /dev/null +++ b/lib/src/main/java/oiccli/client_info/ClientInfo.java @@ -0,0 +1,389 @@ +package oiccli.client_info; + +import com.google.common.base.Strings; +import com.google.common.collect.ImmutableMap; +import com.sun.org.apache.xml.internal.security.utils.Base64; +import java.io.File; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import oiccli.State; +import oiccli.StringUtil; +import oiccli.exceptions.ExpiredToken; +import oiccli.exceptions.ValueError; +import org.junit.Assert; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class ClientInfo { + + private static final String S256 = "S256"; + private static final String SHA_256 = "SHA-256"; + private static final String SHA_384 = "SHA-384"; + private static final String SHA_512 = "SHA-512"; + private static final String S384 = "S384"; + private static final String S512 = "S512"; + private static final String RS256 = "RS256"; + private static final String HS256 = "HS256"; + private static final String ALG = "alg"; + private static final String ENC = "enc"; + private static final String SIGN = "sign"; + private static final Map userInfoMap = + ImmutableMap.of(SIGN, "userinfoSignedResponseAlg", + ALG, "userinfoEncryptedResponseAlg", + ENC, "userinfoEncryptedResponseEnc"); + private static final Map idTokenMap = + ImmutableMap.of(SIGN, "idTokenSignedResponseAlg", + ALG, "idTokenEncryptedResponseAlg", + ENC, "idTokenEncryptedResponseEnc"); + private static final Map requestMap = + ImmutableMap.of(SIGN, "requestObjectSigningAlg", + ALG, "requestObjectEncryptionAlg", + ENC, "requestObjectEncryptionEnc"); + private static final Map> attributeMap = + ImmutableMap.of("userinfo", userInfoMap, "idToken", idTokenMap, "request", requestMap); + private static final Map defSignAlg = + ImmutableMap.of("idToken", RS256, + "userInfo", RS256, + "requestObject", RS256, + "clientSecretJwt", HS256, + "privateKeyJwt", RS256); + + private KeyJar keyJar; + private State stateDb; + private List events; + private boolean shouldBeStrictOnPreferences; + private Map> providerInfo; + private Map> registrationResponse; + private String registrationExpires; + private String registrationAccessToken; + private Map> kid; + private Map> config; + private Map allow; + private Map> behavior; + private Map> clientPrefs; + private String baseUrl; + private String requestsDir; + private String cId; + private String cSecret; + private String issuer; + private String redirectUris; + final private static Logger logger = LoggerFactory.getLogger(ClientInfo.class); + + public ClientInfo(KeyJar keyjar, Map config, List events, + Database db, String dbName, boolean shouldBeStrictOnPreferences, + Map args) throws NoSuchFieldException, IllegalAccessException { + this.keyJar = keyjar; + this.stateDb = new State(" ", db, dbName); + this.events = events; + this.shouldBeStrictOnPreferences = shouldBeStrictOnPreferences; + this.providerInfo = new HashMap<>(); + this.registrationResponse = new HashMap<>(); + this.kid = new HashMap() {{ + put("sig", new HashMap<>()); + put(ENC, new HashMap<>()); + }}; + this.config = config; + this.baseUrl = ""; + this.requestsDir = ""; + this.allow = new HashMap<>(); + this.behavior = new HashMap<>(); + this.clientPrefs = new HashMap<>(); + this.cId = ""; + this.cSecret = ""; + this.issuer = ""; + + for (String key : args.keySet()) { + this.getClass().getField(key).set(key, args.get(key)); + } + + List attributes = new ArrayList<>(Arrays.asList("clientId", "issuer", "clientSecret", "baseUrl", "requestsDir")); + String value; + for (String attribute : attributes) { + value = config.get(attribute); + if (value != null) { + this.getClass().getField(attribute).set(attribute, value); + } else { + this.getClass().getField(attribute).set(attribute, ""); + } + + /*if (attribute.equals("clientId")) { + this.stateDb.setClientId(config.get(attribute)); + }*/ + } + + attributes = new ArrayList<>(Arrays.asList("allow", "clientPrefs", "behavior", "providerInfo")); + for (String attribute : attributes) { + value = config.get(attribute); + if (value != null) { + this.getClass().getField(attribute).set(attribute, value); + } else { + this.getClass().getField(attribute).set(attribute, new HashMap<>()); + } + } + + if (!Strings.isNullOrEmpty(requestsDir)) { + File file = new File(requestsDir); + if (!file.isDirectory()) { + try { + file.mkdir(); + } catch (SecurityException se) { + logger.error("Directory " + requestsDir + " was not created"); + } + } + } + + String redirectUris = config.get("redirectUris"); + if (redirectUris != null) { + this.redirectUris = redirectUris; + } else { + this.redirectUris = null; + } + + this.importKeys(config.get("keys")); + + if (config.containsKey("keydefs")) { + this.keyJar = buildKeyJar(config.get("keydefs"), this.keyJar)[1]; + } + } + + public void importKeys(Map>> keySpec) { + for (String key : keySpec.keySet()) { + if (key.equals("file")) { + Map> hMap = keySpec.get(key); + for (String hMapKey : hMap.keySet()) { + if (hMapKey.equals("rsa")) { + Key key; + KeyBundle keyBundle; + for (String file : hMap.get(hMapKey)) { + key = new RSAKey(importPrivateRsaKeyFromFile(file), "sig"); + keyBundle = new KeyBundle(); + keyBundle.append(key); + this.keyJar.addKb("", keyBundle); + } + } + } + } else if (key.equals("url")) { + KeyBundle keyBundle; + for (String issuer : keySpec.keySet()) { + keyBundle = new KeyBundle(keySpec.get(issuer)); + this.keyJar.addKb(keyBundle); + } + } + } + } + + public String getIssuer() { + return issuer; + } + + public Map> getProviderInfo() { + return providerInfo; + } + + public Map> getClientPrefs() { + return clientPrefs; + } + + public boolean getShouldBeStrictOnPreferences() { + return shouldBeStrictOnPreferences; + } + + public Map> getBehavior() { + return behavior; + } + + public void setBehavior(Map> behavior) { + this.behavior = behavior; + } + + public String getRequestsDir() { + return requestsDir; + } + + public String getClientSecret() { + return cSecret; + } + + public Map> getKid() { + return kid; + } + + public void setClientSecret(String cSecret) { + if(Strings.isNullOrEmpty(cSecret)) { + this.cSecret = ""; + } else { + this.cSecret = cSecret; + if(this.keyJar == null) { + this.keyJar = new KeyJar(); + } + this.keyJar.addSymmetric("", cSecret); + } + } + + public State getState() { + return stateDb; + } + + public void setCSecret(String secret) { + if (secret == null) { + this.cSecret = ""; + } else { + this.cSecret = secret; + if (this.keyJar == null) { + this.keyJar = new KeyJar(); + } + + this.keyjar.addSymmetric("", secret); + } + } + + //client_secret = property(get_client_secret, set_client_secret) + + public String getClientId() { + return this.cId; + } + + public void setClientId(String clientId) { + this.cId = clientId; + this.stateDb.setClientId(clientId); + } + + public void setRegistrationResponse(Map> registrationResponse) { + this.registrationResponse = registrationResponse; + } + + public Map> getRegistrationResponse() { + return this.registrationResponse; + } + + public void setRegistrationAccessToken(String registrationAccessToken) { + this.registrationAccessToken = registrationAccessToken; + } + + public KeyJar getKeyJar() { + return keyJar; + } + + public State getStateDb() { + return stateDb; + } + + //client_id = property(get_client_id, set_client_id) + + public String filenameFromWebname(String webName) throws ValueError { + try { + Assert.assertTrue(webName.startsWith(this.baseUrl)); + } catch (AssertionError e) { + throw new ValueError("Webname doesn't match baseUrl"); + } + webName = webName.substring(baseUrl.length()); + if (webName.startsWith("/")) { + return webName.substring(1); + } else { + return webName; + } + } + + public Map> signEncAlgs(SignEncAlgs type) { + Map> response = new HashMap<>(); + List value; + for (String key : attributeMap.get(type).keySet()) { + value = this.registrationResponse.get(attributeMap.get(key)); + if (attributeMap.get(key) != null && value != null) { + response.put(key, value); + } else { + if (key.equals(SIGN)) { + response.put(key, Arrays.asList(defSignAlg.get(type))); + } + } + } + + return response; + } + + public boolean verifyAlgSupport(String algorithm, VerifyAlgSupportUsage usage, VerifyAlgSupportType type) { + List supported = this.providerInfo.get(usage + "" + type + "valuesSupported"); + return supported.contains(algorithm); + } + + public List generateRequestUris(String requestsDir) throws NoSuchAlgorithmException { + MessageDigest digest = MessageDigest.getInstance(SHA_256); + List providerInfoList = this.providerInfo.get("issuer"); + if(providerInfoList != null && !providerInfoList.isEmpty()) { + String providerInfo = providerInfoList.get(0); + if(!Strings.isNullOrEmpty(providerInfo)) { + digest.update(providerInfo.getBytes()); + } + } else { + digest.update(this.issuer.getBytes()); + } + + digest.update(this.baseUrl.getBytes()); + + return Arrays.asList(this.baseUrl + requestsDir + "/" + digest.digest()); + } + + public static Map addCodeChallenge(ClientInfo clientInfo, String state) throws NoSuchAlgorithmException { + Integer cvLength = (Integer) clientInfo.config.get("codeChallenge").get("length"); + if (cvLength == null) { + cvLength = 64; + } + + String codeVerifier = StringUtil.generateRandomString(cvLength); + codeVerifier = Base64.encode(codeVerifier.getBytes()); + + String method = (String) clientInfo.config.get("codeChallenge").get("method"); + if (method == null) { + method = S256; + } + + MessageDigest digest= null; + switch (method) { + case S256: + digest = MessageDigest.getInstance(SHA_256); + break; + case S384: + digest = MessageDigest.getInstance(SHA_384); + break; + case S512: + digest = MessageDigest.getInstance(SHA_512); + break; + } + + String codeVerifierHex = bytesToHex(codeVerifier.getBytes()); + + byte[] codeVerifierHexByteArr = digest.digest(codeVerifierHex.getBytes()); + String codeChallenge = Base64.encode(codeVerifierHexByteArr); + + clientInfo.getStateDb().addInfo(state, codeVerifier, method); + + Map hMap = new HashMap<>(); + hMap.put("codeChallenge", codeChallenge); + hMap.put("codeChallengeMethod", method); + + return hMap; + } + + private static String bytesToHex(byte[] hash) { + StringBuffer hexString = new StringBuffer(); + for (int i = 0; i < hash.length; i++) { + String hex = Integer.toHexString(0xff & hash[i]); + if(hex.length() == 1) hexString.append('0'); + hexString.append(hex); + } + return hexString.toString(); + } + + public void setRegistrationExpires(String registrationExpires) { + this.registrationExpires = registrationExpires; + } + + public static Object getCodeVerifier(ClientInfo clientInfo, String state) throws ExpiredToken { + return clientInfo.getStateDb().getTokenInfo("state" + state).get("codeVerifier"); + } +} diff --git a/lib/src/main/java/oiccli/client_info/SignEncAlgs.java b/lib/src/main/java/oiccli/client_info/SignEncAlgs.java new file mode 100644 index 0000000..a29aaf2 --- /dev/null +++ b/lib/src/main/java/oiccli/client_info/SignEncAlgs.java @@ -0,0 +1,5 @@ +package oiccli.client_info; + +public enum SignEncAlgs { + ID_TOKEN, USER_INFO, REQUEST_OBJECT +} diff --git a/lib/src/main/java/oiccli/client_info/VerifyAlgSupportType.java b/lib/src/main/java/oiccli/client_info/VerifyAlgSupportType.java new file mode 100644 index 0000000..43e9bbc --- /dev/null +++ b/lib/src/main/java/oiccli/client_info/VerifyAlgSupportType.java @@ -0,0 +1,5 @@ +package oiccli.client_info; + +public enum VerifyAlgSupportType { + SIGNING_ALG, ENCRYPTION_ALG, ENCRYPTION_ENC; +} diff --git a/lib/src/main/java/oiccli/client_info/VerifyAlgSupportUsage.java b/lib/src/main/java/oiccli/client_info/VerifyAlgSupportUsage.java new file mode 100644 index 0000000..1d37df8 --- /dev/null +++ b/lib/src/main/java/oiccli/client_info/VerifyAlgSupportUsage.java @@ -0,0 +1,5 @@ +package oiccli.client_info; + +public enum VerifyAlgSupportUsage { + USER_INFO, ID_TOKEN, REQUEST_OBJECT, TOKEN_ENDPOINT_AUTH; +} diff --git a/lib/src/main/java/oiccli/exceptions/AESError.java b/lib/src/main/java/oiccli/exceptions/AESError.java new file mode 100644 index 0000000..269cdb1 --- /dev/null +++ b/lib/src/main/java/oiccli/exceptions/AESError.java @@ -0,0 +1,7 @@ +package oiccli.exceptions; + +public class AESError extends Exception { + public AESError(String message) { + super(message); + } +} diff --git a/lib/src/main/java/oiccli/exceptions/AtHashError.java b/lib/src/main/java/oiccli/exceptions/AtHashError.java new file mode 100644 index 0000000..043ad7e --- /dev/null +++ b/lib/src/main/java/oiccli/exceptions/AtHashError.java @@ -0,0 +1,7 @@ +package oiccli.exceptions; + +public class AtHashError extends Exception { + public AtHashError(String message) { + super(message); + } +} diff --git a/lib/src/main/java/oiccli/exceptions/AuthenticationFailure.java b/lib/src/main/java/oiccli/exceptions/AuthenticationFailure.java new file mode 100644 index 0000000..da38213 --- /dev/null +++ b/lib/src/main/java/oiccli/exceptions/AuthenticationFailure.java @@ -0,0 +1,7 @@ +package oiccli.exceptions; + +public class AuthenticationFailure extends Exception { + public AuthenticationFailure(String message) { + super(message); + } +} diff --git a/lib/src/main/java/oiccli/exceptions/CHashError.java b/lib/src/main/java/oiccli/exceptions/CHashError.java new file mode 100644 index 0000000..ade61e3 --- /dev/null +++ b/lib/src/main/java/oiccli/exceptions/CHashError.java @@ -0,0 +1,7 @@ +package oiccli.exceptions; + +public class CHashError extends Exception { + public CHashError(String message) { + super(message); + } +} diff --git a/lib/src/main/java/oiccli/exceptions/ConfigurationError.java b/lib/src/main/java/oiccli/exceptions/ConfigurationError.java new file mode 100644 index 0000000..458e7ac --- /dev/null +++ b/lib/src/main/java/oiccli/exceptions/ConfigurationError.java @@ -0,0 +1,7 @@ +package oiccli.exceptions; + +public class ConfigurationError extends Exception { + public ConfigurationError(String message) { + super(message); + } +} diff --git a/lib/src/main/java/oiccli/exceptions/ExpiredToken.java b/lib/src/main/java/oiccli/exceptions/ExpiredToken.java new file mode 100644 index 0000000..a86a473 --- /dev/null +++ b/lib/src/main/java/oiccli/exceptions/ExpiredToken.java @@ -0,0 +1,7 @@ +package oiccli.exceptions; + +public class ExpiredToken extends Exception { + public ExpiredToken(String message) { + super(message); + } +} diff --git a/lib/src/main/java/oiccli/exceptions/MessageException.java b/lib/src/main/java/oiccli/exceptions/MessageException.java new file mode 100644 index 0000000..64bdfce --- /dev/null +++ b/lib/src/main/java/oiccli/exceptions/MessageException.java @@ -0,0 +1,7 @@ +package oiccli.exceptions; + +public class MessageException extends Exception { + public MessageException(String message) { + super(message); + } +} diff --git a/lib/src/main/java/oiccli/exceptions/MissingEndpoint.java b/lib/src/main/java/oiccli/exceptions/MissingEndpoint.java new file mode 100644 index 0000000..5d945bc --- /dev/null +++ b/lib/src/main/java/oiccli/exceptions/MissingEndpoint.java @@ -0,0 +1,7 @@ +package oiccli.exceptions; + +public class MissingEndpoint extends Exception { + public MissingEndpoint(String message) { + super(message); + } +} diff --git a/lib/src/main/java/oiccli/exceptions/MissingParameter.java b/lib/src/main/java/oiccli/exceptions/MissingParameter.java new file mode 100644 index 0000000..ec7e0e9 --- /dev/null +++ b/lib/src/main/java/oiccli/exceptions/MissingParameter.java @@ -0,0 +1,7 @@ +package oiccli.exceptions; + +public class MissingParameter extends Exception { + public MissingParameter(String message) { + super(message); + } +} diff --git a/lib/src/main/java/oiccli/exceptions/MissingRequiredAttribute.java b/lib/src/main/java/oiccli/exceptions/MissingRequiredAttribute.java new file mode 100644 index 0000000..661f797 --- /dev/null +++ b/lib/src/main/java/oiccli/exceptions/MissingRequiredAttribute.java @@ -0,0 +1,7 @@ +package oiccli.exceptions; + +public class MissingRequiredAttribute extends Exception { + public MissingRequiredAttribute(String message) { + super(message); + } +} diff --git a/lib/src/main/java/oiccli/exceptions/NoMatchingKey.java b/lib/src/main/java/oiccli/exceptions/NoMatchingKey.java new file mode 100644 index 0000000..a7277e0 --- /dev/null +++ b/lib/src/main/java/oiccli/exceptions/NoMatchingKey.java @@ -0,0 +1,7 @@ +package oiccli.exceptions; + +public class NoMatchingKey extends Exception { + public NoMatchingKey(String message) { + super(message); + } +} diff --git a/lib/src/main/java/oiccli/exceptions/OicCliError.java b/lib/src/main/java/oiccli/exceptions/OicCliError.java new file mode 100644 index 0000000..93cc18d --- /dev/null +++ b/lib/src/main/java/oiccli/exceptions/OicCliError.java @@ -0,0 +1,7 @@ +package oiccli.exceptions; + +public class OicCliError extends Exception { + public OicCliError(String message) { + super(message); + } +} diff --git a/lib/src/main/java/oiccli/exceptions/OicMsgError.java b/lib/src/main/java/oiccli/exceptions/OicMsgError.java new file mode 100644 index 0000000..94629b5 --- /dev/null +++ b/lib/src/main/java/oiccli/exceptions/OicMsgError.java @@ -0,0 +1,7 @@ +package oiccli.exceptions; + +public class OicMsgError extends Exception { + public OicMsgError(String message) { + super(message); + } +} diff --git a/lib/src/main/java/oiccli/exceptions/ParameterError.java b/lib/src/main/java/oiccli/exceptions/ParameterError.java new file mode 100644 index 0000000..f682232 --- /dev/null +++ b/lib/src/main/java/oiccli/exceptions/ParameterError.java @@ -0,0 +1,7 @@ +package oiccli.exceptions; + +public class ParameterError extends Exception { + public ParameterError(String message) { + super(message); + } +} diff --git a/lib/src/main/java/oiccli/exceptions/UnknownState.java b/lib/src/main/java/oiccli/exceptions/UnknownState.java new file mode 100644 index 0000000..5d68355 --- /dev/null +++ b/lib/src/main/java/oiccli/exceptions/UnknownState.java @@ -0,0 +1,7 @@ +package oiccli.exceptions; + +public class UnknownState extends Throwable { + public UnknownState(String state) { + super(state); + } +} diff --git a/lib/src/main/java/oiccli/exceptions/UnsupportedType.java b/lib/src/main/java/oiccli/exceptions/UnsupportedType.java new file mode 100644 index 0000000..9482405 --- /dev/null +++ b/lib/src/main/java/oiccli/exceptions/UnsupportedType.java @@ -0,0 +1,7 @@ +package oiccli.exceptions; + +public class UnsupportedType extends Exception { + public UnsupportedType(String message) { + super(message); + } +} diff --git a/lib/src/main/java/oiccli/exceptions/ValueError.java b/lib/src/main/java/oiccli/exceptions/ValueError.java new file mode 100644 index 0000000..e548c67 --- /dev/null +++ b/lib/src/main/java/oiccli/exceptions/ValueError.java @@ -0,0 +1,7 @@ +package oiccli.exceptions; + +public class ValueError extends Exception { + public ValueError(String message) { + super(message); + } +} diff --git a/lib/src/main/java/oiccli/exceptions/WebFingerError.java b/lib/src/main/java/oiccli/exceptions/WebFingerError.java new file mode 100644 index 0000000..d1e8813 --- /dev/null +++ b/lib/src/main/java/oiccli/exceptions/WebFingerError.java @@ -0,0 +1,7 @@ +package oiccli.exceptions; + +public class WebFingerError extends Exception { + public WebFingerError(String message) { + super(message); + } +} diff --git a/lib/src/main/java/oiccli/exceptions/WrongContentType.java b/lib/src/main/java/oiccli/exceptions/WrongContentType.java new file mode 100644 index 0000000..306aaf8 --- /dev/null +++ b/lib/src/main/java/oiccli/exceptions/WrongContentType.java @@ -0,0 +1,7 @@ +package oiccli.exceptions; + +public class WrongContentType extends Exception { + public WrongContentType(String message) { + super(message); + } +} diff --git a/lib/src/main/java/oiccli/service/AccessToken.java b/lib/src/main/java/oiccli/service/AccessToken.java new file mode 100644 index 0000000..bbbde11 --- /dev/null +++ b/lib/src/main/java/oiccli/service/AccessToken.java @@ -0,0 +1,29 @@ +package oiccli.service; + +import java.util.Arrays; +import java.util.Map; +import oiccli.AuthorizationResponse; +import oiccli.client_info.ClientInfo; +import oiccli.exceptions.ParameterError; +import oiccli.exceptions.UnknownState; + +public class AccessToken extends service.AccessToken { + private AccessTokenRequest accessTokenRequest; + private AccessTokenResponse accessTokenResponse; + private TokenErrorResponse tokenErrorResponse; + private List<> postParseResponse; + + public AccessToken(String httpLib, KeyJar keyJar, String clientAuthenticationMethod) { + super(httpLib, keyJar, clientAuthenticationMethod); + this.postParseResponse = Arrays.asList(this.oicPostParseResponse); + } + + public void oicPostParseResponse(AuthorizationResponse response, ClientInfo cliInfo, String state) throws ParameterError, UnknownState { + cliInfo.getStateDb().addResponse(response, state); + Map idt = response.get("verifiedIdToken"); + + if (!cliInfo.getStateDb().nonceToState(idt.get("nonce")).equals(state)) { + throw new ParameterError("Someone has messed with the 'nonce'"); + } + } +} diff --git a/lib/src/main/java/oiccli/service/Authorization.java b/lib/src/main/java/oiccli/service/Authorization.java new file mode 100644 index 0000000..8fd9db1 --- /dev/null +++ b/lib/src/main/java/oiccli/service/Authorization.java @@ -0,0 +1,139 @@ +package oiccli.service; + +import com.auth0.jwt.creators.Message; +import com.google.common.base.Strings; +import java.io.BufferedWriter; +import java.io.FileWriter; +import java.io.IOException; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import oiccli.AuthorizationResponse; +import oiccli.StringUtil; +import oiccli.Tuple; +import oiccli.Utils; +import oiccli.client_info.ClientInfo; + +public class Authorization extends service.Authorization { + + private AuthorizationRequest authorizationRequest; + private AuthorizationResponse authorizationResponse; + private AuthorizationErrorResponse authorizationErrorResponse; + private Map> defaultRequestArgs; + private List<> + + public Authorization(Object httpLib, Object keyJar, Map clientAuthenticationMethod) { + super(httpLib, keyJar, clientAuthenticationMethod); + this.defaultRequestArgs = new HashMap() {{ + put("scope", Arrays.asList("openId")); + }}; + /*self.pre_construct = [self.oic_pre_construct] + self.post_construct = [self.oic_post_construct]*/ + } + + public List> oicPreConstruct(ClientInfo clientInfo, Map requestArgs, Map args) { + if (requestArgs != null) { + String responseType = (String) requestArgs.get("responseType"); + if (responseType == null) { + requestArgs.put("responseType", clientInfo.getBehavior().get("responseTypes").get(0)); + + } + + if (responseType.contains("token") || responseType.contains("idToken")) { + if (!requestArgs.containsKey("nonce")) { + requestArgs.put("nonce", StringUtil.generateRandomString(32)); + } + } + } else { + requestArgs = new HashMap() {{ + put("nonce", StringUtil.generateRandomString(32)); + }}; + } + + Map postArgs = new HashMap<>(); + for (String attribute : Arrays.asList("requestObjectSigningAlg", "algorithm", "sigKid")) { + postArgs.put(attribute, args.get(attribute)); + args.remove(attribute); + } + + if (args.containsKey("requestMethod")) { + if (args.get("requestMethod").equals("reference")) { + postArgs.put("requestParam", "requestUri"); + } else { + postArgs.put("requestParam", "request"); + } + args.remove("requestMethod"); + } + + List responseMode = clientInfo.getBehavior().get("responseMode"); + if (responseMode.contains("formPost")) { + requestArgs.put("responseMode", responseMode); + } + + if (!requestArgs.containsKey("state")) { + requestArgs.put("state", clientInfo.getStateDb().createState(clientInfo.getIssuer(), requestArgs)); + } + + return Arrays.asList(requestArgs, postArgs); + } + + public Map oicPostConstruct(ClientInfo clientInfo, Map req, Map args) { + String requestParam = args.get("requestParam"); + args.remove("requestParam"); + + String algorithm = null; + for (String argument : Arrays.asList("requestObjectSigningAlg", "algorithm")) { + algorithm = args.get(argument); + } + + if (algorithm == null) { + algorithm = clientInfo.getBehavior().get("requestObjectSigningAlg"); + if (algorithm == null) { + algorithm = "RS256"; + } + } + + args.put("requestObjectSigningAlg", algorithm); + + if (!args.containsKey("keys") && algorithm != null && !algorithm.equals("none")) { + String kty = StringUtil.alg2keytype(algorithm); + String kid = args.get("sigKid"); + if (kid == null) { + kid = clientInfo.getKid().get("sig").get(kty); + } + args.put("keys", clientInfo.getKeyJar().getSigningKey(kty, kid)); + } + + /* + _req = make_openid_request(req, **kwargs) + */ + + Message _req = Utils.requestObjectEncryption(_req, clientInfo, args); + + String webName; + String fileName; + if (requestParam.equals("request")) { + req.put("request", _req); + } else { + webName = clientInfo.getRegistrationResponse().get("requestUris").get(0); + fileName = clientInfo.filenameFromWebname(webName); + if(Strings.isNullOrEmpty(fileName)) { + Tuple tuple = Utils.constructRequestUri(null, null, args); + webName = tuple.getA(); + fileName = tuple.getB(); + } + BufferedWriter writer = null; + try { + writer = new BufferedWriter(new FileWriter(fileName)); + writer.write(_req); + writer.close(); + } catch (IOException e) { + e.printStackTrace(); + } + req.put("requestUri", webName); + } + + return req; + } +} diff --git a/lib/src/main/java/oiccli/service/CheckID.java b/lib/src/main/java/oiccli/service/CheckID.java new file mode 100644 index 0000000..f569f2c --- /dev/null +++ b/lib/src/main/java/oiccli/service/CheckID.java @@ -0,0 +1,29 @@ +package oiccli.service; + +import com.auth0.jwt.creators.Message; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import oiccli.client_info.ClientInfo; +import oiccli.exceptions.MissingParameter; + +public class CheckID extends Service { + private static CheckIDRequest checkIDRequest; + private static Message message; + private static ErrorResponse errorResponse; + private static String endpointName = ""; + private static boolean isSynchronous = true; + private static String request = "checkSession"; + + public CheckID(String httpLib, KeyJar keyJar, String clientAuthenticationMethod) { + super(httpLib, keyJar, clientAuthenticationMethod); + //self.pre_construct = [self.oic_pre_construct] + } + + public List> oicPreConstruct(ClientInfo clientInfo, Map requestArgs, Map args) throws MissingParameter { + requestArgs = UserInfo.setIdToken(clientInfo, requestArgs, args); + return Arrays.asList(requestArgs, new HashMap()); + } + +} \ No newline at end of file diff --git a/lib/src/main/java/oiccli/service/CheckSession.java b/lib/src/main/java/oiccli/service/CheckSession.java new file mode 100644 index 0000000..d22ef11 --- /dev/null +++ b/lib/src/main/java/oiccli/service/CheckSession.java @@ -0,0 +1,29 @@ +package oiccli.service; + +import com.auth0.jwt.creators.Message; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import oiccli.client_info.ClientInfo; +import oiccli.exceptions.MissingParameter; + +public class CheckSession extends Service { + private static CheckSessionRequest checkSessionRequest; + private static Message message; + private static ErrorResponse errorResponse; + private static String endpointName = ""; + private static boolean isSynchronous = true; + private static String request = "checkSession"; + + public CheckSession(String httpLib, KeyJar keyJar, String clientAuthenticationMethod) { + super(httpLib, keyJar, clientAuthenticationMethod); + //self.pre_construct = [self.oic_pre_construct] + } + + public List> oicPreConstruct(ClientInfo clientInfo, Map requestArgs, Map args) throws MissingParameter { + requestArgs = UserInfo.setIdToken(clientInfo, requestArgs, args); + return Arrays.asList(requestArgs, new HashMap()); + } + +} diff --git a/lib/src/main/java/oiccli/service/EndSession.java b/lib/src/main/java/oiccli/service/EndSession.java new file mode 100644 index 0000000..29afe09 --- /dev/null +++ b/lib/src/main/java/oiccli/service/EndSession.java @@ -0,0 +1,41 @@ +package oiccli.service; + +import com.auth0.jwt.creators.Message; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import oiccli.client_info.ClientInfo; +import oiccli.exceptions.MissingParameter; + +public class EndSession extends Service { + private static EndSessionRequest endSessionRequest; + private static Message message; + private static ErrorResponse errorResponse; + private static String endpointName = "endSessionEndpoint"; + private static boolean isSynchronous = true; + private static String request = "endSession"; + + public EndSession(String httpLib, KeyJar keyJar, String clientAuthenticationMethod) { + super(httpLib, keyJar, clientAuthenticationMethod); + //self.pre_construct = [self.oic_pre_construct] + } + + public List> oicPreConstruct(ClientInfo clientInfo, Map requestArgs, Map args) throws MissingParameter { + requestArgs = UserInfo.setIdToken(clientInfo, requestArgs, args); + return Arrays.asList(requestArgs, new HashMap()); + } + + /* + def factory(req_name, **kwargs): + for name, obj in inspect.getmembers(sys.modules[__name__]): + if inspect.isclass(obj) and issubclass(obj, oiccli.Service): + try: + if obj.__name__ == req_name: + return obj(**kwargs) + except AttributeError: + pass + + return service.factory(req_name, **kwargs) + */ +} diff --git a/lib/src/main/java/oiccli/service/ProviderInfoDiscovery.java b/lib/src/main/java/oiccli/service/ProviderInfoDiscovery.java new file mode 100644 index 0000000..5854d24 --- /dev/null +++ b/lib/src/main/java/oiccli/service/ProviderInfoDiscovery.java @@ -0,0 +1,122 @@ +package oiccli.service; + +import com.auth0.jwt.creators.Message; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Set; +import oiccli.client_info.ClientInfo; +import oiccli.exceptions.ConfigurationError; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class ProviderInfoDiscovery extends service.ProviderInfoDiscovery { + private Message message; + private ProviderConfigurationResponse providerConfigurationResponse; + private ErrorResponse errorResponse; + private final static Logger logger = LoggerFactory.getLogger(ProviderInfoDiscovery.class); + + public ProviderInfoDiscovery(String httpLib, KeyJar keyJar, String clientAuthenticationMethod) { + super(httpLib, keyJar, clientAuthenticationMethod); + this.postParseResponse.insert(0, this.oicPostParseResponse); + } + + public void oicPostParseResponse(String response, ClientInfo clientInfo) { + this.matchPreferences(clientInfo, response, clientInfo.getIssuer()); + } + + public static void matchPreferences(ClientInfo clientInfo, Map> pcr) throws ConfigurationError { + if (pcr == null) { + pcr = clientInfo.getProviderInfo(); + } + + RegistrationRequest rr = new oic.RegistrationRequest(); + Set set = Service.PREFERENCE2PROVIDER.entrySet(); + Iterator iterator = set.iterator(); + Map.Entry mapEntry; + String key, value; + List listOfValues, values; + while (iterator.hasNext()) { + mapEntry = (Map.Entry) iterator.next(); + key = (String) mapEntry.getKey(); + value = (String) mapEntry.getValue(); + values = clientInfo.getClientPrefs().get(key); + listOfValues = pcr.get(value); + if (listOfValues == null) { + listOfValues = Arrays.asList(Service.PROVIDERDEFAULT.get(key)); + } + if (listOfValues == null) { + logger.info("No info from provider on " + key + " and no default."); + if (clientInfo.getShouldBeStrictOnPreferences()) { + throw new ConfigurationError("OP couldn't match preferences: " + key); + } else { + listOfValues = values; + } + } + + if (listOfValues.contains(values)) { + setBehavior(clientInfo, key, values); + } else { + /* + vtyp = regreq.c_param[_pref] + + if isinstance(vtyp[0], list): + cli_info.behaviour[_pref] = [] + for val in vals: + if val in _pvals: + cli_info.behaviour[_pref].append(val) + */ + for (String valueIndex : values) { + if (listOfValues.contains(valueIndex)) { + Map> behavior = clientInfo.getBehavior(); + behavior.put(key, Arrays.asList(valueIndex)); + clientInfo.setBehavior(behavior); + break; + } + } + } + + if (!clientInfo.getBehavior().containsKey(key)) { + throw new ConfigurationError("OP couldn't match preferences " + key); + } + } + + for (String keyIndex : clientInfo.getClientPrefs().keySet()) { + if (!clientInfo.getBehavior().containsKey(keyIndex)) { + /* + vtyp = regreq.c_param[key] + if isinstance(vtyp[0], list): + pass + elif isinstance(val, list) and not isinstance(val, + six.string_types): + val = val[0] + */ + + if (!Service.PREFERENCE2PROVIDER.containsKey(keyIndex)) { + List behavior = clientInfo.getBehavior().get(keyIndex); + if (behavior == null) { + behavior = new ArrayList<>(); + } + behavior.addAll(clientInfo.getClientPrefs().get(keyIndex)); + } + } + } + + logger.debug("cliInfo behavior " + clientInfo.getBehavior().toString()); + + } + + private static void setBehavior(ClientInfo clientInfo, String key, List values) { + Map> behavior = clientInfo.getBehavior(); + if (behavior.containsKey(key)) { + List returnedValue = behavior.get(key); + returnedValue.addAll(values); + behavior.put(key, returnedValue); + } else { + behavior.put(key, values); + } + clientInfo.setBehavior(behavior); + } +} diff --git a/lib/src/main/java/oiccli/service/Registration.java b/lib/src/main/java/oiccli/service/Registration.java new file mode 100644 index 0000000..b02da4b --- /dev/null +++ b/lib/src/main/java/oiccli/service/Registration.java @@ -0,0 +1,64 @@ +package oiccli.service; + +import com.google.common.base.Strings; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import oiccli.client_info.ClientInfo; + +public class Registration extends Service { + + private RegistrationRequest registrationRequest; + private RegistrationResponse registrationResponse; + private ErrorResponse errorResponse; + private static String endpointName = "registrationEndpoint"; + private static boolean isSynchronous = true; + private static String request = "registration"; + private static String bodyType = "json"; + private static String httpMethod = "POST"; + + public Registration(String httpLib, KeyJar keyJar, String clientAuthenticationMethod) { + super(httpLib, keyJar, clientAuthenticationMethod); + /* + self.pre_construct = [self.oic_pre_construct] + self.post_parse_response.append(self.oic_post_parse_response) + */ + } + + public List>> oicPreConstruct(ClientInfo clientInfo, Map> requestArgs) { + for (String key : this.registrationRequest.getCParam().keySet()) { + if (!requestArgs.containsKey(key)) { + requestArgs.put(key, clientInfo.getBehavior().get(key)); + } + } + + if (!requestArgs.containsKey("postLogoutRedirectUris")) { + requestArgs.put("postLogoutRedirectUris", clientInfo.getPostLogoutRedirectUris()); + } + + if (!requestArgs.containsKey("redirectUris")) { + requestArgs.put("redirectUris", clientInfo.getRedirectUris()); + } + + if (!Strings.isNullOrEmpty(clientInfo.getProviderInfo().get("requireRequestUriRegistration").get(0))) { + requestArgs.put("requestUris", clientInfo.generateRequestUris(clientInfo.getRequestsDir())); + } + + return Arrays.asList(requestArgs, new HashMap>()); + } + + public void oicPostParseResponse(Map> response, ClientInfo cliInfo) { + cliInfo.setRegistrationResponse(response); + if (!cliInfo.getRegistrationResponse().containsKey("tokenEndpointAuthMethod")) { + Map> hMap = cliInfo.getRegistrationResponse(); + hMap.put("tokenEndpointAuthMethod", Arrays.asList("clientSecretBasic")); + cliInfo.setRegistrationResponse(hMap); + } + + cliInfo.setClientId(response.get("clientId").get(0)); + cliInfo.setClientSecret(response.get("clientSecret").get(0)); + cliInfo.setRegistrationExpires(response.get("clientSecretExpiresAt").get(0)); + cliInfo.setRegistrationAccessToken(response.get("registrationAccessToken").get(0)); + } +} diff --git a/lib/src/main/java/oiccli/service/Service.java b/lib/src/main/java/oiccli/service/Service.java new file mode 100644 index 0000000..34bf940 --- /dev/null +++ b/lib/src/main/java/oiccli/service/Service.java @@ -0,0 +1,36 @@ +package oiccli.service; + +import java.util.HashMap; +import java.util.Map; + +public class Service { + + public static final Map PREFERENCE2PROVIDER = + new HashMap() {{ + put("request_object_signing_alg", "request_object_signing_alg_values_supported"); + put("request_object_encryption_alg", "request_object_encryption_alg_values_supported"); + put("request_object_encryption_enc", "request_object_encryption_enc_values_supported"); + put("userinfo_signed_response_alg", "userinfo_signing_alg_values_supported"); + put("userinfo_encrypted_response_alg", "userinfo_encryption_alg_values_supported"); + put("userinfo_encrypted_response_enc", "userinfo_encryption_enc_values_supported"); + put("id_token_signed_response_alg", "id_token_signing_alg_values_supported"); + put("id_token_encrypted_response_alg", "id_token_encryption_alg_values_supported"); + put("id_token_encrypted_response_enc", "id_token_encryption_enc_values_supported"); + put("default_acr_values", "acr_values_supported"); + put("subject_type", "subject_types_supported"); + put("token_endpoint_auth_method", "token_endpoint_auth_methods_supported"); + put("token_endpoint_auth_signing_alg", "token_endpoint_auth_signing_alg_values_supported"); + put("response_types", "response_types_supported"); + put("grant_types", "grant_types_supported"); + }}; + + public static final Map PROVIDERDEFAULT = + new HashMap() {{ + put("token_endpoint_auth_method", "client_secret_basic"); + put("id_token_signed_response_alg", "RS256"); + }}; + + + public Service(String httpLib, KeyJar keyJar, String clientAuthenticationMethod) { + } +} diff --git a/lib/src/main/java/oiccli/service/UserInfo.java b/lib/src/main/java/oiccli/service/UserInfo.java new file mode 100644 index 0000000..cec3af3 --- /dev/null +++ b/lib/src/main/java/oiccli/service/UserInfo.java @@ -0,0 +1,170 @@ +package oiccli.service; + +import com.auth0.jwt.creators.Message; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Set; +import oiccli.client_info.ClientInfo; +import oiccli.exceptions.MissingParameter; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class UserInfo extends Service { + private Message message; + private OpenIDSchema openIDSchema; + private UserInfoErrorResponse userInfoErrorResponse; + private static String endpointName = "userInfoEndpoint"; + private static boolean isSynchronous = true; + private static String request = "userInfo"; + private static String defaultAuthenticationMethod = "bearerHeader"; + private static String httpMethod = "GET"; + private final static Logger logger = LoggerFactory.getLogger(UserInfo.class); + + public UserInfo(String httpLib, KeyJar keyJar, String clientAuthenticationMethod) { + super(httpLib, keyJar, clientAuthenticationMethod); + /* + self.pre_construct = [self.oic_pre_construct] + self.post_parse_response.insert(0, self.oic_post_parse_response) + */ + } + + public List> oicPreConstruct(ClientInfo clientInfo, Map requestArgs, Map args) { + if (requestArgs == null) { + requestArgs = new HashMap<>(); + } + + if (!requestArgs.containsKey("accessToken")) { + //_tinfo = cli_info.state_db.get_token_info(**kwargs) + requestArgs.put("accessToken", tInfo.get("accessToken")); + } + + return Arrays.asList(requestArgs, new HashMap()); + } + + public Map> oicPostParseResponse(Map> userInfo, ClientInfo clientInfo) { + Map> unpacked = this.unpackAggregatedClaims(userInfo, clientInfo); + return this.fetchDistributedClaims(unpacked, clientInfo, null); + } + + public Map> unpackAggregatedClaims(Map> userInfo, ClientInfo clientInfo) { + Map csrc = userInfo.get("claimSources"); + Set set = csrc.entrySet(); + Iterator iterator = set.iterator(); + Map.Entry mapEntry, mapEntryInner; + String key, keyInner; + Map value, valueInner; + while (iterator.hasNext()) { + mapEntry = (Map.Entry) iterator.next(); + key = (String) mapEntry.getKey(); + value = (Map) mapEntry.getValue(); + if (value.containsKey("JWT")) { + Map> aggregatedClaims = new Message().fromJWT(value.get("JWT"), clientInfo.getKeyJar()); + Map cName = userInfo.get("claimNames"); + set = cName.entrySet(); + iterator = set.iterator(); + List claims = new ArrayList<>(); + while (iterator.hasNext()) { + mapEntryInner = (Map.Entry) iterator.next(); + keyInner = (String) mapEntryInner.getKey(); + valueInner = (Map) mapEntryInner.getValue(); + if (valueInner.equals(value)) { + claims.add(keyInner); + } + } + + for (String claim : claims) { + userInfo.put(claim, aggregatedClaims.get(claim)); + } + } + } + + return userInfo; + } + + public Map> fetchDistributedClaims(Map> userInfo, ClientInfo clientInfo, Method callBack) { + Map csrc = userInfo.get("claimSources"); + Set set = csrc.entrySet(); + Iterator iterator = set.iterator(); + Map.Entry mapEntry, mapEntryInner; + String key, keyInner; + Map value, valueInner; + while (iterator.hasNext()) { + mapEntry = (Map.Entry) iterator.next(); + key = (String) mapEntry.getKey(); + value = (Map) mapEntry.getValue(); + ErrorResponse errorResponse; + if (value.containsKey("endpoint")) { + if (value.containsKey("accessToken")) { + //TODO:callback is a method; figure out how to pass a method as a param to a function + errorResponse = oiccli.Service.serviceRequest(value.get("endpoint"), "GET", value.get("accessToken"), clientInfo); + } else { + if (callBack != null) { + errorResponse = oiccli.Service.serviceRequest(value.get("endpoint"), "GET", callBack(value.get("endpoint")), clientInfo); + } else { + errorResponse = oiccli.Service.serviceRequest(value.get("endpoint"), "GET", clientInfo); + } + } + + List claims = new ArrayList<>(); + Set keys = userInfo.get("claimNames").keySet(); + String valueIndex; + for (String keyIndex : keys) { + valueIndex = userInfo.get("claimNames").get(keyIndex); + if (valueIndex.equals(key)) { + claims.add(valueIndex); + } + } + + if (new HashSet<>(claims).equals(new HashSet<>(errorResponse.getKeys()))) { + logger.warn("Claims from claim source doesn't match what's in the userinfo"); + } + + for (String errorResponseKey : errorResponse.keySet()) { + userInfo.put(errorResponseKey, errorResponse.get(errorResponseKey)); + } + } + } + return userInfo; + } + + public static Map setIdToken(ClientInfo cliInfo, Map requestArgs, Map args) throws MissingParameter { + if (requestArgs == null) { + requestArgs = new HashMap<>(); + } + + String property = args.get("prop"); + if (property == null) { + property = "idToken"; + } + + if (!requestArgs.containsKey(property)) { + String idToken; + String state = getState(requestArgs, args); + idToken = cliInfo.getStateDb().getIdToken(state); + if (idToken == null) { + throw new MissingParameter("No valid id token available"); + } + requestArgs.put(property, idToken); + } + + return requestArgs; + } + + public static String getState(Map requestArgs, Map args) throws MissingParameter { + String state = args.get("state"); + if (state == null) { + state = requestArgs.get("state"); + if (state == null) { + throw new MissingParameter("state"); + } + } + + return state; + } + +} diff --git a/lib/src/main/java/oiccli/webfinger/Base.java b/lib/src/main/java/oiccli/webfinger/Base.java new file mode 100644 index 0000000..a8a64cb --- /dev/null +++ b/lib/src/main/java/oiccli/webfinger/Base.java @@ -0,0 +1,112 @@ +package oiccli.webfinger; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import oiccli.exceptions.ValueError; +import org.junit.Assert; + +public class Base { + + private Map ava; + private Map>> cParams; + static final Map link = new HashMap() {{ + put("rel", new HashMap() {{ + put("type", String.class); + put("required", true); + }}); + put("type", new HashMap() {{ + put("type", String.class); + put("required", false); + }}); + put("href", new HashMap() {{ + put("type", String.class); + put("required", false); + }}); + put("titles", new HashMap() {{ + put("type", Map.class); + put("required", false); + }}); + put("properties", new HashMap() {{ + put("type", Map.class); + put("required", false); + }}); + }}; + + public Base(Map hMap) { + ava = new HashMap<>(); + if (hMap != null) { + this.load(hMap); + } + cParams = new HashMap<>(); + } + + public void setItem(String key, List value) { + HashMap> spec = this.cParams.get(key); + if (spec == null) { + spec = new HashMap>() {{ + put("type", Arrays.asList(this.toString())); + put("isRequired", Arrays.asList(false)); + }}; + } + + List types = spec.get("type"); + Object t1, t2; + if (types != null && types.size() == 2) { + t1 = types.get(0); + t2 = types.get(1); + } else { + throw new IllegalArgumentException("'Type' should have returned 2 values"); + } + + if (t1.getClass().equals(List.class)) { + List result = new ArrayList<>(); + if (t2.equals(link)) { + for (String index : value) { + result.add(link.get(index)); + } + } else { + for (String index : value) { + Assert.assertTrue(index instanceof t2); + result.add(index); + } + } + ava.put(key, result); + } + } + + public void load(Map> hMap) throws ValueError { + for (String key : this.cParams.keySet()) { + if (!hMap.containsKey(key) && cParams.get(key).get("required")) { + throw new ValueError("Required attribute " + key + " missing"); + } + } + + for (String key : hMap.keySet()) { + if (!hMap.get(key).equals("") || !hMap.get(key).isEmpty()) { + setItem(key, hMap.get(key)); + } + } + } + + public Map> dump() { + Map> result = new HashMap<>(); + List list = new ArrayList<>(); + for (String key : this.ava.keySet()) { + list = this.cParams.get(key).get("type"); + + if (list != null && list.size() == 2 && (list.get(0) instanceof List) && (list.get(1) instanceof Map)) { + List sRes = new ArrayList(list); + for (Base index : list) { + sRes.add(index.dump()); + } + list = sRes; + } + result.put(key, this.cParams.get(key)); + } + + return result; + } +} diff --git a/lib/src/main/java/oiccli/webfinger/JRD.java b/lib/src/main/java/oiccli/webfinger/JRD.java new file mode 100644 index 0000000..ea7388d --- /dev/null +++ b/lib/src/main/java/oiccli/webfinger/JRD.java @@ -0,0 +1,60 @@ +package oiccli.webfinger; + +import com.auth0.jwt.creators.Message; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class JRD extends Message { + private static final Map cParam = new HashMap() {{ + put("subject", new HashMap() {{ + put("type", String.class); + put("required", false); + }}); + put("aliases", new HashMap() {{ + put("type", Arrays.asList(String.class, List.class)); + put("required", false); + }}); + put("properties", new HashMap() {{ + put("type", Map.class); + put("required", false); + }}); + put("links", new HashMap() {{ + put("type", Arrays.asList(List.class, Map.class)); + put("required", false); + }}); + }}; + private int expDays; + private int expSeconds; + private int expMins; + private int expHour; + private int expWeek; + + public JRD(Map hMap, int days, int seconds, int minutes, int hours, int weeks) { + super(hMap); + this.expiresIn(days, seconds, minutes, hours, weeks); + } + + public JRD() { + this(null, 0, 0, 0, 0, 0); + } + + public void expiresIn(int days, int seconds, int minutes, int hours, int weeks) { + this.expDays = days; + this.expSeconds = seconds; + this.expMins = minutes; + this.expHour = hours; + this.expWeek = weeks; + } + + public Map> export() { + Map> result = dump(); + result.put("expires", inAWhile(this.expDays, this.expSeconds, this.expMins, this.expHour, this.expWeek)); + return result; + } + + public Map getcParam() { + return cParam; + } +} diff --git a/lib/src/main/java/oiccli/webfinger/URINormalizer.java b/lib/src/main/java/oiccli/webfinger/URINormalizer.java new file mode 100644 index 0000000..1d8cdb6 --- /dev/null +++ b/lib/src/main/java/oiccli/webfinger/URINormalizer.java @@ -0,0 +1,48 @@ +package oiccli.webfinger; + +public class URINormalizer { + + public boolean hasScheme(String path) { + if (path.contains("://")) { + return true; + } else { + String authority = path.replace("/", "#") + .replace("?", "#").split("#")[0]; + + String hostOrPort; + if (authority.contains(":")) { + hostOrPort = authority.split(":", 1)[1]; + if (hostOrPort.matches("^\\d+$")) { + return false; + } + } else { + return false; + } + } + + return true; + } + + public static boolean isAccountSchemeAssumed(String path) { + String[] arr; + if (path.contains("@")) { + arr = path.split("@"); + String host = arr[arr.length - 1]; + return !(host.contains(":") || host.contains("/") || host.contains("?")); + } else { + return false; + } + } + + public String normalize(String path) { + if (!this.hasScheme(path)) { + if (this.isAccountSchemeAssumed(path)) { + path = "acct:" + path; + } else { + path = "https://" + path; + } + } + + return path.split("#")[0]; + } +} diff --git a/lib/src/main/java/oiccli/webfinger/WebFinger.java b/lib/src/main/java/oiccli/webfinger/WebFinger.java new file mode 100644 index 0000000..ba497f3 --- /dev/null +++ b/lib/src/main/java/oiccli/webfinger/WebFinger.java @@ -0,0 +1,154 @@ +package oiccli.webfinger; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.google.common.base.Strings; +import com.google.gson.Gson; +import java.net.URI; +import java.net.URISyntaxException; +import java.net.URLEncoder; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import oiccli.Tuple; +import oiccli.exceptions.MessageException; +import oiccli.exceptions.OicMsgError; +import oiccli.exceptions.WebFingerError; +import org.apache.commons.codec.binary.Base64; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class WebFinger { + + public String defaultRelt; + public Object httpd; + private JRD jrd; + private List> events; + private static final String WF_URL = "https://%s/.well-known/webfinger"; + final private static Logger logger = LoggerFactory.getLogger(WebFinger.class); + private static final String OIC_ISSUER = "http://openid.net/specs/connect/1.0/issuer"; + private static final Gson gson = new Gson(); + + public WebFinger(String defaultRelt, Object httpd) { + this.defaultRelt = defaultRelt; + this.httpd = httpd; + this.jrd = null; + this.events = new ArrayList<>(); + } + + public String query(String resource, List rel) throws URISyntaxException, WebFingerError { + resource = new URINormalizer().normalize(resource); + List queryParamsTuple = new ArrayList<>(Arrays.asList(new Tuple("resource", resource))); + + if (rel == null) { + if (!Strings.isNullOrEmpty(this.defaultRelt)) { + queryParamsTuple.add(new Tuple("rel", this.defaultRelt)); + } + } else { + for (String index : rel) { + queryParamsTuple.add(new Tuple("rel", index)); + } + } + + String host; + if (resource.startsWith("http")) { + URI uri = new URI(resource); + host = uri.getHost(); + int port = uri.getPort(); + if (port != -1) { + host += ":" + port; + } + } else if (resource.startsWith("acct:")) { + String[] arr = resource.split("@"); + host = arr[arr.length - 1]; + arr = host.replace("/", "#").replace("?", "#").split("#"); + host = arr[0]; + } else if (resource.startsWith("device:")) { + String[] arr = resource.split(":"); + host = arr[1]; + } else { + throw new WebFingerError("Unknown schema"); + } + + String queryParams = ""; + for (int i = 0; + i < queryParamsTuple.size(); + i++) { + queryParams += queryParamsTuple.get(i).getA() + "=" + queryParamsTuple.get(i).getB(); + if (i != queryParamsTuple.size() - 1) { + queryParams += "&"; + } + } + + return String.format(WF_URL, host) + "?" + URLEncoder.encode(queryParams); + } + + public static JRD load(Map item) { + return new JRD(json.loads(item)); + } + + public Map httpArgs(JRD jrd) throws JsonProcessingException { + if (jrd == null) { + if (this.jrd != null) { + jrd = this.jrd; + } else { + return null; + } + } + + Map hMap = new HashMap() {{ + put("Access-Control-Allow-Origin", "*"); + put("Content-Type", "application/json; charset=UTF-8"); + }}; + + Map headersAndBody = new HashMap<>(); + headersAndBody.put("headers", hMap); + headersAndBody.put("body", jrd.toJSON()); + + return headersAndBody; + } + + public static Object linkDeser(Object val, String sFormat) { + if (val instanceof Map) { + return val; + } else if (sFormat.equals("dict") || sFormat.equals("json")) { + if (!(val instanceof String)) { + val = gson.toJson(val); + sFormat = "json"; + } + } + + return link().deserialize(val, sFormat); + } + + public static Object messageSer(Object inst, String sFormat, int lev) throws MessageException, OicMsgError { + Object res; + if (sFormat.equals("urlencoded") || sFormat.equals("json")) { + if (inst instanceof Map) { + if (sFormat.equals("json")) { + res = gson.toJson(inst); + } else { + res = Base64.encodeBase64URLSafe() + } + } + //elif isinstance(inst, LINK): + //res = inst.serialize(sformat, lev) + else { + res = inst; + } + } else if (sFormat.equals("dict")) { + if (inst instanceof Map) { + res = inst.serialize(sFormat, lev); + } else if (inst instanceof String) { + res = inst; + } else { + throw new MessageException("Wrong type: " + inst.getClass()); + } + } else { + throw new OicMsgError("Unknown sFormat" + inst); + } + + return res; + } +} diff --git a/lib/src/test/java/com/auth0/jwt/creators/AccessJwtCreatorTest.java b/lib/src/test/java/com/auth0/jwt/creators/AccessJwtCreatorTest.java index b9e7087..2f2b769 100644 --- a/lib/src/test/java/com/auth0/jwt/creators/AccessJwtCreatorTest.java +++ b/lib/src/test/java/com/auth0/jwt/creators/AccessJwtCreatorTest.java @@ -317,14 +317,11 @@ public void testAccessJwtCreatorNonStandardClaimDateValue() throws Exception { @Test public void testAccessJwtCreatorExpTimeHasPassed() throws Exception { - thrown.expect(TokenExpiredException.class); - thrown.expectMessage("The Token has expired on Wed Oct 29 00:00:00 PDT 2014."); + Calendar calendar = Calendar.getInstance(); + calendar.set(2014, Calendar.OCTOBER, 29); - String myDate = "2014/10/29"; - SimpleDateFormat sdf = new SimpleDateFormat("yyyy/MM/dd"); - Date date = sdf.parse(myDate); - long expLong = date.getTime(); - Date expDate = new Date(expLong); + thrown.expect(TokenExpiredException.class); + thrown.expectMessage(String.format("The Token has expired on %s", calendar.getTime())); Algorithm algorithm = Algorithm.HMAC256("secret"); String token = AccessJwtCreator.build() @@ -332,7 +329,7 @@ public void testAccessJwtCreatorExpTimeHasPassed() throws Exception { .withSubject("subject") .withAudience("audience") .withNonStandardClaim("nonStandardClaim", new Date()) - .withExp(expDate) + .withExp(calendar.getTime()) .withIat(iat) .sign(algorithm); Verification verification = AccessJWT.require(algorithm); diff --git a/lib/src/test/java/com/auth0/jwt/creators/ExtendedJwtCreatorTest.java b/lib/src/test/java/com/auth0/jwt/creators/ExtendedJwtCreatorTest.java index 445f0b0..87a583f 100644 --- a/lib/src/test/java/com/auth0/jwt/creators/ExtendedJwtCreatorTest.java +++ b/lib/src/test/java/com/auth0/jwt/creators/ExtendedJwtCreatorTest.java @@ -40,6 +40,7 @@ import org.junit.rules.ExpectedException; import java.text.SimpleDateFormat; +import java.util.Calendar; import java.util.Date; import java.util.Map; @@ -441,14 +442,11 @@ public void testExtendedJwtCreatorNonStandardClaimDateValue() throws Exception { @Test public void testExtendedJwtCreatorExpTimeHasPassed() throws Exception { - thrown.expect(TokenExpiredException.class); - thrown.expectMessage("The Token has expired on Wed Oct 29 00:00:00 PDT 2014."); + Calendar calendar = Calendar.getInstance(); + calendar.set(2014, Calendar.OCTOBER, 29); - String myDate = "2014/10/29"; - SimpleDateFormat sdf = new SimpleDateFormat("yyyy/MM/dd"); - Date date = sdf.parse(myDate); - long expLong = date.getTime(); - Date expDate = new Date(expLong); + thrown.expect(TokenExpiredException.class); + thrown.expectMessage(String.format("The Token has expired on %s", calendar.getTime())); Algorithm algorithm = Algorithm.HMAC256("secret"); String token = ExtendedJwtCreator.build() @@ -458,7 +456,7 @@ public void testExtendedJwtCreatorExpTimeHasPassed() throws Exception { .withIssuer("issuer") .withSubject("subject") .withAudience("audience") - .withExp(expDate) + .withExp(calendar.getTime()) .withIat(iat) .withName(NAME) .withNonStandardClaim("nonStandardClaim", new Date()) diff --git a/lib/src/test/java/com/auth0/jwt/creators/FbJwtCreatorTest.java b/lib/src/test/java/com/auth0/jwt/creators/FbJwtCreatorTest.java index b6cb74e..b6b1121 100644 --- a/lib/src/test/java/com/auth0/jwt/creators/FbJwtCreatorTest.java +++ b/lib/src/test/java/com/auth0/jwt/creators/FbJwtCreatorTest.java @@ -322,18 +322,15 @@ public void testFbJwtCreatorNonStandardClaimDateValue() throws Exception { } @Test public void testFbJwtCreatorExpTimeHasPassed() throws Exception { - thrown.expect(TokenExpiredException.class); - thrown.expectMessage("The Token has expired on Wed Oct 29 00:00:00 PDT 2014."); + Calendar calendar = Calendar.getInstance(); + calendar.set(2014, Calendar.OCTOBER, 29); - String myDate = "2014/10/29"; - SimpleDateFormat sdf = new SimpleDateFormat("yyyy/MM/dd"); - Date date = sdf.parse(myDate); - long expLong = date.getTime(); - Date expDate = new Date(expLong); + thrown.expect(TokenExpiredException.class); + thrown.expectMessage(String.format("The Token has expired on %s", calendar.getTime())); Algorithm algorithm = Algorithm.HMAC256("secret"); String token = FbJwtCreator.build() - .withExp(expDate) + .withExp(calendar.getTime()) .withIat(iat) .withUserId(USER_ID) .withAppId(APP_ID) diff --git a/lib/src/test/java/com/auth0/jwt/creators/GoogleJwtCreatorTest.java b/lib/src/test/java/com/auth0/jwt/creators/GoogleJwtCreatorTest.java index ceba3d9..b0ec885 100644 --- a/lib/src/test/java/com/auth0/jwt/creators/GoogleJwtCreatorTest.java +++ b/lib/src/test/java/com/auth0/jwt/creators/GoogleJwtCreatorTest.java @@ -19,10 +19,7 @@ package com.auth0.jwt.creators; -import static com.auth0.jwt.TimeUtil.generateRandomExpDateInFuture; -import static com.auth0.jwt.TimeUtil.generateRandomIatDateInPast; import com.auth0.jwt.algorithms.Algorithm; -import com.auth0.jwt.creators.GoogleJwtCreator; import com.auth0.jwt.exceptions.InvalidClaimException; import com.auth0.jwt.exceptions.TokenExpiredException; import com.auth0.jwt.impl.PublicClaims; @@ -31,15 +28,18 @@ import com.auth0.jwt.interfaces.GoogleVerification; import com.auth0.jwt.jwts.GoogleJWT; import com.auth0.jwt.jwts.JWT; -import static java.util.Arrays.asList; -import static org.junit.Assert.assertTrue; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; +import java.util.Calendar; +import java.util.Date; +import java.util.Map; -import java.text.SimpleDateFormat; -import java.util.*; +import static com.auth0.jwt.TimeUtil.generateRandomExpDateInFuture; +import static com.auth0.jwt.TimeUtil.generateRandomIatDateInPast; +import static java.util.Arrays.asList; +import static org.junit.Assert.assertTrue; public class GoogleJwtCreatorTest { @@ -133,7 +133,7 @@ public void testGoogleJwtCreatorWhenCertainRequiredClaimIsntProvided() throws Ex GoogleVerification verification = GoogleJWT.require(algorithm); JWT verifier = verification.createVerifierForGoogle(PICTURE, EMAIL, asList("issuer"), asList("audience"), - NAME, 1, 1).build(); + NAME, 1, 1).build(); DecodedJWT jwt = verifier.decode(token); } @@ -157,7 +157,7 @@ public void testGoogleJwtCreatorNoneAlgorithmNotAllowed() throws Exception { GoogleVerification verification = GoogleJWT.require(algorithm); JWT verifier = verification.createVerifierForGoogle(PICTURE, EMAIL, asList("issuer"), asList("audience"), - NAME, 1, 1).build(); + NAME, 1, 1).build(); DecodedJWT jwt = verifier.decode(token); } @@ -180,7 +180,7 @@ public void testGoogleJwtCreatorNoneAlgorithmNotSpecifiedButStillNotAllowed() th GoogleVerification verification = GoogleJWT.require(algorithm); JWT verifier = verification.createVerifierForGoogle(PICTURE, EMAIL, asList("issuer"), asList("audience"), - NAME, 1, 1).build(); + NAME, 1, 1).build(); DecodedJWT jwt = verifier.decode(token); } @@ -201,7 +201,7 @@ public void testGoogleJwtCreatorNoneAlgorithmAllowed() throws Exception { GoogleVerification verification = GoogleJWT.require(algorithm); JWT verifier = verification.createVerifierForGoogle(PICTURE, EMAIL, asList("issuer"), asList("audience"), - NAME, 1, 1).build(); + NAME, 1, 1).build(); DecodedJWT jwt = verifier.decode(token); Map claims = jwt.getClaims(); verifyClaims(claims, exp); @@ -251,7 +251,7 @@ public void testGoogleJwtCreatorInvalidIssuer() throws Exception { GoogleVerification verification = GoogleJWT.require(algorithm); JWT verifier = verification.createVerifierForGoogle(PICTURE, EMAIL, asList("issuer"), asList("audience"), - NAME, 1, 1).build(); + NAME, 1, 1).build(); DecodedJWT jwt = verifier.decode(token); } @@ -363,7 +363,7 @@ public void testGoogleJwtCreatorNonStandardClaimString() throws Exception { .sign(algorithm); GoogleVerification verification = GoogleJWT.require(algorithm); JWT verifier = verification.createVerifierForGoogle(PICTURE, EMAIL, asList("issuer"), asList("audience"), - NAME, 1, 1).build(); + NAME, 1, 1).build(); DecodedJWT jwt = verifier.decode(token); Map claims = jwt.getClaims(); verifyClaims(claims, exp); @@ -481,14 +481,11 @@ public void testGoogleJwtCreatorNonStandardClaimDate() throws Exception { @Test public void testGoogleJwtCreatorExpTimeHasPassed() throws Exception { - thrown.expect(TokenExpiredException.class); - thrown.expectMessage("The Token has expired on Wed Oct 29 00:00:00 PDT 2014."); + Calendar calendar = Calendar.getInstance(); + calendar.set(2014, Calendar.OCTOBER, 29); - String myDate = "2014/10/29"; - SimpleDateFormat sdf = new SimpleDateFormat("yyyy/MM/dd"); - Date date = sdf.parse(myDate); - long expLong = date.getTime(); - Date expDate = new Date(expLong); + thrown.expect(TokenExpiredException.class); + thrown.expectMessage(String.format("The Token has expired on %s", calendar.getTime())); Algorithm algorithm = Algorithm.HMAC256("secret"); String token = GoogleJwtCreator.build() @@ -497,7 +494,7 @@ public void testGoogleJwtCreatorExpTimeHasPassed() throws Exception { .withIssuer("issuer") .withSubject("subject") .withAudience("audience") - .withExp(expDate) + .withExp(calendar.getTime()) .withIat(iat) .withName(NAME) .sign(algorithm); @@ -509,14 +506,11 @@ public void testGoogleJwtCreatorExpTimeHasPassed() throws Exception { @Test public void testGoogleJwtCreatorTokenCantBeUsedBefore() throws Exception { - thrown.expect(InvalidClaimException.class); - thrown.expectMessage("The Token can't be used before Mon Oct 29 00:00:00 PDT 2018."); + Calendar calendar = Calendar.getInstance(); + calendar.set(2018, Calendar.OCTOBER, 29); - String myDate = "2018/10/29"; - SimpleDateFormat sdf = new SimpleDateFormat("yyyy/MM/dd"); - Date date = sdf.parse(myDate); - long expLong = date.getTime(); - Date iatDate = new Date(expLong); + thrown.expect(InvalidClaimException.class); + thrown.expectMessage(String.format("The Token can't be used before %s", calendar.getTime())); Algorithm algorithm = Algorithm.HMAC256("secret"); String token = GoogleJwtCreator.build() @@ -526,7 +520,7 @@ public void testGoogleJwtCreatorTokenCantBeUsedBefore() throws Exception { .withSubject("subject") .withAudience("audience") .withExp(exp) - .withIat(iatDate) + .withIat(calendar.getTime()) .withName(NAME) .sign(algorithm); GoogleVerification verification = GoogleJWT.require(algorithm); @@ -536,14 +530,14 @@ public void testGoogleJwtCreatorTokenCantBeUsedBefore() throws Exception { } @Test - public void testCreateVerifierForExtended() throws Exception{ + public void testCreateVerifierForExtended() throws Exception { thrown.expect(UnsupportedOperationException.class); thrown.expectMessage("you shouldn't be calling this method"); GoogleVerification verification = GoogleJWT.require(Algorithm.HMAC256("secret")); verification.createVerifierForExtended(null, null, null, null, null, 1L, 1L, 1L); } - protected static void verifyClaims(Map claims, Date exp) { + protected static void verifyClaims(Map claims, Date exp) { assertTrue(claims.get(PICTURE).asString().equals(PICTURE)); assertTrue(claims.get(EMAIL).asString().equals(EMAIL)); assertTrue(claims.get(PublicClaims.ISSUER).asList(String.class).get(0).equals("issuer")); diff --git a/lib/src/test/java/com/auth0/jwt/creators/RiscJwtCreatorTest.java b/lib/src/test/java/com/auth0/jwt/creators/RiscJwtCreatorTest.java index 31a03ef..2248ae0 100644 --- a/lib/src/test/java/com/auth0/jwt/creators/RiscJwtCreatorTest.java +++ b/lib/src/test/java/com/auth0/jwt/creators/RiscJwtCreatorTest.java @@ -378,14 +378,11 @@ public void testRiscJwtCreatorNonStandardClaimDateValue() throws Exception { @Test public void testRiscJwtCreatorExpTimeHasPassed() throws Exception { - thrown.expect(TokenExpiredException.class); - thrown.expectMessage("The Token has expired on Wed Oct 29 00:00:00 PDT 2014."); + Calendar calendar = Calendar.getInstance(); + calendar.set(2014, Calendar.OCTOBER, 29); - String myDate = "2014/10/29"; - SimpleDateFormat sdf = new SimpleDateFormat("yyyy/MM/dd"); - Date date = sdf.parse(myDate); - long expLong = date.getTime(); - Date expDate = new Date(expLong); + thrown.expect(TokenExpiredException.class); + thrown.expectMessage(String.format("The Token has expired on %s", calendar.getTime())); Algorithm algorithm = Algorithm.HMAC256("secret"); String token = RiscJwtCreator.build() @@ -394,7 +391,7 @@ public void testRiscJwtCreatorExpTimeHasPassed() throws Exception { .withSubject("subject") .withAudience("audience") .withNonStandardClaim("nonStandardClaim", new Date()) - .withExp(expDate) + .withExp(calendar.getTime()) .withIat(iat) .sign(algorithm); Verification verification = RiscJWT.require(algorithm); diff --git a/lib/src/test/java/com/auth0/jwt/creators/ScopedJwtCreatorTest.java b/lib/src/test/java/com/auth0/jwt/creators/ScopedJwtCreatorTest.java index 5ebb397..5878ed0 100644 --- a/lib/src/test/java/com/auth0/jwt/creators/ScopedJwtCreatorTest.java +++ b/lib/src/test/java/com/auth0/jwt/creators/ScopedJwtCreatorTest.java @@ -370,14 +370,11 @@ public void testScopedJwtCreatorNonStandardClaimDateValue() throws Exception { @Test public void testScopedJwtCreatorExpTimeHasPassed() throws Exception { - thrown.expect(TokenExpiredException.class); - thrown.expectMessage("The Token has expired on Wed Oct 29 00:00:00 PDT 2014."); + Calendar calendar = Calendar.getInstance(); + calendar.set(2014, Calendar.OCTOBER, 29); - String myDate = "2014/10/29"; - SimpleDateFormat sdf = new SimpleDateFormat("yyyy/MM/dd"); - Date date = sdf.parse(myDate); - long expLong = date.getTime(); - Date expDate = new Date(expLong); + thrown.expect(TokenExpiredException.class); + thrown.expectMessage(String.format("The Token has expired on %s", calendar.getTime())); Algorithm algorithm = Algorithm.HMAC256("secret"); String token = ScopedJwtCreator.build() @@ -386,14 +383,14 @@ public void testScopedJwtCreatorExpTimeHasPassed() throws Exception { .withSubject("subject") .withAudience("audience") .withNonStandardClaim("nonStandardClaim", new Date()) - .withExp(expDate) + .withExp(calendar.getTime()) .withIat(iat) .sign(algorithm); Verification verification = ScopedJWT.require(algorithm); JWT verifier = verification.createVerifierForScoped("scope", asList("issuer"), asList("audience"), 1, 1).build(); DecodedJWT jwt = verifier.decode(token); Map claims = jwt.getClaims(); - verifyClaims(claims, expDate); + verifyClaims(claims, calendar.getTime()); } private static void verifyClaims(Map claims, Date exp) {