// Copyright 2014 Michel Kraemer // // 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 de.undercouch.citeproc.helper.oauth; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.OutputStream; import java.net.HttpURLConnection; import java.net.URL; import java.net.URLEncoder; import java.util.HashMap; import java.util.Map; import javax.xml.bind.DatatypeConverter; import de.undercouch.citeproc.helper.json.JsonLexer; import de.undercouch.citeproc.helper.json.JsonParser; /** * Performs OAuth v2 authentication. Very minimal implementation, only used * for Mendeley authentication currently. * @author Michel Kraemer */ public class OAuth2 implements OAuth { private static final String ACCESS_TOKEN = "access_token"; private static final String AUTHORIZATION_CODE = "authorization_code"; private static final String BASIC = "Basic"; private static final String BEARER = "Bearer"; private static final String CODE = "code"; private static final String GRANT_TYPE = "grant_type"; private static final String REDIRECT_URI = "redirect_uri"; private static final String UTF8 = "UTF-8"; private static final String HEADER_AUTHORIZATION = "Authorization"; private static final String HEADER_CONTENT_LENGTH = "Content-Length"; private final String consumerKey; private final String consumerSecret; private final String redirectUri; /** * Creates a new OAuth client * @param consumerKey the consumer key * @param consumerSecret the consumer secret * @param redirectUri the location users are redirected to after * they granted access */ public OAuth2(String consumerKey, String consumerSecret, String redirectUri) { this.consumerKey = consumerKey; this.consumerSecret = consumerSecret; this.redirectUri = redirectUri; } @Override public Token requestTemporaryCredentials(URL url, Method method) throws IOException { throw new UnsupportedOperationException("OAuth 2 does not require " + "temporary credentials"); } @Override public Token requestTokenCredentials(URL url, Method method, Token temporaryCredentials, String verifier) throws IOException { //prepare body String body = ""; body += GRANT_TYPE + "=" + URLEncoder.encode(AUTHORIZATION_CODE, UTF8); body += "&" + CODE + "=" + URLEncoder.encode(verifier, UTF8); body += "&" + REDIRECT_URI + "=" + URLEncoder.encode(redirectUri, UTF8); //prepare Authorization header Map<String, String> headers = new HashMap<>(); String encodedUserPass = DatatypeConverter.printBase64Binary( (consumerKey + ":" + consumerSecret).getBytes(UTF8)); headers.put(HEADER_AUTHORIZATION, BASIC + " " + encodedUserPass); //perform request Response r = request(url, method, null, headers, body); //read response try (InputStream is = r.getInputStream()) { JsonParser parser = new JsonParser(new JsonLexer( new InputStreamReader(is, UTF8))); Map<String, Object> obj = parser.parseObject(); Object at = obj.get(ACCESS_TOKEN); if (at == null) { return null; } return new Token(at.toString(), at.toString()); } } @Override public Response request(URL url, Method method, Token token) throws IOException { return request(url, method, token, null); } @Override public Response request(URL url, Method method, Token token, Map<String, String> additionalHeaders) throws IOException { return request(url, method, token, additionalHeaders, null); } private Response request(URL url, Method method, Token token, Map<String, String> additionalHeaders, String body) throws IOException { //prepare HTTP connection HttpURLConnection conn = (HttpURLConnection)url.openConnection(); conn.setInstanceFollowRedirects(true); conn.setRequestMethod(method.toString()); if (additionalHeaders != null) { for (Map.Entry<String, String> e : additionalHeaders.entrySet()) { conn.setRequestProperty(e.getKey(), e.getValue()); } } if (token != null) { conn.setRequestProperty(HEADER_AUTHORIZATION, BEARER + " " + token.getSecret()); } //perform request if (body == null) { conn.connect(); } else { byte[] by = body.getBytes(); conn.setRequestProperty(HEADER_CONTENT_LENGTH, Integer.toString(by.length)); conn.setUseCaches(false); conn.setDoInput(true); conn.setDoOutput(true); try (OutputStream os = conn.getOutputStream()) { os.write(by); os.flush(); } } //check response if (conn.getResponseCode() == 401) { throw new UnauthorizedException("Not authorized"); } else if (conn.getResponseCode() != 200) { throw new RequestException("HTTP request failed with error code: " + conn.getResponseCode()); } return new Response(conn); } }