diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/com/p4square/f1oauth/Attribute.java | 60 | ||||
-rw-r--r-- | src/com/p4square/f1oauth/F1API.java | 44 | ||||
-rw-r--r-- | src/com/p4square/f1oauth/F1Access.java | 361 | ||||
-rw-r--r-- | src/com/p4square/f1oauth/F1Exception.java | 15 | ||||
-rw-r--r-- | src/com/p4square/f1oauth/F1OAuthHelper.java | 137 | ||||
-rw-r--r-- | src/com/p4square/f1oauth/SecondPartyAuthenticator.java | 4 | ||||
-rw-r--r-- | src/com/p4square/f1oauth/SecondPartyVerifier.java | 27 | ||||
-rw-r--r-- | src/com/p4square/grow/GrowProcessComponent.java | 1 | ||||
-rw-r--r-- | src/com/p4square/grow/frontend/AssessmentResultsPage.java | 39 | ||||
-rw-r--r-- | src/com/p4square/grow/frontend/ChapterCompletePage.java | 36 | ||||
-rw-r--r-- | src/com/p4square/grow/frontend/GrowFrontend.java | 12 | ||||
-rw-r--r-- | src/com/p4square/grow/frontend/NewAccountResource.java | 6 | ||||
-rw-r--r-- | src/com/p4square/grow/tools/AttributeTool.java | 143 | ||||
-rw-r--r-- | src/com/p4square/restlet/oauth/OAuthHelper.java | 4 |
14 files changed, 712 insertions, 177 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(); - } - } - } } diff --git a/src/com/p4square/grow/GrowProcessComponent.java b/src/com/p4square/grow/GrowProcessComponent.java index 18b3d5b..791f177 100644 --- a/src/com/p4square/grow/GrowProcessComponent.java +++ b/src/com/p4square/grow/GrowProcessComponent.java @@ -10,7 +10,6 @@ import java.io.IOException; import org.apache.log4j.Logger; import org.restlet.Application; -import org.restlet.Client; import org.restlet.Component; import org.restlet.Restlet; import org.restlet.data.ChallengeScheme; diff --git a/src/com/p4square/grow/frontend/AssessmentResultsPage.java b/src/com/p4square/grow/frontend/AssessmentResultsPage.java index 9c69c69..ff1832c 100644 --- a/src/com/p4square/grow/frontend/AssessmentResultsPage.java +++ b/src/com/p4square/grow/frontend/AssessmentResultsPage.java @@ -4,6 +4,7 @@ package com.p4square.grow.frontend; +import java.util.Date; import java.util.Map; import freemarker.template.Template; @@ -22,7 +23,12 @@ import com.p4square.fmfacade.json.JsonRequestClient; import com.p4square.fmfacade.json.JsonResponse; import com.p4square.fmfacade.json.ClientException; +import com.p4square.f1oauth.Attribute; +import com.p4square.f1oauth.F1API; +import com.p4square.f1oauth.F1User; + import com.p4square.grow.config.Config; +import com.p4square.grow.provider.JsonEncodedProvider; /** * This page fetches the user's final score and displays the transitional page between @@ -82,6 +88,9 @@ public class AssessmentResultsPage extends FreeMarkerPageResource { return new StringRepresentation("Redirecting to " + nextPage); } + // Publish results in F1 + publishScoreInF1(response.getMap()); + root.put("stage", score); return new TemplateRepresentation(t, root, MediaType.TEXT_HTML); @@ -92,6 +101,33 @@ public class AssessmentResultsPage extends FreeMarkerPageResource { } } + private void publishScoreInF1(Map results) { + if (!(getRequest().getClientInfo().getUser() instanceof F1User)) { + // Only useful if the user is from F1. + return; + } + + F1User user = (F1User) getRequest().getClientInfo().getUser(); + + // Update the attribute. + String attributeName = "Assessment Complete - " + results.get("result"); + + try { + Attribute attribute = new Attribute(); + attribute.setStartDate(new Date()); + attribute.setComment(JsonEncodedProvider.MAPPER.writeValueAsString(results)); + + F1API f1 = mGrowFrontend.getF1Access().getAuthenticatedApi(user); + if (!f1.addAttribute(user.getIdentifier(), attributeName, attribute)) { + LOG.error("addAttribute failed for " + user.getIdentifier() + + " with attribute " + attributeName); + } + } catch (Exception e) { + LOG.error("addAttribute failed for " + user.getIdentifier() + + " with attribute " + attributeName, e); + } + } + /** * @return The backend endpoint URI */ @@ -108,7 +144,8 @@ public class AssessmentResultsPage extends FreeMarkerPageResource { final JsonResponse response = mJsonClient.get(getBackendEndpoint() + uri); final Status status = response.getStatus(); if (!status.isSuccess() && !Status.CLIENT_ERROR_NOT_FOUND.equals(status)) { - LOG.warn("Error making backend request for '" + uri + "'. status = " + response.getStatus().toString()); + LOG.warn("Error making backend request for '" + uri + "'. status = " + + response.getStatus().toString()); } return response; diff --git a/src/com/p4square/grow/frontend/ChapterCompletePage.java b/src/com/p4square/grow/frontend/ChapterCompletePage.java index b3896e9..a2c4ebe 100644 --- a/src/com/p4square/grow/frontend/ChapterCompletePage.java +++ b/src/com/p4square/grow/frontend/ChapterCompletePage.java @@ -4,6 +4,7 @@ package com.p4square.grow.frontend; +import java.util.Date; import java.util.Map; import freemarker.template.Template; @@ -22,10 +23,14 @@ import com.p4square.fmfacade.json.JsonRequestClient; import com.p4square.fmfacade.json.JsonResponse; import com.p4square.fmfacade.json.ClientException; +import com.p4square.f1oauth.Attribute; +import com.p4square.f1oauth.F1API; +import com.p4square.f1oauth.F1User; + import com.p4square.grow.config.Config; import com.p4square.grow.model.TrainingRecord; -import com.p4square.grow.provider.TrainingRecordProvider; import com.p4square.grow.provider.Provider; +import com.p4square.grow.provider.TrainingRecordProvider; /** * This resource displays the transitional page between chapters. @@ -93,6 +98,9 @@ public class ChapterCompletePage extends FreeMarkerPageResource { return new StringRepresentation("Redirecting to " + nextPage); } + // Publish the training chapter complete attribute. + assignAttribute(); + // Find the next chapter String nextChapter = null; { @@ -149,6 +157,32 @@ public class ChapterCompletePage extends FreeMarkerPageResource { } } + private void assignAttribute() { + if (!(getRequest().getClientInfo().getUser() instanceof F1User)) { + // Only useful if the user is from F1. + return; + } + + F1User user = (F1User) getRequest().getClientInfo().getUser(); + + // Update the attribute. + String attributeName = "Training Complete - " + mChapter; + + try { + Attribute attribute = new Attribute(); + attribute.setStartDate(new Date()); + + F1API f1 = mGrowFrontend.getF1Access().getAuthenticatedApi(user); + if (!f1.addAttribute(user.getIdentifier(), attributeName, attribute)) { + LOG.error("addAttribute failed for " + user.getIdentifier() + + " with attribute " + attributeName); + } + } catch (Exception e) { + LOG.error("addAttribute failed for " + user.getIdentifier() + + " with attribute " + attributeName, e); + } + } + /** * @return The backend endpoint URI */ diff --git a/src/com/p4square/grow/frontend/GrowFrontend.java b/src/com/p4square/grow/frontend/GrowFrontend.java index 4b193d0..926670b 100644 --- a/src/com/p4square/grow/frontend/GrowFrontend.java +++ b/src/com/p4square/grow/frontend/GrowFrontend.java @@ -30,7 +30,7 @@ import com.p4square.fmfacade.FreeMarkerPageResource; import com.p4square.grow.config.Config; -import com.p4square.f1oauth.F1OAuthHelper; +import com.p4square.f1oauth.F1Access; import com.p4square.f1oauth.SecondPartyVerifier; import com.p4square.session.SessionCheckingAuthenticator; @@ -49,7 +49,7 @@ public class GrowFrontend extends FMFacade { private Config mConfig; - private F1OAuthHelper mHelper; + private F1Access mHelper; public GrowFrontend() { this(new Config()); @@ -73,13 +73,13 @@ public class GrowFrontend extends FMFacade { super.start(); } - synchronized F1OAuthHelper getHelper() { + synchronized F1Access getF1Access() { if (mHelper == null) { - mHelper = new F1OAuthHelper(getContext(), mConfig.getString("f1ConsumerKey", ""), + mHelper = new F1Access(getContext(), mConfig.getString("f1ConsumerKey", ""), mConfig.getString("f1ConsumerSecret", ""), mConfig.getString("f1BaseUrl", "staging.fellowshiponeapi.com"), mConfig.getString("f1ChurchCode", "pfseawa"), - F1OAuthHelper.UserType.WEBLINK); + F1Access.UserType.WEBLINK); } return mHelper; @@ -129,7 +129,7 @@ public class GrowFrontend extends FMFacade { SessionCheckingAuthenticator sessionChk = new SessionCheckingAuthenticator(context, true); // This is used to authenticate the user - SecondPartyVerifier f1Verifier = new SecondPartyVerifier(context, getHelper()); + SecondPartyVerifier f1Verifier = new SecondPartyVerifier(context, getF1Access()); LoginFormAuthenticator loginAuth = new LoginFormAuthenticator(context, false, f1Verifier); loginAuth.setLoginFormUrl(loginPage); loginAuth.setLoginPostUrl(loginPost); diff --git a/src/com/p4square/grow/frontend/NewAccountResource.java b/src/com/p4square/grow/frontend/NewAccountResource.java index 9155a00..54c1790 100644 --- a/src/com/p4square/grow/frontend/NewAccountResource.java +++ b/src/com/p4square/grow/frontend/NewAccountResource.java @@ -18,7 +18,7 @@ import org.restlet.ext.freemarker.TemplateRepresentation; import org.apache.log4j.Logger; -import com.p4square.f1oauth.F1OAuthHelper; +import com.p4square.f1oauth.F1Access; import com.p4square.restlet.oauth.OAuthException; import com.p4square.fmfacade.FreeMarkerPageResource; @@ -32,7 +32,7 @@ public class NewAccountResource extends FreeMarkerPageResource { private static Logger LOG = Logger.getLogger(NewAccountResource.class); private GrowFrontend mGrowFrontend; - private F1OAuthHelper mHelper; + private F1Access mHelper; private String mErrorMessage; @@ -44,7 +44,7 @@ public class NewAccountResource extends FreeMarkerPageResource { super.doInit(); mGrowFrontend = (GrowFrontend) getApplication(); - mHelper = mGrowFrontend.getHelper(); + mHelper = mGrowFrontend.getF1Access(); mErrorMessage = ""; diff --git a/src/com/p4square/grow/tools/AttributeTool.java b/src/com/p4square/grow/tools/AttributeTool.java new file mode 100644 index 0000000..0775087 --- /dev/null +++ b/src/com/p4square/grow/tools/AttributeTool.java @@ -0,0 +1,143 @@ +/* + * Copyright 2014 Jesse Morgan + */ + +package com.p4square.grow.tools; + +import java.util.Arrays; +import java.util.Date; +import java.util.Map; + +import org.restlet.Client; +import org.restlet.Context; +import org.restlet.data.Protocol; + +import com.p4square.grow.config.Config; +import com.p4square.f1oauth.Attribute; +import com.p4square.f1oauth.F1Access; +import com.p4square.f1oauth.F1API; +import com.p4square.f1oauth.F1Exception; +import com.p4square.restlet.oauth.OAuthUser; + +/** + * Tool for manipulating F1 Attributes. + * + * @author Jesse Morgan <jesse@jesterpm.net> + */ +public class AttributeTool { + + private static Config mConfig; + private static F1API mF1API; + + public static void usage() { + System.out.println("java com.p4square.grow.tools.AttributeTool <command>...\n"); + System.out.println("Commands:"); + System.out.println("\t--domain <domain> Set config domain"); + System.out.println("\t--dev Set config domain to dev"); + System.out.println("\t--config <file> Merge in config file"); + System.out.println("\t--list List all attributes"); + System.out.println("\t--assign <user> <attribute> <comment> Assign an attribute"); + } + + public static void main(String... args) { + if (args.length == 0) { + usage(); + System.exit(1); + } + + mConfig = new Config(); + + try { + mConfig.updateConfig(AttributeTool.class.getResourceAsStream("/grow.properties")); + + int offset = 0; + while (offset < args.length) { + if ("--domain".equals(args[offset])) { + mConfig.setDomain(args[offset + 1]); + mF1API = null; + offset += 2; + + } else if ("--dev".equals(args[offset])) { + mConfig.setDomain("dev"); + mF1API = null; + offset += 1; + + } else if ("--config".equals(args[offset])) { + mConfig.updateConfig(args[offset + 1]); + mF1API = null; + offset += 2; + + } else if ("--list".equals(args[offset])) { + offset = list(args, ++offset); + + } else if ("--assign".equals(args[offset])) { + offset = assign(args, ++offset); + + } else { + throw new IllegalArgumentException("Unknown command " + args[offset]); + } + } + } catch (Exception e) { + e.printStackTrace(); + System.exit(2); + } + } + + private static F1API getF1API() throws Exception { + if (mF1API == null) { + Context context = new Context(); + Client client = new Client(context, Arrays.asList(Protocol.HTTP, Protocol.HTTPS)); + context.setClientDispatcher(client); + + F1Access f1Access = new F1Access(context, + mConfig.getString("f1ConsumerKey"), + mConfig.getString("f1ConsumerSecret"), + mConfig.getString("f1BaseUrl"), + mConfig.getString("f1ChurchCode"), + F1Access.UserType.WEBLINK); + + // Gather Username and Password + String username = System.console().readLine("F1 Username: "); + char[] password = System.console().readPassword("F1 Password: "); + + OAuthUser user = f1Access.getAccessToken(username, new String(password)); + Arrays.fill(password, ' '); // Lost cause, but I'll still try. + + mF1API = f1Access.getAuthenticatedApi(user); + } + + return mF1API; + } + + private static int list(String[] args, int offset) throws Exception { + final F1API f1 = getF1API(); + + final Map<String, String> attributes = f1.getAttributeList(); + System.out.printf("%7s %s\n", "ID", "Name"); + for (Map.Entry<String, String> entry : attributes.entrySet()) { + System.out.printf("%7s %s\n", entry.getValue(), entry.getKey()); + } + + return offset; + } + + private static int assign(String[] args, int offset) throws Exception { + final String userId = args[offset++]; + final String attributeName = args[offset++]; + final String comment = args[offset++]; + + final F1API f1 = getF1API(); + + Attribute attribute = new Attribute(); + attribute.setStartDate(new Date()); + attribute.setComment(comment); + + if (f1.addAttribute(userId, attributeName, attribute)) { + System.out.println("Added attribute " + attributeName + " for " + userId); + } else { + System.out.println("Failed to add attribute " + attributeName + " for " + userId); + } + + return offset; + } +} diff --git a/src/com/p4square/restlet/oauth/OAuthHelper.java b/src/com/p4square/restlet/oauth/OAuthHelper.java index 39c1b02..67dd238 100644 --- a/src/com/p4square/restlet/oauth/OAuthHelper.java +++ b/src/com/p4square/restlet/oauth/OAuthHelper.java @@ -127,7 +127,7 @@ public abstract class OAuthHelper { * @return An OAuthUser object wrapping the AccessToken. * @throws OAuthException if the request failed. */ - protected OAuthUser processAccessTokenRequest(Request request) throws OAuthException { + public OAuthUser processAccessTokenRequest(Request request) throws OAuthException { Response response = getResponse(request); Token accessToken = processTokenRequest(response); @@ -143,7 +143,7 @@ public abstract class OAuthHelper { /** * Helper method to get a Response for a Request. */ - protected Response getResponse(Request request) { + public Response getResponse(Request request) { return mDispatcher.handle(request); } } |