/*
* Copyright 2015 Open mHealth
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.openmhealth.shim;
import oauth.signpost.OAuth;
import org.apache.http.HttpResponse;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpRequestBase;
import org.apache.http.impl.client.HttpClients;
import org.springframework.data.domain.Sort;
import org.springframework.http.HttpMethod;
import org.springframework.web.client.HttpClientErrorException;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.net.URL;
import java.util.HashMap;
import java.util.Map;
import static org.springframework.data.domain.Sort.Direction.DESC;
/**
* Common code for all OAuth1.0 based shims.
*
* @author Danilo Bonilla
*/
public abstract class OAuth1ShimBase extends ShimBase implements OAuth1Shim {
private AccessParametersRepo accessParametersRepo;
protected HttpClient httpClient = HttpClients.createDefault();
private AuthorizationRequestParametersRepo authorizationRequestParametersRepo;
private ShimServerConfig shimServerConfig;
protected OAuth1ShimBase(ApplicationAccessParametersRepo applicationParametersRepo,
AuthorizationRequestParametersRepo authorizationRequestParametersRepo,
ShimServerConfig shimServerConfig,
AccessParametersRepo accessParametersRepo) {
super(applicationParametersRepo);
this.authorizationRequestParametersRepo = authorizationRequestParametersRepo;
this.shimServerConfig = shimServerConfig;
this.accessParametersRepo = accessParametersRepo;
}
@Override
@SuppressWarnings("unchecked")
public AuthorizationRequestParameters getAuthorizationRequestParameters(String username,
Map<String, String> additionalParameters)
throws ShimException {
String stateKey = OAuth1Utils.generateStateKey();
AccessParameters accessParams = accessParametersRepo
.findByUsernameAndShimKey(username, getShimKey(), new Sort(DESC, "dateCreated"));
if (accessParams != null && accessParams.getAccessToken() != null) {
return AuthorizationRequestParameters.authorized();
}
HttpRequestBase tokenRequest = null;
try {
String callbackUrl = shimServerConfig.getCallbackUrl(getShimKey(), stateKey);
Map<String, String> requestTokenParameters = new HashMap<>();
requestTokenParameters.put("oauth_callback", callbackUrl);
String initiateAuthUrl = getBaseRequestTokenUrl();
tokenRequest =
getRequestTokenRequest(initiateAuthUrl, null, null, requestTokenParameters);
HttpResponse httpResponse = httpClient.execute(tokenRequest);
Map<String, String> tokenParameters = OAuth1Utils.parseRequestTokenResponse(httpResponse);
String token = tokenParameters.get(OAuth.OAUTH_TOKEN);
String tokenSecret = tokenParameters.get(OAuth.OAUTH_TOKEN_SECRET);
if (tokenSecret == null) {
throw new ShimException("Request token could not be retrieved");
}
URL authorizeUrl = signUrl(getBaseAuthorizeUrl(), token, tokenSecret, null);
System.out.println("The authorization url is: ");
System.out.println(authorizeUrl);
/**
* Build the auth parameters entity to return
*/
AuthorizationRequestParameters parameters = new AuthorizationRequestParameters();
parameters.setUsername(username);
parameters.setRedirectUri(callbackUrl);
parameters.setStateKey(stateKey);
parameters.setAuthorizationUrl(authorizeUrl.toString());
parameters.setRequestParams(tokenParameters);
/**
* Store the parameters in a repo.
*/
authorizationRequestParametersRepo.save(parameters);
return parameters;
}
catch (HttpClientErrorException e) {
e.printStackTrace();
throw new ShimException("HTTP Error: " + e.getMessage());
}
catch (IOException e) {
e.printStackTrace();
throw new ShimException("Unable to initiate OAuth1 authorization, could not parse token parameters");
}
finally {
if (tokenRequest != null) {
tokenRequest.releaseConnection();
}
}
}
@Override
public AuthorizationResponse handleAuthorizationResponse(HttpServletRequest servletRequest) throws ShimException {
// Fetch the access token.
String stateKey = servletRequest.getParameter("state");
String requestToken = servletRequest.getParameter(OAuth.OAUTH_TOKEN);
final String requestVerifier = servletRequest.getParameter(OAuth.OAUTH_VERIFIER);
AuthorizationRequestParameters authParams = authorizationRequestParametersRepo.findByStateKey(stateKey);
if (authParams == null) {
throw new ShimException("Invalid state, could not find corresponding auth parameters");
}
// Get the token secret from the original access request.
String requestTokenSecret = authParams.getRequestParams().get(OAuth.OAUTH_TOKEN_SECRET);
HttpResponse response;
HttpRequestBase accessTokenRequest = null;
try {
accessTokenRequest = getAccessTokenRequest(getBaseTokenUrl(),
requestToken, requestTokenSecret, new HashMap<String, String>() {{
put(OAuth.OAUTH_VERIFIER, requestVerifier);
}});
response = httpClient.execute(accessTokenRequest);
}
catch (IOException e) {
e.printStackTrace();
throw new ShimException("Could not retrieve response from token URL");
}
finally {
if (accessTokenRequest != null) {
accessTokenRequest.releaseConnection();
}
}
Map<String, String> accessTokenParameters = OAuth1Utils.parseRequestTokenResponse(response);
String accessToken = accessTokenParameters.get(OAuth.OAUTH_TOKEN);
String accessTokenSecret = accessTokenParameters.get(OAuth.OAUTH_TOKEN_SECRET);
if (accessToken == null) {
throw new ShimException("Access token could not be retrieved");
}
ApplicationAccessParameters parameters = findApplicationAccessParameters();
AccessParameters accessParameters = new AccessParameters();
accessParameters.setClientId(parameters.getClientId());
accessParameters.setClientSecret(parameters.getClientSecret());
accessParameters.setStateKey(stateKey);
accessParameters.setUsername(authParams.getUsername());
accessParameters.setAccessToken(accessToken);
accessParameters.setTokenSecret(accessTokenSecret);
accessParameters.setAdditionalParameters(new HashMap<String, Object>() {{
put(OAuth.OAUTH_VERIFIER, requestVerifier);
}});
loadAdditionalAccessParameters(servletRequest, accessParameters);
return AuthorizationResponse.authorized(accessParameters);
}
protected void loadAdditionalAccessParameters(HttpServletRequest request, AccessParameters accessParameters) {
//noop, override if additional parameters must be set here
}
protected HttpRequestBase getSignedRequest(String unsignedUrl, String token, String tokenSecret,
Map<String, String> oauthParams)
throws ShimException {
ApplicationAccessParameters parameters = findApplicationAccessParameters();
return OAuth1Utils.getSignedRequest(unsignedUrl, parameters.getClientId(), parameters.getClientSecret(), token,
tokenSecret, oauthParams);
}
protected URL signUrl(String unsignedUrl, String token, String tokenSecret, Map<String, String> oauthParams)
throws ShimException {
ApplicationAccessParameters parameters = findApplicationAccessParameters();
return OAuth1Utils.buildSignedUrl(unsignedUrl, parameters.getClientId(), parameters.getClientSecret(), token,
tokenSecret, oauthParams);
}
/**
* Some external data providers require POST vs GET. In which case the signing of the requests may differ.
*
* @param unsignedUrl - The unsigned URL for the request.
* @param token - The request token or access token.
* @param tokenSecret - The token secret, if any.
* @param oauthParams - Any additional Oauth params.
* @return - The appropriate request, signed.
*/
protected HttpRequestBase getRequestTokenRequest(String unsignedUrl, String token, String tokenSecret,
Map<String, String> oauthParams)
throws ShimException {
if (HttpMethod.GET == getRequestTokenMethod()) {
return new HttpGet(signUrl(unsignedUrl, token, tokenSecret, oauthParams).toString());
}
else {
return getSignedRequest(unsignedUrl, token, tokenSecret, oauthParams);
}
}
/**
* NOTE: Same as getRequestTokenRequest with difference being that this is for access tokens.
* <p>
* Some external data providers require POST vs GET. In which case the signing of the requests may differ.
*
* @param unsignedUrl - The unsigned URL for the request.
* @param token - The request token or access token.
* @param tokenSecret - The token secret, if any.
* @param oauthParams - Any additional Oauth params.
* @return - The appropriate request, signed.
*/
protected HttpRequestBase getAccessTokenRequest(String unsignedUrl, String token, String tokenSecret,
Map<String, String> oauthParams)
throws ShimException {
if (HttpMethod.GET == getAccessTokenMethod()) {
return new HttpGet(signUrl(unsignedUrl, token, tokenSecret, oauthParams).toString());
}
else {
return getSignedRequest(unsignedUrl, token, tokenSecret, oauthParams);
}
}
protected HttpMethod getRequestTokenMethod() {
return HttpMethod.GET;
}
protected HttpMethod getAccessTokenMethod() {
return HttpMethod.GET;
}
}