diff options
| author | Jesse Morgan <jesse@jesterpm.net> | 2013-08-27 08:28:16 -0700 |
|---|---|---|
| committer | Jesse Morgan <jesse@jesterpm.net> | 2013-08-27 08:28:16 -0700 |
| commit | 1cdb43bb3e432040aed18c05e129f0131ee7d20a (patch) | |
| tree | a4c5ad41d183b3874c990de0c5416d1810a1dc85 /src/com/p4square/restlet/oauth/OAuthAuthenticatorHelper.java | |
| parent | 9b33aaf27cd8f73402ee9967c6b0fd76a90f8ebe (diff) | |
Introducing F1 Authentication and Adding Site Content.
This change introduced the f1oauth and jesterpm oauth packages for
interacting with Fellowship One's developer API. I have also reworked
the login authentication to verify credentials through F1 and added
session management to track logged in users.
The Authenticator chain works as follows: on every page load we check
for a session cookie, if the cookie exists, the Request is marked as
authenticated and the OAuthUser object is restored in ClientInfo. If
this request is going to an account page, we require authentication. The
LoginFormAuthenticator checks if the user is already authenticated (via
cookie) and if not redirects the user to the login page. When the login
form is submitted, LoginFormAuthenticator catches the POST request and
authenticates the user through F1.
I'm also adding a new account page, but it is currently a work in
progress.
This commit also adds Allen's content to the site.
Diffstat (limited to 'src/com/p4square/restlet/oauth/OAuthAuthenticatorHelper.java')
| -rw-r--r-- | src/com/p4square/restlet/oauth/OAuthAuthenticatorHelper.java | 177 |
1 files changed, 177 insertions, 0 deletions
diff --git a/src/com/p4square/restlet/oauth/OAuthAuthenticatorHelper.java b/src/com/p4square/restlet/oauth/OAuthAuthenticatorHelper.java new file mode 100644 index 0000000..76ff044 --- /dev/null +++ b/src/com/p4square/restlet/oauth/OAuthAuthenticatorHelper.java @@ -0,0 +1,177 @@ +/* + * Copyright 2013 Jesse Morgan + */ + +package com.p4square.restlet.oauth; + +import java.io.IOException; +import java.io.UnsupportedEncodingException; + +import java.net.URLEncoder; + +import java.security.InvalidKeyException; +import java.security.NoSuchAlgorithmException; + +import java.util.Collections; +import java.util.Random; + +import javax.crypto.Mac; +import javax.crypto.SecretKey; +import javax.crypto.spec.SecretKeySpec; + +import org.restlet.Request; +import org.restlet.Response; +import org.restlet.data.ChallengeRequest; +import org.restlet.data.ChallengeResponse; +import org.restlet.data.ChallengeScheme; +import org.restlet.data.CharacterSet; +import org.restlet.data.Form; +import org.restlet.data.Method; +import org.restlet.data.Parameter; +import org.restlet.data.Reference; +import org.restlet.engine.header.ChallengeWriter; +import org.restlet.engine.header.Header; +import org.restlet.engine.security.AuthenticatorHelper; +import org.restlet.engine.util.Base64; +import org.restlet.util.Series; + +/** + * Authentication helper for signing OAuth Requests. + * + * This implementation is limited to one consumer token/secret per restlet + * engine. In practice this means you will only be able to interact with one + * service provider unless you loaded/unloaded the AuthenticationHelper for + * each request. + * + * @author Jesse Morgan <jesse@jesterpm.net> + */ +public class OAuthAuthenticatorHelper extends AuthenticatorHelper { + private static final String SIGNATURE_METHOD = "HMAC-SHA1"; + private static final String JAVA_SIGNATURE_METHOD = "HmacSHA1"; + private static final String ENCODING = "UTF-8"; + + private final Random mRandom; + private final Token mConsumerToken; + + /** + * Package-private constructor. + * + * This class should only be instantiated by OAuthHelper. + */ + OAuthAuthenticatorHelper(Token consumerToken) { + super(ChallengeScheme.HTTP_OAUTH, true, false); + + mRandom = new Random(); + mConsumerToken = consumerToken; + } + + @Override + public void formatRequest(ChallengeWriter cw, ChallengeRequest cr, + Response response, Series<Header> httpHeaders) throws IOException { + + throw new UnsupportedOperationException("OAuth Requests are not implemented"); + } + + @Override + public void formatResponse(ChallengeWriter cw, ChallengeResponse response, + Request request, Series<Header> httpHeaders) { + + try { + Series<Parameter> authParams = new Series<Parameter>(Parameter.class); + + String nonce = String.valueOf(mRandom.nextInt()); + String timestamp = String.valueOf(System.currentTimeMillis() / 1000); + + authParams.add(new Parameter("oauth_consumer_key", mConsumerToken.getToken())); + authParams.add(new Parameter("oauth_nonce", nonce)); + authParams.add(new Parameter("oauth_signature_method", SIGNATURE_METHOD)); + authParams.add(new Parameter("oauth_timestamp", timestamp)); + authParams.add(new Parameter("oauth_version", "1.0")); + + String accessToken = response.getIdentifier(); + if (accessToken != null) { + authParams.add(new Parameter("oauth_token", accessToken)); + } + + // Generate Signature + String signature = generateSignature(response, request, authParams); + authParams.add(new Parameter("oauth_signature", signature)); + + // Write Header + for (Parameter p : authParams) { + cw.appendQuotedChallengeParameter(encode(p.getName()), encode(p.getValue())); + } + + } catch (IOException e) { + throw new RuntimeException(e); + + } catch (InvalidKeyException e) { + throw new RuntimeException(e); + + } catch (NoSuchAlgorithmException e) { + throw new RuntimeException(e); + } + } + + /** + * Helper method to generate an OAuth Signature. + */ + private String generateSignature(ChallengeResponse response, Request request, + Series<Parameter> authParams) + throws NoSuchAlgorithmException, InvalidKeyException, IOException, + UnsupportedEncodingException { + + // HTTP Request Method + String httpMethod = request.getMethod().getName(); + + // Request Url + Reference url = request.getResourceRef(); + String requestUrl = encode(url.getScheme() + ":" + url.getHierarchicalPart()); + + // Normalized parameters + Series<Parameter> params = new Series<Parameter>(Parameter.class); + + // OAUTH Params + params.addAll(authParams); + + // Query Params + Form query = url.getQueryAsForm(); + params.addAll(query); + + // Sort it + Collections.sort(params); + + StringBuilder normalizedParamsBuilder = new StringBuilder(); + for (Parameter p : params) { + normalizedParamsBuilder.append('&'); + normalizedParamsBuilder.append(p.encode(CharacterSet.UTF_8)); + } + String normalizedParams = encode(normalizedParamsBuilder.substring(1)); // remove the first & + + // Generate signature base + String sigBase = httpMethod + "&" + requestUrl + "&" + normalizedParams.toString(); + + // Sign the signature base + Mac mac = Mac.getInstance(JAVA_SIGNATURE_METHOD); + + String accessTokenSecret = ""; + if (response.getIdentifier() != null) { + accessTokenSecret = new String(response.getSecret()); + } + + byte[] keyBytes = (encode(mConsumerToken.getSecret()) + "&" + encode(accessTokenSecret)).getBytes(ENCODING); + SecretKey key = new SecretKeySpec(keyBytes, JAVA_SIGNATURE_METHOD); + mac.init(key); + + byte[] signature = mac.doFinal(sigBase.getBytes(ENCODING)); + + return Base64.encode(signature, false).trim(); + } + + /** + * Helper method to URL Encode Strings. + */ + private String encode(String input) throws UnsupportedEncodingException { + return URLEncoder.encode(input, ENCODING); + } +} |
