diff --git a/src/main/java/com/toopher/ApiRequestDetails.java b/src/main/java/com/toopher/ApiRequestDetails.java new file mode 100644 index 0000000..21ac071 --- /dev/null +++ b/src/main/java/com/toopher/ApiRequestDetails.java @@ -0,0 +1,19 @@ +package com.toopher; + +import org.apache.http.NameValuePair; + +import java.util.List; +import java.util.Map; + +public abstract class ApiRequestDetails { + private List params; + private Map extras; + + public ApiRequestDetails(List params, Map extras) { + this.params = params; + this.extras = extras; + } + + public List getParams() { return params; } + public Map getExtras() { return extras; } +} diff --git a/src/main/java/com/toopher/ApiResponseObject.java b/src/main/java/com/toopher/ApiResponseObject.java index 49c1b17..14aa92b 100644 --- a/src/main/java/com/toopher/ApiResponseObject.java +++ b/src/main/java/com/toopher/ApiResponseObject.java @@ -1,25 +1,29 @@ package com.toopher; +import org.json.JSONException; +import org.json.JSONObject; + import java.util.HashMap; import java.util.Iterator; import java.util.Map; -import org.json.JSONException; -import org.json.JSONObject; - public class ApiResponseObject { /** * A map of the raw API response data */ public Map raw; - + public ApiResponseObject(JSONObject json) throws JSONException { this.raw = jsonToMap(json); } private Map jsonToMap(JSONObject json) throws JSONException{ Map result = new HashMap(); - + + if (json == null) { + return result; + } + for (Iterator i = json.keys(); i.hasNext(); ) { String key = i.next(); Object o = json.get(key); diff --git a/src/main/java/com/toopher/AuthenticationRequestDetails.java b/src/main/java/com/toopher/AuthenticationRequestDetails.java new file mode 100644 index 0000000..ad6f8b9 --- /dev/null +++ b/src/main/java/com/toopher/AuthenticationRequestDetails.java @@ -0,0 +1,66 @@ +package com.toopher; + +import org.apache.http.NameValuePair; +import org.apache.http.message.BasicNameValuePair; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class AuthenticationRequestDetails extends ApiRequestDetails { + public AuthenticationRequestDetails(List params, Map extras) { + super(params, extras); + } + + public static class Builder { + private List params = new ArrayList(); + private Map extras = new HashMap(); + + public Builder() { + this(null); + } + + public Builder(Map extras) { + this.extras = extras == null ? new HashMap() : extras; + } + + public Builder addExtra(String key, String value) { + extras.put(key, value); + return this; + } + + public Builder setActionName(String actionName) { + if (actionName != null && actionName.length() > 0) { + params.add(new BasicNameValuePair("action_name", actionName)); + } + return this; + } + + public Builder setPairingId(String pairingId) { + if (pairingId != null) { + params.add(new BasicNameValuePair("pairing_id", pairingId)); + } + return this; + } + + public Builder setTerminalName(String terminalName) { + if (terminalName != null) { + params.add(new BasicNameValuePair("terminal_name", terminalName)); + } + return this; + } + + public Builder setTerminalNameExtra(String terminalNameExtra) { + return addExtra("terminal_name_extra", terminalNameExtra); + } + + public Builder setUserName(String userName) { + return addExtra("user_name", userName); + } + + public AuthenticationRequestDetails build() { + return new AuthenticationRequestDetails(params, extras); + } + } +} diff --git a/src/main/java/com/toopher/AuthenticationStatus.java b/src/main/java/com/toopher/AuthenticationStatus.java index 311c2d0..a8341ad 100644 --- a/src/main/java/com/toopher/AuthenticationStatus.java +++ b/src/main/java/com/toopher/AuthenticationStatus.java @@ -2,7 +2,6 @@ import org.json.JSONException; import org.json.JSONObject; -import java.util.Map; /** * Provide information about the status of an authentication request @@ -44,9 +43,8 @@ public class AuthenticationStatus extends ApiResponseObject { * The descriptive name for the terminal associated with the request */ public String terminalName; - - - public AuthenticationStatus(JSONObject json) throws JSONException{ + + public AuthenticationStatus(JSONObject json) throws JSONException { super(json); this.id = json.getString("id"); @@ -58,13 +56,30 @@ public AuthenticationStatus(JSONObject json) throws JSONException{ JSONObject terminal = json.getJSONObject("terminal"); this.terminalId = terminal.getString("id"); this.terminalName = terminal.getString("name"); - } + public AuthenticationStatus( + String id, + boolean pending, + boolean granted, + boolean automated, + String reason, + String terminalId, + String terminalName, + JSONObject json) { + super(json); + this.id = id; + this.pending = pending; + this.granted = granted; + this.automated = automated; + this.reason = reason; + this.terminalId = terminalId; + this.terminalName = terminalName; + } + @Override public String toString() { return String.format("[AuthenticationStatus: id=%s; pending=%b; granted=%b; automated=%b; reason=%s; terminalId=%s; terminalName=%s]", id, pending, granted, automated, reason, terminalId, terminalName); } - } diff --git a/src/main/java/com/toopher/AuthenticationStatusFactory.java b/src/main/java/com/toopher/AuthenticationStatusFactory.java new file mode 100644 index 0000000..381d50c --- /dev/null +++ b/src/main/java/com/toopher/AuthenticationStatusFactory.java @@ -0,0 +1,21 @@ +package com.toopher; + +import org.json.JSONException; +import org.json.JSONObject; + +public class AuthenticationStatusFactory { + public AuthenticationStatusFactory() {} + + public AuthenticationStatus create(JSONObject json) throws JSONException { + final JSONObject terminal = json.getJSONObject("terminal"); + return new AuthenticationStatus( + json.getString("id"), + json.getBoolean("pending"), + json.getBoolean("granted"), + json.getBoolean("automated"), + json.getString("reason"), + terminal.getString("id"), + terminal.getString("name"), + json); + } +} diff --git a/src/main/java/com/toopher/RequestError.java b/src/main/java/com/toopher/RequestError.java index 42cbc0b..8f89f89 100644 --- a/src/main/java/com/toopher/RequestError.java +++ b/src/main/java/com/toopher/RequestError.java @@ -1,9 +1,10 @@ package com.toopher; -import java.io.IOException; import org.apache.http.client.ClientProtocolException; import org.json.JSONException; +import java.io.IOException; + /** * Request errors from API calls * diff --git a/src/main/java/com/toopher/ToopherAPI.java b/src/main/java/com/toopher/ToopherAPI.java index ae20740..b487434 100644 --- a/src/main/java/com/toopher/ToopherAPI.java +++ b/src/main/java/com/toopher/ToopherAPI.java @@ -1,16 +1,7 @@ package com.toopher; -import java.io.IOException; -import java.net.URI; -import java.net.URISyntaxException; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; -import java.util.HashMap; - import oauth.signpost.OAuthConsumer; import oauth.signpost.commonshttp.CommonsHttpOAuthConsumer; - import org.apache.http.HttpEntity; import org.apache.http.HttpResponse; import org.apache.http.NameValuePair; @@ -28,27 +19,32 @@ import org.apache.http.message.BasicNameValuePair; import org.apache.http.params.HttpProtocolParams; import org.apache.http.util.EntityUtils; +import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; -import org.json.JSONArray; import org.json.JSONTokener; +import java.io.IOException; +import java.net.URI; +import java.net.URISyntaxException; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + /** * A Java binding for the Toopher API * */ public class ToopherAPI { private static final String DEFAULT_URI_SCHEME = "https"; - private static final String DEFAULT_URI_HOST = "api.toopher.com"; - private static final String DEFAULT_URI_BASE = "/v1/"; - private static final int DEFAULT_URI_PORT = 443; + private static final String DEFAULT_URI_HOST = "api.toopher.com"; + private static final int DEFAULT_URI_PORT = 443; + private static final String DEFAULT_URI_BASE = "/v1/"; - private final HttpClient httpClient; + private final URI baseUri; private final OAuthConsumer consumer; - private final String uriScheme; - private final String uriHost; - private final int uriPort; - private final String uriBase; + private final HttpClient httpClient; + private final AuthenticationStatusFactory authenticationStatusFactory; /** * The ToopherJava binding library version @@ -63,11 +59,10 @@ public class ToopherAPI { * @param consumerSecret * The consumer secret for a requester (obtained from the developer portal) */ - public ToopherAPI(String consumerKey, String consumerSecret) { - this(consumerKey, consumerSecret, (URI)null); + public ToopherAPI(String consumerKey, String consumerSecret) throws URISyntaxException { + this(new Builder(consumerKey, consumerSecret)); } - /** * Create an API object with the supplied credentials, overriding the default API URI of https://api.toopher.com/v1/ * @@ -80,7 +75,7 @@ public ToopherAPI(String consumerKey, String consumerSecret) { * @throws URISyntaxException */ public ToopherAPI(String consumerKey, String consumerSecret, String uri) throws URISyntaxException { - this(consumerKey, consumerSecret, new URI(uri)); + this(new Builder(consumerKey, consumerSecret).setBaseUri(uri)); } /** @@ -99,7 +94,8 @@ public ToopherAPI(String consumerKey, String consumerSecret, String uri) throws * @throws URISyntaxException */ public ToopherAPI(String consumerKey, String consumerSecret, String uriScheme, String uriHost, String uriBase) throws URISyntaxException { - this(consumerKey, consumerSecret, new URIBuilder().setScheme(uriScheme).setHost(uriHost).setPath(uriBase).build()); + this(new Builder(consumerKey, consumerSecret) + .setBaseUri(new URIBuilder().setScheme(uriScheme).setHost(uriHost).setPath(uriBase).build())); } /** @@ -113,10 +109,9 @@ public ToopherAPI(String consumerKey, String consumerSecret, String uriScheme, S * The alternate URI */ public ToopherAPI(String consumerKey, String consumerSecret, URI uri) { - this(consumerKey, consumerSecret, uri, null); + this(new Builder(consumerKey, consumerSecret).setBaseUri(uri)); } - /** * Create an API object with the supplied credentials and API URI, * overriding the default HTTP client. @@ -131,28 +126,24 @@ public ToopherAPI(String consumerKey, String consumerSecret, URI uri) { * The alternate HTTP client */ public ToopherAPI(String consumerKey, String consumerSecret, URI uri, HttpClient httpClient) { - if (httpClient == null) { - this.httpClient = new DefaultHttpClient(); - HttpProtocolParams.setUserAgent(this.httpClient.getParams(), - String.format("Toopher-Java/%s", VERSION)); - } else { - this.httpClient = httpClient; - } + this(new ToopherAPI.Builder(consumerKey, consumerSecret) + .setBaseUri(uri).setHttpClient(httpClient)); + } - consumer = new CommonsHttpOAuthConsumer(consumerKey, consumerSecret); - if (uri == null){ - this.uriScheme = ToopherAPI.DEFAULT_URI_SCHEME; - this.uriHost = ToopherAPI.DEFAULT_URI_HOST; - this.uriPort = ToopherAPI.DEFAULT_URI_PORT; - this.uriBase = ToopherAPI.DEFAULT_URI_BASE; - } else { - this.uriScheme = uri.getScheme(); - this.uriHost = uri.getHost(); - this.uriPort = uri.getPort(); - this.uriBase = uri.getPath(); - } + public ToopherAPI(Builder builder) { + this(builder.getConsumer(), builder.getBaseUri(), builder.getHttpClient(), + builder.getAuthenticationStatusFactory()); } + private ToopherAPI(OAuthConsumer consumer, + URI baseUri, + HttpClient httpClient, + AuthenticationStatusFactory authenticationStatusFactory) { + this.consumer = consumer; + this.baseUri = baseUri; + this.httpClient = httpClient; + this.authenticationStatusFactory = authenticationStatusFactory; + } /** * Create a pairing @@ -267,7 +258,9 @@ public PairingStatus pairWithQrCode(String userName, Map extras) * Thrown when an exceptional condition is encountered */ public AuthenticationStatus authenticate(String pairingId, String terminalName) throws RequestError { - return authenticate(pairingId, terminalName, null, null); + AuthenticationRequestDetails.Builder builder = new AuthenticationRequestDetails.Builder(); + builder.setPairingId(pairingId).setTerminalName(terminalName); + return authenticate(builder.build()); } /** @@ -284,7 +277,9 @@ public AuthenticationStatus authenticate(String pairingId, String terminalName) * Thrown when an exceptional condition is encountered */ public AuthenticationStatus authenticate(String pairingId, String terminalName, String actionName) throws RequestError { - return authenticate(pairingId, terminalName, actionName, null); + AuthenticationRequestDetails.Builder builder = new AuthenticationRequestDetails.Builder(); + builder.setPairingId(pairingId).setTerminalName(terminalName).setActionName(actionName); + return authenticate(builder.build()); } /** @@ -304,23 +299,25 @@ public AuthenticationStatus authenticate(String pairingId, String terminalName, */ public AuthenticationStatus authenticate(String pairingId, String terminalName, String actionName, Map extras) throws RequestError { - final String endpoint = "authentication_requests/initiate"; + AuthenticationRequestDetails.Builder builder = new AuthenticationRequestDetails.Builder(extras); + builder.setPairingId(pairingId).setTerminalName(terminalName).setActionName(actionName); + return authenticate(builder.build()); + } - List params = new ArrayList(); - if (pairingId != null) { - params.add(new BasicNameValuePair("pairing_id", pairingId)); - } - if (terminalName != null) { - params.add(new BasicNameValuePair("terminal_name", terminalName)); - } - if (actionName != null && actionName.length() > 0) { - params.add(new BasicNameValuePair("action_name", actionName)); - } + /** + * Initiate an authentication request. + * + * @param details An AuthenticationRequestDetails object that contains the request parameters. + * @return An AuthenticationStatus object. + * @throws RequestError Thrown when an exceptional condition is encountered. + */ + public AuthenticationStatus authenticate(AuthenticationRequestDetails details) throws RequestError { + final String endpoint = "authentication_requests/initiate"; - JSONObject json = post(endpoint, params, extras); + JSONObject json = post(endpoint, details); try { - return new AuthenticationStatus(json); - } catch (Exception e) { + return authenticationStatusFactory.create(json); + } catch (JSONException e) { throw new RequestError(e); } } @@ -338,14 +335,11 @@ public AuthenticationStatus authenticate(String pairingId, String terminalName, * @throws RequestError * Thrown when an exceptional condition is encountered */ - public AuthenticationStatus authenticateByUserName(String userName, String terminalNameExtra, String actionName, Map extras) throws RequestError { - if (extras == null) { - extras = new HashMap(); - } - extras.put("user_name", userName); - extras.put("terminal_name_extra", terminalNameExtra); - - return authenticate(null, null, actionName, extras); + public AuthenticationStatus authenticateByUserName(String userName, String terminalNameExtra, String actionName, + Map extras) throws RequestError { + AuthenticationRequestDetails.Builder builder = new AuthenticationRequestDetails.Builder(extras); + builder.setUserName(userName).setTerminalNameExtra(terminalNameExtra).setActionName(actionName); + return authenticate(builder.build()); } /** @@ -357,8 +351,7 @@ public AuthenticationStatus authenticateByUserName(String userName, String termi * @throws RequestError * Thrown when an exceptional condition is encountered */ - public AuthenticationStatus getAuthenticationStatus(String authenticationRequestId) - throws RequestError { + public AuthenticationStatus getAuthenticationStatus(String authenticationRequestId) throws RequestError { final String endpoint = String.format("authentication_requests/%s", authenticationRequestId); JSONObject json = get(endpoint); @@ -412,7 +405,7 @@ public void assignUserFriendlyNameToTerminal(String userName, String terminalNam * * @param userName * The name of the user - * @param enabled + * @param toopherEnabled * Whether or not the user is Toopher-enabled * @throws RequestError * Thrown when an exceptional condition is encountered, or the @@ -484,11 +477,13 @@ private T post(String endpoint, List params, Map T request(HttpRequestBase httpRequest, String endpoint, List queryStringParameters) throws RequestError { try { - URIBuilder uriBuilder = new URIBuilder().setScheme(this.uriScheme).setHost(this.uriHost) - .setPort(this.uriPort) - .setPath(this.uriBase + endpoint); + URIBuilder uriBuilder = new URIBuilder(this.baseUri).setPath(this.baseUri.getPath() + endpoint); if (queryStringParameters != null && queryStringParameters.size() > 0) { for (NameValuePair nvp : queryStringParameters) { uriBuilder.setParameter(nvp.getName(), nvp.getValue()); @@ -579,4 +574,97 @@ private static void parseRequestError(StatusLine statusLine, HttpResponse respon statusLine.getReasonPhrase())); } } + + public static class Builder { + public String consumerKey; + public String consumerSecret; + public URI baseUri; + public HttpClient httpClient; + public AuthenticationStatusFactory authenticationStatusFactory; + + public Builder(String consumerKey, String consumerSecret) { + this.consumerKey = consumerKey; + this.consumerSecret = consumerSecret; + } + + public Builder setAuthenticationStatusFactory(AuthenticationStatusFactory authenticationStatusFactory) { + this.authenticationStatusFactory = authenticationStatusFactory; + return this; + } + + public Builder setBaseUri(String baseUri) throws URISyntaxException { + this.baseUri = new URI(baseUri); + return this; + } + + public Builder setBaseUri(URI baseUri) { + this.baseUri = baseUri; + return this; + } + + public Builder setHttpClient(HttpClient httpClient) { + this.httpClient = httpClient; + return this; + } + + public ToopherAPI build() { + return new ToopherAPI(this); + } + + /** + * Returns the AuthenticationStatus factory, or the default AuthenticationStatus factory. + * + * @return An AuthenticationStatusFactory object. + */ + public AuthenticationStatusFactory getAuthenticationStatusFactory() { + if (authenticationStatusFactory == null) { + authenticationStatusFactory = new AuthenticationStatusFactory(); + } + + return authenticationStatusFactory; + } + + /** + * Returns the base URI, or the default base URI. + * + * @return A URI object. + */ + public URI getBaseUri() { + if (baseUri == null) { + try { + baseUri = new URIBuilder() + .setScheme(DEFAULT_URI_SCHEME) + .setHost(DEFAULT_URI_HOST) + .setPort(DEFAULT_URI_PORT) + .setPath(DEFAULT_URI_BASE) + .build(); + } catch (URISyntaxException urise) { + // There's not really any way to recover when the DEFAULT_URI_* components are invalid and + // the client tries to use the default URI, so preemptively crash and try not to do that again. + throw new RuntimeException("DEFAULT_URI_* components are invalid."); + } + } + + return baseUri; + } + + public OAuthConsumer getConsumer() { + return new CommonsHttpOAuthConsumer(consumerKey, consumerSecret); + } + + /** + * Returns the HTTP client, or the default HTTP client. + * + * @return An HttpClient object. + */ + public HttpClient getHttpClient() { + if (httpClient == null) { + httpClient = new DefaultHttpClient(); + HttpProtocolParams.setUserAgent(this.httpClient.getParams(), + String.format("Toopher-Java/%s", VERSION)); + } + + return httpClient; + } + } } diff --git a/src/main/java/com/toopher/ToopherIframe.java b/src/main/java/com/toopher/ToopherIframe.java index 4dd4354..5083aa4 100644 --- a/src/main/java/com/toopher/ToopherIframe.java +++ b/src/main/java/com/toopher/ToopherIframe.java @@ -1,28 +1,22 @@ package com.toopher; -import java.util.Date; -import java.util.List; -import java.util.ArrayList; -import java.util.Map; -import java.util.HashMap; -import java.util.TreeSet; -import java.net.URLEncoder; - -import oauth.signpost.http.HttpParameters; -import org.apache.http.NameValuePair; -import org.apache.http.message.BasicNameValuePair; -import org.apache.http.client.utils.URLEncodedUtils; import oauth.signpost.OAuthConsumer; import oauth.signpost.basic.DefaultOAuthConsumer; import oauth.signpost.exception.OAuthException; -import java.io.UnsupportedEncodingException; +import oauth.signpost.http.HttpParameters; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.apache.http.NameValuePair; +import org.apache.http.client.utils.URLEncodedUtils; +import org.apache.http.message.BasicNameValuePair; + import javax.crypto.Mac; import javax.crypto.spec.SecretKeySpec; -import java.security.NoSuchAlgorithmException; +import java.io.UnsupportedEncodingException; +import java.net.URLEncoder; import java.security.InvalidKeyException; - -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; +import java.security.NoSuchAlgorithmException; +import java.util.*; /** * Java helper library to generate Toopher iframe requests and validate responses. diff --git a/src/test/java/com/toopher/AuthenticationStatusFactoryMock.java b/src/test/java/com/toopher/AuthenticationStatusFactoryMock.java new file mode 100644 index 0000000..bf3351c --- /dev/null +++ b/src/test/java/com/toopher/AuthenticationStatusFactoryMock.java @@ -0,0 +1,11 @@ +package com.toopher; + +import org.json.JSONException; +import org.json.JSONObject; + +public class AuthenticationStatusFactoryMock extends AuthenticationStatusFactory { + @Override + public AuthenticationStatus create(JSONObject object) throws JSONException { + return new AuthenticationStatus(null, false, false, false, null, null, null, null); + } +} diff --git a/src/test/java/com/toopher/HttpClientMock.java b/src/test/java/com/toopher/HttpClientMock.java index b13927b..b12918a 100644 --- a/src/test/java/com/toopher/HttpClientMock.java +++ b/src/test/java/com/toopher/HttpClientMock.java @@ -15,9 +15,18 @@ import org.apache.http.params.HttpParams; import org.junit.Ignore; +import javax.xml.ws.Response; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.UnsupportedEncodingException; +import java.net.MalformedURLException; +import java.net.URI; +import java.net.URISyntaxException; +import java.net.URL; +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.Map; import java.util.concurrent.Semaphore; @Ignore @@ -25,9 +34,12 @@ public class HttpClientMock extends DefaultHttpClient { public HttpParams lastParams; public Semaphore done; + private java.net.URI lastURI; private HttpUriRequest lastRequest; private int expectedResponseStatus; private String expectedResponseBody; + private Map expectedUriResponses; + public HttpClientMock(int responseStatus, String responseBody) throws InterruptedException { this.expectedResponseStatus = responseStatus; @@ -36,6 +48,15 @@ public HttpClientMock(int responseStatus, String responseBody) throws Interrupte done.acquire(); } + public HttpClientMock(Map responses) throws InterruptedException { + expectedUriResponses = new HashMap(); + for (URI url : responses.keySet()) { + expectedUriResponses.put(url, responses.get(url)); + } + done = new Semaphore(1); + done.acquire(); + } + public String getLastCalledMethod() { if (lastRequest != null) { return lastRequest.getMethod(); @@ -50,13 +71,31 @@ public String getLastCalledData(String key) { return null; } + public URI getLastCalledEndpoint() { + return lastURI; + } + + public String getExpectedResponse() { + if (expectedUriResponses != null) { + return expectedUriResponses.get(lastURI).getResponseBody(); + } + return null; + } + + public int getExpectedResponseStatus() { + if (expectedUriResponses != null) { + return expectedUriResponses.get(lastURI).getStatusCode(); + } + return -1; + } + @Override - public T execute(HttpUriRequest req, ResponseHandler responseHandler) { + public T execute(HttpUriRequest req, ResponseHandler responseHandler) throws IOException { lastRequest = req; - if (req instanceof HttpPost){ + if (req instanceof HttpPost) { try { lastParams = new BasicHttpParams(); - for(NameValuePair nvp : URLEncodedUtils.parse(((HttpPost) req).getEntity())){ + for (NameValuePair nvp : URLEncodedUtils.parse(((HttpPost) req).getEntity())) { lastParams.setParameter(nvp.getName(), nvp.getValue()); } } catch (IOException e) { @@ -65,23 +104,33 @@ public T execute(HttpUriRequest req, ResponseHandler responseHa } else { lastParams = req.getParams(); } - HttpResponse resp = new BasicHttpResponse(new BasicStatusLine(HttpVersion.HTTP_1_1, expectedResponseStatus, null)); BasicHttpEntity entity = new BasicHttpEntity(); + if (expectedResponseBody == null && expectedUriResponses != null) { + expectedResponseBody = expectedUriResponses.get(req.getURI()).getResponseBody(); + expectedResponseStatus = expectedUriResponses.get(req.getURI()).getStatusCode(); + } try { entity.setContent(new ByteArrayInputStream(expectedResponseBody.getBytes("UTF-8"))); } catch (UnsupportedEncodingException e) { e.printStackTrace(); } + HttpResponse resp = new BasicHttpResponse(new BasicStatusLine(HttpVersion.HTTP_1_1, expectedResponseStatus, null)); resp.setEntity(entity); - + lastURI = req.getURI(); T result; - try { - result = responseHandler.handleResponse(resp); - } catch (IOException e) { - result = null; - e.printStackTrace(); - } + result = responseHandler.handleResponse(resp); + done.release(); return result; } + + public URI createURI(String url) { + try { + return new URL(url).toURI(); + } catch (MalformedURLException e) { + return null; + } catch (URISyntaxException e) { + return null; + } + } } diff --git a/src/test/java/com/toopher/ResponseMock.java b/src/test/java/com/toopher/ResponseMock.java new file mode 100644 index 0000000..a39ebcd --- /dev/null +++ b/src/test/java/com/toopher/ResponseMock.java @@ -0,0 +1,23 @@ +package com.toopher; + +/** + * Created by lindsey on 6/10/14. + */ +public class ResponseMock { + + private int statusCode; + private String jsonAuthCode; + + public ResponseMock(int statusCode, String authCode){ + this.statusCode = statusCode; + jsonAuthCode = authCode; + } + + public int getStatusCode(){ + return statusCode; + } + + public String getResponseBody(){ + return jsonAuthCode; + } +} diff --git a/src/test/java/com/toopher/TestToopherAPI.java b/src/test/java/com/toopher/TestToopherAPI.java index f239276..7113cd4 100644 --- a/src/test/java/com/toopher/TestToopherAPI.java +++ b/src/test/java/com/toopher/TestToopherAPI.java @@ -1,27 +1,41 @@ package com.toopher; +import oauth.signpost.http.HttpResponse; +import org.json.JSONObject; import org.junit.Test; import java.net.MalformedURLException; import java.net.URI; import java.net.URISyntaxException; import java.net.URL; +import java.util.HashMap; +import java.util.Map; import static org.junit.Assert.*; public class TestToopherAPI { + + private static String AUTH_REQUEST_JSON = "{'id':'1', 'granted':true, 'pending':true,'automated':true, 'reason':'test', 'terminal':{'id':'1','name':'some user'}}".replace("'", "\""); + private final String BASE_URI_STRING = "https://api.toopher.test/v1/"; + private final String CONSUMER_KEY = "key"; + private final String CONSUMER_SECRET = "secret"; + private final String PAIRING_ID = "pairing_id"; + private final String TERMINAL_NAME = "terminal_name"; + private final String ACTION_NAME = "action_name"; + private final String USER_NAME = "user_name"; + @Test public void testCreatePairing() throws InterruptedException, RequestError { HttpClientMock httpClient = new HttpClientMock(200, "{'id':'1','enabled':true,'pending':true,'user':{'id':'1','name':'some user'}}".replace("'", "\"")); ToopherAPI toopherApi = new ToopherAPI("key", "secret", - createURI("https://api.toopher.test/v1"), httpClient); + createURI("https://api.toopher.test/v1/"), httpClient); PairingStatus pairing = toopherApi.pair("awkward turtle", "some user"); assertEquals(httpClient.getLastCalledMethod(), "POST"); assertEquals(httpClient.getLastCalledData("pairing_phrase"), "awkward turtle"); - + assertEquals(pairing.userId, "1"); assertEquals(pairing.userName, "some user"); assertTrue(pairing.pending); @@ -34,7 +48,7 @@ public void testGetPairingStatus() throws InterruptedException, RequestError { "{'id':'1','enabled':true,'pending':true,'user':{'id':'1','name':'some user'}}".replace("'", "\"")); ToopherAPI toopherApi = new ToopherAPI("key", "secret", - createURI("https://api.toopher.test/v1"), httpClient); + createURI("https://api.toopher.test/v1/"), httpClient); PairingStatus pairing = toopherApi.getPairingStatus("1"); assertEquals(httpClient.getLastCalledMethod(), "GET"); @@ -45,17 +59,213 @@ public void testGetPairingStatus() throws InterruptedException, RequestError { assertTrue(pairing.enabled); } + @Test + public void testAuthenticateWithActionName() throws InterruptedException, RequestError { + HttpClientMock httpClient = new HttpClientMock(200, AUTH_REQUEST_JSON); + ToopherAPI toopherApi = new ToopherAPI("key", "secret", + createURI("https://api.toopher.test/v1/"), httpClient); + toopherApi.authenticate(PAIRING_ID, TERMINAL_NAME, ACTION_NAME); + assertEquals(httpClient.getLastCalledMethod(), "POST"); + assertEquals(httpClient.getLastCalledData("pairing_id"), PAIRING_ID); + assertEquals(httpClient.getLastCalledData("terminal_name"), TERMINAL_NAME); + assertEquals(httpClient.getLastCalledData("action_name"), ACTION_NAME); + } + + @Test + public void testAuthenticateThrowingRequestError() throws InterruptedException, RequestError { + HttpClientMock httpClient = new HttpClientMock(200, "{'id':'1'}"); + ToopherAPI toopherApi = new ToopherAPI("key", "secret", + createURI("https://api.toopher.test/v1/"), httpClient); + try { + toopherApi.authenticate(PAIRING_ID, TERMINAL_NAME); + fail(); + } catch (RequestError re) { + } + } + + @Test + public void testAuthenticateWithTerminalName() throws InterruptedException, RequestError { + String pairingId = "pairing ID"; + String terminalName = "my computer"; + HttpClientMock httpClient = new HttpClientMock(200, AUTH_REQUEST_JSON); + ToopherAPI toopherApi = new ToopherAPI("key", "secret", + createURI("https://api.toopher.test/v1/"), httpClient); + toopherApi.authenticate(pairingId, terminalName); + assertEquals(httpClient.getLastCalledMethod(), "POST"); + assertEquals(httpClient.getLastCalledData("pairing_id"), pairingId); + assertEquals(httpClient.getLastCalledData("terminal_name"), terminalName); + } + + @Test + public void testAuthenticateWithExtras() throws InterruptedException, RequestError { + String pairingId = "pairing ID"; + String terminalName = "my computer"; + String actionName = "action"; + String extraParameter = "extraParam"; + Map extras = new HashMap(); + extras.put("extra_parameter", extraParameter); + HttpClientMock httpClient = new HttpClientMock(200, AUTH_REQUEST_JSON); + ToopherAPI toopherApi = new ToopherAPI("key", "secret", + createURI("https://api.toopher.test/v1/"), httpClient); + toopherApi.authenticate(pairingId, terminalName, actionName, extras); + assertEquals(httpClient.getLastCalledMethod(), "POST"); + assertEquals(httpClient.getLastCalledData("pairing_id"), pairingId); + assertEquals(httpClient.getLastCalledData("terminal_name"), terminalName); + assertEquals(httpClient.getLastCalledData("extra_parameter"), extraParameter); + } + + @Test + public void testAuthenticateByUserNameNoExtras() throws InterruptedException, RequestError { + String extraTerminalName = "terminalNameExtra"; + String userName = "userName"; + String actionName = "action"; + HttpClientMock httpClient = new HttpClientMock(200, AUTH_REQUEST_JSON); + ToopherAPI toopherApi = new ToopherAPI("key", "secret", + createURI("https://api.toopher.test/v1/"), httpClient); + toopherApi.authenticateByUserName(userName, extraTerminalName, actionName, null); + assertEquals(httpClient.getLastCalledData("user_name"), userName); + assertEquals(httpClient.getLastCalledData("terminal_name_extra"), extraTerminalName); + assertEquals(httpClient.getLastCalledData("action_name"), actionName); + } + + @Test + public void testAuthenticateByUserNameWithExtras() throws InterruptedException, RequestError { + String extraTerminalName = "terminalNameExtra"; + String userName = "userName"; + String actionName = "action"; + Map extras = new HashMap(); + HttpClientMock httpClient = new HttpClientMock(200, AUTH_REQUEST_JSON); + ToopherAPI toopherApi = new ToopherAPI("key", "secret", + createURI("https://api.toopher.test/v1/"), httpClient); + toopherApi.authenticateByUserName(userName, extraTerminalName, actionName, extras); + assertEquals(httpClient.getLastCalledData("user_name"), userName); + assertEquals(httpClient.getLastCalledData("terminal_name_extra"), extraTerminalName); + assertEquals(httpClient.getLastCalledData("action_name"), actionName); + } + + @Test + public void testGetAuthenticationStatus() throws InterruptedException, RequestError { + HttpClientMock httpClient = new HttpClientMock(200, AUTH_REQUEST_JSON); + ToopherAPI toopherApi = new ToopherAPI("key", "secret", + createURI("https://api.toopher.test/v1/"), httpClient); + AuthenticationStatus authReq = toopherApi.getAuthenticationStatus("1"); + assertEquals(createURI("https://api.toopher.test/v1/authentication_requests/1"), httpClient.getLastCalledEndpoint()); + assertEquals(authReq.id, "1"); + assertTrue(authReq.pending); + assertTrue(authReq.granted); + assertTrue(authReq.automated); + assertEquals(authReq.terminalName, "some user"); + assertEquals(authReq.terminalId, "1"); + assertEquals(authReq.reason, "test"); + } + + @Test + public void testGetAuthenticationStatusThrowRequestError() throws InterruptedException, RequestError { + HttpClientMock httpClient = new HttpClientMock(200, "{'id':'1'}"); + ToopherAPI toopherApi = new ToopherAPI("key", "secret", + createURI("https://api.toopher.test/v1/"), httpClient); + try { + toopherApi.getAuthenticationStatus("1"); + fail("My method didn't throw when I expected it to"); + } catch (RequestError re) { + } + } + + @Test + public void testGetAuthenticationStatusWithOTP() throws InterruptedException, RequestError { + HttpClientMock httpClient = new HttpClientMock(200, AUTH_REQUEST_JSON); + ToopherAPI toopherApi = new ToopherAPI("key", "secret", + createURI("https://api.toopher.test/v1/"), httpClient); + AuthenticationStatus authReq = toopherApi.getAuthenticationStatusWithOTP("1", "ImAPassword"); + assertEquals(createURI("https://api.toopher.test/v1/authentication_requests/1/otp_auth"), httpClient.getLastCalledEndpoint()); + assertEquals(authReq.id, "1"); + assertTrue(authReq.pending); + assertTrue(authReq.granted); + assertTrue(authReq.automated); + assertEquals(authReq.terminalName, "some user"); + assertEquals(authReq.terminalId, "1"); + assertEquals(authReq.reason, "test"); + } + + @Test + public void testGetAuthenticationStatusWithOTPThrowRequestError() throws InterruptedException, RequestError { + HttpClientMock httpClient = new HttpClientMock(200, "{'id':'1'}"); + ToopherAPI toopherApi = new ToopherAPI("key", "secret", + createURI("https://api.toopher.test/v1/"), httpClient); + try { + toopherApi.getAuthenticationStatusWithOTP("1", "ImAPassword"); + } catch (RequestError re) { + } + } + + @Test + public void testAssignUserFriendlyNameToTerminal() throws InterruptedException, RequestError { + HttpClientMock httpClient = new HttpClientMock(200, AUTH_REQUEST_JSON); + ToopherAPI toopherApi = new ToopherAPI("key", "secret", + createURI("https://api.toopher.test/v1/"), httpClient); + final String extraTerminalName = "my extra computer"; + toopherApi.assignUserFriendlyNameToTerminal(USER_NAME, TERMINAL_NAME, extraTerminalName); + assertEquals(httpClient.getLastCalledData("user_name"), USER_NAME); + assertEquals(httpClient.getLastCalledData("name"), TERMINAL_NAME); + assertEquals(httpClient.getLastCalledData("name_extra"), extraTerminalName); + } + + @Test + public void testEnableToopherForUser() throws InterruptedException, RequestError { + Map expectedUriResponses = new HashMap(); + expectedUriResponses.put(createURI("https://api.toopher.test/v1/users?name=1"), new ResponseMock(200, "[{'id': '1'}]")); + expectedUriResponses.put(createURI("https://api.toopher.test/v1/users/1"), new ResponseMock(200, "{}")); + + HttpClientMock httpClient = new HttpClientMock(expectedUriResponses); + ToopherAPI toopherApi = new ToopherAPI("key", "secret", + createURI("https://api.toopher.test/v1/"), httpClient); + toopherApi.setToopherEnabledForUser("1", false); + String actualResponse = httpClient.getExpectedResponse(); + String expectedResponse = expectedUriResponses.get(httpClient.getLastCalledEndpoint()).getResponseBody(); + assertEquals(actualResponse, expectedResponse); + assertEquals(httpClient.getLastCalledMethod(), "POST"); + assertEquals(httpClient.getLastCalledData("disable_toopher_auth"), "true"); + } + + @Test + public void testEnableToopherForUserWhenUserNameIsNotPresent() throws InterruptedException, RequestError { + Map expectedUriResponses = new HashMap(); + expectedUriResponses.put(createURI("https://api.toopher.test/v1/users?name=1"), new ResponseMock(200, "[]")); + + HttpClientMock httpClient = new HttpClientMock(expectedUriResponses); + ToopherAPI toopherApi = new ToopherAPI("key", "secret", + createURI("https://api.toopher.test/v1/"), httpClient); + try { + toopherApi.setToopherEnabledForUser("1", true); + fail(); + } catch (RequestError re) { + } + } + + @Test + public void testEnableToopherForUserWithDuplicateUserName() throws InterruptedException, RequestError { + Map expectedUriResponses = new HashMap(); + expectedUriResponses.put(createURI("https://api.toopher.test/v1/users?name=1"), new ResponseMock(200, "[{'id': '1'}, {'id': '1'}]")); + + HttpClientMock httpClient = new HttpClientMock(expectedUriResponses); + ToopherAPI toopherApi = new ToopherAPI("key", "secret", + createURI("https://api.toopher.test/v1/"), httpClient); + try { + toopherApi.setToopherEnabledForUser("1", true); + fail(); + } catch (RequestError re) { + } + } + @Test public void testCreateQrPairing() throws InterruptedException, RequestError { HttpClientMock httpClient = new HttpClientMock(200, "{'id':'1','enabled':true,'pending':true,'user':{'id':'1','name':'some user'}}".replace("'", "\"")); ToopherAPI toopherApi = new ToopherAPI("key", "secret", - createURI("https://api.toopher.test/v1"), httpClient); + createURI("https://api.toopher.test/v1/"), httpClient); PairingStatus pairing = toopherApi.pairWithQrCode("some user"); - assertEquals(httpClient.getLastCalledMethod(), "POST"); - assertEquals(pairing.userId, "1"); assertEquals(pairing.userName, "some user"); assertTrue(pairing.pending); @@ -86,4 +296,133 @@ public void testBaseURL() { public void testVersion() { assertNotNull("Version is not null.", ToopherAPI.VERSION); } + + @Test + public void testAuthenticationStatusFactoryBuildsValidJSON() throws InterruptedException, URISyntaxException { + HttpClientMock clientMock = new HttpClientMock(200, "{}"); + AuthenticationStatusFactoryMock factoryMock = new AuthenticationStatusFactoryMock(); + + ToopherAPI api = new ToopherAPI.Builder(CONSUMER_KEY, CONSUMER_SECRET) + .setBaseUri(BASE_URI_STRING) + .setHttpClient(clientMock) + .setAuthenticationStatusFactory(factoryMock) + .build(); + + try { + api.authenticate(PAIRING_ID, TERMINAL_NAME); + } catch (RequestError re) { + fail(); + } + } + + @Test + public void testParseRequestErrorUserDisabled() throws InterruptedException, URISyntaxException, RequestError { + Map expectedStatusResponse = new HashMap(); + expectedStatusResponse.put(createURI("https://api.toopher.test/v1/authentication_requests/initiate"), new ResponseMock(409, "{'error_code' : '704', 'error_message' : 'Toopher User Disabled Error'}")); + + HttpClientMock httpClient = new HttpClientMock(expectedStatusResponse); + AuthenticationStatusFactoryMock factoryMock = new AuthenticationStatusFactoryMock(); + + ToopherAPI toopherApi = new ToopherAPI.Builder(CONSUMER_KEY, CONSUMER_SECRET) + .setBaseUri(BASE_URI_STRING) + .setHttpClient(httpClient) + .setAuthenticationStatusFactory(factoryMock) + .build(); + + try { + toopherApi.authenticateByUserName(USER_NAME, TERMINAL_NAME, ACTION_NAME, null); + fail(); + } catch (ToopherUserDisabledError toopherUserDisabledError) { + } + + } + + @Test + public void testParseRequestErrorUnknownUser() throws InterruptedException, URISyntaxException, RequestError { + Map expectedStatusResponse = new HashMap(); + expectedStatusResponse.put(createURI("https://api.toopher.test/v1/authentication_requests/initiate"), new ResponseMock(409, "{'error_code' : '705', 'error_message' : 'Toopher Unknown User Error'}")); + + HttpClientMock httpClient = new HttpClientMock(expectedStatusResponse); + AuthenticationStatusFactoryMock factoryMock = new AuthenticationStatusFactoryMock(); + + ToopherAPI toopherApi = new ToopherAPI.Builder(CONSUMER_KEY, CONSUMER_SECRET) + .setBaseUri(BASE_URI_STRING) + .setHttpClient(httpClient) + .setAuthenticationStatusFactory(factoryMock) + .build(); + + try { + toopherApi.authenticateByUserName(USER_NAME, TERMINAL_NAME, ACTION_NAME, null); + fail(); + } catch (ToopherUnknownUserError toopherUnknownUserError) { + } + + } + + @Test + public void testParseRequestErrorUnknownTerminal() throws InterruptedException, URISyntaxException, RequestError { + Map expectedStatusResponse = new HashMap(); + expectedStatusResponse.put(createURI("https://api.toopher.test/v1/authentication_requests/initiate"), new ResponseMock(409, "{'error_code' : '706', 'error_message' : 'Toopher Unknown Terminal Error'}")); + + HttpClientMock httpClient = new HttpClientMock(expectedStatusResponse); + AuthenticationStatusFactoryMock factoryMock = new AuthenticationStatusFactoryMock(); + + ToopherAPI toopherApi = new ToopherAPI.Builder(CONSUMER_KEY, CONSUMER_SECRET) + .setBaseUri(BASE_URI_STRING) + .setHttpClient(httpClient) + .setAuthenticationStatusFactory(factoryMock) + .build(); + + try { + toopherApi.authenticateByUserName(USER_NAME, TERMINAL_NAME, ACTION_NAME, null); + fail(); + } catch (ToopherUnknownTerminalError toopherUnknownTerminalError) { + } + + } + + @Test + public void testParseRequestErrorClient() throws InterruptedException, URISyntaxException, RequestError { + Map expectedStatusResponse = new HashMap(); + expectedStatusResponse.put(createURI("https://api.toopher.test/v1/authentication_requests/initiate"), new ResponseMock(409, "{'error_code' : '707', 'error_message' : 'Toopher Client Error'}")); + + HttpClientMock httpClient = new HttpClientMock(expectedStatusResponse); + AuthenticationStatusFactoryMock factoryMock = new AuthenticationStatusFactoryMock(); + + ToopherAPI toopherApi = new ToopherAPI.Builder(CONSUMER_KEY, CONSUMER_SECRET) + .setBaseUri(BASE_URI_STRING) + .setHttpClient(httpClient) + .setAuthenticationStatusFactory(factoryMock) + .build(); + + try { + toopherApi.authenticateByUserName(USER_NAME, TERMINAL_NAME, ACTION_NAME, null); + fail(); + } catch (ToopherClientError toopherClientError) { + } + + } + + @Test + public void testParseRequestErrorNotJSON() throws InterruptedException, URISyntaxException, RequestError { + Map expectedStatusResponse = new HashMap(); + expectedStatusResponse.put(createURI("https://api.toopher.test/v1/authentication_requests/initiate"), new ResponseMock(409, "{'error_code' : '707', 'I'm not valid JSON body text'}")); + + HttpClientMock httpClient = new HttpClientMock(expectedStatusResponse); + AuthenticationStatusFactoryMock factoryMock = new AuthenticationStatusFactoryMock(); + + ToopherAPI toopherApi = new ToopherAPI.Builder(CONSUMER_KEY, CONSUMER_SECRET) + .setBaseUri(BASE_URI_STRING) + .setHttpClient(httpClient) + .setAuthenticationStatusFactory(factoryMock) + .build(); + + try { + toopherApi.authenticateByUserName(USER_NAME, TERMINAL_NAME, ACTION_NAME, null); + fail(); + } catch (RequestError re) { + } + + } + } diff --git a/src/test/java/com/toopher/TestToopherIframe.java b/src/test/java/com/toopher/TestToopherIframe.java index fefaf41..7a58740 100644 --- a/src/test/java/com/toopher/TestToopherIframe.java +++ b/src/test/java/com/toopher/TestToopherIframe.java @@ -1,14 +1,16 @@ package com.toopher; +import org.apache.http.NameValuePair; +import org.apache.http.client.utils.URLEncodedUtils; +import org.junit.Before; +import org.junit.Test; + import java.nio.charset.Charset; import java.util.Date; -import java.util.Map; import java.util.HashMap; import java.util.List; +import java.util.Map; -import org.apache.http.NameValuePair; -import org.junit.*; -import org.apache.http.client.utils.URLEncodedUtils; import static org.junit.Assert.*; public class TestToopherIframe {