summaryrefslogtreecommitdiff
path: root/src/com/p4square/f1oauth
diff options
context:
space:
mode:
authorJesse Morgan <jesse@jesterpm.net>2014-09-22 06:40:21 -0700
committerJesse Morgan <jesse@jesterpm.net>2014-09-22 06:40:21 -0700
commitdd4f34e216132e3a066566daf30a6f1fc1e1b872 (patch)
tree8c9e2ac3b30f207e7945dfe5f885101a6dcee995 /src/com/p4square/f1oauth
parent6eba410e5eb53ee887e430f4f98ba03ffaa2a474 (diff)
parent3e703186928c5bd8f2c31f90b1c6e262c4080328 (diff)
Merge branch 'f1-attributes'
Diffstat (limited to 'src/com/p4square/f1oauth')
-rw-r--r--src/com/p4square/f1oauth/Attribute.java60
-rw-r--r--src/com/p4square/f1oauth/F1API.java44
-rw-r--r--src/com/p4square/f1oauth/F1Access.java361
-rw-r--r--src/com/p4square/f1oauth/F1Exception.java15
-rw-r--r--src/com/p4square/f1oauth/F1OAuthHelper.java137
-rw-r--r--src/com/p4square/f1oauth/SecondPartyAuthenticator.java4
-rw-r--r--src/com/p4square/f1oauth/SecondPartyVerifier.java27
7 files changed, 485 insertions, 163 deletions
diff --git a/src/com/p4square/f1oauth/Attribute.java b/src/com/p4square/f1oauth/Attribute.java
new file mode 100644
index 0000000..cc7cfc4
--- /dev/null
+++ b/src/com/p4square/f1oauth/Attribute.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright 2014 Jesse Morgan
+ */
+
+package com.p4square.f1oauth;
+
+import java.util.Date;
+
+/**
+ * F1 Attribute Data.
+ *
+ * @author Jesse Morgan <jesse@jesterpm.net>
+ */
+public class Attribute {
+ private Date mStartDate;
+ private Date mEndDate;
+ private String mComment;
+
+ /**
+ * @return the start date for the attribute.
+ */
+ public Date getStartDate() {
+ return mStartDate;
+ }
+
+ /**
+ * Set the start date for the attribute.
+ */
+ public void setStartDate(Date date) {
+ mStartDate = date;
+ }
+
+ /**
+ * @return the end date for the attribute.
+ */
+ public Date getEndDate() {
+ return mEndDate;
+ }
+
+ /**
+ * Set the end date for the attribute.
+ */
+ public void setEndDate(Date date) {
+ mEndDate = date;
+ }
+
+ /**
+ * @return The comment on the Attribute.
+ */
+ public String getComment() {
+ return mComment;
+ }
+
+ /**
+ * Set the comment on the attribute.
+ */
+ public void setComment(String comment) {
+ mComment = comment;
+ }
+}
diff --git a/src/com/p4square/f1oauth/F1API.java b/src/com/p4square/f1oauth/F1API.java
new file mode 100644
index 0000000..88801db
--- /dev/null
+++ b/src/com/p4square/f1oauth/F1API.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2014 Jesse Morgan
+ */
+
+package com.p4square.f1oauth;
+
+import java.io.IOException;
+import java.util.Map;
+
+import com.p4square.restlet.oauth.OAuthException;
+import com.p4square.restlet.oauth.OAuthUser;
+
+/**
+ * F1 API methods which require an authenticated user.
+ *
+ * @author Jesse Morgan <jesse@jesterpm.net>
+ */
+public interface F1API {
+ /**
+ * Fetch information about a user.
+ *
+ * @param user The user to fetch information about.
+ * @return An F1User object.
+ */
+ F1User getF1User(OAuthUser user) throws OAuthException, IOException;
+
+ /**
+ * Fetch a list of all attributes ids and names.
+ *
+ * @return A Map of attribute name to attribute id.
+ */
+ Map<String, String> getAttributeList() throws F1Exception;
+
+ /**
+ * Add an attribute to the user.
+ *
+ * @param user The user to add the attribute to.
+ * @param attributeName The attribute to add.
+ * @param attribute The attribute to add.
+ */
+ boolean addAttribute(String userId, String attributeName, Attribute attribute)
+ throws F1Exception;
+
+}
diff --git a/src/com/p4square/f1oauth/F1Access.java b/src/com/p4square/f1oauth/F1Access.java
new file mode 100644
index 0000000..32550c4
--- /dev/null
+++ b/src/com/p4square/f1oauth/F1Access.java
@@ -0,0 +1,361 @@
+/*
+ * Copyright 2014 Jesse Morgan
+ */
+
+package com.p4square.f1oauth;
+
+import java.io.IOException;
+import java.net.URLEncoder;
+import java.text.SimpleDateFormat;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.log4j.Logger;
+
+import org.restlet.Context;
+import org.restlet.Request;
+import org.restlet.Response;
+import org.restlet.data.ChallengeResponse;
+import org.restlet.data.ChallengeScheme;
+import org.restlet.data.MediaType;
+import org.restlet.data.Method;
+import org.restlet.data.Status;
+import org.restlet.engine.util.Base64;
+import org.restlet.ext.jackson.JacksonRepresentation;
+import org.restlet.representation.Representation;
+import org.restlet.representation.StringRepresentation;
+
+import com.p4square.restlet.oauth.OAuthException;
+import com.p4square.restlet.oauth.OAuthHelper;
+import com.p4square.restlet.oauth.OAuthUser;
+import com.p4square.restlet.oauth.Token;
+
+/**
+ * F1 API Access.
+ *
+ * @author Jesse Morgan <jesse@jesterpm.net>
+ */
+public class F1Access {
+ public enum UserType {
+ WEBLINK, PORTAL;
+ }
+
+ private static final Logger LOG = Logger.getLogger(F1Access.class);
+
+ private static final String VERSION_STRING = "/v1/";
+ private static final String REQUESTTOKEN_URL = "Tokens/RequestToken";
+ private static final String AUTHORIZATION_URL = "Login";
+ private static final String ACCESSTOKEN_URL= "Tokens/AccessToken";
+ private static final String TRUSTED_ACCESSTOKEN_URL = "/AccessToken";
+
+ private static final SimpleDateFormat DATE_FORMAT =
+ new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");
+
+ private final String mBaseUrl;
+ private final String mMethod;
+
+ private final OAuthHelper mOAuthHelper;
+
+ private final Map<String, String> mAttributeIdByName;
+
+ /**
+ */
+ public F1Access(Context context, String consumerKey, String consumerSecret,
+ String baseUrl, String churchCode, UserType userType) {
+
+ switch (userType) {
+ case WEBLINK:
+ mMethod = "WeblinkUser";
+ break;
+ case PORTAL:
+ mMethod = "PortalUser";
+ break;
+ default:
+ throw new IllegalArgumentException("Unknown UserType");
+ }
+
+ mBaseUrl = "https://" + churchCode + "." + baseUrl + VERSION_STRING;
+
+ // Create the OAuthHelper. This implicitly registers the helper to
+ // handle outgoing requests which need OAuth authentication.
+ mOAuthHelper = new OAuthHelper(context, consumerKey, consumerSecret) {
+ @Override
+ protected String getRequestTokenUrl() {
+ return mBaseUrl + REQUESTTOKEN_URL;
+ }
+
+ @Override
+ public String getLoginUrl(Token requestToken, String callback) {
+ String loginUrl = mBaseUrl + mMethod + AUTHORIZATION_URL
+ + "?oauth_token=" + URLEncoder.encode(requestToken.getToken());
+
+ if (callback != null) {
+ loginUrl += "&oauth_callback=" + URLEncoder.encode(callback);
+ }
+
+ return loginUrl;
+ }
+
+ @Override
+ protected String getAccessTokenUrl() {
+ return mBaseUrl + ACCESSTOKEN_URL;
+ }
+ };
+
+ mAttributeIdByName = new HashMap<>();
+ }
+
+ /**
+ * Request an AccessToken for a particular username and password.
+ *
+ * This is an F1 extension to OAuth:
+ * http://developer.fellowshipone.com/docs/v1/Util/AuthDocs.help#2creds
+ */
+ public OAuthUser getAccessToken(String username, String password) throws OAuthException {
+ Request request = new Request(Method.POST, mBaseUrl + mMethod + TRUSTED_ACCESSTOKEN_URL);
+ request.setChallengeResponse(new ChallengeResponse(ChallengeScheme.HTTP_OAUTH));
+
+ String base64String = Base64.encode((username + " " + password).getBytes(), false);
+ request.setEntity(new StringRepresentation(base64String));
+
+ return mOAuthHelper.processAccessTokenRequest(request);
+ }
+
+ /**
+ * Create a new Account.
+ *
+ * @param firstname The user's first name.
+ * @param lastname The user's last name.
+ * @param email The user's email address.
+ * @param redirect The URL to send the user to after confirming his address.
+ *
+ * @return true if created, false if the account already exists.
+ */
+ public boolean createAccount(String firstname, String lastname, String email, String redirect)
+ throws OAuthException {
+ String req = String.format("{\n\"account\":{\n\"firstName\":\"%s\",\n"
+ + "\"lastName\":\"%s\",\n\"email\":\"%s\",\n"
+ + "\"urlRedirect\":\"%s\"\n}\n}",
+ firstname, lastname, email, redirect);
+
+ Request request = new Request(Method.POST, mBaseUrl + "Accounts");
+ request.setChallengeResponse(new ChallengeResponse(ChallengeScheme.HTTP_OAUTH));
+ request.setEntity(new StringRepresentation(req, MediaType.APPLICATION_JSON));
+
+ Response response = mOAuthHelper.getResponse(request);
+
+ Status status = response.getStatus();
+ if (Status.SUCCESS_NO_CONTENT.equals(status)) {
+ return true;
+
+ } else if (Status.CLIENT_ERROR_CONFLICT.equals(status)) {
+ return false;
+
+ } else {
+ throw new OAuthException(status);
+ }
+ }
+
+ /**
+ * @return An F1API authenticated by the given user.
+ */
+ public F1API getAuthenticatedApi(OAuthUser user) {
+ return new AuthenticatedApi(user);
+ }
+
+ private class AuthenticatedApi implements F1API {
+ private final OAuthUser mUser;
+
+ public AuthenticatedApi(OAuthUser user) {
+ mUser = user;
+ }
+
+ /**
+ * Fetch information about a user.
+ *
+ * @param user The user to fetch information about.
+ * @return An F1User object.
+ */
+ @Override
+ public F1User getF1User(OAuthUser user) throws OAuthException, IOException {
+ Request request = new Request(Method.GET, user.getLocation() + ".json");
+ request.setChallengeResponse(mUser.getChallengeResponse());
+ Response response = mOAuthHelper.getResponse(request);
+
+ try {
+ Status status = response.getStatus();
+ if (status.isSuccess()) {
+ JacksonRepresentation<Map> entity =
+ new JacksonRepresentation<Map>(response.getEntity(), Map.class);
+ Map data = entity.getObject();
+ return new F1User(user, data);
+
+ } else {
+ throw new OAuthException(status);
+ }
+ } finally {
+ if (response.getEntity() != null) {
+ response.release();
+ }
+ }
+ }
+
+ @Override
+ public Map<String, String> getAttributeList() throws F1Exception {
+ // Note: this list is shared by all F1 users.
+ synchronized (mAttributeIdByName) {
+ if (mAttributeIdByName.size() == 0) {
+ // Reload attributes. Maybe it will be there now...
+ Request request = new Request(Method.GET,
+ mBaseUrl + "People/AttributeGroups.json");
+ request.setChallengeResponse(mUser.getChallengeResponse());
+ Response response = mOAuthHelper.getResponse(request);
+
+ Representation representation = response.getEntity();
+ try {
+ Status status = response.getStatus();
+ if (status.isSuccess()) {
+ JacksonRepresentation<Map> entity =
+ new JacksonRepresentation<Map>(response.getEntity(), Map.class);
+
+ Map attributeGroups = (Map) entity.getObject().get("attributeGroups");
+ List<Map> groups = (List<Map>) attributeGroups.get("attributeGroup");
+
+ for (Map group : groups) {
+ List<Map> attributes = (List<Map>) group.get("attribute");
+ if (attributes != null) {
+ for (Map attribute : attributes) {
+ String id = (String) attribute.get("@id");
+ String name = ((String) attribute.get("name"));
+ mAttributeIdByName.put(name.toLowerCase(), id);
+ LOG.debug("Caching attribute '" + name
+ + "' with id '" + id + "'");
+ }
+ }
+ }
+ }
+
+ } catch (IOException e) {
+ throw new F1Exception("Could not parse AttributeGroups.", e);
+
+ } finally {
+ if (representation != null) {
+ representation.release();
+ }
+ }
+ }
+
+ return mAttributeIdByName;
+ }
+ }
+
+ /**
+ * Add an attribute to the user.
+ *
+ * @param user The user to add the attribute to.
+ * @param attributeName The attribute to add.
+ * @param attribute The attribute to add.
+ */
+ public boolean addAttribute(String userId, String attributeName, Attribute attribute)
+ throws F1Exception {
+
+ // Get the attribute id.
+ String attributeId = getAttributeId(attributeName);
+ if (attributeId == null) {
+ throw new F1Exception("Could not find id for " + attributeName);
+ }
+
+ // Get Attribute Template
+ Map attributeTemplate = null;
+
+ {
+ Request request = new Request(Method.GET,
+ mBaseUrl + "People/" + userId + "/Attributes/new.json");
+ request.setChallengeResponse(mUser.getChallengeResponse());
+ Response response = mOAuthHelper.getResponse(request);
+
+ Representation representation = response.getEntity();
+ try {
+ Status status = response.getStatus();
+ if (status.isSuccess()) {
+ JacksonRepresentation<Map> entity =
+ new JacksonRepresentation<Map>(response.getEntity(), Map.class);
+ attributeTemplate = entity.getObject();
+
+ } else {
+ throw new F1Exception("Failed to retrieve attribute template: "
+ + status);
+ }
+
+ } catch (IOException e) {
+ throw new F1Exception("Could not parse attribute template.", e);
+
+ } finally {
+ if (representation != null) {
+ representation.release();
+ }
+ }
+ }
+
+ if (attributeTemplate == null) {
+ throw new F1Exception("Could not retrieve attribute template.");
+ }
+
+ // Populate Attribute Template
+ Map attributeMap = (Map) attributeTemplate.get("attribute");
+ Map attributeGroup = (Map) attributeMap.get("attributeGroup");
+
+ Map<String, String> attributeIdMap = new HashMap<>();
+ attributeIdMap.put("@id", attributeId);
+ attributeGroup.put("attribute", attributeIdMap);
+
+ if (attribute.getStartDate() != null) {
+ attributeMap.put("startDate", DATE_FORMAT.format(attribute.getStartDate()));
+ }
+
+ if (attribute.getStartDate() != null) {
+ attributeMap.put("endDate", DATE_FORMAT.format(attribute.getStartDate()));
+ }
+
+ attributeMap.put("comment", attribute.getComment());
+
+ // POST new attribute
+ Status status;
+ {
+ Request request = new Request(Method.POST,
+ mBaseUrl + "People/" + userId + "/Attributes.json");
+ request.setChallengeResponse(mUser.getChallengeResponse());
+ request.setEntity(new JacksonRepresentation<Map>(attributeTemplate));
+ Response response = mOAuthHelper.getResponse(request);
+
+ Representation representation = response.getEntity();
+ try {
+ status = response.getStatus();
+
+ if (status.isSuccess()) {
+ return true;
+ }
+
+ } finally {
+ if (representation != null) {
+ representation.release();
+ }
+ }
+ }
+
+ LOG.debug("addAttribute failed POST: " + status);
+ return false;
+ }
+
+ /**
+ * @return an attribute id for the given attribute name.
+ */
+ private String getAttributeId(String attributeName) throws F1Exception {
+ Map<String, String> attributeMap = getAttributeList();
+
+ return attributeMap.get(attributeName.toLowerCase());
+ }
+
+ }
+}
diff --git a/src/com/p4square/f1oauth/F1Exception.java b/src/com/p4square/f1oauth/F1Exception.java
new file mode 100644
index 0000000..54c1a77
--- /dev/null
+++ b/src/com/p4square/f1oauth/F1Exception.java
@@ -0,0 +1,15 @@
+/*
+ * Copyright 2014 Jesse Morgan
+ */
+
+package com.p4square.f1oauth;
+
+public class F1Exception extends Exception {
+ public F1Exception(String message) {
+ super(message);
+ }
+
+ public F1Exception(String message, Exception cause) {
+ super(message, cause);
+ }
+}
diff --git a/src/com/p4square/f1oauth/F1OAuthHelper.java b/src/com/p4square/f1oauth/F1OAuthHelper.java
deleted file mode 100644
index b5241c4..0000000
--- a/src/com/p4square/f1oauth/F1OAuthHelper.java
+++ /dev/null
@@ -1,137 +0,0 @@
-/*
- * Copyright 2013 Jesse Morgan
- */
-
-package com.p4square.f1oauth;
-
-import java.net.URLEncoder;
-
-import org.apache.log4j.Logger;
-
-import org.restlet.Context;
-import org.restlet.Response;
-import org.restlet.Request;
-import org.restlet.data.ChallengeResponse;
-import org.restlet.data.ChallengeScheme;
-import org.restlet.data.MediaType;
-import org.restlet.data.Method;
-import org.restlet.data.Status;
-import org.restlet.engine.util.Base64;
-import org.restlet.representation.StringRepresentation;
-
-import com.p4square.restlet.oauth.OAuthException;
-import com.p4square.restlet.oauth.OAuthHelper;
-import com.p4square.restlet.oauth.OAuthUser;
-import com.p4square.restlet.oauth.Token;
-
-/**
- *
- * @author Jesse Morgan <jesse@jesterpm.net>
- */
-public class F1OAuthHelper extends OAuthHelper {
- public enum UserType {
- WEBLINK, PORTAL;
- }
-
- private static final Logger LOG = Logger.getLogger(F1OAuthHelper.class);
-
- private static final String VERSION_STRING = "/v1/";
- private static final String REQUESTTOKEN_URL = "Tokens/RequestToken";
- private static final String AUTHORIZATION_URL = "Login";
- private static final String ACCESSTOKEN_URL= "Tokens/AccessToken";
- private static final String TRUSTED_ACCESSTOKEN_URL = "/AccessToken";
-
- private final String mBaseUrl;
- private final String mMethod;
-
- /**
- * @param method Either WeblinkUser or PortalUser.
- */
- public F1OAuthHelper(Context context, String consumerKey, String consumerSecret,
- String baseUrl, String churchCode, UserType userType) {
- super(context, consumerKey, consumerSecret);
-
- switch (userType) {
- case WEBLINK:
- mMethod = "WeblinkUser";
- break;
- case PORTAL:
- mMethod = "PortalUser";
- break;
- default:
- throw new IllegalArgumentException("Unknown UserType");
- }
-
- mBaseUrl = "https://" + churchCode + "." + baseUrl + VERSION_STRING;
- }
-
- /**
- * @return the URL for the initial RequestToken request.
- */
- protected String getRequestTokenUrl() {
- return mBaseUrl + REQUESTTOKEN_URL;
- }
-
- /**
- * @return the URL to redirect the user to for Authentication.
- */
- public String getLoginUrl(Token requestToken, String callback) {
- String loginUrl = mBaseUrl + mMethod + AUTHORIZATION_URL
- + "?oauth_token=" + URLEncoder.encode(requestToken.getToken());
-
- if (callback != null) {
- loginUrl += "&oauth_callback=" + URLEncoder.encode(callback);
- }
-
- return loginUrl;
- }
-
-
- /**
- * @return the URL for the AccessToken request.
- */
- protected String getAccessTokenUrl() {
- return mBaseUrl + ACCESSTOKEN_URL;
- }
-
- /**
- * Request an AccessToken for a particular username and password.
- *
- * This is an F1 extension to OAuth:
- * http://developer.fellowshipone.com/docs/v1/Util/AuthDocs.help#2creds
- */
- public OAuthUser getAccessToken(String username, String password) throws OAuthException {
- Request request = new Request(Method.POST, mBaseUrl + mMethod + TRUSTED_ACCESSTOKEN_URL);
- request.setChallengeResponse(new ChallengeResponse(ChallengeScheme.HTTP_OAUTH));
-
- String base64String = Base64.encode((username + " " + password).getBytes(), false);
- request.setEntity(new StringRepresentation(base64String));
-
- return processAccessTokenRequest(request);
- }
-
- public boolean createAccount(String firstname, String lastname, String email, String redirect)
- throws OAuthException {
- String req = String.format("{\n\"account\":{\n\"firstName\":\"%s\",\n"
- + "\"lastName\":\"%s\",\n\"email\":\"%s\",\n"
- + "\"urlRedirect\":\"%s\"\n}\n}",
- firstname, lastname, email, redirect);
-
- Request request = new Request(Method.POST, mBaseUrl + "Accounts");
- request.setChallengeResponse(new ChallengeResponse(ChallengeScheme.HTTP_OAUTH));
- request.setEntity(new StringRepresentation(req, MediaType.APPLICATION_JSON));
-
- Response response = getResponse(request);
-
- Status status = response.getStatus();
- if (Status.SUCCESS_NO_CONTENT.equals(status)) {
- return true;
-
- } else if (Status.CLIENT_ERROR_CONFLICT.equals(status)) {
- return false;
-
- } else {
- throw new OAuthException(status);
- }
- }
-}
diff --git a/src/com/p4square/f1oauth/SecondPartyAuthenticator.java b/src/com/p4square/f1oauth/SecondPartyAuthenticator.java
index 1983d69..8deefec 100644
--- a/src/com/p4square/f1oauth/SecondPartyAuthenticator.java
+++ b/src/com/p4square/f1oauth/SecondPartyAuthenticator.java
@@ -21,9 +21,9 @@ import org.restlet.security.Authenticator;
public class SecondPartyAuthenticator extends Authenticator {
private static final Logger LOG = Logger.getLogger(SecondPartyAuthenticator.class);
- private final F1OAuthHelper mHelper;
+ private final F1Access mHelper;
- public SecondPartyAuthenticator(Context context, boolean optional, F1OAuthHelper helper) {
+ public SecondPartyAuthenticator(Context context, boolean optional, F1Access helper) {
super(context, optional);
mHelper = helper;
diff --git a/src/com/p4square/f1oauth/SecondPartyVerifier.java b/src/com/p4square/f1oauth/SecondPartyVerifier.java
index b1afcfa..882c7e7 100644
--- a/src/com/p4square/f1oauth/SecondPartyVerifier.java
+++ b/src/com/p4square/f1oauth/SecondPartyVerifier.java
@@ -30,9 +30,9 @@ public class SecondPartyVerifier implements Verifier {
private static final Logger LOG = Logger.getLogger(SecondPartyVerifier.class);
private final Restlet mDispatcher;
- private final F1OAuthHelper mHelper;
+ private final F1Access mHelper;
- public SecondPartyVerifier(Context context, F1OAuthHelper helper) {
+ public SecondPartyVerifier(Context context, F1Access helper) {
if (helper == null) {
throw new IllegalArgumentException("Helper can not be null.");
}
@@ -54,7 +54,7 @@ public class SecondPartyVerifier implements Verifier {
OAuthUser ouser = mHelper.getAccessToken(username, password);
// Once we have a user, fetch the people record to get the user id.
- F1User user = getF1User(ouser);
+ F1User user = mHelper.getAuthenticatedApi(ouser).getF1User(ouser);
user.setEmail(username);
// This seems like a hack... but it'll work
@@ -69,25 +69,4 @@ public class SecondPartyVerifier implements Verifier {
return RESULT_INVALID; // Invalid credentials
}
- private F1User getF1User(OAuthUser user) throws OAuthException, IOException {
- Request request = new Request(Method.GET, user.getLocation() + ".json");
- request.setChallengeResponse(user.getChallengeResponse());
- Response response = mDispatcher.handle(request);
-
- try {
- Status status = response.getStatus();
- if (status.isSuccess()) {
- JacksonRepresentation<Map> entity = new JacksonRepresentation<Map>(response.getEntity(), Map.class);
- Map data = entity.getObject();
- return new F1User(user, data);
-
- } else {
- throw new OAuthException(status);
- }
- } finally {
- if (response.getEntity() != null) {
- response.release();
- }
- }
- }
}