From 12692e66cb04afcf2dfa16f38b5e9a9f0ff0ce69 Mon Sep 17 00:00:00 2001 From: Jesus Federico <jesus@123it.ca> Date: Fri, 29 Aug 2014 10:47:46 -0400 Subject: [PATCH] Updated OAuth library --- .../org/bigbluebutton/ToolController.groovy | 53 +- .../java/net/oauth/ConsumerProperties.java | 260 +-- bbb-lti/src/java/net/oauth/OAuth.java | 703 ++++---- bbb-lti/src/java/net/oauth/OAuthAccessor.java | 200 +-- bbb-lti/src/java/net/oauth/OAuthConsumer.java | 138 +- bbb-lti/src/java/net/oauth/OAuthMessage.java | 941 +++++------ .../java/net/oauth/OAuthProblemException.java | 209 ++- .../java/net/oauth/OAuthServiceProvider.java | 82 +- .../src/java/net/oauth/ParameterStyle.java | 64 +- .../java/net/oauth/SimpleOAuthValidator.java | 4 +- .../net/oauth/client/ExcerptInputStream.java | 166 +- .../java/net/oauth/client/OAuthClient.java | 632 ++++---- .../oauth/client/OAuthResponseMessage.java | 232 +-- .../net/oauth/client/URLConnectionClient.java | 244 +-- .../java/net/oauth/consumer.properties.sample | 32 +- .../src/java/net/oauth/http/HttpClient.java | 110 +- .../src/java/net/oauth/http/HttpMessage.java | 382 +++-- .../net/oauth/http/HttpMessageDecoder.java | 190 +-- .../net/oauth/http/HttpResponseMessage.java | 118 +- .../net/oauth/server/HttpRequestMessage.java | 184 +-- .../java/net/oauth/server/OAuthServlet.java | 317 ++-- .../src/java/net/oauth/signature/Base64.java | 1428 ++++++++--------- .../java/net/oauth/signature/HMAC_SHA1.java | 204 +-- .../oauth/signature/OAuthSignatureMethod.java | 1 - .../java/net/oauth/signature/PLAINTEXT.java | 128 +- .../java/net/oauth/signature/RSA_SHA1.java | 211 ++- .../net/oauth/signature/pem/Asn1Object.java | 300 ++-- .../net/oauth/signature/pem/DerParser.java | 340 ++-- .../net/oauth/signature/pem/PEMReader.java | 268 ++-- .../signature/pem/PKCS1EncodedKeySpec.java | 232 +-- 30 files changed, 4353 insertions(+), 4020 deletions(-) diff --git a/bbb-lti/grails-app/controllers/org/bigbluebutton/ToolController.groovy b/bbb-lti/grails-app/controllers/org/bigbluebutton/ToolController.groovy index e31091d955..36fcb2f105 100644 --- a/bbb-lti/grails-app/controllers/org/bigbluebutton/ToolController.groovy +++ b/bbb-lti/grails-app/controllers/org/bigbluebutton/ToolController.groovy @@ -57,7 +57,6 @@ class ToolController { def endPoint = (request.isSecure()?"https":"http") + "://" + ltiService.endPoint + "/" + grailsApplication.metadata['app.name'] + "/" + params.get("controller") + (params.get("format") != null? "." + params.get("format"): "") Map<String, String> result = new HashMap<String, String>() ArrayList<String> missingParams = new ArrayList<String>() - log.debug "Checking for required parameters" if (hasAllRequiredParams(params, missingParams)) { def sanitizedParams = sanitizePrametersForBaseString(params) @@ -283,7 +282,6 @@ class ToolController { * @return the key:val pairs needed for Basic LTI */ private Properties sanitizePrametersForBaseString(params) { - log.debug params Properties reqProp = new Properties(); for (String key : params.keySet()) { if (key == "action" || key == "controller" || key == "format") { @@ -299,7 +297,6 @@ class ToolController { reqProp.setProperty(key, params.get(key)); } - return reqProp } @@ -309,20 +306,22 @@ class ToolController { * @param missingParams - a list of missing parameters * @return - true if all required parameters have been passed in */ - private boolean hasAllRequiredParams(Object params, Object missingParams) { + private boolean hasAllRequiredParams(Map<String, String> params, ArrayList<String> missingParams) { + log.debug "Checking for required parameters" + boolean hasAllParams = true - if (! ((Map<String, String>)params).containsKey(Parameter.CONSUMER_ID)) { - ((ArrayList<String>)missingParams).add(Parameter.CONSUMER_ID); + if ( !params.containsKey(Parameter.CONSUMER_ID) ) { + missingParams.add(Parameter.CONSUMER_ID); hasAllParams = false; } - if (! ((Map<String, String>)params).containsKey(Parameter.OAUTH_SIGNATURE)) { - ((ArrayList<String>)missingParams).add(Parameter.OAUTH_SIGNATURE); + if ( !params.containsKey(Parameter.OAUTH_SIGNATURE)) { + missingParams.add(Parameter.OAUTH_SIGNATURE); hasAllParams = false; } - if (! ((Map<String, String>)params).containsKey(Parameter.RESOURCE_LINK_ID)) { - ((ArrayList<String>)missingParams).add(Parameter.RESOURCE_LINK_ID); + if ( !params.containsKey(Parameter.RESOURCE_LINK_ID) ) { + missingParams.add(Parameter.RESOURCE_LINK_ID); hasAllParams = false; } @@ -338,18 +337,28 @@ class ToolController { * @param signature - the passed in signature calculated from the client * @return - TRUE if the signatures matches the calculated signature */ - private boolean checkValidSignature(String method, String URL, String conSecret, Object postProp, String signature) { - log.debug( "Starting checkValidSignature()" ) - OAuthMessage oam = new OAuthMessage(method, URL, ((Properties)postProp).entrySet()) - log.debug( "OAuthMessage oam = " + oam.toString() ) - HMAC_SHA1 hmac = new HMAC_SHA1() - log.debug( "HMAC_SHA1 hmac = " + hmac.toString() ) - hmac.setConsumerSecret(conSecret) - - log.debug("Base Message String = [ " + hmac.getBaseString(oam) + " ]\n") - String calculatedSignature = hmac.getSignature(hmac.getBaseString(oam)) - log.debug("Calculated: " + calculatedSignature + " Received: " + signature) - return calculatedSignature.equals(signature) + private boolean checkValidSignature(String method, String url, String conSecret, Properties postProp, String signature) { + def validSignature = false + + try { + OAuthMessage oam = new OAuthMessage(method, url, postProp.entrySet()) + //log.debug "OAuthMessage oam = " + oam.toString() + + HMAC_SHA1 hmac = new HMAC_SHA1() + //log.debug "HMAC_SHA1 hmac = " + hmac.toString() + + hmac.setConsumerSecret(conSecret) + + log.debug "Base Message String = [ " + hmac.getBaseString(oam) + " ]\n" + String calculatedSignature = hmac.getSignature(hmac.getBaseString(oam)) + log.debug "Calculated: " + calculatedSignature + " Received: " + signature + + validSignature = calculatedSignature.equals(signature) + } catch( Exception e ) { + log.debug "Exception error: " + e.message + } + + return validSignature } private String getCartridgeXML(){ diff --git a/bbb-lti/src/java/net/oauth/ConsumerProperties.java b/bbb-lti/src/java/net/oauth/ConsumerProperties.java index 23dc2271cd..7da36daa8a 100644 --- a/bbb-lti/src/java/net/oauth/ConsumerProperties.java +++ b/bbb-lti/src/java/net/oauth/ConsumerProperties.java @@ -1,130 +1,130 @@ -/* - * Copyright 2007 Netflix, Inc. - * - * 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 net.oauth; - -import java.io.IOException; -import java.io.InputStream; -import java.net.MalformedURLException; -import java.net.URL; -import java.util.HashMap; -import java.util.Map; -import java.util.Properties; - -/** - * A pool of OAuthConsumers that are constructed from Properties. Each consumer - * has a name, which is a property of the OAuthConsumer. Other properties come - * from Properties whose names are prefixed with the consumer's name. For - * example, a consumer's credentials come from properties named - * [name].consumerKey and [name].consumerSecret. - * - * @author John Kristian - */ -public class ConsumerProperties { - - public static URL getResource(String name, ClassLoader loader) - throws IOException { - URL resource = loader.getResource(name); - if (resource == null) { - throw new IOException("resource not found: " + name); - } - return resource; - } - - public static Properties getProperties(URL source) throws IOException { - InputStream input = source.openStream(); - try { - Properties p = new Properties(); - p.load(input); - return p; - } finally { - input.close(); - } - } - - public ConsumerProperties(String resourceName, ClassLoader loader) - throws IOException { - this(getProperties(getResource(resourceName, loader))); - } - - public ConsumerProperties(Properties consumerProperties) { - this.consumerProperties = consumerProperties; - } - - private final Properties consumerProperties; - - private final Map<String, OAuthConsumer> pool = new HashMap<String, OAuthConsumer>(); - - /** Get the consumer with the given name. */ - public OAuthConsumer getConsumer(String name) throws MalformedURLException { - OAuthConsumer consumer; - synchronized (pool) { - consumer = pool.get(name); - } - if (consumer == null) { - consumer = newConsumer(name); - } - synchronized (pool) { - OAuthConsumer first = pool.get(name); - if (first == null) { - pool.put(name, consumer); - } else { - /* - * Another thread just constructed an identical OAuthConsumer. - * Use that one (and discard the one we just constructed). - */ - consumer = first; - } - } - return consumer; - } - - protected OAuthConsumer newConsumer(String name) - throws MalformedURLException { - String base = consumerProperties.getProperty(name - + ".serviceProvider.baseURL"); - URL baseURL = (base == null) ? null : new URL(base); - OAuthServiceProvider serviceProvider = new OAuthServiceProvider(getURL( - baseURL, name + ".serviceProvider.requestTokenURL"), getURL( - baseURL, name + ".serviceProvider.userAuthorizationURL"), - getURL(baseURL, name + ".serviceProvider.accessTokenURL")); - OAuthConsumer consumer = new OAuthConsumer(consumerProperties - .getProperty(name + ".callbackURL"), consumerProperties - .getProperty(name + ".consumerKey"), consumerProperties - .getProperty(name + ".consumerSecret"), serviceProvider); - consumer.setProperty("name", name); - if (baseURL != null) { - consumer.setProperty("serviceProvider.baseURL", baseURL); - } - for (Map.Entry prop : consumerProperties.entrySet()) { - String propName = (String) prop.getKey(); - if (propName.startsWith(name + ".consumer.")) { - String c = propName.substring(name.length() + 10); - consumer.setProperty(c, prop.getValue()); - } - } - return consumer; - } - - private String getURL(URL base, String name) throws MalformedURLException { - String url = consumerProperties.getProperty(name); - if (base != null) { - url = (new URL(base, url)).toExternalForm(); - } - return url; - } - -} +/* + * Copyright 2007 Netflix, Inc. + * + * 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 net.oauth; + +import java.io.IOException; +import java.io.InputStream; +import java.net.MalformedURLException; +import java.net.URL; +import java.util.HashMap; +import java.util.Map; +import java.util.Properties; + +/** + * A pool of OAuthConsumers that are constructed from Properties. Each consumer + * has a name, which is a property of the OAuthConsumer. Other properties come + * from Properties whose names are prefixed with the consumer's name. For + * example, a consumer's credentials come from properties named + * [name].consumerKey and [name].consumerSecret. + * + * @author John Kristian + */ +public class ConsumerProperties { + + public static URL getResource(String name, ClassLoader loader) + throws IOException { + URL resource = loader.getResource(name); + if (resource == null) { + throw new IOException("resource not found: " + name); + } + return resource; + } + + public static Properties getProperties(URL source) throws IOException { + InputStream input = source.openStream(); + try { + Properties p = new Properties(); + p.load(input); + return p; + } finally { + input.close(); + } + } + + public ConsumerProperties(String resourceName, ClassLoader loader) + throws IOException { + this(getProperties(getResource(resourceName, loader))); + } + + public ConsumerProperties(Properties consumerProperties) { + this.consumerProperties = consumerProperties; + } + + private final Properties consumerProperties; + + private final Map<String, OAuthConsumer> pool = new HashMap<String, OAuthConsumer>(); + + /** Get the consumer with the given name. */ + public OAuthConsumer getConsumer(String name) throws MalformedURLException { + OAuthConsumer consumer; + synchronized (pool) { + consumer = pool.get(name); + } + if (consumer == null) { + consumer = newConsumer(name); + } + synchronized (pool) { + OAuthConsumer first = pool.get(name); + if (first == null) { + pool.put(name, consumer); + } else { + /* + * Another thread just constructed an identical OAuthConsumer. + * Use that one (and discard the one we just constructed). + */ + consumer = first; + } + } + return consumer; + } + + protected OAuthConsumer newConsumer(String name) + throws MalformedURLException { + String base = consumerProperties.getProperty(name + + ".serviceProvider.baseURL"); + URL baseURL = (base == null) ? null : new URL(base); + OAuthServiceProvider serviceProvider = new OAuthServiceProvider(getURL( + baseURL, name + ".serviceProvider.requestTokenURL"), getURL( + baseURL, name + ".serviceProvider.userAuthorizationURL"), + getURL(baseURL, name + ".serviceProvider.accessTokenURL")); + OAuthConsumer consumer = new OAuthConsumer(consumerProperties + .getProperty(name + ".callbackURL"), consumerProperties + .getProperty(name + ".consumerKey"), consumerProperties + .getProperty(name + ".consumerSecret"), serviceProvider); + consumer.setProperty("name", name); + if (baseURL != null) { + consumer.setProperty("serviceProvider.baseURL", baseURL); + } + for (Map.Entry prop : consumerProperties.entrySet()) { + String propName = (String) prop.getKey(); + if (propName.startsWith(name + ".consumer.")) { + String c = propName.substring(name.length() + 10); + consumer.setProperty(c, prop.getValue()); + } + } + return consumer; + } + + private String getURL(URL base, String name) throws MalformedURLException { + String url = consumerProperties.getProperty(name); + if (base != null) { + url = (new URL(base, url)).toExternalForm(); + } + return url; + } + +} diff --git a/bbb-lti/src/java/net/oauth/OAuth.java b/bbb-lti/src/java/net/oauth/OAuth.java index ea71b5d867..e042aa3319 100644 --- a/bbb-lti/src/java/net/oauth/OAuth.java +++ b/bbb-lti/src/java/net/oauth/OAuth.java @@ -1,351 +1,352 @@ -/* - * Copyright 2007 Netflix, Inc. - * - * 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 net.oauth; - -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.OutputStream; -import java.io.UnsupportedEncodingException; -import java.net.URLDecoder; -import java.net.URLEncoder; -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -/** - * @author John Kristian - */ -public class OAuth { - - public static final String VERSION_1_0 = "1.0"; - - /** The encoding used to represent characters as bytes. */ - public static final String ENCODING = "UTF-8"; - - /** The MIME type for a sequence of OAuth parameters. */ - public static final String FORM_ENCODED = "application/x-www-form-urlencoded"; - - public static final String OAUTH_CONSUMER_KEY = "oauth_consumer_key"; - public static final String OAUTH_TOKEN = "oauth_token"; - public static final String OAUTH_TOKEN_SECRET = "oauth_token_secret"; - public static final String OAUTH_SIGNATURE_METHOD = "oauth_signature_method"; - public static final String OAUTH_SIGNATURE = "oauth_signature"; - public static final String OAUTH_TIMESTAMP = "oauth_timestamp"; - public static final String OAUTH_NONCE = "oauth_nonce"; - public static final String OAUTH_VERSION = "oauth_version"; - public static final String OAUTH_CALLBACK = "oauth_callback"; - - public static final String HMAC_SHA1 = "HMAC-SHA1"; - public static final String RSA_SHA1 = "RSA-SHA1"; - - /** - * Strings used for <a href="http://wiki.oauth.net/ProblemReporting">problem - * reporting</a>. - */ - public static class Problems { - public static final String VERSION_REJECTED = "version_rejected"; - public static final String PARAMETER_ABSENT = "parameter_absent"; - public static final String PARAMETER_REJECTED = "parameter_rejected"; - public static final String TIMESTAMP_REFUSED = "timestamp_refused"; - public static final String NONCE_USED = "nonce_used"; - public static final String SIGNATURE_METHOD_REJECTED = "signature_method_rejected"; - public static final String SIGNATURE_INVALID = "signature_invalid"; - public static final String CONSUMER_KEY_UNKNOWN = "consumer_key_unknown"; - public static final String CONSUMER_KEY_REJECTED = "consumer_key_rejected"; - public static final String CONSUMER_KEY_REFUSED = "consumer_key_refused"; - public static final String TOKEN_USED = "token_used"; - public static final String TOKEN_EXPIRED = "token_expired"; - public static final String TOKEN_REVOKED = "token_revoked"; - public static final String TOKEN_REJECTED = "token_rejected"; - public static final String ADDITIONAL_AUTHORIZATION_REQUIRED = "additional_authorization_required"; - public static final String PERMISSION_UNKNOWN = "permission_unknown"; - public static final String PERMISSION_DENIED = "permission_denied"; - public static final String USER_REFUSED = "user_refused"; - - public static final String OAUTH_ACCEPTABLE_VERSIONS = "oauth_acceptable_versions"; - public static final String OAUTH_ACCEPTABLE_TIMESTAMPS = "oauth_acceptable_timestamps"; - public static final String OAUTH_PARAMETERS_ABSENT = "oauth_parameters_absent"; - public static final String OAUTH_PARAMETERS_REJECTED = "oauth_parameters_rejected"; - public static final String OAUTH_PROBLEM_ADVICE = "oauth_problem_advice"; - - /** - * A map from an <a - * href="http://wiki.oauth.net/ProblemReporting">oauth_problem</a> value to - * the appropriate HTTP response code. - */ - public static final Map<String, Integer> TO_HTTP_CODE = mapToHttpCode(); - - private static Map<String, Integer> mapToHttpCode() { - Integer badRequest = new Integer(400); - Integer unauthorized = new Integer(401); - Integer serviceUnavailable = new Integer(503); - Map<String, Integer> map = new HashMap<String, Integer>(); - - map.put(Problems.VERSION_REJECTED, badRequest); - map.put(Problems.PARAMETER_ABSENT, badRequest); - map.put(Problems.PARAMETER_REJECTED, badRequest); - map.put(Problems.TIMESTAMP_REFUSED, badRequest); - map.put(Problems.SIGNATURE_METHOD_REJECTED, badRequest); - - map.put(Problems.NONCE_USED, unauthorized); - map.put(Problems.TOKEN_USED, unauthorized); - map.put(Problems.TOKEN_EXPIRED, unauthorized); - map.put(Problems.TOKEN_REVOKED, unauthorized); - map.put(Problems.TOKEN_REJECTED, unauthorized); - map.put("token_not_authorized", unauthorized); - map.put(Problems.SIGNATURE_INVALID, unauthorized); - map.put(Problems.CONSUMER_KEY_UNKNOWN, unauthorized); - map.put(Problems.CONSUMER_KEY_REJECTED, unauthorized); - map.put(Problems.ADDITIONAL_AUTHORIZATION_REQUIRED, unauthorized); - map.put(Problems.PERMISSION_UNKNOWN, unauthorized); - map.put(Problems.PERMISSION_DENIED, unauthorized); - - map.put(Problems.USER_REFUSED, serviceUnavailable); - map.put(Problems.CONSUMER_KEY_REFUSED, serviceUnavailable); - return Collections.unmodifiableMap(map); - } - - } - - /** Return true if the given Content-Type header means FORM_ENCODED. */ - public static boolean isFormEncoded(String contentType) { - if (contentType == null) { - return false; - } - int semi = contentType.indexOf(";"); - if (semi >= 0) { - contentType = contentType.substring(0, semi); - } - return FORM_ENCODED.equalsIgnoreCase(contentType.trim()); - } - - /** - * Construct a form-urlencoded document containing the given sequence of - * name/value pairs. Use OAuth percent encoding (not exactly the encoding - * mandated by HTTP). - */ - public static String formEncode(Iterable<? extends Map.Entry> parameters) - throws IOException { - ByteArrayOutputStream b = new ByteArrayOutputStream(); - formEncode(parameters, b); - return new String(b.toByteArray()); - } - - /** - * Write a form-urlencoded document into the given stream, containing the - * given sequence of name/value pairs. - */ - public static void formEncode(Iterable<? extends Map.Entry> parameters, - OutputStream into) throws IOException { - if (parameters != null) { - boolean first = true; - for (Map.Entry parameter : parameters) { - if (first) { - first = false; - } else { - into.write('&'); - } - into.write(percentEncode(toString(parameter.getKey())) - .getBytes()); - into.write('='); - into.write(percentEncode(toString(parameter.getValue())) - .getBytes()); - } - } - } - - /** Parse a form-urlencoded document. */ - public static List<Parameter> decodeForm(String form) { - List<Parameter> list = new ArrayList<Parameter>(); - if (!isEmpty(form)) { - for (String nvp : form.split("\\&")) { - int equals = nvp.indexOf('='); - String name; - String value; - if (equals < 0) { - name = decodePercent(nvp); - value = null; - } else { - name = decodePercent(nvp.substring(0, equals)); - value = decodePercent(nvp.substring(equals + 1)); - } - list.add(new Parameter(name, value)); - } - } - return list; - } - - /** Construct a &-separated list of the given values, percentEncoded. */ - public static String percentEncode(Iterable values) { - StringBuilder p = new StringBuilder(); - for (Object v : values) { - if (p.length() > 0) { - p.append("&"); - } - p.append(OAuth.percentEncode(toString(v))); - } - return p.toString(); - } - - public static String percentEncode(String s) { - if (s == null) { - return ""; - } - try { - return URLEncoder.encode(s, ENCODING) - // OAuth encodes some characters differently: - .replace("+", "%20").replace("*", "%2A") - .replace("%7E", "~"); - // This could be done faster with more hand-crafted code. - } catch (UnsupportedEncodingException wow) { - throw new RuntimeException(wow.getMessage(), wow); - } - } - - public static String decodePercent(String s) { - try { - return URLDecoder.decode(s, ENCODING); - // This implements http://oauth.pbwiki.com/FlexibleDecoding - } catch (java.io.UnsupportedEncodingException wow) { - throw new RuntimeException(wow.getMessage(), wow); - } - } - - /** - * Construct a Map containing a copy of the given parameters. If several - * parameters have the same name, the Map will contain the first value, - * only. - */ - public static Map<String, String> newMap(Iterable<? extends Map.Entry> from) { - Map<String, String> map = new HashMap<String, String>(); - if (from != null) { - for (Map.Entry f : from) { - String key = toString(f.getKey()); - if (!map.containsKey(key)) { - map.put(key, toString(f.getValue())); - } - } - } - return map; - } - - /** Construct a list of Parameters from name, value, name, value... */ - public static List<Parameter> newList(String... parameters) { - List<Parameter> list = new ArrayList<Parameter>(parameters.length / 2); - for (int p = 0; p + 1 < parameters.length; p += 2) { - list.add(new Parameter(parameters[p], parameters[p + 1])); - } - return list; - } - - /** A name/value pair. */ - public static class Parameter implements Map.Entry<String, String> { - - public Parameter(String key, String value) { - this.key = key; - this.value = value; - } - - private final String key; - - private String value; - - public String getKey() { - return key; - } - - public String getValue() { - return value; - } - - public String setValue(String value) { - try { - return this.value; - } finally { - this.value = value; - } - } - - @Override - public String toString() { - return percentEncode(getKey()) + '=' + percentEncode(getValue()); - } - - @Override - public int hashCode() - { - final int prime = 31; - int result = 1; - result = prime * result + ((key == null) ? 0 : key.hashCode()); - result = prime * result + ((value == null) ? 0 : value.hashCode()); - return result; - } - - @Override - public boolean equals(Object obj) - { - if (this == obj) - return true; - if (obj == null) - return false; - if (getClass() != obj.getClass()) - return false; - final Parameter that = (Parameter) obj; - if (key == null) { - if (that.key != null) - return false; - } else if (!key.equals(that.key)) - return false; - if (value == null) { - if (that.value != null) - return false; - } else if (!value.equals(that.value)) - return false; - return true; - } - } - - private static final String toString(Object from) { - return (from == null) ? null : from.toString(); - } - - /** - * Construct a URL like the given one, but with the given parameters added - * to its query string. - */ - public static String addParameters(String url, String... parameters) - throws IOException { - return addParameters(url, newList(parameters)); - } - - public static String addParameters(String url, - Iterable<? extends Map.Entry<String, String>> parameters) - throws IOException { - String form = formEncode(parameters); - if (form == null || form.length() <= 0) { - return url; - } else { - return url + ((url.indexOf("?") < 0) ? '?' : '&') + form; - } - } - - public static boolean isEmpty(String str) { - return (str == null) || (str.length() == 0); - } -} +/* + * Copyright 2007 Netflix, Inc. + * + * 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 net.oauth; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.io.UnsupportedEncodingException; +import java.net.URLDecoder; +import java.net.URLEncoder; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * @author John Kristian + */ +public class OAuth { + + public static final String VERSION_1_0 = "1.0"; + + /** The encoding used to represent characters as bytes. */ + public static final String ENCODING = "UTF-8"; + + /** The MIME type for a sequence of OAuth parameters. */ + public static final String FORM_ENCODED = "application/x-www-form-urlencoded"; + + public static final String OAUTH_CONSUMER_KEY = "oauth_consumer_key"; + public static final String OAUTH_TOKEN = "oauth_token"; + public static final String OAUTH_TOKEN_SECRET = "oauth_token_secret"; + public static final String OAUTH_SIGNATURE_METHOD = "oauth_signature_method"; + public static final String OAUTH_SIGNATURE = "oauth_signature"; + public static final String OAUTH_TIMESTAMP = "oauth_timestamp"; + public static final String OAUTH_NONCE = "oauth_nonce"; + public static final String OAUTH_VERSION = "oauth_version"; + public static final String OAUTH_CALLBACK = "oauth_callback"; + public static final String OAUTH_BODY_HASH = "oauth_body_hash"; + + public static final String HMAC_SHA1 = "HMAC-SHA1"; + public static final String RSA_SHA1 = "RSA-SHA1"; + + /** + * Strings used for <a href="http://wiki.oauth.net/ProblemReporting">problem + * reporting</a>. + */ + public static class Problems { + public static final String VERSION_REJECTED = "version_rejected"; + public static final String PARAMETER_ABSENT = "parameter_absent"; + public static final String PARAMETER_REJECTED = "parameter_rejected"; + public static final String TIMESTAMP_REFUSED = "timestamp_refused"; + public static final String NONCE_USED = "nonce_used"; + public static final String SIGNATURE_METHOD_REJECTED = "signature_method_rejected"; + public static final String SIGNATURE_INVALID = "signature_invalid"; + public static final String CONSUMER_KEY_UNKNOWN = "consumer_key_unknown"; + public static final String CONSUMER_KEY_REJECTED = "consumer_key_rejected"; + public static final String CONSUMER_KEY_REFUSED = "consumer_key_refused"; + public static final String TOKEN_USED = "token_used"; + public static final String TOKEN_EXPIRED = "token_expired"; + public static final String TOKEN_REVOKED = "token_revoked"; + public static final String TOKEN_REJECTED = "token_rejected"; + public static final String ADDITIONAL_AUTHORIZATION_REQUIRED = "additional_authorization_required"; + public static final String PERMISSION_UNKNOWN = "permission_unknown"; + public static final String PERMISSION_DENIED = "permission_denied"; + public static final String USER_REFUSED = "user_refused"; + + public static final String OAUTH_ACCEPTABLE_VERSIONS = "oauth_acceptable_versions"; + public static final String OAUTH_ACCEPTABLE_TIMESTAMPS = "oauth_acceptable_timestamps"; + public static final String OAUTH_PARAMETERS_ABSENT = "oauth_parameters_absent"; + public static final String OAUTH_PARAMETERS_REJECTED = "oauth_parameters_rejected"; + public static final String OAUTH_PROBLEM_ADVICE = "oauth_problem_advice"; + + /** + * A map from an <a + * href="http://wiki.oauth.net/ProblemReporting">oauth_problem</a> value to + * the appropriate HTTP response code. + */ + public static final Map<String, Integer> TO_HTTP_CODE = mapToHttpCode(); + + private static Map<String, Integer> mapToHttpCode() { + Integer badRequest = new Integer(400); + Integer unauthorized = new Integer(401); + Integer serviceUnavailable = new Integer(503); + Map<String, Integer> map = new HashMap<String, Integer>(); + + map.put(Problems.VERSION_REJECTED, badRequest); + map.put(Problems.PARAMETER_ABSENT, badRequest); + map.put(Problems.PARAMETER_REJECTED, badRequest); + map.put(Problems.TIMESTAMP_REFUSED, badRequest); + map.put(Problems.SIGNATURE_METHOD_REJECTED, badRequest); + + map.put(Problems.NONCE_USED, unauthorized); + map.put(Problems.TOKEN_USED, unauthorized); + map.put(Problems.TOKEN_EXPIRED, unauthorized); + map.put(Problems.TOKEN_REVOKED, unauthorized); + map.put(Problems.TOKEN_REJECTED, unauthorized); + map.put("token_not_authorized", unauthorized); + map.put(Problems.SIGNATURE_INVALID, unauthorized); + map.put(Problems.CONSUMER_KEY_UNKNOWN, unauthorized); + map.put(Problems.CONSUMER_KEY_REJECTED, unauthorized); + map.put(Problems.ADDITIONAL_AUTHORIZATION_REQUIRED, unauthorized); + map.put(Problems.PERMISSION_UNKNOWN, unauthorized); + map.put(Problems.PERMISSION_DENIED, unauthorized); + + map.put(Problems.USER_REFUSED, serviceUnavailable); + map.put(Problems.CONSUMER_KEY_REFUSED, serviceUnavailable); + return Collections.unmodifiableMap(map); + } + + } + + /** Return true if the given Content-Type header means FORM_ENCODED. */ + public static boolean isFormEncoded(String contentType) { + if (contentType == null) { + return false; + } + int semi = contentType.indexOf(";"); + if (semi >= 0) { + contentType = contentType.substring(0, semi); + } + return FORM_ENCODED.equalsIgnoreCase(contentType.trim()); + } + + /** + * Construct a form-urlencoded document containing the given sequence of + * name/value pairs. Use OAuth percent encoding (not exactly the encoding + * mandated by HTTP). + */ + public static String formEncode(Iterable<? extends Map.Entry> parameters) + throws IOException { + ByteArrayOutputStream b = new ByteArrayOutputStream(); + formEncode(parameters, b); + return new String(b.toByteArray()); + } + + /** + * Write a form-urlencoded document into the given stream, containing the + * given sequence of name/value pairs. + */ + public static void formEncode(Iterable<? extends Map.Entry> parameters, + OutputStream into) throws IOException { + if (parameters != null) { + boolean first = true; + for (Map.Entry parameter : parameters) { + if (first) { + first = false; + } else { + into.write('&'); + } + into.write(percentEncode(toString(parameter.getKey())) + .getBytes()); + into.write('='); + into.write(percentEncode(toString(parameter.getValue())) + .getBytes()); + } + } + } + + /** Parse a form-urlencoded document. */ + public static List<Parameter> decodeForm(String form) { + List<Parameter> list = new ArrayList<Parameter>(); + if (!isEmpty(form)) { + for (String nvp : form.split("\\&")) { + int equals = nvp.indexOf('='); + String name; + String value; + if (equals < 0) { + name = decodePercent(nvp); + value = null; + } else { + name = decodePercent(nvp.substring(0, equals)); + value = decodePercent(nvp.substring(equals + 1)); + } + list.add(new Parameter(name, value)); + } + } + return list; + } + + /** Construct a &-separated list of the given values, percentEncoded. */ + public static String percentEncode(Iterable values) { + StringBuilder p = new StringBuilder(); + for (Object v : values) { + if (p.length() > 0) { + p.append("&"); + } + p.append(OAuth.percentEncode(toString(v))); + } + return p.toString(); + } + + public static String percentEncode(String s) { + if (s == null) { + return ""; + } + try { + return URLEncoder.encode(s, ENCODING) + // OAuth encodes some characters differently: + .replace("+", "%20").replace("*", "%2A") + .replace("%7E", "~"); + // This could be done faster with more hand-crafted code. + } catch (UnsupportedEncodingException wow) { + throw new RuntimeException(wow.getMessage(), wow); + } + } + + public static String decodePercent(String s) { + try { + return URLDecoder.decode(s, ENCODING); + // This implements http://oauth.pbwiki.com/FlexibleDecoding + } catch (java.io.UnsupportedEncodingException wow) { + throw new RuntimeException(wow.getMessage(), wow); + } + } + + /** + * Construct a Map containing a copy of the given parameters. If several + * parameters have the same name, the Map will contain the first value, + * only. + */ + public static Map<String, String> newMap(Iterable<? extends Map.Entry> from) { + Map<String, String> map = new HashMap<String, String>(); + if (from != null) { + for (Map.Entry f : from) { + String key = toString(f.getKey()); + if (!map.containsKey(key)) { + map.put(key, toString(f.getValue())); + } + } + } + return map; + } + + /** Construct a list of Parameters from name, value, name, value... */ + public static List<Parameter> newList(String... parameters) { + List<Parameter> list = new ArrayList<Parameter>(parameters.length / 2); + for (int p = 0; p + 1 < parameters.length; p += 2) { + list.add(new Parameter(parameters[p], parameters[p + 1])); + } + return list; + } + + /** A name/value pair. */ + public static class Parameter implements Map.Entry<String, String> { + + public Parameter(String key, String value) { + this.key = key; + this.value = value; + } + + private final String key; + + private String value; + + public String getKey() { + return key; + } + + public String getValue() { + return value; + } + + public String setValue(String value) { + try { + return this.value; + } finally { + this.value = value; + } + } + + @Override + public String toString() { + return percentEncode(getKey()) + '=' + percentEncode(getValue()); + } + + @Override + public int hashCode() + { + final int prime = 31; + int result = 1; + result = prime * result + ((key == null) ? 0 : key.hashCode()); + result = prime * result + ((value == null) ? 0 : value.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) + { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + final Parameter that = (Parameter) obj; + if (key == null) { + if (that.key != null) + return false; + } else if (!key.equals(that.key)) + return false; + if (value == null) { + if (that.value != null) + return false; + } else if (!value.equals(that.value)) + return false; + return true; + } + } + + private static final String toString(Object from) { + return (from == null) ? null : from.toString(); + } + + /** + * Construct a URL like the given one, but with the given parameters added + * to its query string. + */ + public static String addParameters(String url, String... parameters) + throws IOException { + return addParameters(url, newList(parameters)); + } + + public static String addParameters(String url, + Iterable<? extends Map.Entry<String, String>> parameters) + throws IOException { + String form = formEncode(parameters); + if (form == null || form.length() <= 0) { + return url; + } else { + return url + ((url.indexOf("?") < 0) ? '?' : '&') + form; + } + } + + public static boolean isEmpty(String str) { + return (str == null) || (str.length() == 0); + } +} diff --git a/bbb-lti/src/java/net/oauth/OAuthAccessor.java b/bbb-lti/src/java/net/oauth/OAuthAccessor.java index b2545eb7cb..198fc84f13 100644 --- a/bbb-lti/src/java/net/oauth/OAuthAccessor.java +++ b/bbb-lti/src/java/net/oauth/OAuthAccessor.java @@ -1,100 +1,100 @@ -/* - * Copyright 2007 Netflix, Inc. - * - * 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 net.oauth; - -import java.io.IOException; -import java.io.InputStream; -import java.io.Serializable; -import java.net.URISyntaxException; -import java.util.Collection; -import java.util.HashMap; -import java.util.Map; - -/** - * Properties of one User of an OAuthConsumer. Properties may be added freely, - * e.g. to support extensions. - * - * @author John Kristian - */ -public class OAuthAccessor implements Cloneable, Serializable { - - private static final long serialVersionUID = 5590788443138352999L; - - public final OAuthConsumer consumer; - public String requestToken; - public String accessToken; - public String tokenSecret; - - public OAuthAccessor(OAuthConsumer consumer) { - this.consumer = consumer; - this.requestToken = null; - this.accessToken = null; - this.tokenSecret = null; - } - - private final Map<String, Object> properties = new HashMap<String, Object>(); - - @Override - public OAuthAccessor clone() { - try { - return (OAuthAccessor) super.clone(); - } catch (CloneNotSupportedException e) { - throw new RuntimeException(e); - } - } - - public Object getProperty(String name) { - return properties.get(name); - } - - public void setProperty(String name, Object value) { - properties.put(name, value); - } - - /** - * Construct a request message containing the given parameters but no body. - * Don't send the message, merely construct it. The caller will ordinarily - * send it, for example by calling OAuthClient.invoke or access. - * - * @param method - * the HTTP request method. If this is null, use the default - * method; that is getProperty("httpMethod") or (if that's null) - * consumer.getProperty("httpMethod") or (if that's null) - * OAuthMessage.GET. - */ - public OAuthMessage newRequestMessage(String method, String url, Collection<? extends Map.Entry> parameters, - InputStream body) throws OAuthException, IOException, URISyntaxException { - if (method == null) { - method = (String) this.getProperty("httpMethod"); - if (method == null) { - method = (String) this.consumer.getProperty("httpMethod"); - if (method == null) { - method = OAuthMessage.GET; - } - } - } - OAuthMessage message = new OAuthMessage(method, url, parameters, body); - message.addRequiredParameters(this); - return message; - } - - public OAuthMessage newRequestMessage(String method, String url, Collection<? extends Map.Entry> parameters) - throws OAuthException, IOException, URISyntaxException { - return newRequestMessage(method, url, parameters, null); - } - -} +/* + * Copyright 2007 Netflix, Inc. + * + * 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 net.oauth; + +import java.io.IOException; +import java.io.InputStream; +import java.io.Serializable; +import java.net.URISyntaxException; +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; + +/** + * Properties of one User of an OAuthConsumer. Properties may be added freely, + * e.g. to support extensions. + * + * @author John Kristian + */ +public class OAuthAccessor implements Cloneable, Serializable { + + private static final long serialVersionUID = 5590788443138352999L; + + public final OAuthConsumer consumer; + public String requestToken; + public String accessToken; + public String tokenSecret; + + public OAuthAccessor(OAuthConsumer consumer) { + this.consumer = consumer; + this.requestToken = null; + this.accessToken = null; + this.tokenSecret = null; + } + + private final Map<String, Object> properties = new HashMap<String, Object>(); + + @Override + public OAuthAccessor clone() { + try { + return (OAuthAccessor) super.clone(); + } catch (CloneNotSupportedException e) { + throw new RuntimeException(e); + } + } + + public Object getProperty(String name) { + return properties.get(name); + } + + public void setProperty(String name, Object value) { + properties.put(name, value); + } + + /** + * Construct a request message containing the given parameters but no body. + * Don't send the message, merely construct it. The caller will ordinarily + * send it, for example by calling OAuthClient.invoke or access. + * + * @param method + * the HTTP request method. If this is null, use the default + * method; that is getProperty("httpMethod") or (if that's null) + * consumer.getProperty("httpMethod") or (if that's null) + * OAuthMessage.GET. + */ + public OAuthMessage newRequestMessage(String method, String url, Collection<? extends Map.Entry> parameters, + InputStream body) throws OAuthException, IOException, URISyntaxException { + if (method == null) { + method = (String) this.getProperty("httpMethod"); + if (method == null) { + method = (String) this.consumer.getProperty("httpMethod"); + if (method == null) { + method = OAuthMessage.GET; + } + } + } + OAuthMessage message = new OAuthMessage(method, url, parameters, body); + message.addRequiredParameters(this); + return message; + } + + public OAuthMessage newRequestMessage(String method, String url, Collection<? extends Map.Entry> parameters) + throws OAuthException, IOException, URISyntaxException { + return newRequestMessage(method, url, parameters, null); + } + +} diff --git a/bbb-lti/src/java/net/oauth/OAuthConsumer.java b/bbb-lti/src/java/net/oauth/OAuthConsumer.java index 94d7809f12..d79a2d418a 100644 --- a/bbb-lti/src/java/net/oauth/OAuthConsumer.java +++ b/bbb-lti/src/java/net/oauth/OAuthConsumer.java @@ -1,69 +1,69 @@ -/* - * Copyright 2007 Netflix, Inc. - * - * 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 net.oauth; - -import java.io.Serializable; -import java.util.HashMap; -import java.util.Map; -import net.oauth.http.HttpMessage; - -/** - * Properties of an OAuth Consumer. Properties may be added freely, e.g. to - * support extensions. - * - * @author John Kristian - */ -public class OAuthConsumer implements Serializable { - - private static final long serialVersionUID = -2258581186977818580L; - - public final String callbackURL; - public final String consumerKey; - public final String consumerSecret; - public final OAuthServiceProvider serviceProvider; - - public OAuthConsumer(String callbackURL, String consumerKey, - String consumerSecret, OAuthServiceProvider serviceProvider) { - this.callbackURL = callbackURL; - this.consumerKey = consumerKey; - this.consumerSecret = consumerSecret; - this.serviceProvider = serviceProvider; - } - - private final Map<String, Object> properties = new HashMap<String, Object>(); - - public Object getProperty(String name) { - return properties.get(name); - } - - public void setProperty(String name, Object value) { - properties.put(name, value); - } - - /** - * The name of the property whose value is the Accept-Encoding header in - * HTTP requests. - */ - public static final String ACCEPT_ENCODING = "HTTP.header." + HttpMessage.ACCEPT_ENCODING; - - /** - * The name of the property whose value is the <a - * href="http://oauth.pbwiki.com/AccessorSecret">Accessor Secret</a>. - */ - public static final String ACCESSOR_SECRET = "oauth_accessor_secret"; - -} +/* + * Copyright 2007 Netflix, Inc. + * + * 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 net.oauth; + +import java.io.Serializable; +import java.util.HashMap; +import java.util.Map; +import net.oauth.http.HttpMessage; + +/** + * Properties of an OAuth Consumer. Properties may be added freely, e.g. to + * support extensions. + * + * @author John Kristian + */ +public class OAuthConsumer implements Serializable { + + private static final long serialVersionUID = -2258581186977818580L; + + public final String callbackURL; + public final String consumerKey; + public final String consumerSecret; + public final OAuthServiceProvider serviceProvider; + + public OAuthConsumer(String callbackURL, String consumerKey, + String consumerSecret, OAuthServiceProvider serviceProvider) { + this.callbackURL = callbackURL; + this.consumerKey = consumerKey; + this.consumerSecret = consumerSecret; + this.serviceProvider = serviceProvider; + } + + private final Map<String, Object> properties = new HashMap<String, Object>(); + + public Object getProperty(String name) { + return properties.get(name); + } + + public void setProperty(String name, Object value) { + properties.put(name, value); + } + + /** + * The name of the property whose value is the Accept-Encoding header in + * HTTP requests. + */ + public static final String ACCEPT_ENCODING = "HTTP.header." + HttpMessage.ACCEPT_ENCODING; + + /** + * The name of the property whose value is the <a + * href="http://oauth.pbwiki.com/AccessorSecret">Accessor Secret</a>. + */ + public static final String ACCESSOR_SECRET = "oauth_accessor_secret"; + +} diff --git a/bbb-lti/src/java/net/oauth/OAuthMessage.java b/bbb-lti/src/java/net/oauth/OAuthMessage.java index a1efd88e2a..6c9fc29955 100644 --- a/bbb-lti/src/java/net/oauth/OAuthMessage.java +++ b/bbb-lti/src/java/net/oauth/OAuthMessage.java @@ -1,456 +1,485 @@ -/* - * Copyright 2007, 2008 Netflix, Inc. - * - * 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 net.oauth; - -import java.io.ByteArrayInputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.io.Reader; -import java.net.URISyntaxException; -import java.net.URL; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.HashMap; -import java.util.Iterator; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.regex.Matcher; -import java.util.regex.Pattern; -import net.oauth.ParameterStyle; -import net.oauth.http.HttpMessage; -import net.oauth.signature.OAuthSignatureMethod; - -/** - * A request or response message used in the OAuth protocol. - * <p> - * The parameters in this class are not percent-encoded. Methods like - * OAuthClient.invoke and OAuthResponseMessage.completeParameters are - * responsible for percent-encoding parameters before transmission and decoding - * them after reception. - * - * @author John Kristian - */ -public class OAuthMessage { - - public OAuthMessage(String method, String URL, Collection<? extends Map.Entry> parameters) { - this(method, URL, parameters, null); - } - - public OAuthMessage(String method, String URL, Collection<? extends Map.Entry> parameters, - InputStream bodyAsStream) { - this.method = method; - this.URL = URL; - this.bodyAsStream = bodyAsStream; - if (parameters == null) { - this.parameters = new ArrayList<Map.Entry<String, String>>(); - } else { - this.parameters = new ArrayList<Map.Entry<String, String>>(parameters.size()); - for (Map.Entry p : parameters) { - this.parameters.add(new OAuth.Parameter( - toString(p.getKey()), toString(p.getValue()))); - } - } - } - - public String method; - public String URL; - - private final List<Map.Entry<String, String>> parameters; - private Map<String, String> parameterMap; - private boolean parametersAreComplete = false; - private final List<Map.Entry<String, String>> headers = new ArrayList<Map.Entry<String, String>>(); - private final InputStream bodyAsStream; - - public String toString() { - return "OAuthMessage(" + method + ", " + URL + ", " + parameters + ")"; - } - - /** A caller is about to get a parameter. */ - private void beforeGetParameter() throws IOException { - if (!parametersAreComplete) { - completeParameters(); - parametersAreComplete = true; - } - } - - /** - * Finish adding parameters; for example read an HTTP response body and - * parse parameters from it. - */ - protected void completeParameters() throws IOException { - } - - public List<Map.Entry<String, String>> getParameters() throws IOException { - beforeGetParameter(); - return Collections.unmodifiableList(parameters); - } - - public void addParameter(String key, String value) { - addParameter(new OAuth.Parameter(key, value)); - } - - public void addParameter(Map.Entry<String, String> parameter) { - parameters.add(parameter); - parameterMap = null; - } - - public void addParameters( - Collection<? extends Map.Entry<String, String>> parameters) { - this.parameters.addAll(parameters); - parameterMap = null; - } - - public String getParameter(String name) throws IOException { - return getParameterMap().get(name); - } - - public String getConsumerKey() throws IOException { - return getParameter(OAuth.OAUTH_CONSUMER_KEY); - } - - public String getToken() throws IOException { - return getParameter(OAuth.OAUTH_TOKEN); - } - - public String getSignatureMethod() throws IOException { - return getParameter(OAuth.OAUTH_SIGNATURE_METHOD); - } - - public String getSignature() throws IOException { - return getParameter(OAuth.OAUTH_SIGNATURE); - } - - protected Map<String, String> getParameterMap() throws IOException { - beforeGetParameter(); - if (parameterMap == null) { - parameterMap = OAuth.newMap(parameters); - } - return parameterMap; - } - - /** - * The MIME type of the body of this message. - * - * @return the MIME type, or null to indicate the type is unknown. - */ - public String getBodyType() { - return getHeader(HttpMessage.CONTENT_TYPE); - } - - /** - * The character encoding of the body of this message. - * - * @return the name of an encoding, or "ISO-8859-1" if no charset has been - * specified. - */ - public String getBodyEncoding() { - return HttpMessage.DEFAULT_CHARSET; - } - - /** - * The value of the last HTTP header with the given name. The name is case - * insensitive. - * - * @return the value of the last header, or null to indicate that there is - * no such header in this message. - */ - public final String getHeader(String name) { - String value = null; // no such header - for (Map.Entry<String, String> header : getHeaders()) { - if (name.equalsIgnoreCase(header.getKey())) { - value = header.getValue(); - } - } - return value; - } - - /** All HTTP headers. You can add headers to this list. */ - public final List<Map.Entry<String, String>> getHeaders() { - return headers; - } - - /** - * Read the body of the HTTP request or response and convert it to a String. - * This method isn't repeatable, since it consumes and closes getBodyAsStream. - * - * @return the body, or null to indicate there is no body. - */ - public final String readBodyAsString() throws IOException - { - InputStream body = getBodyAsStream(); - return readAll(body, getBodyEncoding()); - } - - /** - * Get a stream from which to read the body of the HTTP request or response. - * This is designed to support efficient streaming of a large message. - * The caller must close the returned stream, to release the underlying - * resources such as the TCP connection for an HTTP response. - * - * @return a stream from which to read the body, or null to indicate there - * is no body. - */ - public InputStream getBodyAsStream() throws IOException { - return bodyAsStream; - } - - /** Construct a verbose description of this message and its origins. */ - public Map<String, Object> getDump() throws IOException { - Map<String, Object> into = new HashMap<String, Object>(); - dump(into); - return into; - } - - protected void dump(Map<String, Object> into) throws IOException { - into.put("URL", URL); - if (parametersAreComplete) { - try { - into.putAll(getParameterMap()); - } catch (Exception ignored) { - } - } - } - - /** - * Verify that the required parameter names are contained in the actual - * collection. - * - * @throws OAuthProblemException - * one or more parameters are absent. - * @throws IOException - */ - public void requireParameters(String... names) - throws OAuthProblemException, IOException { - Set<String> present = getParameterMap().keySet(); - List<String> absent = new ArrayList<String>(); - for (String required : names) { - if (!present.contains(required)) { - absent.add(required); - } - } - if (!absent.isEmpty()) { - OAuthProblemException problem = new OAuthProblemException(OAuth.Problems.PARAMETER_ABSENT); - problem.setParameter(OAuth.Problems.OAUTH_PARAMETERS_ABSENT, OAuth.percentEncode(absent)); - throw problem; - } - } - - /** - * Add some of the parameters needed to request access to a protected - * resource, if they aren't already in the message. - * - * @throws IOException - * @throws URISyntaxException - */ - public void addRequiredParameters(OAuthAccessor accessor) - throws OAuthException, IOException, URISyntaxException { - final Map<String, String> pMap = OAuth.newMap(parameters); - if (pMap.get(OAuth.OAUTH_TOKEN) == null && accessor.accessToken != null) { - addParameter(OAuth.OAUTH_TOKEN, accessor.accessToken); - } - final OAuthConsumer consumer = accessor.consumer; - if (pMap.get(OAuth.OAUTH_CONSUMER_KEY) == null) { - addParameter(OAuth.OAUTH_CONSUMER_KEY, consumer.consumerKey); - } - String signatureMethod = pMap.get(OAuth.OAUTH_SIGNATURE_METHOD); - if (signatureMethod == null) { - signatureMethod = (String) consumer.getProperty(OAuth.OAUTH_SIGNATURE_METHOD); - if (signatureMethod == null) { - signatureMethod = OAuth.HMAC_SHA1; - } - addParameter(OAuth.OAUTH_SIGNATURE_METHOD, signatureMethod); - } - if (pMap.get(OAuth.OAUTH_TIMESTAMP) == null) { - addParameter(OAuth.OAUTH_TIMESTAMP, (System.currentTimeMillis() / 1000) + ""); - } - if (pMap.get(OAuth.OAUTH_NONCE) == null) { - addParameter(OAuth.OAUTH_NONCE, System.nanoTime() + ""); - } - if (pMap.get(OAuth.OAUTH_VERSION) == null) { - addParameter(OAuth.OAUTH_VERSION, OAuth.VERSION_1_0); - } - this.sign(accessor); - } - - /** - * Add a signature to the message. - * - * @throws URISyntaxException - */ - public void sign(OAuthAccessor accessor) throws IOException, - OAuthException, URISyntaxException { - OAuthSignatureMethod.newSigner(this, accessor).sign(this); - } - - /** - * Construct an HTTP request from this OAuth message. - * - * @param style - * where to put the OAuth parameters, within the HTTP request - */ - public HttpMessage toHttpRequest(ParameterStyle style) throws IOException { - final boolean isPost = POST.equalsIgnoreCase(method); - InputStream body = getBodyAsStream(); - if (style == ParameterStyle.BODY && !(isPost && body == null)) { - style = ParameterStyle.QUERY_STRING; - } - String url = this.URL; - final List<Map.Entry<String, String>> headers = new ArrayList<Map.Entry<String, String>>(getHeaders()); - switch (style) { - case QUERY_STRING: - url = OAuth.addParameters(url, getParameters()); - break; - case BODY: { - byte[] form = OAuth.formEncode(getParameters()).getBytes(getBodyEncoding()); - headers.add(new OAuth.Parameter(HttpMessage.CONTENT_TYPE, OAuth.FORM_ENCODED)); - headers.add(new OAuth.Parameter(HttpMessage.CONTENT_LENGTH, form.length + "")); - body = new ByteArrayInputStream(form); - break; - } - case AUTHORIZATION_HEADER: - headers.add(new OAuth.Parameter("Authorization", getAuthorizationHeader(null))); - // Find the non-OAuth parameters: - List<Map.Entry<String, String>> others = getParameters(); - if (others != null && !others.isEmpty()) { - others = new ArrayList<Map.Entry<String, String>>(others); - for (Iterator<Map.Entry<String, String>> p = others.iterator(); p.hasNext();) { - if (p.next().getKey().startsWith("oauth_")) { - p.remove(); - } - } - // Place the non-OAuth parameters elsewhere in the request: - if (isPost && body == null) { - byte[] form = OAuth.formEncode(others).getBytes(getBodyEncoding()); - headers.add(new OAuth.Parameter(HttpMessage.CONTENT_TYPE, OAuth.FORM_ENCODED)); - headers.add(new OAuth.Parameter(HttpMessage.CONTENT_LENGTH, form.length + "")); - body = new ByteArrayInputStream(form); - } else { - url = OAuth.addParameters(url, others); - } - } - break; - } - HttpMessage httpRequest = new HttpMessage(method, new URL(url), body); - httpRequest.headers.addAll(headers); - return httpRequest; - } - - /** - * Check that the message is valid. - * - * @throws IOException - * @throws URISyntaxException - * - * @throws OAuthProblemException - * the message is invalid - */ - public void validateMessage(OAuthAccessor accessor, OAuthValidator validator) - throws OAuthException, IOException, URISyntaxException { - validator.validateMessage(this, accessor); - } - - /** - * Construct a WWW-Authenticate or Authentication header value, containing - * the given realm plus all the parameters whose names begin with "oauth_". - */ - public String getAuthorizationHeader(String realm) throws IOException { - StringBuilder into = new StringBuilder(); - if (realm != null) { - into.append(" realm=\"").append(OAuth.percentEncode(realm)).append('"'); - } - beforeGetParameter(); - if (parameters != null) { - for (Map.Entry parameter : parameters) { - String name = toString(parameter.getKey()); - if (name.startsWith("oauth_")) { - if (into.length() > 0) into.append(","); - into.append(" "); - into.append(OAuth.percentEncode(name)).append("=\""); - into.append(OAuth.percentEncode(toString(parameter.getValue()))).append('"'); - } - } - } - return AUTH_SCHEME + into.toString(); - } - - /** - * Read all the data from the given stream, and close it. - * - * @return null if from is null, or the data from the stream converted to a - * String - */ - public static String readAll(InputStream from, String encoding) throws IOException - { - if (from == null) { - return null; - } - try { - StringBuilder into = new StringBuilder(); - Reader r = new InputStreamReader(from, encoding); - char[] s = new char[512]; - for (int n; 0 < (n = r.read(s));) { - into.append(s, 0, n); - } - return into.toString(); - } finally { - from.close(); - } - } - - /** - * Parse the parameters from an OAuth Authorization or WWW-Authenticate - * header. The realm is included as a parameter. If the given header doesn't - * start with "OAuth ", return an empty list. - */ - public static List<OAuth.Parameter> decodeAuthorization(String authorization) { - List<OAuth.Parameter> into = new ArrayList<OAuth.Parameter>(); - if (authorization != null) { - Matcher m = AUTHORIZATION.matcher(authorization); - if (m.matches()) { - if (AUTH_SCHEME.equalsIgnoreCase(m.group(1))) { - for (String nvp : m.group(2).split("\\s*,\\s*")) { - m = NVP.matcher(nvp); - if (m.matches()) { - String name = OAuth.decodePercent(m.group(1)); - String value = OAuth.decodePercent(m.group(2)); - into.add(new OAuth.Parameter(name, value)); - } - } - } - } - } - return into; - } - - public static final String AUTH_SCHEME = "OAuth"; - - public static final String GET = "GET"; - public static final String POST = "POST"; - public static final String PUT = "PUT"; - public static final String DELETE = "DELETE"; - - private static final Pattern AUTHORIZATION = Pattern.compile("\\s*(\\w*)\\s+(.*)"); - private static final Pattern NVP = Pattern.compile("(\\S*)\\s*\\=\\s*\"([^\"]*)\""); - - private static final String toString(Object from) { - return (from == null) ? null : from.toString(); - } - -} +/* + * Copyright 2007, 2008 Netflix, Inc. + * + * 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 net.oauth; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.Reader; +import java.net.URISyntaxException; +import java.security.MessageDigest; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import net.oauth.client.OAuthClient; +import net.oauth.http.HttpMessage; +import net.oauth.http.HttpMessageDecoder; +import net.oauth.signature.OAuthSignatureMethod; + +/** + * A request or response message used in the OAuth protocol. + * <p> + * The parameters in this class are not percent-encoded. Methods like + * OAuthClient.invoke and OAuthResponseMessage.completeParameters are + * responsible for percent-encoding parameters before transmission and decoding + * them after reception. + * + * @author John Kristian + */ +public class OAuthMessage { + + public OAuthMessage(String method, String URL, Collection<? extends Map.Entry> parameters) { + this(method, URL, parameters, null); + } + + public OAuthMessage(String method, String URL, Collection<? extends Map.Entry> parameters, + InputStream bodyAsStream) { + this(method, URL, parameters, bodyAsStream, null); + } + + public OAuthMessage(String method, String URL, Collection<? extends Map.Entry> parameters, + InputStream bodyAsStream, String contentType) { + this.method = method; + this.URL = URL; + if (parameters == null) { + this.parameters = new ArrayList<Map.Entry<String, String>>(); + } else { + this.parameters = new ArrayList<Map.Entry<String, String>>(parameters.size()); + for (Map.Entry p : parameters) { + this.parameters.add(new OAuth.Parameter( + toString(p.getKey()), toString(p.getValue()))); + } + } + + ByteArrayInputStream bais = null; + byte[] body = null; + if( bodyAsStream != null ) { + try{ + body = getBodyAsByteArray(bodyAsStream); + + Collection<Map.Entry<String, String>> headers = getHeaders(); + headers.add(new OAuth.Parameter(HttpMessage.CONTENT_LENGTH, String.valueOf(body.length))); + headers.add(new OAuth.Parameter(HttpMessage.CONTENT_TYPE, contentType != null? contentType: "null")); + + bais = new ByteArrayInputStream(body); + }catch(Exception e){ + } + } + this.bodyAsStream = bais; + this.bodyAsByteArray = body; + + } + + private byte[] getBodyAsByteArray(InputStream bodyAsStream) throws IOException { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + int reads; + + try { + reads = bodyAsStream.read(); + while(reads != -1){ + baos.write(reads); + reads = bodyAsStream.read(); + } + } finally { + bodyAsStream.close(); + } + return baos.toByteArray(); + } + + public String method; + public String URL; + + private final List<Map.Entry<String, String>> parameters; + private Map<String, String> parameterMap; + private boolean parametersAreComplete = false; + private final List<Map.Entry<String, String>> headers = new ArrayList<Map.Entry<String, String>>(); + private final InputStream bodyAsStream; + private final byte[] bodyAsByteArray; + + public String toString() { + return "OAuthMessage(" + method + ", " + URL + ", " + parameters + ")"; + } + + /** A caller is about to get a parameter. */ + private void beforeGetParameter() throws IOException { + if (!parametersAreComplete) { + completeParameters(); + parametersAreComplete = true; + } + } + + /** + * Finish adding parameters; for example read an HTTP response body and + * parse parameters from it. + */ + protected void completeParameters() throws IOException { + } + + public List<Map.Entry<String, String>> getParameters() throws IOException { + beforeGetParameter(); + return Collections.unmodifiableList(parameters); + } + + public void addParameter(String key, String value) { + addParameter(new OAuth.Parameter(key, value)); + } + + public void addParameter(Map.Entry<String, String> parameter) { + parameters.add(parameter); + parameterMap = null; + } + + public void addParameters( + Collection<? extends Map.Entry<String, String>> parameters) { + this.parameters.addAll(parameters); + parameterMap = null; + } + + public String getParameter(String name) throws IOException { + return getParameterMap().get(name); + } + + public String getConsumerKey() throws IOException { + return getParameter(OAuth.OAUTH_CONSUMER_KEY); + } + + public String getToken() throws IOException { + return getParameter(OAuth.OAUTH_TOKEN); + } + + public String getSignatureMethod() throws IOException { + return getParameter(OAuth.OAUTH_SIGNATURE_METHOD); + } + + public String getSignature() throws IOException { + return getParameter(OAuth.OAUTH_SIGNATURE); + } + + protected Map<String, String> getParameterMap() throws IOException { + beforeGetParameter(); + if (parameterMap == null) { + parameterMap = OAuth.newMap(parameters); + } + return parameterMap; + } + + /** + * The MIME type of the body of this message. + * + * @return the MIME type, or null to indicate the type is unknown. + */ + public String getBodyType() { + return getHeader(HttpMessage.CONTENT_TYPE); + } + + /** + * The character encoding of the body of this message. + * + * @return the name of an encoding, or "ISO-8859-1" if no charset has been + * specified. + */ + public String getBodyEncoding() { + return HttpMessage.DEFAULT_CHARSET; + } + + /** + * The value of the last HTTP header with the given name. The name is case + * insensitive. + * + * @return the value of the last header, or null to indicate that there is + * no such header in this message. + */ + public final String getHeader(String name) { + String value = null; // no such header + for (Map.Entry<String, String> header : getHeaders()) { + if (name.equalsIgnoreCase(header.getKey())) { + value = header.getValue(); + } + } + return value; + } + + /** All HTTP headers. You can add headers to this list. */ + public final List<Map.Entry<String, String>> getHeaders() { + return headers; + } + + /** + * Read the body of the HTTP request or response and convert it to a String. + * + * @return the body, or null to indicate there is no body. + */ + public final String readBodyAsString() throws IOException + { + return getBodyAsString(); + } + + /** + * Get a stream from which to read the body of the HTTP request or response. + * This is designed to support efficient streaming of a large message. + * The caller must close the returned stream, to release the underlying + * resources such as the TCP connection for an HTTP response. + * + * @return a stream from which to read the body, or null to indicate there + * is no body. + */ + public InputStream getBodyAsStream() throws IOException { + return bodyAsStream; + } + + /** + * @return the body, or null to indicate there is no body. + */ + public byte[] getBodyAsByteArray(){ + return bodyAsByteArray; + } + + /** + * @return the body, or null to indicate there is no body. + */ + public String getBodyAsString(){ + return new String(bodyAsByteArray); + } + + /** Construct a verbose description of this message and its origins. */ + public Map<String, Object> getDump() throws IOException { + Map<String, Object> into = new HashMap<String, Object>(); + dump(into); + return into; + } + + protected void dump(Map<String, Object> into) throws IOException { + into.put("URL", URL); + if (parametersAreComplete) { + try { + into.putAll(getParameterMap()); + } catch (Exception ignored) { + } + } + } + + /** + * Verify that the required parameter names are contained in the actual + * collection. + * + * @throws OAuthProblemException + * one or more parameters are absent. + * @throws IOException + */ + public void requireParameters(String... names) + throws OAuthProblemException, IOException { + Set<String> present = getParameterMap().keySet(); + List<String> absent = new ArrayList<String>(); + for (String required : names) { + if (!present.contains(required)) { + absent.add(required); + } + } + if (!absent.isEmpty()) { + OAuthProblemException problem = new OAuthProblemException(OAuth.Problems.PARAMETER_ABSENT); + problem.setParameter(OAuth.Problems.OAUTH_PARAMETERS_ABSENT, OAuth.percentEncode(absent)); + throw problem; + } + } + + /** + * Add some of the parameters needed to request access to a protected + * resource, if they aren't already in the message. + * + * @throws IOException + * @throws URISyntaxException + */ + public void addRequiredParameters(OAuthAccessor accessor) + throws OAuthException, IOException, URISyntaxException { + final Map<String, String> pMap = OAuth.newMap(parameters); + if (pMap.get(OAuth.OAUTH_TOKEN) == null && accessor.accessToken != null) { + addParameter(OAuth.OAUTH_TOKEN, accessor.accessToken); + } + final OAuthConsumer consumer = accessor.consumer; + if (pMap.get(OAuth.OAUTH_CONSUMER_KEY) == null) { + addParameter(OAuth.OAUTH_CONSUMER_KEY, consumer.consumerKey); + } + String signatureMethod = pMap.get(OAuth.OAUTH_SIGNATURE_METHOD); + if (signatureMethod == null) { + signatureMethod = (String) consumer.getProperty(OAuth.OAUTH_SIGNATURE_METHOD); + if (signatureMethod == null) { + signatureMethod = OAuth.HMAC_SHA1; + } + addParameter(OAuth.OAUTH_SIGNATURE_METHOD, signatureMethod); + } + if (pMap.get(OAuth.OAUTH_TIMESTAMP) == null) { + addParameter(OAuth.OAUTH_TIMESTAMP, (System.currentTimeMillis() / 1000) + ""); + } + if (pMap.get(OAuth.OAUTH_NONCE) == null) { + addParameter(OAuth.OAUTH_NONCE, System.nanoTime() + ""); + } + if (pMap.get(OAuth.OAUTH_VERSION) == null) { + addParameter(OAuth.OAUTH_VERSION, OAuth.VERSION_1_0); + } + if (pMap.get(OAuth.OAUTH_BODY_HASH) == null && bodyAsStream != null) { + addParameter(OAuth.OAUTH_BODY_HASH, getBodyHash() ); + } + this.sign(accessor); + } + + public String getBodyHash() + throws OAuthException{ + byte[] output; + try{ + MessageDigest md = MessageDigest.getInstance("SHA1"); + md.update(bodyAsByteArray); + output = OAuthSignatureMethod.base64Encode(md.digest()).getBytes(); + }catch(Exception e){ + throw new OAuthException("Could not compute body hash: " + e.getMessage()); + } + + return new String(output); + } + + /** + * Add a signature to the message. + * + * @throws URISyntaxException + */ + public void sign(OAuthAccessor accessor) throws IOException, + OAuthException, URISyntaxException { + OAuthSignatureMethod.newSigner(this, accessor).sign(this); + } + + /** + * Construct an HTTP request from this OAuth message. + * + * @param style + * where to put the OAuth parameters, within the HTTP request + * @deprecated use HttpMessage.newRequest + */ + public HttpMessage toHttpRequest(OAuthClient.ParameterStyle style) throws IOException { + return HttpMessage.newRequest(this, style.getReplacement()); + } + + /** + * Check that the message is valid. + * + * @throws IOException + * @throws URISyntaxException + * + * @throws OAuthProblemException + * the message is invalid + */ + public void validateMessage(OAuthAccessor accessor, OAuthValidator validator) + throws OAuthException, IOException, URISyntaxException { + validator.validateMessage(this, accessor); + } + + /** + * Construct a WWW-Authenticate or Authentication header value, containing + * the given realm plus all the parameters whose names begin with "oauth_". + */ + public String getAuthorizationHeader(String realm) throws IOException { + StringBuilder into = new StringBuilder(); + if (realm != null) { + into.append(" realm=\"").append(OAuth.percentEncode(realm)).append('"'); + } else { + into.append(" realm=\"\""); + } + beforeGetParameter(); + if (parameters != null) { + for (Map.Entry parameter : parameters) { + String name = toString(parameter.getKey()); + if (name.startsWith("oauth_")) { + if (into.length() > 0) into.append(","); + into.append(OAuth.percentEncode(name)).append("=\""); + into.append(OAuth.percentEncode(toString(parameter.getValue()))).append('"'); + } + } + } + return AUTH_SCHEME + into.toString(); + } + + /** + * Read all the data from the given stream, and close it. + * + * @return null if from is null, or the data from the stream converted to a + * String + */ + public static String readAll(InputStream from, String encoding) throws IOException + { + if (from == null) { + return null; + } + try { + StringBuilder into = new StringBuilder(); + Reader r = new InputStreamReader(from, encoding); + char[] s = new char[512]; + for (int n; 0 < (n = r.read(s));) { + into.append(s, 0, n); + } + return into.toString(); + } finally { + from.close(); + } + } + + /** + * Parse the parameters from an OAuth Authorization or WWW-Authenticate + * header. The realm is included as a parameter. If the given header doesn't + * start with "OAuth ", return an empty list. + */ + public static List<OAuth.Parameter> decodeAuthorization(String authorization) { + List<OAuth.Parameter> into = new ArrayList<OAuth.Parameter>(); + if (authorization != null) { + Matcher m = AUTHORIZATION.matcher(authorization); + if (m.matches()) { + if (AUTH_SCHEME.equalsIgnoreCase(m.group(1))) { + for (String nvp : m.group(2).split("\\s*,\\s*")) { + m = NVP.matcher(nvp); + if (m.matches()) { + String name = OAuth.decodePercent(m.group(1)); + String value = OAuth.decodePercent(m.group(2)); + into.add(new OAuth.Parameter(name, value)); + } + } + } + } + } + return into; + } + + public static final String AUTH_SCHEME = "OAuth"; + + public static final String GET = "GET"; + public static final String POST = "POST"; + public static final String PUT = "PUT"; + public static final String DELETE = "DELETE"; + + private static final Pattern AUTHORIZATION = Pattern.compile("\\s*(\\w*)\\s+(.*)"); + private static final Pattern NVP = Pattern.compile("(\\S*)\\s*\\=\\s*\"([^\"]*)\""); + + private static final String toString(Object from) { + return (from == null) ? null : from.toString(); + } + +} diff --git a/bbb-lti/src/java/net/oauth/OAuthProblemException.java b/bbb-lti/src/java/net/oauth/OAuthProblemException.java index fa4468ed80..a985031329 100644 --- a/bbb-lti/src/java/net/oauth/OAuthProblemException.java +++ b/bbb-lti/src/java/net/oauth/OAuthProblemException.java @@ -1,105 +1,104 @@ -/* - * Copyright 2007 Netflix, Inc. - * - * 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 net.oauth; - -import java.util.HashMap; -import java.util.Map; -import net.oauth.http.HttpMessage; -import net.oauth.http.HttpResponseMessage; - -/** - * Describes an OAuth-related problem, using a set of named parameters. One - * parameter identifies the basic problem, and the others provide supplementary - * diagnostic information. This can be used to capture information from a - * response that conforms to the OAuth <a - * href="http://wiki.oauth.net/ProblemReporting">Problem Reporting - * extension</a>. - * - * @author John Kristian - */ -public class OAuthProblemException extends OAuthException { - - public static final String OAUTH_PROBLEM = "oauth_problem"; - - public OAuthProblemException() { - } - - public OAuthProblemException(String problem) { - super(problem); - if (problem != null) { - parameters.put(OAUTH_PROBLEM, problem); - } - } - - private final Map<String, Object> parameters = new HashMap<String, Object>(); - - @Override - public String getMessage() { - String msg = super.getMessage(); - if (msg != null) - return msg; - msg = getProblem(); - if (msg != null) - return msg; - Object response = getParameters().get(HttpMessage.RESPONSE); - if (response != null) { - msg = response.toString(); - int eol = msg.indexOf("\n"); - if (eol < 0) { - eol = msg.indexOf("\r"); - } - if (eol >= 0) { - msg = msg.substring(0, eol); - } - msg = msg.trim(); - if (msg.length() > 0) { - return msg; - } - } - response = getHttpStatusCode(); - if (response != null) { - return HttpResponseMessage.STATUS_CODE + " " + response; - } - return null; - } - - public void setParameter(String name, Object value) { - getParameters().put(name, value); - } - - public Map<String, Object> getParameters() { - return parameters; - } - - public String getProblem() { - return (String) getParameters().get(OAUTH_PROBLEM); - } - - public int getHttpStatusCode() { - Object code = getParameters().get(HttpResponseMessage.STATUS_CODE); - if (code == null) { - return 200; - } else if (code instanceof Number) { // the usual case - return ((Number) code).intValue(); - } else { - return Integer.parseInt(code.toString()); - } - } - - private static final long serialVersionUID = 1L; - -} +/* + * Copyright 2007 Netflix, Inc. + * + * 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 net.oauth; + +import java.util.HashMap; +import java.util.Map; +import net.oauth.http.HttpMessage; + +/** + * Describes an OAuth-related problem, using a set of named parameters. One + * parameter identifies the basic problem, and the others provide supplementary + * diagnostic information. This can be used to capture information from a + * response that conforms to the OAuth <a + * href="http://wiki.oauth.net/ProblemReporting">Problem Reporting + * extension</a>. + * + * @author John Kristian + */ +public class OAuthProblemException extends OAuthException { + + public static final String OAUTH_PROBLEM = "oauth_problem"; + + public OAuthProblemException() { + } + + public OAuthProblemException(String problem) { + super(problem); + if (problem != null) { + parameters.put(OAUTH_PROBLEM, problem); + } + } + + private final Map<String, Object> parameters = new HashMap<String, Object>(); + + @Override + public String getMessage() { + String msg = super.getMessage(); + if (msg != null) + return msg; + msg = getProblem(); + if (msg != null) + return msg; + Object response = getParameters().get(HttpMessage.RESPONSE); + if (response != null) { + msg = response.toString(); + int eol = msg.indexOf("\n"); + if (eol < 0) { + eol = msg.indexOf("\r"); + } + if (eol >= 0) { + msg = msg.substring(0, eol); + } + msg = msg.trim(); + if (msg.length() > 0) { + return msg; + } + } + response = getHttpStatusCode(); + if (response != null) { + return HttpMessage.STATUS_CODE + " " + response; + } + return null; + } + + public void setParameter(String name, Object value) { + getParameters().put(name, value); + } + + public Map<String, Object> getParameters() { + return parameters; + } + + public String getProblem() { + return (String) getParameters().get(OAUTH_PROBLEM); + } + + public int getHttpStatusCode() { + Object code = getParameters().get(HttpMessage.STATUS_CODE); + if (code == null) { + return 200; + } else if (code instanceof Number) { // the usual case + return ((Number) code).intValue(); + } else { + return Integer.parseInt(code.toString()); + } + } + + private static final long serialVersionUID = 1L; + +} diff --git a/bbb-lti/src/java/net/oauth/OAuthServiceProvider.java b/bbb-lti/src/java/net/oauth/OAuthServiceProvider.java index 1196feacb7..82d5f7c28c 100644 --- a/bbb-lti/src/java/net/oauth/OAuthServiceProvider.java +++ b/bbb-lti/src/java/net/oauth/OAuthServiceProvider.java @@ -1,41 +1,41 @@ -/* - * Copyright 2007 Netflix, Inc. - * - * 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 net.oauth; - -import java.io.Serializable; - -/** - * Properties of an OAuth Service Provider. - * - * @author John Kristian - */ -public class OAuthServiceProvider implements Serializable { - - private static final long serialVersionUID = 3306534392621038574L; - - public final String requestTokenURL; - public final String userAuthorizationURL; - public final String accessTokenURL; - - public OAuthServiceProvider(String requestTokenURL, - String userAuthorizationURL, String accessTokenURL) { - this.requestTokenURL = requestTokenURL; - this.userAuthorizationURL = userAuthorizationURL; - this.accessTokenURL = accessTokenURL; - } - -} +/* + * Copyright 2007 Netflix, Inc. + * + * 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 net.oauth; + +import java.io.Serializable; + +/** + * Properties of an OAuth Service Provider. + * + * @author John Kristian + */ +public class OAuthServiceProvider implements Serializable { + + private static final long serialVersionUID = 3306534392621038574L; + + public final String requestTokenURL; + public final String userAuthorizationURL; + public final String accessTokenURL; + + public OAuthServiceProvider(String requestTokenURL, + String userAuthorizationURL, String accessTokenURL) { + this.requestTokenURL = requestTokenURL; + this.userAuthorizationURL = userAuthorizationURL; + this.accessTokenURL = accessTokenURL; + } + +} diff --git a/bbb-lti/src/java/net/oauth/ParameterStyle.java b/bbb-lti/src/java/net/oauth/ParameterStyle.java index 0c896dfd0e..30e2862171 100644 --- a/bbb-lti/src/java/net/oauth/ParameterStyle.java +++ b/bbb-lti/src/java/net/oauth/ParameterStyle.java @@ -1,24 +1,40 @@ -package net.oauth; - -/** - * Where to place OAuth parameters in an HTTP message. The alternatives are - * summarized in OAuth Core section 5.2. - */ -public enum ParameterStyle { - /** - * Send parameters whose names begin with "oauth_" in an HTTP header, and - * other parameters (whose names don't begin with "oauth_") in either the - * message body or URL query string. The header formats are specified by - * OAuth Core section 5.4. - */ - AUTHORIZATION_HEADER, - - /** - * Send all parameters in the message body, with a Content-Type of - * application/x-www-form-urlencoded. - */ - BODY, - - /** Send all parameters in the query string part of the URL. */ - QUERY_STRING; -} +/* + * Copyright 2009 John Kristian + * + * 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 net.oauth; + +/** + * Where to place OAuth parameters in an HTTP message. The alternatives are + * summarized in OAuth Core section 5.2. + */ +public enum ParameterStyle { + /** + * Send parameters whose names begin with "oauth_" in an HTTP header, and + * other parameters (whose names don't begin with "oauth_") in either the + * message body or URL query string. The header formats are specified by + * OAuth Core section 5.4. + */ + AUTHORIZATION_HEADER, + + /** + * Send all parameters in the message body, with a Content-Type of + * application/x-www-form-urlencoded. + */ + BODY, + + /** Send all parameters in the query string part of the URL. */ + QUERY_STRING; +} diff --git a/bbb-lti/src/java/net/oauth/SimpleOAuthValidator.java b/bbb-lti/src/java/net/oauth/SimpleOAuthValidator.java index 33b96884c8..d23f53a6a8 100644 --- a/bbb-lti/src/java/net/oauth/SimpleOAuthValidator.java +++ b/bbb-lti/src/java/net/oauth/SimpleOAuthValidator.java @@ -100,7 +100,6 @@ public class SimpleOAuthValidator implements OAuthValidator { // Check for repeated oauth_ parameters: boolean repeated = false; Map<String, Collection<String>> nameToValues = new HashMap<String, Collection<String>>(); - String repeatedParameter = ""; for (Map.Entry<String, String> parameter : message.getParameters()) { String name = parameter.getKey(); if (SINGLE_PARAMETERS.contains(name)) { @@ -110,7 +109,6 @@ public class SimpleOAuthValidator implements OAuthValidator { nameToValues.put(name, values); } else { repeated = true; - repeatedParameter = name; } values.add(parameter.getValue()); } @@ -126,7 +124,7 @@ public class SimpleOAuthValidator implements OAuthValidator { } } } - OAuthProblemException problem = new OAuthProblemException(OAuth.Problems.PARAMETER_REJECTED + ":" + repeatedParameter); + OAuthProblemException problem = new OAuthProblemException(OAuth.Problems.PARAMETER_REJECTED); problem.setParameter(OAuth.Problems.OAUTH_PARAMETERS_REJECTED, OAuth.formEncode(rejected)); throw problem; } diff --git a/bbb-lti/src/java/net/oauth/client/ExcerptInputStream.java b/bbb-lti/src/java/net/oauth/client/ExcerptInputStream.java index 277c970cae..480b0d2deb 100644 --- a/bbb-lti/src/java/net/oauth/client/ExcerptInputStream.java +++ b/bbb-lti/src/java/net/oauth/client/ExcerptInputStream.java @@ -1,42 +1,124 @@ -package net.oauth.client; - -import java.io.BufferedInputStream; -import java.io.IOException; -import java.io.InputStream; - -/** A decorator that retains a copy of the first few bytes of data. */ -public class ExcerptInputStream extends BufferedInputStream -{ - /** - * A marker that's appended to the excerpt if it's less than the complete - * stream. - */ - public static final byte[] ELLIPSIS = " ...".getBytes(); - - public ExcerptInputStream(InputStream in) throws IOException { - super(in); - mark(LIMIT); - int total = 0; - int read; - while ((read = read(excerpt, total, LIMIT - total)) != -1 && ((total += read) < LIMIT)); - if (total == LIMIT) { - // Only add the ellipsis if there are at least LIMIT bytes - System.arraycopy(ELLIPSIS, 0, excerpt, total, ELLIPSIS.length); - } else { - byte[] tmp = new byte[total]; - System.arraycopy(excerpt, 0, tmp, 0, total); - excerpt = tmp; - } - reset(); - } - - private static final int LIMIT = 1024; - private byte[] excerpt = new byte[LIMIT + ELLIPSIS.length]; - - /** The first few bytes of data, plus ELLIPSIS if there are more bytes. */ - public byte[] getExcerpt() - { - return excerpt; - } - -} +package net.oauth.client; + +import java.io.FilterInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +/** A decorator that retains a copy of the first few bytes of data. */ +public class ExcerptInputStream extends FilterInputStream +{ + /** + * A marker that's appended to the excerpt if it's less than the complete + * stream. + */ + public static final byte[] ELLIPSIS = " ...".getBytes(); + + public ExcerptInputStream(InputStream in) + { + super(in); + } + + private static final int LIMIT = 1024; + + private byte[] excerpt = new byte[LIMIT + ELLIPSIS.length]; + + private int taken = 0; // bytes received from in + + private int given = Integer.MAX_VALUE; // bytes delivered to callers + + @Override + public void close() throws IOException + { + super.close(); + byte[] complete = new byte[taken]; + System.arraycopy(excerpt, 0, complete, 0, taken); + excerpt = complete; + } + + /** The first few bytes of data, plus ELLIPSIS if there are more bytes. */ + public byte[] getExcerpt() throws IOException + { + if (taken < excerpt.length) { + final int mark = Math.min(given, taken); + given = Integer.MAX_VALUE; + while (taken < excerpt.length) { + read(excerpt, taken, LIMIT - taken); + } + given = mark; + } + return excerpt; + } + + @Override + public int read(byte[] b, int offset, int length) throws IOException + { + int total = 0; + if (given < taken) { + final int e = Math.min(length, taken - given); + System.arraycopy(excerpt, given, b, offset, e); + total += e; + given += e; + if (given < taken) { + return total; + } + given = Integer.MAX_VALUE; + offset += e; + length -= e; + } + final int r = super.read(b, offset, length); + if (r > 0) { + total += r; + final int e = Math.min(r, LIMIT - taken); + if (e >= 0) { + System.arraycopy(b, offset, excerpt, taken, e); + taken += e; + if (taken >= LIMIT) { + System.arraycopy(ELLIPSIS, 0, excerpt, LIMIT, ELLIPSIS.length); + taken = excerpt.length; + } + } + } else if (taken < excerpt.length) { + byte[] complete = new byte[taken]; + System.arraycopy(excerpt, 0, complete, 0, taken); + excerpt = complete; + } + return (total > 0) ? total : r; + } + + @Override + public int read(byte[] b) throws IOException + { + return read(b, 0, b.length); + } + + @Override + public int read() throws IOException + { + byte[] b = new byte[1]; + return (read(b) <= 0) ? -1 : unsigned(b[0]); + } + + /** @return an excerpt from the data copied. */ + public static byte[] copyAll(InputStream from, OutputStream into) throws IOException + { + final ExcerptInputStream ex = new ExcerptInputStream(from); + ex.copyAll(into); + return ex.getExcerpt(); + } + + /** Copy all the data from this stream to the given output stream. */ + private void copyAll(OutputStream into) throws IOException + { + byte[] b = new byte[1024]; + for (int n; 0 < (n = read(b));) { + into.write(b, 0, n); + } + } + + private static int unsigned(byte b) + { + return (b >= 0) ? b : ((int) b) + 256; + } + +} diff --git a/bbb-lti/src/java/net/oauth/client/OAuthClient.java b/bbb-lti/src/java/net/oauth/client/OAuthClient.java index 1087b0b61b..d470d766b4 100644 --- a/bbb-lti/src/java/net/oauth/client/OAuthClient.java +++ b/bbb-lti/src/java/net/oauth/client/OAuthClient.java @@ -1,300 +1,332 @@ -/* - * Copyright 2007, 2008 Netflix, Inc. - * - * 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 net.oauth.client; - -import java.io.IOException; -import java.net.URISyntaxException; -import java.util.ArrayList; -import java.util.Collection; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import net.oauth.OAuth; -import net.oauth.OAuthAccessor; -import net.oauth.OAuthConsumer; -import net.oauth.OAuthException; -import net.oauth.OAuthMessage; -import net.oauth.OAuthProblemException; -import net.oauth.ParameterStyle; -import net.oauth.http.HttpClient; -import net.oauth.http.HttpMessage; -import net.oauth.http.HttpMessageDecoder; -import net.oauth.http.HttpResponseMessage; - -/** - * Methods for an OAuth consumer to request tokens from a service provider. - * <p> - * This class can also be used to request access to protected resources, in some - * cases. But not in all cases. For example, this class can't handle arbitrary - * HTTP headers. - * <p> - * Methods of this class return a response as an OAuthMessage, from which you - * can get a body or parameters but not both. Calling a getParameter method will - * read and close the body (like readBodyAsString), so you can't read it later. - * If you read or close the body first, then getParameter can't read it. The - * response headers should tell you whether the response contains encoded - * parameters, that is whether you should call getParameter or not. - * <p> - * Methods of this class don't follow redirects. When they receive a redirect - * response, they throw an OAuthProblemException, with properties - * HttpResponseMessage.STATUS_CODE = the redirect code - * HttpResponseMessage.LOCATION = the redirect URL. Such a redirect can't be - * handled at the HTTP level, if the second request must carry another OAuth - * signature (with different parameters). For example, Google's Service Provider - * routinely redirects requests for access to protected resources, and requires - * the redirected request to be signed. - * - * @author John Kristian - */ -public class OAuthClient { - - public OAuthClient(HttpClient http) - { - this.http = http; - httpParameters.put(HttpClient.FOLLOW_REDIRECTS, Boolean.FALSE); - } - - private HttpClient http; - protected final Map<String, Object> httpParameters = new HashMap<String, Object>(); - - public void setHttpClient(HttpClient http) { - this.http = http; - } - - public HttpClient getHttpClient() { - return http; - } - - /** - * HTTP client parameters, as a map from parameter name to value. - * - * @see HttpClient for parameter names. - */ - public Map<String, Object> getHttpParameters() { - return httpParameters; - } - - /** - * Get a fresh request token from the service provider. - * - * @param accessor - * should contain a consumer that contains a non-null consumerKey - * and consumerSecret. Also, - * accessor.consumer.serviceProvider.requestTokenURL should be - * the URL (determined by the service provider) for getting a - * request token. - * @throws OAuthProblemException - * the HTTP response status code was not 200 (OK) - */ - public void getRequestToken(OAuthAccessor accessor) throws IOException, - OAuthException, URISyntaxException { - getRequestToken(accessor, null); - } - - /** - * Get a fresh request token from the service provider. - * - * @param accessor - * should contain a consumer that contains a non-null consumerKey - * and consumerSecret. Also, - * accessor.consumer.serviceProvider.requestTokenURL should be - * the URL (determined by the service provider) for getting a - * request token. - * @param httpMethod - * typically OAuthMessage.POST or OAuthMessage.GET, or null to - * use the default method. - * @throws OAuthProblemException - * the HTTP response status code was not 200 (OK) - */ - public void getRequestToken(OAuthAccessor accessor, String httpMethod) - throws IOException, OAuthException, URISyntaxException { - getRequestToken(accessor, httpMethod, null); - } - - /** Get a fresh request token from the service provider. - * - * @param accessor - * should contain a consumer that contains a non-null consumerKey - * and consumerSecret. Also, - * accessor.consumer.serviceProvider.requestTokenURL should be - * the URL (determined by the service provider) for getting a - * request token. - * @param httpMethod - * typically OAuthMessage.POST or OAuthMessage.GET, or null to - * use the default method. - * @param parameters - * additional parameters for this request, or null to indicate - * that there are no additional parameters. - * @throws OAuthProblemException - * the HTTP response status code was not 200 (OK) - */ - public void getRequestToken(OAuthAccessor accessor, String httpMethod, - Collection<? extends Map.Entry> parameters) throws IOException, - OAuthException, URISyntaxException { - accessor.accessToken = null; - accessor.tokenSecret = null; - { - // This code supports the 'Variable Accessor Secret' extension - // described in http://oauth.pbwiki.com/AccessorSecret - Object accessorSecret = accessor - .getProperty(OAuthConsumer.ACCESSOR_SECRET); - if (accessorSecret != null) { - List<Map.Entry> p = (parameters == null) ? new ArrayList<Map.Entry>( - 1) - : new ArrayList<Map.Entry>(parameters); - p.add(new OAuth.Parameter("oauth_accessor_secret", - accessorSecret.toString())); - parameters = p; - // But don't modify the caller's parameters. - } - } - OAuthMessage response = invoke(accessor, httpMethod, - accessor.consumer.serviceProvider.requestTokenURL, parameters); - accessor.requestToken = response.getParameter(OAuth.OAUTH_TOKEN); - accessor.tokenSecret = response.getParameter(OAuth.OAUTH_TOKEN_SECRET); - response.requireParameters(OAuth.OAUTH_TOKEN, OAuth.OAUTH_TOKEN_SECRET); - } - - /** - * Get an access token from the service provider, in exchange for an - * authorized request token. - * - * @param accessor - * should contain a non-null requestToken and tokenSecret, and a - * consumer that contains a consumerKey and consumerSecret. Also, - * accessor.consumer.serviceProvider.accessTokenURL should be the - * URL (determined by the service provider) for getting an access - * token. - * @param httpMethod - * typically OAuthMessage.POST or OAuthMessage.GET, or null to - * use the default method. - * @param parameters - * additional parameters for this request, or null to indicate - * that there are no additional parameters. - * @throws OAuthProblemException - * the HTTP response status code was not 200 (OK) - */ - public OAuthMessage getAccessToken(OAuthAccessor accessor, String httpMethod, - Collection<? extends Map.Entry> parameters) throws IOException, OAuthException, URISyntaxException { - if (accessor.requestToken != null) { - if (parameters == null) { - parameters = OAuth.newList(OAuth.OAUTH_TOKEN, accessor.requestToken); - } else if (!OAuth.newMap(parameters).containsKey(OAuth.OAUTH_TOKEN)) { - List<Map.Entry> p = new ArrayList<Map.Entry>(parameters); - p.add(new OAuth.Parameter(OAuth.OAUTH_TOKEN, accessor.requestToken)); - parameters = p; - } - } - OAuthMessage response = invoke(accessor, httpMethod, - accessor.consumer.serviceProvider.accessTokenURL, parameters); - response.requireParameters(OAuth.OAUTH_TOKEN, OAuth.OAUTH_TOKEN_SECRET); - accessor.accessToken = response.getParameter(OAuth.OAUTH_TOKEN); - accessor.tokenSecret = response.getParameter(OAuth.OAUTH_TOKEN_SECRET); - return response; - } - - /** - * Construct a request message, send it to the service provider and get the - * response. - * - * @param httpMethod - * the HTTP request method, or null to use the default method - * @return the response - * @throws URISyntaxException - * the given url isn't valid syntactically - * @throws OAuthProblemException - * the HTTP response status code was not 200 (OK) - */ - public OAuthMessage invoke(OAuthAccessor accessor, String httpMethod, - String url, Collection<? extends Map.Entry> parameters) - throws IOException, OAuthException, URISyntaxException { - OAuthMessage request = accessor.newRequestMessage(httpMethod, url, parameters); - Object accepted = accessor.consumer.getProperty(OAuthConsumer.ACCEPT_ENCODING); - if (accepted != null) { - request.getHeaders().add(new OAuth.Parameter(HttpMessage.ACCEPT_ENCODING, accepted.toString())); - } - Object ps = accessor.consumer.getProperty(PARAMETER_STYLE); - ParameterStyle style = (ps == null) ? ParameterStyle.BODY - : Enum.valueOf(ParameterStyle.class, ps.toString()); - return invoke(request, style); - } - - /** - * The name of the OAuthConsumer property whose value is the ParameterStyle - * to be used by invoke. - */ - public static final String PARAMETER_STYLE = "parameterStyle"; - - /** - * The name of the OAuthConsumer property whose value is the Accept-Encoding - * header in HTTP requests. - * @deprecated use {@link OAuthConsumer#ACCEPT_ENCODING} instead - */ - @Deprecated - public static final String ACCEPT_ENCODING = OAuthConsumer.ACCEPT_ENCODING; - - /** - * Construct a request message, send it to the service provider and get the - * response. - * - * @return the response - * @throws URISyntaxException - * the given url isn't valid syntactically - * @throws OAuthProblemException - * the HTTP response status code was not 200 (OK) - */ - public OAuthMessage invoke(OAuthAccessor accessor, String url, - Collection<? extends Map.Entry> parameters) throws IOException, - OAuthException, URISyntaxException { - return invoke(accessor, null, url, parameters); - } - - /** - * Send a request message to the service provider and get the response. - * - * @return the response - * @throws IOException - * failed to communicate with the service provider - * @throws OAuthProblemException - * the HTTP response status code was not 200 (OK) - */ - public OAuthMessage invoke(OAuthMessage request, ParameterStyle style) - throws IOException, OAuthException { - OAuthResponseMessage response = access(request, style); - if ((response.getHttpResponse().getStatusCode() / 100) != 2) { - throw response.toOAuthProblemException(); - } - return response; - } - - /** - * Send a request and return the response. Don't try to decide whether the - * response indicates success; merely return it. - */ - public OAuthResponseMessage access(OAuthMessage request, ParameterStyle style) throws IOException { - HttpMessage httpRequest = request.toHttpRequest(style); - HttpResponseMessage httpResponse = http.execute(httpRequest, httpParameters); - httpResponse = HttpMessageDecoder.decode(httpResponse); - return new OAuthResponseMessage(httpResponse); - } - - protected static final String PUT = OAuthMessage.PUT; - protected static final String POST = OAuthMessage.POST; - protected static final String DELETE = OAuthMessage.DELETE; - protected static final String CONTENT_LENGTH = HttpMessage.CONTENT_LENGTH; - -} +/* + * Copyright 2007, 2008 Netflix, Inc. + * + * 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 net.oauth.client; + +import java.io.IOException; +import java.net.URISyntaxException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import net.oauth.OAuth; +import net.oauth.OAuthAccessor; +import net.oauth.OAuthConsumer; +import net.oauth.OAuthException; +import net.oauth.OAuthMessage; +import net.oauth.OAuthProblemException; +import net.oauth.http.HttpClient; +import net.oauth.http.HttpMessage; +import net.oauth.http.HttpMessageDecoder; +import net.oauth.http.HttpResponseMessage; + +/** + * Methods for an OAuth consumer to request tokens from a service provider. + * <p> + * This class can also be used to request access to protected resources, in some + * cases. But not in all cases. For example, this class can't handle arbitrary + * HTTP headers. + * <p> + * Methods of this class return a response as an OAuthMessage, from which you + * can get a body or parameters but not both. Calling a getParameter method will + * read and close the body (like readBodyAsString), so you can't read it later. + * If you read or close the body first, then getParameter can't read it. The + * response headers should tell you whether the response contains encoded + * parameters, that is whether you should call getParameter or not. + * <p> + * Methods of this class don't follow redirects. When they receive a redirect + * response, they throw an OAuthProblemException, with properties + * HttpResponseMessage.STATUS_CODE = the redirect code + * HttpResponseMessage.LOCATION = the redirect URL. Such a redirect can't be + * handled at the HTTP level, if the second request must carry another OAuth + * signature (with different parameters). For example, Google's Service Provider + * routinely redirects requests for access to protected resources, and requires + * the redirected request to be signed. + * + * @author John Kristian + */ +public class OAuthClient { + + public OAuthClient(HttpClient http) + { + this.http = http; + httpParameters.put(HttpClient.FOLLOW_REDIRECTS, Boolean.FALSE); + } + + private HttpClient http; + protected final Map<String, Object> httpParameters = new HashMap<String, Object>(); + + public void setHttpClient(HttpClient http) { + this.http = http; + } + + public HttpClient getHttpClient() { + return http; + } + + /** + * HTTP client parameters, as a map from parameter name to value. + * + * @see HttpClient for parameter names. + */ + public Map<String, Object> getHttpParameters() { + return httpParameters; + } + + /** + * Get a fresh request token from the service provider. + * + * @param accessor + * should contain a consumer that contains a non-null consumerKey + * and consumerSecret. Also, + * accessor.consumer.serviceProvider.requestTokenURL should be + * the URL (determined by the service provider) for getting a + * request token. + * @throws OAuthProblemException + * the HTTP response status code was not 200 (OK) + */ + public void getRequestToken(OAuthAccessor accessor) throws IOException, + OAuthException, URISyntaxException { + getRequestToken(accessor, null); + } + + /** + * Get a fresh request token from the service provider. + * + * @param accessor + * should contain a consumer that contains a non-null consumerKey + * and consumerSecret. Also, + * accessor.consumer.serviceProvider.requestTokenURL should be + * the URL (determined by the service provider) for getting a + * request token. + * @param httpMethod + * typically OAuthMessage.POST or OAuthMessage.GET, or null to + * use the default method. + * @throws OAuthProblemException + * the HTTP response status code was not 200 (OK) + */ + public void getRequestToken(OAuthAccessor accessor, String httpMethod) + throws IOException, OAuthException, URISyntaxException { + getRequestToken(accessor, httpMethod, null); + } + + /** Get a fresh request token from the service provider. + * + * @param accessor + * should contain a consumer that contains a non-null consumerKey + * and consumerSecret. Also, + * accessor.consumer.serviceProvider.requestTokenURL should be + * the URL (determined by the service provider) for getting a + * request token. + * @param httpMethod + * typically OAuthMessage.POST or OAuthMessage.GET, or null to + * use the default method. + * @param parameters + * additional parameters for this request, or null to indicate + * that there are no additional parameters. + * @throws OAuthProblemException + * the HTTP response status code was not 200 (OK) + */ + public void getRequestToken(OAuthAccessor accessor, String httpMethod, + Collection<? extends Map.Entry> parameters) throws IOException, + OAuthException, URISyntaxException { + accessor.accessToken = null; + accessor.tokenSecret = null; + { + // This code supports the 'Variable Accessor Secret' extension + // described in http://oauth.pbwiki.com/AccessorSecret + Object accessorSecret = accessor + .getProperty(OAuthConsumer.ACCESSOR_SECRET); + if (accessorSecret != null) { + List<Map.Entry> p = (parameters == null) ? new ArrayList<Map.Entry>( + 1) + : new ArrayList<Map.Entry>(parameters); + p.add(new OAuth.Parameter("oauth_accessor_secret", + accessorSecret.toString())); + parameters = p; + // But don't modify the caller's parameters. + } + } + OAuthMessage response = invoke(accessor, httpMethod, + accessor.consumer.serviceProvider.requestTokenURL, parameters); + accessor.requestToken = response.getParameter(OAuth.OAUTH_TOKEN); + accessor.tokenSecret = response.getParameter(OAuth.OAUTH_TOKEN_SECRET); + response.requireParameters(OAuth.OAUTH_TOKEN, OAuth.OAUTH_TOKEN_SECRET); + } + + /** + * Get an access token from the service provider, in exchange for an + * authorized request token. + * + * @param accessor + * should contain a non-null requestToken and tokenSecret, and a + * consumer that contains a consumerKey and consumerSecret. Also, + * accessor.consumer.serviceProvider.accessTokenURL should be the + * URL (determined by the service provider) for getting an access + * token. + * @param httpMethod + * typically OAuthMessage.POST or OAuthMessage.GET, or null to + * use the default method. + * @param parameters + * additional parameters for this request, or null to indicate + * that there are no additional parameters. + * @throws OAuthProblemException + * the HTTP response status code was not 200 (OK) + */ + public OAuthMessage getAccessToken(OAuthAccessor accessor, String httpMethod, + Collection<? extends Map.Entry> parameters) throws IOException, OAuthException, URISyntaxException { + if (accessor.requestToken != null) { + if (parameters == null) { + parameters = OAuth.newList(OAuth.OAUTH_TOKEN, accessor.requestToken); + } else if (!OAuth.newMap(parameters).containsKey(OAuth.OAUTH_TOKEN)) { + List<Map.Entry> p = new ArrayList<Map.Entry>(parameters); + p.add(new OAuth.Parameter(OAuth.OAUTH_TOKEN, accessor.requestToken)); + parameters = p; + } + } + OAuthMessage response = invoke(accessor, httpMethod, + accessor.consumer.serviceProvider.accessTokenURL, parameters); + response.requireParameters(OAuth.OAUTH_TOKEN, OAuth.OAUTH_TOKEN_SECRET); + accessor.accessToken = response.getParameter(OAuth.OAUTH_TOKEN); + accessor.tokenSecret = response.getParameter(OAuth.OAUTH_TOKEN_SECRET); + return response; + } + + /** + * Construct a request message, send it to the service provider and get the + * response. + * + * @param httpMethod + * the HTTP request method, or null to use the default method + * @return the response + * @throws URISyntaxException + * the given url isn't valid syntactically + * @throws OAuthProblemException + * the HTTP response status code was not 200 (OK) + */ + public OAuthMessage invoke(OAuthAccessor accessor, String httpMethod, + String url, Collection<? extends Map.Entry> parameters) + throws IOException, OAuthException, URISyntaxException { + OAuthMessage request = accessor.newRequestMessage(httpMethod, url, parameters); + Object accepted = accessor.consumer.getProperty(OAuthConsumer.ACCEPT_ENCODING); + if (accepted != null) { + request.getHeaders().add(new OAuth.Parameter(HttpMessage.ACCEPT_ENCODING, accepted.toString())); + } + Object ps = accessor.consumer.getProperty(PARAMETER_STYLE); + net.oauth.ParameterStyle style = (ps == null) ? net.oauth.ParameterStyle.BODY + : Enum.valueOf(net.oauth.ParameterStyle.class, ps.toString()); + return invoke(request, style); + } + + /** + * The name of the OAuthConsumer property whose value is the ParameterStyle + * to be used by invoke. + */ + public static final String PARAMETER_STYLE = "parameterStyle"; + + /** + * The name of the OAuthConsumer property whose value is the Accept-Encoding + * header in HTTP requests. + * @deprecated use {@link OAuthConsumer#ACCEPT_ENCODING} instead + */ + @Deprecated + public static final String ACCEPT_ENCODING = OAuthConsumer.ACCEPT_ENCODING; + + /** + * Construct a request message, send it to the service provider and get the + * response. + * + * @return the response + * @throws URISyntaxException + * the given url isn't valid syntactically + * @throws OAuthProblemException + * the HTTP response status code was not 200 (OK) + */ + public OAuthMessage invoke(OAuthAccessor accessor, String url, + Collection<? extends Map.Entry> parameters) throws IOException, + OAuthException, URISyntaxException { + return invoke(accessor, null, url, parameters); + } + + /** + * Send a request message to the service provider and get the response. + * + * @return the response + * @throws IOException + * failed to communicate with the service provider + * @throws OAuthProblemException + * the HTTP response status code was not 200 (OK) + */ + public OAuthMessage invoke(OAuthMessage request, net.oauth.ParameterStyle style) + throws IOException, OAuthException { + OAuthResponseMessage response = access(request, style); + if ((response.getHttpResponse().getStatusCode() / 100) != 2) { + throw response.toOAuthProblemException(); + } + return response; + } + + /** + * Send a request and return the response. Don't try to decide whether the + * response indicates success; merely return it. + */ + public OAuthResponseMessage access(OAuthMessage request, net.oauth.ParameterStyle style) throws IOException { + HttpMessage httpRequest = HttpMessage.newRequest(request, style); + HttpResponseMessage httpResponse = http.execute(httpRequest, httpParameters); + httpResponse = HttpMessageDecoder.decode(httpResponse); + return new OAuthResponseMessage(httpResponse); + } + + /** + * Where to place parameters in an HTTP message. + * + * @deprecated use net.oauth.ParameterStyle. + */ + public static enum ParameterStyle { + AUTHORIZATION_HEADER(net.oauth.ParameterStyle.AUTHORIZATION_HEADER), + BODY (net.oauth.ParameterStyle.BODY), + QUERY_STRING (net.oauth.ParameterStyle.QUERY_STRING); + + public net.oauth.ParameterStyle getReplacement() { + return replacement; + } + + private ParameterStyle(net.oauth.ParameterStyle replacement) { + this.replacement = replacement; + } + + private final net.oauth.ParameterStyle replacement; + } + + /** @deprecated */ + public OAuthMessage invoke(OAuthMessage request, ParameterStyle style) + throws IOException, OAuthException { + return invoke(request, style.getReplacement()); + } + + /** @deprecated */ + public OAuthResponseMessage access(OAuthMessage request, ParameterStyle style) + throws IOException { + return access(request, style.getReplacement()); + } + + protected static final String PUT = OAuthMessage.PUT; + protected static final String POST = OAuthMessage.POST; + protected static final String DELETE = OAuthMessage.DELETE; + protected static final String CONTENT_LENGTH = HttpMessage.CONTENT_LENGTH; + +} diff --git a/bbb-lti/src/java/net/oauth/client/OAuthResponseMessage.java b/bbb-lti/src/java/net/oauth/client/OAuthResponseMessage.java index 71e7473ba6..4adc431f8a 100644 --- a/bbb-lti/src/java/net/oauth/client/OAuthResponseMessage.java +++ b/bbb-lti/src/java/net/oauth/client/OAuthResponseMessage.java @@ -1,116 +1,116 @@ -/* - * Copyright 2008 Netflix, Inc. - * - * 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 net.oauth.client; - -import java.io.IOException; -import java.io.InputStream; -import java.util.Map; -import net.oauth.OAuth; -import net.oauth.OAuthMessage; -import net.oauth.OAuthProblemException; -import net.oauth.http.HttpResponseMessage; - -/** - * An HTTP response, encapsulated as an OAuthMessage. - * - * @author John Kristian - */ -public class OAuthResponseMessage extends OAuthMessage -{ - OAuthResponseMessage(HttpResponseMessage http) throws IOException - { - super(http.method, http.url.toExternalForm(), null); - this.http = http; - getHeaders().addAll(http.headers); - for (Map.Entry<String, String> header : http.headers) { - if ("WWW-Authenticate".equalsIgnoreCase(header.getKey())) { - for (OAuth.Parameter parameter : decodeAuthorization(header.getValue())) { - if (!"realm".equalsIgnoreCase(parameter.getKey())) { - addParameter(parameter); - } - } - } - } - } - - private final HttpResponseMessage http; - - public HttpResponseMessage getHttpResponse() { - return http; - } - - @Override - public InputStream getBodyAsStream() throws IOException - { - return http.getBody(); - } - - @Override - public String getBodyEncoding() - { - return http.getContentCharset(); - } - - @Override - public void requireParameters(String... names) throws OAuthProblemException, IOException { - try { - super.requireParameters(names); - } catch (OAuthProblemException problem) { - problem.getParameters().putAll(getDump()); - throw problem; - } - } - - /** - * Encapsulate this message as an exception. Read and close the body of this - * message. - */ - public OAuthProblemException toOAuthProblemException() throws IOException { - OAuthProblemException problem = new OAuthProblemException(); - try { - getParameters(); // decode the response body - } catch (IOException ignored) { - } - problem.getParameters().putAll(getDump()); - try { - InputStream b = getBodyAsStream(); - if (b != null) { - b.close(); // release resources - } - } catch (IOException ignored) { - } - return problem; - } - - @Override - protected void completeParameters() throws IOException - { - super.completeParameters(); - String body = readBodyAsString(); - if (body != null) { - addParameters(OAuth.decodeForm(body.trim())); - } - } - - @Override - protected void dump(Map<String, Object> into) throws IOException - { - super.dump(into); - http.dump(into); - } - -} +/* + * Copyright 2008 Netflix, Inc. + * + * 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 net.oauth.client; + +import java.io.IOException; +import java.io.InputStream; +import java.util.Map; +import net.oauth.OAuth; +import net.oauth.OAuthMessage; +import net.oauth.OAuthProblemException; +import net.oauth.http.HttpResponseMessage; + +/** + * An HTTP response, encapsulated as an OAuthMessage. + * + * @author John Kristian + */ +public class OAuthResponseMessage extends OAuthMessage +{ + OAuthResponseMessage(HttpResponseMessage http) throws IOException + { + super(http.method, http.url.toExternalForm(), null); + this.http = http; + getHeaders().addAll(http.headers); + for (Map.Entry<String, String> header : http.headers) { + if ("WWW-Authenticate".equalsIgnoreCase(header.getKey())) { + for (OAuth.Parameter parameter : decodeAuthorization(header.getValue())) { + if (!"realm".equalsIgnoreCase(parameter.getKey())) { + addParameter(parameter); + } + } + } + } + } + + private final HttpResponseMessage http; + + public HttpResponseMessage getHttpResponse() { + return http; + } + + @Override + public InputStream getBodyAsStream() throws IOException + { + return http.getBody(); + } + + @Override + public String getBodyEncoding() + { + return http.getContentCharset(); + } + + @Override + public void requireParameters(String... names) throws OAuthProblemException, IOException { + try { + super.requireParameters(names); + } catch (OAuthProblemException problem) { + problem.getParameters().putAll(getDump()); + throw problem; + } + } + + /** + * Encapsulate this message as an exception. Read and close the body of this + * message. + */ + public OAuthProblemException toOAuthProblemException() throws IOException { + OAuthProblemException problem = new OAuthProblemException(); + try { + getParameters(); // decode the response body + } catch (IOException ignored) { + } + problem.getParameters().putAll(getDump()); + try { + InputStream b = getBodyAsStream(); + if (b != null) { + b.close(); // release resources + } + } catch (IOException ignored) { + } + return problem; + } + + @Override + protected void completeParameters() throws IOException + { + super.completeParameters(); + String body = readBodyAsString(); + if (body != null) { + addParameters(OAuth.decodeForm(body.trim())); + } + } + + @Override + protected void dump(Map<String, Object> into) throws IOException + { + super.dump(into); + http.dump(into); + } + +} diff --git a/bbb-lti/src/java/net/oauth/client/URLConnectionClient.java b/bbb-lti/src/java/net/oauth/client/URLConnectionClient.java index 017ec8989f..e06c7544e9 100644 --- a/bbb-lti/src/java/net/oauth/client/URLConnectionClient.java +++ b/bbb-lti/src/java/net/oauth/client/URLConnectionClient.java @@ -1,122 +1,122 @@ -/* - * Copyright 2008 Netflix, Inc. - * - * 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 net.oauth.client; - -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.net.HttpURLConnection; -import java.net.URL; -import java.net.URLConnection; -import java.util.Collection; -import java.util.List; -import java.util.Map; -import net.oauth.http.HttpClient; -import net.oauth.http.HttpMessage; -import net.oauth.http.HttpResponseMessage; - -/** - * An HttpClient based on HttpURLConnection. - * <p> - * HttpClient3 or HttpClient4 perform better than this class, as a rule; since - * they do things like connection pooling. They also support reading the body - * of an HTTP response whose status code isn't 200 (OK), which can enable your - * application to handle problems better. - * - * @author John Kristian - */ -public class URLConnectionClient implements HttpClient { - - /** Send a message to the service provider and get the response. */ - public HttpResponseMessage execute(HttpMessage request, Map<String, Object> parameters) throws IOException { - final String httpMethod = request.method; - final Collection<Map.Entry<String, String>> addHeaders = request.headers; - final URL url = request.url; - final URLConnection connection = url.openConnection(); - connection.setDoInput(true); - if (connection instanceof HttpURLConnection) { - HttpURLConnection http = (HttpURLConnection) connection; - http.setRequestMethod(httpMethod); - for (Map.Entry<String, Object> p : parameters.entrySet()) { - String name = p.getKey(); - String value = p.getValue().toString(); - if (FOLLOW_REDIRECTS.equals(name)) { - http.setInstanceFollowRedirects(Boolean.parseBoolean(value)); - } else if (CONNECT_TIMEOUT.equals(name)) { - http.setConnectTimeout(Integer.parseInt(value)); - } else if (READ_TIMEOUT.equals(name)) { - http.setReadTimeout(Integer.parseInt(value)); - } - } - } - StringBuilder headers = new StringBuilder(httpMethod); - { - headers.append(" ").append(url.getPath()); - String query = url.getQuery(); - if (query != null && query.length() > 0) { - headers.append("?").append(query); - } - headers.append(EOL); - for (Map.Entry<String, List<String>> header : connection - .getRequestProperties().entrySet()) { - String key = header.getKey(); - for (String value : header.getValue()) { - headers.append(key).append(": ").append(value).append(EOL); - } - } - } - String contentLength = null; - for (Map.Entry<String, String> header : addHeaders) { - String key = header.getKey(); - if (HttpMessage.CONTENT_LENGTH.equalsIgnoreCase(key) - && connection instanceof HttpURLConnection) { - contentLength = header.getValue(); - } else { - connection.setRequestProperty(key, header.getValue()); - } - headers.append(key).append(": ").append(header.getValue()).append(EOL); - } - byte[] excerpt = null; - final InputStream body = request.getBody(); - if (body != null) { - try { - if (contentLength != null) { - ((HttpURLConnection) connection) - .setFixedLengthStreamingMode(Integer.parseInt(contentLength)); - } - connection.setDoOutput(true); - OutputStream output = connection.getOutputStream(); - try { - final ExcerptInputStream ex = new ExcerptInputStream(body); - byte[] b = new byte[1024]; - for (int n; 0 < (n = ex.read(b));) { - output.write(b, 0, n); - } - excerpt = ex.getExcerpt(); - } finally { - output.close(); - } - } finally { - body.close(); - } - } - return new URLConnectionResponse(request, headers.toString(), excerpt, connection); - } - - private static final String EOL = HttpResponseMessage.EOL; - -} +/* + * Copyright 2008 Netflix, Inc. + * + * 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 net.oauth.client; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.HttpURLConnection; +import java.net.URL; +import java.net.URLConnection; +import java.util.Collection; +import java.util.List; +import java.util.Map; +import net.oauth.http.HttpClient; +import net.oauth.http.HttpMessage; +import net.oauth.http.HttpResponseMessage; + +/** + * An HttpClient based on HttpURLConnection. + * <p> + * HttpClient3 or HttpClient4 perform better than this class, as a rule; since + * they do things like connection pooling. They also support reading the body + * of an HTTP response whose status code isn't 200 (OK), which can enable your + * application to handle problems better. + * + * @author John Kristian + */ +public class URLConnectionClient implements HttpClient { + + /** Send a message to the service provider and get the response. */ + public HttpResponseMessage execute(HttpMessage request, Map<String, Object> parameters) throws IOException { + final String httpMethod = request.method; + final Collection<Map.Entry<String, String>> addHeaders = request.headers; + final URL url = request.url; + final URLConnection connection = url.openConnection(); + connection.setDoInput(true); + if (connection instanceof HttpURLConnection) { + HttpURLConnection http = (HttpURLConnection) connection; + http.setRequestMethod(httpMethod); + for (Map.Entry<String, Object> p : parameters.entrySet()) { + String name = p.getKey(); + String value = p.getValue().toString(); + if (FOLLOW_REDIRECTS.equals(name)) { + http.setInstanceFollowRedirects(Boolean.parseBoolean(value)); + } else if (CONNECT_TIMEOUT.equals(name)) { + http.setConnectTimeout(Integer.parseInt(value)); + } else if (READ_TIMEOUT.equals(name)) { + http.setReadTimeout(Integer.parseInt(value)); + } + } + } + StringBuilder headers = new StringBuilder(httpMethod); + { + headers.append(" ").append(url.getPath()); + String query = url.getQuery(); + if (query != null && query.length() > 0) { + headers.append("?").append(query); + } + headers.append(EOL); + for (Map.Entry<String, List<String>> header : connection + .getRequestProperties().entrySet()) { + String key = header.getKey(); + for (String value : header.getValue()) { + headers.append(key).append(": ").append(value).append(EOL); + } + } + } + String contentLength = null; + for (Map.Entry<String, String> header : addHeaders) { + String key = header.getKey(); + if (HttpMessage.CONTENT_LENGTH.equalsIgnoreCase(key) + && connection instanceof HttpURLConnection) { + contentLength = header.getValue(); + } else { + connection.setRequestProperty(key, header.getValue()); + } + headers.append(key).append(": ").append(header.getValue()).append(EOL); + } + byte[] excerpt = null; + final InputStream body = request.getBody(); + if (body != null) { + try { + if (contentLength != null) { + ((HttpURLConnection) connection) + .setFixedLengthStreamingMode(Integer.parseInt(contentLength)); + } + connection.setDoOutput(true); + OutputStream output = connection.getOutputStream(); + try { + final ExcerptInputStream ex = new ExcerptInputStream(body); + byte[] b = new byte[1024]; + for (int n; 0 < (n = ex.read(b));) { + output.write(b, 0, n); + } + excerpt = ex.getExcerpt(); + } finally { + output.close(); + } + } finally { + body.close(); + } + } + return new URLConnectionResponse(request, headers.toString(), excerpt, connection); + } + + private static final String EOL = HttpResponseMessage.EOL; + +} diff --git a/bbb-lti/src/java/net/oauth/consumer.properties.sample b/bbb-lti/src/java/net/oauth/consumer.properties.sample index 7235c08e53..ea26c9012a 100644 --- a/bbb-lti/src/java/net/oauth/consumer.properties.sample +++ b/bbb-lti/src/java/net/oauth/consumer.properties.sample @@ -1,16 +1,16 @@ -# NamedConsumerPool can gets consumer configuration parameters from a file like this. - -ma.gnolia.consumerKey: - Your key here - -ma.gnolia.consumerSecret: - Your secret here - -ma.gnolia.serviceProvider.requestTokenURL: http://ma.gnolia.com/oauth/get_request_token -ma.gnolia.serviceProvider.userAuthorizationURL: http://ma.gnolia.com/oauth/authorize -ma.gnolia.serviceProvider.accessTokenURL: http://ma.gnolia.com/oauth/get_access_token - -twitter.consumerKey: - Your key here - -twitter.consumerSecret: - Your secret here - -twitter.callbackURL: - Your URL here - -twitter.consumer.oauth_signature_method: PLAINTEXT -# There can be more consumer properties. -twitter.serviceProvider.requestTokenURL: http://twitter.com/oauth/request_token -twitter.serviceProvider.userAuthorizationURL: http://twitter.com/oauth/authorize -twitter.serviceProvider.accessTokenURL: http://twitter.com/oauth/access_token +# NamedConsumerPool can gets consumer configuration parameters from a file like this. + +ma.gnolia.consumerKey: - Your key here - +ma.gnolia.consumerSecret: - Your secret here - +ma.gnolia.serviceProvider.requestTokenURL: http://ma.gnolia.com/oauth/get_request_token +ma.gnolia.serviceProvider.userAuthorizationURL: http://ma.gnolia.com/oauth/authorize +ma.gnolia.serviceProvider.accessTokenURL: http://ma.gnolia.com/oauth/get_access_token + +twitter.consumerKey: - Your key here - +twitter.consumerSecret: - Your secret here - +twitter.callbackURL: - Your URL here - +twitter.consumer.oauth_signature_method: PLAINTEXT +# There can be more consumer properties. +twitter.serviceProvider.requestTokenURL: http://twitter.com/oauth/request_token +twitter.serviceProvider.userAuthorizationURL: http://twitter.com/oauth/authorize +twitter.serviceProvider.accessTokenURL: http://twitter.com/oauth/access_token diff --git a/bbb-lti/src/java/net/oauth/http/HttpClient.java b/bbb-lti/src/java/net/oauth/http/HttpClient.java index 05f78e716a..afd393da0e 100644 --- a/bbb-lti/src/java/net/oauth/http/HttpClient.java +++ b/bbb-lti/src/java/net/oauth/http/HttpClient.java @@ -1,54 +1,56 @@ -/* - * Copyright 2008 Netflix, Inc. - * - * 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 net.oauth.http; - -import java.io.IOException; -import java.util.Map; -import net.oauth.OAuthMessage; - -public interface HttpClient { - - /** - * Send an HTTP request and return the response. - * - * @param httpParameters - * HTTP client parameters, as a map from parameter name to value. - * Parameter names are defined as constants below. - */ - HttpResponseMessage execute(HttpMessage request, Map<String, Object> httpParameters) throws IOException; - - /** - * The name of the parameter that is the maximum time to wait to connect to - * the server. (Integer msec) - */ - static final String CONNECT_TIMEOUT = "connectTimeout"; - - /** - * The name of the parameter that is the maximum time to wait for response - * data. (Integer msec) - */ - static final String READ_TIMEOUT = "readTimeout"; - - /** The name of the parameter to automatically follow redirects. (Boolean) */ - static final String FOLLOW_REDIRECTS = "followRedirects"; - - static final String GET = OAuthMessage.GET; - static final String POST = OAuthMessage.POST; - static final String PUT = OAuthMessage.PUT; - static final String DELETE = OAuthMessage.DELETE; - -} +/* + * Copyright 2008 Netflix, Inc. + * + * 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 net.oauth.http; + +import java.io.IOException; +import java.util.Map; +import net.oauth.OAuthMessage; + +// TODO: move this class into oauth-core-consumer, together with HttpMessage. +// The sticky part is deleting the method OAuthMessage.toHttpRequest. +public interface HttpClient { + + /** + * Send an HTTP request and return the response. + * + * @param httpParameters + * HTTP client parameters, as a map from parameter name to value. + * Parameter names are defined as constants below. + */ + HttpResponseMessage execute(HttpMessage request, Map<String, Object> httpParameters) throws IOException; + + /** + * The name of the parameter that is the maximum time to wait to connect to + * the server. (Integer msec) + */ + static final String CONNECT_TIMEOUT = "connectTimeout"; + + /** + * The name of the parameter that is the maximum time to wait for response + * data. (Integer msec) + */ + static final String READ_TIMEOUT = "readTimeout"; + + /** The name of the parameter to automatically follow redirects. (Boolean) */ + static final String FOLLOW_REDIRECTS = "followRedirects"; + + static final String GET = OAuthMessage.GET; + static final String POST = OAuthMessage.POST; + static final String PUT = OAuthMessage.PUT; + static final String DELETE = OAuthMessage.DELETE; + +} diff --git a/bbb-lti/src/java/net/oauth/http/HttpMessage.java b/bbb-lti/src/java/net/oauth/http/HttpMessage.java index 0cd8646bb1..749dbca18d 100644 --- a/bbb-lti/src/java/net/oauth/http/HttpMessage.java +++ b/bbb-lti/src/java/net/oauth/http/HttpMessage.java @@ -1,160 +1,222 @@ -/* - * Copyright 2008 Netflix, Inc. - * - * 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 net.oauth.http; - -import java.io.IOException; -import java.io.InputStream; -import java.net.URL; -import java.util.ArrayList; -import java.util.Iterator; -import java.util.List; -import java.util.Map; -import java.util.regex.Matcher; -import java.util.regex.Pattern; -import net.oauth.client.ExcerptInputStream; - -/** - * An HTTP request or response. - * - * @author John Kristian - */ -public class HttpMessage -{ - - public HttpMessage() - { - this(null, null); - } - - public HttpMessage(String method, URL url) - { - this(method, url, null); - } - - public HttpMessage(String method, URL url, InputStream body) - { - this.method = method; - this.url = url; - this.body = body; - } - - public String method; - public URL url; - public final List<Map.Entry<String, String>> headers = new ArrayList<Map.Entry<String, String>>(); - protected InputStream body = null; - - /** - * Get the value of the last header of the given name. The name is - * case-insensitive. - */ - public final String getHeader(String name) - { - String value = null; - for (Map.Entry<String, String> header : headers) { - if (equalsIgnoreCase(name, header.getKey())) { - value = header.getValue(); - } - } - return value; - } - - /** - * Remove all headers of the given name. The name is case insensitive. - * - * @return the value of the last header with that name, or null to indicate - * there was no such header - */ - public String removeHeaders(String name) - { - String value = null; - for (Iterator<Map.Entry<String, String>> i = headers.iterator(); i.hasNext();) { - Map.Entry<String, String> header = i.next(); - if (equalsIgnoreCase(name, header.getKey())) { - value = header.getValue(); - i.remove(); - } - } - return value; - } - - public final String getContentCharset() - { - return getCharset(getHeader(CONTENT_TYPE)); - } - - public final InputStream getBody() throws IOException - { - if (body == null) { - InputStream raw = openBody(); - if (raw != null) { - body = new ExcerptInputStream(raw); - } - } - return body; - } - - protected InputStream openBody() throws IOException - { - return null; - } - - /** Put a description of this message and its origins into the given Map. */ - public void dump(Map<String, Object> into) throws IOException - { - } - - private static boolean equalsIgnoreCase(String x, String y) - { - if (x == null) - return y == null; - else - return x.equalsIgnoreCase(y); - } - - private static final String getCharset(String mimeType) - { - if (mimeType != null) { - Matcher m = CHARSET.matcher(mimeType); - if (m.find()) { - String charset = m.group(1); - if (charset.length() >= 2 && charset.charAt(0) == '"' - && charset.charAt(charset.length() - 1) == '"') { - charset = charset.substring(1, charset.length() - 1); - charset = charset.replace("\\\"", "\""); - } - return charset; - } - } - return DEFAULT_CHARSET; - } - - /** The name of a dump entry whose value is the HTTP request. */ - public static final String REQUEST = "HTTP request"; - - /** The name of a dump entry whose value is the HTTP response. */ - public static final String RESPONSE = "HTTP response"; - - public static final String ACCEPT_ENCODING = "Accept-Encoding"; - public static final String CONTENT_ENCODING = "Content-Encoding"; - public static final String CONTENT_LENGTH = "Content-Length"; - public static final String CONTENT_TYPE = "Content-Type"; - public static final String DEFAULT_CHARSET = "ISO-8859-1"; - - private static final Pattern CHARSET = Pattern - .compile("; *charset *= *([^;\"]*|\"([^\"]|\\\\\")*\")(;|$)"); - -} \ No newline at end of file +/* + * Copyright 2008 Netflix, Inc. + * + * 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 net.oauth.http; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import net.oauth.client.ExcerptInputStream; +import net.oauth.OAuth; +import net.oauth.OAuthMessage; +import net.oauth.ParameterStyle; + +// TODO: move this class into oauth-core-consumer, together with ExcerptInputStream. +// The sticky part is deleting the method OAuthMessage.toHttpRequest. +/** + * An HTTP request or response. + * + * @author John Kristian + */ +public class HttpMessage +{ + + public HttpMessage() + { + this(null, null); + } + + public HttpMessage(String method, URL url) + { + this(method, url, null); + } + + public HttpMessage(String method, URL url, InputStream body) + { + this.method = method; + this.url = url; + this.body = body; + } + + public String method; + public URL url; + public final List<Map.Entry<String, String>> headers = new ArrayList<Map.Entry<String, String>>(); + protected InputStream body = null; + + /** + * Get the value of the last header of the given name. The name is + * case-insensitive. + */ + public final String getHeader(String name) + { + String value = null; + for (Map.Entry<String, String> header : headers) { + if (equalsIgnoreCase(name, header.getKey())) { + value = header.getValue(); + } + } + return value; + } + + /** + * Remove all headers of the given name. The name is case insensitive. + * + * @return the value of the last header with that name, or null to indicate + * there was no such header + */ + public String removeHeaders(String name) + { + String value = null; + for (Iterator<Map.Entry<String, String>> i = headers.iterator(); i.hasNext();) { + Map.Entry<String, String> header = i.next(); + if (equalsIgnoreCase(name, header.getKey())) { + value = header.getValue(); + i.remove(); + } + } + return value; + } + + public final String getContentCharset() + { + return getCharset(getHeader(CONTENT_TYPE)); + } + + public final InputStream getBody() throws IOException + { + if (body == null) { + InputStream raw = openBody(); + if (raw != null) { + body = new ExcerptInputStream(raw); + } + } + return body; + } + + protected InputStream openBody() throws IOException + { + return null; + } + + /** Put a description of this message and its origins into the given Map. */ + public void dump(Map<String, Object> into) throws IOException + { + } + + /** + * Construct an HTTP request from this OAuth message. + * + * @param style + * where to put the OAuth parameters, within the HTTP request + */ + public static HttpMessage newRequest(OAuthMessage from, ParameterStyle style) throws IOException { + final boolean isPost = OAuthMessage.POST.equalsIgnoreCase(from.method); + InputStream body = from.getBodyAsStream(); + if (style == ParameterStyle.BODY && !(isPost && body == null)) { + style = ParameterStyle.QUERY_STRING; + } + String url = from.URL; + final List<Map.Entry<String, String>> headers = new ArrayList<Map.Entry<String, String>>(from.getHeaders()); + switch (style) { + case QUERY_STRING: + url = OAuth.addParameters(url, from.getParameters()); + break; + case BODY: { + byte[] form = OAuth.formEncode(from.getParameters()).getBytes(from.getBodyEncoding()); + headers.add(new OAuth.Parameter(CONTENT_TYPE, OAuth.FORM_ENCODED)); + headers.add(new OAuth.Parameter(CONTENT_LENGTH, form.length + "")); + body = new ByteArrayInputStream(form); + break; + } + case AUTHORIZATION_HEADER: + headers.add(new OAuth.Parameter("Authorization", from.getAuthorizationHeader(null))); + // Find the non-OAuth parameters: + List<Map.Entry<String, String>> others = from.getParameters(); + if (others != null && !others.isEmpty()) { + others = new ArrayList<Map.Entry<String, String>>(others); + for (Iterator<Map.Entry<String, String>> p = others.iterator(); p.hasNext();) { + if (p.next().getKey().startsWith("oauth_")) { + p.remove(); + } + } + // Place the non-OAuth parameters elsewhere in the request: + if (isPost && body == null) { + byte[] form = OAuth.formEncode(others).getBytes(from.getBodyEncoding()); + headers.add(new OAuth.Parameter(CONTENT_TYPE, OAuth.FORM_ENCODED)); + headers.add(new OAuth.Parameter(CONTENT_LENGTH, form.length + "")); + body = new ByteArrayInputStream(form); + } else { + url = OAuth.addParameters(url, others); + } + } + break; + } + HttpMessage httpRequest = new HttpMessage(from.method, new URL(url), body); + httpRequest.headers.addAll(headers); + return httpRequest; + } + + private static boolean equalsIgnoreCase(String x, String y) + { + if (x == null) + return y == null; + else + return x.equalsIgnoreCase(y); + } + + private static final String getCharset(String mimeType) + { + if (mimeType != null) { + Matcher m = CHARSET.matcher(mimeType); + if (m.find()) { + String charset = m.group(1); + if (charset.length() >= 2 && charset.charAt(0) == '"' + && charset.charAt(charset.length() - 1) == '"') { + charset = charset.substring(1, charset.length() - 1); + charset = charset.replace("\\\"", "\""); + } + return charset; + } + } + return DEFAULT_CHARSET; + } + + /** The name of a dump entry whose value is the HTTP request. */ + public static final String REQUEST = "HTTP request"; + + /** The name of a dump entry whose value is the HTTP response. */ + public static final String RESPONSE = "HTTP response"; + + /** The name of a dump entry whose value is the HTTP status code. */ + public static final String STATUS_CODE = "HTTP status"; + + public static final String ACCEPT_ENCODING = "Accept-Encoding"; + public static final String CONTENT_ENCODING = "Content-Encoding"; + public static final String CONTENT_LENGTH = "Content-Length"; + public static final String CONTENT_TYPE = "Content-Type"; + public static final String DEFAULT_CHARSET = "ISO-8859-1"; + + private static final Pattern CHARSET = Pattern + .compile("; *charset *= *([^;\"]*|\"([^\"]|\\\\\")*\")(;|$)"); + +} diff --git a/bbb-lti/src/java/net/oauth/http/HttpMessageDecoder.java b/bbb-lti/src/java/net/oauth/http/HttpMessageDecoder.java index 5c415a128e..c40d424419 100644 --- a/bbb-lti/src/java/net/oauth/http/HttpMessageDecoder.java +++ b/bbb-lti/src/java/net/oauth/http/HttpMessageDecoder.java @@ -1,94 +1,96 @@ -/* - * Copyright 2008 Netflix, Inc. - * - * 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 net.oauth.http; - -import java.io.IOException; -import java.io.InputStream; -import java.util.Map; -import java.util.zip.GZIPInputStream; -import java.util.zip.InflaterInputStream; - -/** A decorator that handles Content-Encoding. */ -public class HttpMessageDecoder extends HttpResponseMessage { - - /** - * Decode the given message if necessary and possible. - * - * @return a decorator that decodes the body of the given message; or the - * given message if this class can't decode it. - */ - public static HttpResponseMessage decode(HttpResponseMessage message) - throws IOException { - if (message != null) { - String encoding = getEncoding(message); - if (encoding != null) { - return new HttpMessageDecoder(message, encoding); - } - } - return message; - } - - public static final String GZIP = "gzip"; - public static final String DEFLATE = "deflate"; - public static final String ACCEPTED = GZIP + "," + DEFLATE; - - private static String getEncoding(HttpMessage message) { - String encoding = message.getHeader(CONTENT_ENCODING); - if (encoding == null) { - // That's easy. - } else if (GZIP.equalsIgnoreCase(encoding) - || ("x-" + GZIP).equalsIgnoreCase(encoding)) { - return GZIP; - } else if (DEFLATE.equalsIgnoreCase(encoding)) { - return DEFLATE; - } - return null; - } - - private HttpMessageDecoder(HttpResponseMessage in, String encoding) - throws IOException { - super(in.method, in.url); - this.headers.addAll(in.headers); - removeHeaders(CONTENT_ENCODING); // handled here - removeHeaders(CONTENT_LENGTH); // unpredictable - InputStream body = in.getBody(); - if (body != null) { - if (encoding == GZIP) { - body = new GZIPInputStream(body); - } else if (encoding == DEFLATE) { - body = new InflaterInputStream(body); - } else { - assert false; - } - } - this.body = body; - this.in = in; - } - - private final HttpResponseMessage in; - - @Override - public void dump(Map<String, Object> into) throws IOException { - in.dump(into); - } - - @Override - public int getStatusCode() throws IOException { - return in.getStatusCode(); - } - -} +/* + * Copyright 2008 Netflix, Inc. + * + * 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 net.oauth.http; + +import java.io.IOException; +import java.io.InputStream; +import java.util.Map; +import java.util.zip.GZIPInputStream; +import java.util.zip.InflaterInputStream; + +// TODO: move this class into oauth-core-consumer, together with HttpMessage. +// The sticky part is deleting the method OAuthMessage.toHttpRequest. +/** A decorator that handles Content-Encoding. */ +public class HttpMessageDecoder extends HttpResponseMessage { + + /** + * Decode the given message if necessary and possible. + * + * @return a decorator that decodes the body of the given message; or the + * given message if this class can't decode it. + */ + public static HttpResponseMessage decode(HttpResponseMessage message) + throws IOException { + if (message != null) { + String encoding = getEncoding(message); + if (encoding != null) { + return new HttpMessageDecoder(message, encoding); + } + } + return message; + } + + public static final String GZIP = "gzip"; + public static final String DEFLATE = "deflate"; + public static final String ACCEPTED = GZIP + "," + DEFLATE; + + private static String getEncoding(HttpMessage message) { + String encoding = message.getHeader(CONTENT_ENCODING); + if (encoding == null) { + // That's easy. + } else if (GZIP.equalsIgnoreCase(encoding) + || ("x-" + GZIP).equalsIgnoreCase(encoding)) { + return GZIP; + } else if (DEFLATE.equalsIgnoreCase(encoding)) { + return DEFLATE; + } + return null; + } + + private HttpMessageDecoder(HttpResponseMessage in, String encoding) + throws IOException { + super(in.method, in.url); + this.headers.addAll(in.headers); + removeHeaders(CONTENT_ENCODING); // handled here + removeHeaders(CONTENT_LENGTH); // unpredictable + InputStream body = in.getBody(); + if (body != null) { + if (encoding == GZIP) { + body = new GZIPInputStream(body); + } else if (encoding == DEFLATE) { + body = new InflaterInputStream(body); + } else { + assert false; + } + } + this.body = body; + this.in = in; + } + + private final HttpResponseMessage in; + + @Override + public void dump(Map<String, Object> into) throws IOException { + in.dump(into); + } + + @Override + public int getStatusCode() throws IOException { + return in.getStatusCode(); + } + +} diff --git a/bbb-lti/src/java/net/oauth/http/HttpResponseMessage.java b/bbb-lti/src/java/net/oauth/http/HttpResponseMessage.java index 2366f122c3..491252fa97 100644 --- a/bbb-lti/src/java/net/oauth/http/HttpResponseMessage.java +++ b/bbb-lti/src/java/net/oauth/http/HttpResponseMessage.java @@ -1,58 +1,60 @@ -/* - * Copyright 2008 Netflix, Inc. - * - * 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 net.oauth.http; - -import java.io.IOException; -import java.net.URL; -import java.util.Map; - -/** - * An HTTP response. - * - * @author John Kristian - */ -public abstract class HttpResponseMessage extends HttpMessage { - - protected HttpResponseMessage(String method, URL url) { - super(method, url); - } - - @Override - public void dump(Map<String, Object> into) throws IOException { - super.dump(into); - into.put(STATUS_CODE, Integer.valueOf(getStatusCode())); - String location = getHeader(LOCATION); - if (location != null) { - into.put(LOCATION, location); - } - } - - public abstract int getStatusCode() throws IOException; - - /** The name of a dump entry whose value is the response Location header. */ - public static final String LOCATION = "Location"; - - /** The name of a dump entry whose value is the HTTP status code. */ - public static final String STATUS_CODE = "HTTP status"; - - /** The statusCode that indicates a normal outcome. */ - public static final int STATUS_OK = 200; - - /** The standard end-of-line marker in an HTTP message. */ - public static final String EOL = "\r\n"; - -} +/* + * Copyright 2008 Netflix, Inc. + * + * 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 net.oauth.http; + +import java.io.IOException; +import java.net.URL; +import java.util.Map; + +// TODO: move this class into oauth-core-consumer, together with HttpMessage. +// The sticky part is deleting the method OAuthMessage.toHttpRequest. +/** + * An HTTP response. + * + * @author John Kristian + */ +public abstract class HttpResponseMessage extends HttpMessage { + + protected HttpResponseMessage(String method, URL url) { + super(method, url); + } + + @Override + public void dump(Map<String, Object> into) throws IOException { + super.dump(into); + into.put(STATUS_CODE, Integer.valueOf(getStatusCode())); + String location = getHeader(LOCATION); + if (location != null) { + into.put(LOCATION, location); + } + } + + public abstract int getStatusCode() throws IOException; + + /** The name of a dump entry whose value is the response Location header. */ + public static final String LOCATION = "Location"; + + /** The name of a dump entry whose value is the HTTP status code. */ + public static final String STATUS_CODE = "HTTP status"; + + /** The statusCode that indicates a normal outcome. */ + public static final int STATUS_OK = 200; + + /** The standard end-of-line marker in an HTTP message. */ + public static final String EOL = "\r\n"; + +} diff --git a/bbb-lti/src/java/net/oauth/server/HttpRequestMessage.java b/bbb-lti/src/java/net/oauth/server/HttpRequestMessage.java index bab3358712..2eb5195c2a 100644 --- a/bbb-lti/src/java/net/oauth/server/HttpRequestMessage.java +++ b/bbb-lti/src/java/net/oauth/server/HttpRequestMessage.java @@ -1,92 +1,92 @@ -/* - * Copyright 2008 Netflix, Inc. - * - * 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 net.oauth.server; - -import java.io.IOException; -import java.io.InputStream; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Enumeration; -import java.util.List; -import java.util.Map; -import javax.servlet.http.HttpServletRequest; -import net.oauth.OAuth; -import net.oauth.OAuthMessage; - -/** - * An HttpServletRequest, encapsulated as an OAuthMessage. - * - * @author John Kristian - */ -public class HttpRequestMessage extends OAuthMessage { - - public HttpRequestMessage(HttpServletRequest request, String URL) { - super(request.getMethod(), URL, getParameters(request)); - this.request = request; - copyHeaders(request, getHeaders()); - } - - private final HttpServletRequest request; - - @Override - public InputStream getBodyAsStream() throws IOException { - return request.getInputStream(); - } - - @Override - public String getBodyEncoding() { - return request.getCharacterEncoding(); - } - - private static void copyHeaders(HttpServletRequest request, Collection<Map.Entry<String, String>> into) { - Enumeration<String> names = request.getHeaderNames(); - if (names != null) { - while (names.hasMoreElements()) { - String name = names.nextElement(); - Enumeration<String> values = request.getHeaders(name); - if (values != null) { - while (values.hasMoreElements()) { - into.add(new OAuth.Parameter(name, values.nextElement())); - } - } - } - } - } - - public static List<OAuth.Parameter> getParameters(HttpServletRequest request) { - List<OAuth.Parameter> list = new ArrayList<OAuth.Parameter>(); - for (Enumeration<String> headers = request.getHeaders("Authorization"); headers != null - && headers.hasMoreElements();) { - String header = headers.nextElement(); - for (OAuth.Parameter parameter : OAuthMessage - .decodeAuthorization(header)) { - if (!"realm".equalsIgnoreCase(parameter.getKey())) { - list.add(parameter); - } - } - } - for (Object e : request.getParameterMap().entrySet()) { - Map.Entry<String, String[]> entry = (Map.Entry<String, String[]>) e; - String name = entry.getKey(); - for (String value : entry.getValue()) { - list.add(new OAuth.Parameter(name, value)); - } - } - return list; - } - -} +/* + * Copyright 2008 Netflix, Inc. + * + * 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 net.oauth.server; + +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Enumeration; +import java.util.List; +import java.util.Map; +import javax.servlet.http.HttpServletRequest; +import net.oauth.OAuth; +import net.oauth.OAuthMessage; + +/** + * An HttpServletRequest, encapsulated as an OAuthMessage. + * + * @author John Kristian + */ +public class HttpRequestMessage extends OAuthMessage { + + public HttpRequestMessage(HttpServletRequest request, String URL) { + super(request.getMethod(), URL, getParameters(request)); + this.request = request; + copyHeaders(request, getHeaders()); + } + + private final HttpServletRequest request; + + @Override + public InputStream getBodyAsStream() throws IOException { + return request.getInputStream(); + } + + @Override + public String getBodyEncoding() { + return request.getCharacterEncoding(); + } + + private static void copyHeaders(HttpServletRequest request, Collection<Map.Entry<String, String>> into) { + Enumeration<String> names = request.getHeaderNames(); + if (names != null) { + while (names.hasMoreElements()) { + String name = names.nextElement(); + Enumeration<String> values = request.getHeaders(name); + if (values != null) { + while (values.hasMoreElements()) { + into.add(new OAuth.Parameter(name, values.nextElement())); + } + } + } + } + } + + public static List<OAuth.Parameter> getParameters(HttpServletRequest request) { + List<OAuth.Parameter> list = new ArrayList<OAuth.Parameter>(); + for (Enumeration<String> headers = request.getHeaders("Authorization"); headers != null + && headers.hasMoreElements();) { + String header = headers.nextElement(); + for (OAuth.Parameter parameter : OAuthMessage + .decodeAuthorization(header)) { + if (!"realm".equalsIgnoreCase(parameter.getKey())) { + list.add(parameter); + } + } + } + for (Object e : request.getParameterMap().entrySet()) { + Map.Entry<String, String[]> entry = (Map.Entry<String, String[]>) e; + String name = entry.getKey(); + for (String value : entry.getValue()) { + list.add(new OAuth.Parameter(name, value)); + } + } + return list; + } + +} diff --git a/bbb-lti/src/java/net/oauth/server/OAuthServlet.java b/bbb-lti/src/java/net/oauth/server/OAuthServlet.java index 9054bea3eb..2ae266a7d5 100644 --- a/bbb-lti/src/java/net/oauth/server/OAuthServlet.java +++ b/bbb-lti/src/java/net/oauth/server/OAuthServlet.java @@ -1,158 +1,159 @@ -/* - * Copyright 2007 Netflix, Inc. - * - * 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 net.oauth.server; - -import java.io.IOException; -import java.util.Map; -import javax.servlet.ServletException; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import net.oauth.OAuth; -import net.oauth.OAuthMessage; -import net.oauth.OAuthProblemException; -import net.oauth.http.HttpResponseMessage; - -/** - * Utility methods for servlets that implement OAuth. - * - * @author John Kristian - */ -public class OAuthServlet { - - /** - * Extract the parts of the given request that are relevant to OAuth. - * Parameters include OAuth Authorization headers and the usual request - * parameters in the query string and/or form encoded body. The header - * parameters come first, followed by the rest in the order they came from - * request.getParameterMap(). - * - * @param URL - * the official URL of this service; that is the URL a legitimate - * client would use to compute the digital signature. If this - * parameter is null, this method will try to reconstruct the URL - * from the HTTP request; which may be wrong in some cases. - */ - public static OAuthMessage getMessage(HttpServletRequest request, String URL) { - if (URL == null) { - URL = request.getRequestURL().toString(); - } - int q = URL.indexOf('?'); - if (q >= 0) { - URL = URL.substring(0, q); - // The query string parameters will be included in - // the result from getParameters(request). - } - return new HttpRequestMessage(request, URL); - } - - /** Reconstruct the requested URL, complete with query string (if any). */ - public static String getRequestURL(HttpServletRequest request) { - StringBuffer url = request.getRequestURL(); - String queryString = request.getQueryString(); - if (queryString != null) { - url.append("?").append(queryString); - } - return url.toString(); - } - - public static void handleException(HttpServletResponse response, - Exception e, String realm) throws IOException, ServletException { - handleException(response, e, realm, true); - } - - public static void handleException(HttpServletResponse response, - Exception e, String realm, boolean sendBody) throws IOException, - ServletException { - if (e instanceof OAuthProblemException) { - OAuthProblemException problem = (OAuthProblemException) e; - Object httpCode = problem.getParameters().get(HttpResponseMessage.STATUS_CODE); - if (httpCode == null) { - httpCode = PROBLEM_TO_HTTP_CODE.get(problem.getProblem()); - } - if (httpCode == null) { - httpCode = SC_FORBIDDEN; - } - response.reset(); - response.setStatus(Integer.parseInt(httpCode.toString())); - OAuthMessage message = new OAuthMessage(null, null, problem - .getParameters().entrySet()); - response.addHeader("WWW-Authenticate", message - .getAuthorizationHeader(realm)); - if (sendBody) { - sendForm(response, message.getParameters()); - } - } else if (e instanceof IOException) { - throw (IOException) e; - } else if (e instanceof ServletException) { - throw (ServletException) e; - } else if (e instanceof RuntimeException) { - throw (RuntimeException) e; - } else { - throw new ServletException(e); - } - } - - private static final Integer SC_FORBIDDEN = new Integer( - HttpServletResponse.SC_FORBIDDEN); - - private static final Map<String, Integer> PROBLEM_TO_HTTP_CODE = OAuth.Problems.TO_HTTP_CODE; - - /** Send the given parameters as a form-encoded response body. */ - public static void sendForm(HttpServletResponse response, - Iterable<? extends Map.Entry> parameters) throws IOException { - response.resetBuffer(); - response.setContentType(OAuth.FORM_ENCODED + ";charset=" - + OAuth.ENCODING); - OAuth.formEncode(parameters, response.getOutputStream()); - } - - /** - * Return the HTML representation of the given plain text. Characters that - * would have special significance in HTML are replaced by <a - * href="http://www.w3.org/TR/html401/sgml/entities.html">character entity - * references</a>. Whitespace is not converted. - */ - public static String htmlEncode(String s) { - if (s == null) { - return null; - } - StringBuilder html = new StringBuilder(s.length()); - for (char c : s.toCharArray()) { - switch (c) { - case '<': - html.append("<"); - break; - case '>': - html.append(">"); - break; - case '&': - html.append("&"); - // This also takes care of numeric character references; - // for example © becomes &#169. - break; - case '"': - html.append("""); - break; - default: - html.append(c); - break; - } - } - return html.toString(); - } - -} +/* + * Copyright 2007 Netflix, Inc. + * + * 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 net.oauth.server; + +import net.oauth.http.HttpMessage; + +import java.io.IOException; +import java.util.Map; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import net.oauth.OAuth; +import net.oauth.OAuthMessage; +import net.oauth.OAuthProblemException; + +/** + * Utility methods for servlets that implement OAuth. + * + * @author John Kristian + */ +public class OAuthServlet { + + /** + * Extract the parts of the given request that are relevant to OAuth. + * Parameters include OAuth Authorization headers and the usual request + * parameters in the query string and/or form encoded body. The header + * parameters come first, followed by the rest in the order they came from + * request.getParameterMap(). + * + * @param URL + * the official URL of this service; that is the URL a legitimate + * client would use to compute the digital signature. If this + * parameter is null, this method will try to reconstruct the URL + * from the HTTP request; which may be wrong in some cases. + */ + public static OAuthMessage getMessage(HttpServletRequest request, String URL) { + if (URL == null) { + URL = request.getRequestURL().toString(); + } + int q = URL.indexOf('?'); + if (q >= 0) { + URL = URL.substring(0, q); + // The query string parameters will be included in + // the result from getParameters(request). + } + return new HttpRequestMessage(request, URL); + } + + /** Reconstruct the requested URL, complete with query string (if any). */ + public static String getRequestURL(HttpServletRequest request) { + StringBuffer url = request.getRequestURL(); + String queryString = request.getQueryString(); + if (queryString != null) { + url.append("?").append(queryString); + } + return url.toString(); + } + + public static void handleException(HttpServletResponse response, + Exception e, String realm) throws IOException, ServletException { + handleException(response, e, realm, true); + } + + public static void handleException(HttpServletResponse response, + Exception e, String realm, boolean sendBody) throws IOException, + ServletException { + if (e instanceof OAuthProblemException) { + OAuthProblemException problem = (OAuthProblemException) e; + Object httpCode = problem.getParameters().get(HttpMessage.STATUS_CODE); + if (httpCode == null) { + httpCode = PROBLEM_TO_HTTP_CODE.get(problem.getProblem()); + } + if (httpCode == null) { + httpCode = SC_FORBIDDEN; + } + response.reset(); + response.setStatus(Integer.parseInt(httpCode.toString())); + OAuthMessage message = new OAuthMessage(null, null, problem + .getParameters().entrySet()); + response.addHeader("WWW-Authenticate", message + .getAuthorizationHeader(realm)); + if (sendBody) { + sendForm(response, message.getParameters()); + } + } else if (e instanceof IOException) { + throw (IOException) e; + } else if (e instanceof ServletException) { + throw (ServletException) e; + } else if (e instanceof RuntimeException) { + throw (RuntimeException) e; + } else { + throw new ServletException(e); + } + } + + private static final Integer SC_FORBIDDEN = new Integer( + HttpServletResponse.SC_FORBIDDEN); + + private static final Map<String, Integer> PROBLEM_TO_HTTP_CODE = OAuth.Problems.TO_HTTP_CODE; + + /** Send the given parameters as a form-encoded response body. */ + public static void sendForm(HttpServletResponse response, + Iterable<? extends Map.Entry> parameters) throws IOException { + response.resetBuffer(); + response.setContentType(OAuth.FORM_ENCODED + ";charset=" + + OAuth.ENCODING); + OAuth.formEncode(parameters, response.getOutputStream()); + } + + /** + * Return the HTML representation of the given plain text. Characters that + * would have special significance in HTML are replaced by <a + * href="http://www.w3.org/TR/html401/sgml/entities.html">character entity + * references</a>. Whitespace is not converted. + */ + public static String htmlEncode(String s) { + if (s == null) { + return null; + } + StringBuilder html = new StringBuilder(s.length()); + for (char c : s.toCharArray()) { + switch (c) { + case '<': + html.append("<"); + break; + case '>': + html.append(">"); + break; + case '&': + html.append("&"); + // This also takes care of numeric character references; + // for example © becomes &#169. + break; + case '"': + html.append("""); + break; + default: + html.append(c); + break; + } + } + return html.toString(); + } + +} diff --git a/bbb-lti/src/java/net/oauth/signature/Base64.java b/bbb-lti/src/java/net/oauth/signature/Base64.java index 6fc39023ac..fd5ea5fa95 100644 --- a/bbb-lti/src/java/net/oauth/signature/Base64.java +++ b/bbb-lti/src/java/net/oauth/signature/Base64.java @@ -1,714 +1,714 @@ -/* - * Copyright 2001-2008 The Apache Software Foundation. - * The ASF licenses this file to You 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 net.oauth.signature; - -import java.io.UnsupportedEncodingException; -import java.math.BigInteger; - -/** - * Provides Base64 encoding and decoding as defined by RFC 2045. - * - * <p> - * This class implements section <cite>6.8. Base64 Content-Transfer-Encoding</cite> from RFC 2045 <cite>Multipurpose - * Internet Mail Extensions (MIME) Part One: Format of Internet Message Bodies</cite> by Freed and Borenstein. - * </p> - * - * @see <a href="http://www.ietf.org/rfc/rfc2045.txt">RFC 2045</a> - * @author Apache Software Foundation - * @author John Kristian - */ -class Base64 { - /** - * Chunk size per RFC 2045 section 6.8. - * - * <p> - * The {@value} character limit does not count the trailing CRLF, but counts all other characters, including any - * equal signs. - * </p> - * - * @see <a href="http://www.ietf.org/rfc/rfc2045.txt">RFC 2045 section 6.8</a> - */ - static final int CHUNK_SIZE = 76; - - /** - * Chunk separator per RFC 2045 section 2.1. - * - * @see <a href="http://www.ietf.org/rfc/rfc2045.txt">RFC 2045 section 2.1</a> - */ - static final byte[] CHUNK_SEPARATOR = {'\r','\n'}; - - /** - * This array is a lookup table that translates 6-bit positive integer - * index values into their "Base64 Alphabet" equivalents as specified - * in Table 1 of RFC 2045. - * - * Thanks to "commons" project in ws.apache.org for this code. - * http://svn.apache.org/repos/asf/webservices/commons/trunk/modules/util/ - */ - private static final byte[] intToBase64 = { - 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', - 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', - 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', - 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', - '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/' - }; - - /** - * Byte used to pad output. - */ - private static final byte PAD = '='; - - /** - * This array is a lookup table that translates unicode characters - * drawn from the "Base64 Alphabet" (as specified in Table 1 of RFC 2045) - * into their 6-bit positive integer equivalents. Characters that - * are not in the Base64 alphabet but fall within the bounds of the - * array are translated to -1. - * - * Thanks to "commons" project in ws.apache.org for this code. - * http://svn.apache.org/repos/asf/webservices/commons/trunk/modules/util/ - */ - private static final byte[] base64ToInt = { - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1, -1, 63, 52, 53, 54, - 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -1, -1, -1, -1, 0, 1, 2, 3, 4, - 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, - 24, 25, -1, -1, -1, -1, -1, -1, 26, 27, 28, 29, 30, 31, 32, 33, 34, - 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51 - }; - - /** Mask used to extract 6 bits, used when encoding */ - private static final int MASK_6BITS = 0x3f; - - /** Mask used to extract 8 bits, used in decoding base64 bytes */ - private static final int MASK_8BITS = 0xff; - - // The static final fields above are used for the original static byte[] methods on Base64. - // The private member fields below are used with the new streaming approach, which requires - // some state be preserved between calls of encode() and decode(). - - - /** - * Line length for encoding. Not used when decoding. A value of zero or less implies - * no chunking of the base64 encoded data. - */ - private final int lineLength; - - /** - * Line separator for encoding. Not used when decoding. Only used if lineLength > 0. - */ - private final byte[] lineSeparator; - - /** - * Convenience variable to help us determine when our buffer is going to run out of - * room and needs resizing. <code>decodeSize = 3 + lineSeparator.length;</code> - */ - private final int decodeSize; - - /** - * Convenience variable to help us determine when our buffer is going to run out of - * room and needs resizing. <code>encodeSize = 4 + lineSeparator.length;</code> - */ - private final int encodeSize; - - /** - * Buffer for streaming. - */ - private byte[] buf; - - /** - * Position where next character should be written in the buffer. - */ - private int pos; - - /** - * Position where next character should be read from the buffer. - */ - private int readPos; - - /** - * Variable tracks how many characters have been written to the current line. - * Only used when encoding. We use it to make sure each encoded line never - * goes beyond lineLength (if lineLength > 0). - */ - private int currentLinePos; - - /** - * Writes to the buffer only occur after every 3 reads when encoding, an - * every 4 reads when decoding. This variable helps track that. - */ - private int modulus; - - /** - * Boolean flag to indicate the EOF has been reached. Once EOF has been - * reached, this Base64 object becomes useless, and must be thrown away. - */ - private boolean eof; - - /** - * Place holder for the 3 bytes we're dealing with for our base64 logic. - * Bitwise operations store and extract the base64 encoding or decoding from - * this variable. - */ - private int x; - - /** - * Default constructor: lineLength is 76, and the lineSeparator is CRLF - * when encoding, and all forms can be decoded. - */ - public Base64() { - this(CHUNK_SIZE, CHUNK_SEPARATOR); - } - - /** - * <p> - * Consumer can use this constructor to choose a different lineLength - * when encoding (lineSeparator is still CRLF). All forms of data can - * be decoded. - * </p><p> - * Note: lineLengths that aren't multiples of 4 will still essentially - * end up being multiples of 4 in the encoded data. - * </p> - * - * @param lineLength each line of encoded data will be at most this long - * (rounded up to nearest multiple of 4). - * If lineLength <= 0, then the output will not be divided into lines (chunks). - * Ignored when decoding. - */ - public Base64(int lineLength) { - this(lineLength, CHUNK_SEPARATOR); - } - - /** - * <p> - * Consumer can use this constructor to choose a different lineLength - * and lineSeparator when encoding. All forms of data can - * be decoded. - * </p><p> - * Note: lineLengths that aren't multiples of 4 will still essentially - * end up being multiples of 4 in the encoded data. - * </p> - * @param lineLength Each line of encoded data will be at most this long - * (rounded up to nearest multiple of 4). Ignored when decoding. - * If <= 0, then output will not be divided into lines (chunks). - * @param lineSeparator Each line of encoded data will end with this - * sequence of bytes. - * If lineLength <= 0, then the lineSeparator is not used. - * @throws IllegalArgumentException The provided lineSeparator included - * some base64 characters. That's not going to work! - */ - public Base64(int lineLength, byte[] lineSeparator) { - this.lineLength = lineLength; - this.lineSeparator = new byte[lineSeparator.length]; - System.arraycopy(lineSeparator, 0, this.lineSeparator, 0, lineSeparator.length); - if (lineLength > 0) { - this.encodeSize = 4 + lineSeparator.length; - } else { - this.encodeSize = 4; - } - this.decodeSize = encodeSize - 1; - if (containsBase64Byte(lineSeparator)) { - String sep; - try { - sep = new String(lineSeparator, "UTF-8"); - } catch (UnsupportedEncodingException uee) { - sep = new String(lineSeparator); - } - throw new IllegalArgumentException("lineSeperator must not contain base64 characters: [" + sep + "]"); - } - } - - /** - * Returns true if this Base64 object has buffered data for reading. - * - * @return true if there is Base64 object still available for reading. - */ - boolean hasData() { return buf != null; } - - /** - * Returns the amount of buffered data available for reading. - * - * @return The amount of buffered data available for reading. - */ - int avail() { return buf != null ? pos - readPos : 0; } - - /** Doubles our buffer. */ - private void resizeBuf() { - if (buf == null) { - buf = new byte[8192]; - pos = 0; - readPos = 0; - } else { - byte[] b = new byte[buf.length * 2]; - System.arraycopy(buf, 0, b, 0, buf.length); - buf = b; - } - } - - /** - * Extracts buffered data into the provided byte[] array, starting - * at position bPos, up to a maximum of bAvail bytes. Returns how - * many bytes were actually extracted. - * - * @param b byte[] array to extract the buffered data into. - * @param bPos position in byte[] array to start extraction at. - * @param bAvail amount of bytes we're allowed to extract. We may extract - * fewer (if fewer are available). - * @return The number of bytes successfully extracted into the provided - * byte[] array. - */ - int readResults(byte[] b, int bPos, int bAvail) { - if (buf != null) { - int len = Math.min(avail(), bAvail); - if (buf != b) { - System.arraycopy(buf, readPos, b, bPos, len); - readPos += len; - if (readPos >= pos) { - buf = null; - } - } else { - // Re-using the original consumer's output array is only - // allowed for one round. - buf = null; - } - return len; - } else { - return eof ? -1 : 0; - } - } - - /** - * Small optimization where we try to buffer directly to the consumer's - * output array for one round (if consumer calls this method first!) instead - * of starting our own buffer. - * - * @param out byte[] array to buffer directly to. - * @param outPos Position to start buffering into. - * @param outAvail Amount of bytes available for direct buffering. - */ - void setInitialBuffer(byte[] out, int outPos, int outAvail) { - // We can re-use consumer's original output array under - // special circumstances, saving on some System.arraycopy(). - if (out != null && out.length == outAvail) { - buf = out; - pos = outPos; - readPos = outPos; - } - } - - /** - * <p> - * Encodes all of the provided data, starting at inPos, for inAvail bytes. - * Must be called at least twice: once with the data to encode, and once - * with inAvail set to "-1" to alert encoder that EOF has been reached, - * so flush last remaining bytes (if not multiple of 3). - * </p><p> - * Thanks to "commons" project in ws.apache.org for the bitwise operations, - * and general approach. - * http://svn.apache.org/repos/asf/webservices/commons/trunk/modules/util/ - * </p> - * - * @param in byte[] array of binary data to base64 encode. - * @param inPos Position to start reading data from. - * @param inAvail Amount of bytes available from input for encoding. - */ - void encode(byte[] in, int inPos, int inAvail) { - if (eof) { - return; - } - - // inAvail < 0 is how we're informed of EOF in the underlying data we're - // encoding. - if (inAvail < 0) { - eof = true; - if (buf == null || buf.length - pos < encodeSize) { - resizeBuf(); - } - switch (modulus) { - case 1: - buf[pos++] = intToBase64[(x >> 2) & MASK_6BITS]; - buf[pos++] = intToBase64[(x << 4) & MASK_6BITS]; - buf[pos++] = PAD; - buf[pos++] = PAD; - break; - - case 2: - buf[pos++] = intToBase64[(x >> 10) & MASK_6BITS]; - buf[pos++] = intToBase64[(x >> 4) & MASK_6BITS]; - buf[pos++] = intToBase64[(x << 2) & MASK_6BITS]; - buf[pos++] = PAD; - break; - } - if (lineLength > 0) { - System.arraycopy(lineSeparator, 0, buf, pos, lineSeparator.length); - pos += lineSeparator.length; - } - } else { - for (int i = 0; i < inAvail; i++) { - if (buf == null || buf.length - pos < encodeSize) { - resizeBuf(); - } - modulus = (++modulus) % 3; - int b = in[inPos++]; - if (b < 0) { b += 256; } - x = (x << 8) + b; - if (0 == modulus) { - buf[pos++] = intToBase64[(x >> 18) & MASK_6BITS]; - buf[pos++] = intToBase64[(x >> 12) & MASK_6BITS]; - buf[pos++] = intToBase64[(x >> 6) & MASK_6BITS]; - buf[pos++] = intToBase64[x & MASK_6BITS]; - currentLinePos += 4; - if (lineLength > 0 && lineLength <= currentLinePos) { - System.arraycopy(lineSeparator, 0, buf, pos, lineSeparator.length); - pos += lineSeparator.length; - currentLinePos = 0; - } - } - } - } - } - - /** - * <p> - * Decodes all of the provided data, starting at inPos, for inAvail bytes. - * Should be called at least twice: once with the data to decode, and once - * with inAvail set to "-1" to alert decoder that EOF has been reached. - * The "-1" call is not necessary when decoding, but it doesn't hurt, either. - * </p><p> - * Ignores all non-base64 characters. This is how chunked (e.g. 76 character) - * data is handled, since CR and LF are silently ignored, but has implications - * for other bytes, too. This method subscribes to the garbage-in, garbage-out - * philosophy: it will not check the provided data for validity. - * </p><p> - * Thanks to "commons" project in ws.apache.org for the bitwise operations, - * and general approach. - * http://svn.apache.org/repos/asf/webservices/commons/trunk/modules/util/ - * </p> - - * @param in byte[] array of ascii data to base64 decode. - * @param inPos Position to start reading data from. - * @param inAvail Amount of bytes available from input for encoding. - */ - void decode(byte[] in, int inPos, int inAvail) { - if (eof) { - return; - } - if (inAvail < 0) { - eof = true; - } - for (int i = 0; i < inAvail; i++) { - if (buf == null || buf.length - pos < decodeSize) { - resizeBuf(); - } - byte b = in[inPos++]; - if (b == PAD) { - x = x << 6; - switch (modulus) { - case 2: - x = x << 6; - buf[pos++] = (byte) ((x >> 16) & MASK_8BITS); - break; - case 3: - buf[pos++] = (byte) ((x >> 16) & MASK_8BITS); - buf[pos++] = (byte) ((x >> 8) & MASK_8BITS); - break; - } - // WE'RE DONE!!!! - eof = true; - return; - } else { - if (b >= 0 && b < base64ToInt.length) { - int result = base64ToInt[b]; - if (result >= 0) { - modulus = (++modulus) % 4; - x = (x << 6) + result; - if (modulus == 0) { - buf[pos++] = (byte) ((x >> 16) & MASK_8BITS); - buf[pos++] = (byte) ((x >> 8) & MASK_8BITS); - buf[pos++] = (byte) (x & MASK_8BITS); - } - } - } - } - } - } - - /** - * Returns whether or not the <code>octet</code> is in the base 64 alphabet. - * - * @param octet - * The value to test - * @return <code>true</code> if the value is defined in the the base 64 alphabet, <code>false</code> otherwise. - */ - public static boolean isBase64(byte octet) { - return octet == PAD || (octet >= 0 && octet < base64ToInt.length && base64ToInt[octet] != -1); - } - - /** - * Tests a given byte array to see if it contains only valid characters within the Base64 alphabet. - * Currently the method treats whitespace as valid. - * - * @param arrayOctet - * byte array to test - * @return <code>true</code> if all bytes are valid characters in the Base64 alphabet or if the byte array is - * empty; false, otherwise - */ - public static boolean isArrayByteBase64(byte[] arrayOctet) { - for (int i = 0; i < arrayOctet.length; i++) { - if (!isBase64(arrayOctet[i]) && !isWhiteSpace(arrayOctet[i])) { - return false; - } - } - return true; - } - - /* - * Tests a given byte array to see if it contains only valid characters within the Base64 alphabet. - * - * @param arrayOctet - * byte array to test - * @return <code>true</code> if any byte is a valid character in the Base64 alphabet; false herwise - */ - private static boolean containsBase64Byte(byte[] arrayOctet) { - for (int i = 0; i < arrayOctet.length; i++) { - if (isBase64(arrayOctet[i])) { - return true; - } - } - return false; - } - - /** - * Encodes binary data using the base64 algorithm but does not chunk the output. - * - * @param binaryData - * binary data to encode - * @return Base64 characters - */ - public static byte[] encodeBase64(byte[] binaryData) { - return encodeBase64(binaryData, false); - } - - /** - * Encodes binary data using the base64 algorithm and chunks the encoded output into 76 character blocks - * - * @param binaryData - * binary data to encode - * @return Base64 characters chunked in 76 character blocks - */ - public static byte[] encodeBase64Chunked(byte[] binaryData) { - return encodeBase64(binaryData, true); - } - - /** - * Decodes a byte[] containing containing characters in the Base64 alphabet. - * - * @param pArray - * A byte array containing Base64 character data - * @return a byte array containing binary data - */ - public byte[] decode(byte[] pArray) { - return decodeBase64(pArray); - } - - /** - * Encodes binary data using the base64 algorithm, optionally chunking the output into 76 character blocks. - * - * @param binaryData - * Array containing binary data to encode. - * @param isChunked - * if <code>true</code> this encoder will chunk the base64 output into 76 character blocks - * @return Base64-encoded data. - * @throws IllegalArgumentException - * Thrown when the input array needs an output array bigger than {@link Integer#MAX_VALUE} - */ - public static byte[] encodeBase64(byte[] binaryData, boolean isChunked) { - if (binaryData == null || binaryData.length == 0) { - return binaryData; - } - Base64 b64 = isChunked ? new Base64() : new Base64(0); - - long len = (binaryData.length * 4) / 3; - long mod = len % 4; - if (mod != 0) { - len += 4 - mod; - } - if (isChunked) { - len += (1 + (len / CHUNK_SIZE)) * CHUNK_SEPARATOR.length; - } - - if (len > Integer.MAX_VALUE) { - throw new IllegalArgumentException( - "Input array too big, output array would be bigger than Integer.MAX_VALUE=" + Integer.MAX_VALUE); - } - byte[] buf = new byte[(int) len]; - b64.setInitialBuffer(buf, 0, buf.length); - b64.encode(binaryData, 0, binaryData.length); - b64.encode(binaryData, 0, -1); // Notify encoder of EOF. - - // Encoder might have resized, even though it was unnecessary. - if (b64.buf != buf) { - b64.readResults(buf, 0, buf.length); - } - return buf; - } - - /** - * Decodes Base64 data into octets - * - * @param base64Data Byte array containing Base64 data - * @return Array containing decoded data. - */ - public static byte[] decodeBase64(byte[] base64Data) { - if (base64Data == null || base64Data.length == 0) { - return base64Data; - } - Base64 b64 = new Base64(); - - long len = (base64Data.length * 3) / 4; - byte[] buf = new byte[(int) len]; - b64.setInitialBuffer(buf, 0, buf.length); - b64.decode(base64Data, 0, base64Data.length); - b64.decode(base64Data, 0, -1); // Notify decoder of EOF. - - // We have no idea what the line-length was, so we - // cannot know how much of our array wasn't used. - byte[] result = new byte[b64.pos]; - b64.readResults(result, 0, result.length); - return result; - } - - /** - * Check if a byte value is whitespace or not. - * - * @param byteToCheck the byte to check - * @return true if byte is whitespace, false otherwise - */ - private static boolean isWhiteSpace(byte byteToCheck){ - switch (byteToCheck) { - case ' ' : - case '\n' : - case '\r' : - case '\t' : - return true; - default : - return false; - } - } - - /** - * Discards any characters outside of the base64 alphabet, per the requirements on page 25 of RFC 2045 - "Any - * characters outside of the base64 alphabet are to be ignored in base64 encoded data." - * - * @param data - * The base-64 encoded data to groom - * @return The data, less non-base64 characters (see RFC 2045). - */ - static byte[] discardNonBase64(byte[] data) { - byte groomedData[] = new byte[data.length]; - int bytesCopied = 0; - - for (int i = 0; i < data.length; i++) { - if (isBase64(data[i])) { - groomedData[bytesCopied++] = data[i]; - } - } - - byte packedData[] = new byte[bytesCopied]; - - System.arraycopy(groomedData, 0, packedData, 0, bytesCopied); - - return packedData; - } - - // Implementation of the Encoder Interface - - /** - * Encodes a byte[] containing binary data, into a byte[] containing characters in the Base64 alphabet. - * - * @param pArray - * a byte array containing binary data - * @return A byte array containing only Base64 character data - */ - public byte[] encode(byte[] pArray) { - return encodeBase64(pArray, false); - } - - // Implementation of integer encoding used for crypto - /** - * Decode a byte64-encoded integer according to crypto - * standards such as W3C's XML-Signature - * - * @param pArray a byte array containing base64 character data - * @return A BigInteger - */ - public static BigInteger decodeInteger(byte[] pArray) { - return new BigInteger(1, decodeBase64(pArray)); - } - - /** - * Encode to a byte64-encoded integer according to crypto - * standards such as W3C's XML-Signature - * - * @param bigInt a BigInteger - * @return A byte array containing base64 character data - * @throws NullPointerException if null is passed in - */ - public static byte[] encodeInteger(BigInteger bigInt) { - if(bigInt == null) { - throw new NullPointerException("encodeInteger called with null parameter"); - } - - return encodeBase64(toIntegerBytes(bigInt), false); - } - - /** - * Returns a byte-array representation of a <code>BigInteger</code> - * without sign bit. - * - * @param bigInt <code>BigInteger</code> to be converted - * @return a byte array representation of the BigInteger parameter - */ - static byte[] toIntegerBytes(BigInteger bigInt) { - int bitlen = bigInt.bitLength(); - // round bitlen - bitlen = ((bitlen + 7) >> 3) << 3; - byte[] bigBytes = bigInt.toByteArray(); - - if(((bigInt.bitLength() % 8) != 0) && - (((bigInt.bitLength() / 8) + 1) == (bitlen / 8))) { - return bigBytes; - } - - // set up params for copying everything but sign bit - int startSrc = 0; - int len = bigBytes.length; - - // if bigInt is exactly byte-aligned, just skip signbit in copy - if((bigInt.bitLength() % 8) == 0) { - startSrc = 1; - len--; - } - - int startDst = bitlen / 8 - len; // to pad w/ nulls as per spec - byte[] resizedBytes = new byte[bitlen / 8]; - - System.arraycopy(bigBytes, startSrc, resizedBytes, startDst, len); - - return resizedBytes; - } -} +/* + * Copyright 2001-2008 The Apache Software Foundation. + * The ASF licenses this file to You 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 net.oauth.signature; + +import java.io.UnsupportedEncodingException; +import java.math.BigInteger; + +/** + * Provides Base64 encoding and decoding as defined by RFC 2045. + * + * <p> + * This class implements section <cite>6.8. Base64 Content-Transfer-Encoding</cite> from RFC 2045 <cite>Multipurpose + * Internet Mail Extensions (MIME) Part One: Format of Internet Message Bodies</cite> by Freed and Borenstein. + * </p> + * + * @see <a href="http://www.ietf.org/rfc/rfc2045.txt">RFC 2045</a> + * @author Apache Software Foundation + * @author John Kristian + */ +class Base64 { + /** + * Chunk size per RFC 2045 section 6.8. + * + * <p> + * The {@value} character limit does not count the trailing CRLF, but counts all other characters, including any + * equal signs. + * </p> + * + * @see <a href="http://www.ietf.org/rfc/rfc2045.txt">RFC 2045 section 6.8</a> + */ + static final int CHUNK_SIZE = 76; + + /** + * Chunk separator per RFC 2045 section 2.1. + * + * @see <a href="http://www.ietf.org/rfc/rfc2045.txt">RFC 2045 section 2.1</a> + */ + static final byte[] CHUNK_SEPARATOR = {'\r','\n'}; + + /** + * This array is a lookup table that translates 6-bit positive integer + * index values into their "Base64 Alphabet" equivalents as specified + * in Table 1 of RFC 2045. + * + * Thanks to "commons" project in ws.apache.org for this code. + * http://svn.apache.org/repos/asf/webservices/commons/trunk/modules/util/ + */ + private static final byte[] intToBase64 = { + 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', + 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', + 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', + 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', + '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/' + }; + + /** + * Byte used to pad output. + */ + private static final byte PAD = '='; + + /** + * This array is a lookup table that translates unicode characters + * drawn from the "Base64 Alphabet" (as specified in Table 1 of RFC 2045) + * into their 6-bit positive integer equivalents. Characters that + * are not in the Base64 alphabet but fall within the bounds of the + * array are translated to -1. + * + * Thanks to "commons" project in ws.apache.org for this code. + * http://svn.apache.org/repos/asf/webservices/commons/trunk/modules/util/ + */ + private static final byte[] base64ToInt = { + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1, -1, 63, 52, 53, 54, + 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -1, -1, -1, -1, 0, 1, 2, 3, 4, + 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, + 24, 25, -1, -1, -1, -1, -1, -1, 26, 27, 28, 29, 30, 31, 32, 33, 34, + 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51 + }; + + /** Mask used to extract 6 bits, used when encoding */ + private static final int MASK_6BITS = 0x3f; + + /** Mask used to extract 8 bits, used in decoding base64 bytes */ + private static final int MASK_8BITS = 0xff; + + // The static final fields above are used for the original static byte[] methods on Base64. + // The private member fields below are used with the new streaming approach, which requires + // some state be preserved between calls of encode() and decode(). + + + /** + * Line length for encoding. Not used when decoding. A value of zero or less implies + * no chunking of the base64 encoded data. + */ + private final int lineLength; + + /** + * Line separator for encoding. Not used when decoding. Only used if lineLength > 0. + */ + private final byte[] lineSeparator; + + /** + * Convenience variable to help us determine when our buffer is going to run out of + * room and needs resizing. <code>decodeSize = 3 + lineSeparator.length;</code> + */ + private final int decodeSize; + + /** + * Convenience variable to help us determine when our buffer is going to run out of + * room and needs resizing. <code>encodeSize = 4 + lineSeparator.length;</code> + */ + private final int encodeSize; + + /** + * Buffer for streaming. + */ + private byte[] buf; + + /** + * Position where next character should be written in the buffer. + */ + private int pos; + + /** + * Position where next character should be read from the buffer. + */ + private int readPos; + + /** + * Variable tracks how many characters have been written to the current line. + * Only used when encoding. We use it to make sure each encoded line never + * goes beyond lineLength (if lineLength > 0). + */ + private int currentLinePos; + + /** + * Writes to the buffer only occur after every 3 reads when encoding, an + * every 4 reads when decoding. This variable helps track that. + */ + private int modulus; + + /** + * Boolean flag to indicate the EOF has been reached. Once EOF has been + * reached, this Base64 object becomes useless, and must be thrown away. + */ + private boolean eof; + + /** + * Place holder for the 3 bytes we're dealing with for our base64 logic. + * Bitwise operations store and extract the base64 encoding or decoding from + * this variable. + */ + private int x; + + /** + * Default constructor: lineLength is 76, and the lineSeparator is CRLF + * when encoding, and all forms can be decoded. + */ + public Base64() { + this(CHUNK_SIZE, CHUNK_SEPARATOR); + } + + /** + * <p> + * Consumer can use this constructor to choose a different lineLength + * when encoding (lineSeparator is still CRLF). All forms of data can + * be decoded. + * </p><p> + * Note: lineLengths that aren't multiples of 4 will still essentially + * end up being multiples of 4 in the encoded data. + * </p> + * + * @param lineLength each line of encoded data will be at most this long + * (rounded up to nearest multiple of 4). + * If lineLength <= 0, then the output will not be divided into lines (chunks). + * Ignored when decoding. + */ + public Base64(int lineLength) { + this(lineLength, CHUNK_SEPARATOR); + } + + /** + * <p> + * Consumer can use this constructor to choose a different lineLength + * and lineSeparator when encoding. All forms of data can + * be decoded. + * </p><p> + * Note: lineLengths that aren't multiples of 4 will still essentially + * end up being multiples of 4 in the encoded data. + * </p> + * @param lineLength Each line of encoded data will be at most this long + * (rounded up to nearest multiple of 4). Ignored when decoding. + * If <= 0, then output will not be divided into lines (chunks). + * @param lineSeparator Each line of encoded data will end with this + * sequence of bytes. + * If lineLength <= 0, then the lineSeparator is not used. + * @throws IllegalArgumentException The provided lineSeparator included + * some base64 characters. That's not going to work! + */ + public Base64(int lineLength, byte[] lineSeparator) { + this.lineLength = lineLength; + this.lineSeparator = new byte[lineSeparator.length]; + System.arraycopy(lineSeparator, 0, this.lineSeparator, 0, lineSeparator.length); + if (lineLength > 0) { + this.encodeSize = 4 + lineSeparator.length; + } else { + this.encodeSize = 4; + } + this.decodeSize = encodeSize - 1; + if (containsBase64Byte(lineSeparator)) { + String sep; + try { + sep = new String(lineSeparator, "UTF-8"); + } catch (UnsupportedEncodingException uee) { + sep = new String(lineSeparator); + } + throw new IllegalArgumentException("lineSeperator must not contain base64 characters: [" + sep + "]"); + } + } + + /** + * Returns true if this Base64 object has buffered data for reading. + * + * @return true if there is Base64 object still available for reading. + */ + boolean hasData() { return buf != null; } + + /** + * Returns the amount of buffered data available for reading. + * + * @return The amount of buffered data available for reading. + */ + int avail() { return buf != null ? pos - readPos : 0; } + + /** Doubles our buffer. */ + private void resizeBuf() { + if (buf == null) { + buf = new byte[8192]; + pos = 0; + readPos = 0; + } else { + byte[] b = new byte[buf.length * 2]; + System.arraycopy(buf, 0, b, 0, buf.length); + buf = b; + } + } + + /** + * Extracts buffered data into the provided byte[] array, starting + * at position bPos, up to a maximum of bAvail bytes. Returns how + * many bytes were actually extracted. + * + * @param b byte[] array to extract the buffered data into. + * @param bPos position in byte[] array to start extraction at. + * @param bAvail amount of bytes we're allowed to extract. We may extract + * fewer (if fewer are available). + * @return The number of bytes successfully extracted into the provided + * byte[] array. + */ + int readResults(byte[] b, int bPos, int bAvail) { + if (buf != null) { + int len = Math.min(avail(), bAvail); + if (buf != b) { + System.arraycopy(buf, readPos, b, bPos, len); + readPos += len; + if (readPos >= pos) { + buf = null; + } + } else { + // Re-using the original consumer's output array is only + // allowed for one round. + buf = null; + } + return len; + } else { + return eof ? -1 : 0; + } + } + + /** + * Small optimization where we try to buffer directly to the consumer's + * output array for one round (if consumer calls this method first!) instead + * of starting our own buffer. + * + * @param out byte[] array to buffer directly to. + * @param outPos Position to start buffering into. + * @param outAvail Amount of bytes available for direct buffering. + */ + void setInitialBuffer(byte[] out, int outPos, int outAvail) { + // We can re-use consumer's original output array under + // special circumstances, saving on some System.arraycopy(). + if (out != null && out.length == outAvail) { + buf = out; + pos = outPos; + readPos = outPos; + } + } + + /** + * <p> + * Encodes all of the provided data, starting at inPos, for inAvail bytes. + * Must be called at least twice: once with the data to encode, and once + * with inAvail set to "-1" to alert encoder that EOF has been reached, + * so flush last remaining bytes (if not multiple of 3). + * </p><p> + * Thanks to "commons" project in ws.apache.org for the bitwise operations, + * and general approach. + * http://svn.apache.org/repos/asf/webservices/commons/trunk/modules/util/ + * </p> + * + * @param in byte[] array of binary data to base64 encode. + * @param inPos Position to start reading data from. + * @param inAvail Amount of bytes available from input for encoding. + */ + void encode(byte[] in, int inPos, int inAvail) { + if (eof) { + return; + } + + // inAvail < 0 is how we're informed of EOF in the underlying data we're + // encoding. + if (inAvail < 0) { + eof = true; + if (buf == null || buf.length - pos < encodeSize) { + resizeBuf(); + } + switch (modulus) { + case 1: + buf[pos++] = intToBase64[(x >> 2) & MASK_6BITS]; + buf[pos++] = intToBase64[(x << 4) & MASK_6BITS]; + buf[pos++] = PAD; + buf[pos++] = PAD; + break; + + case 2: + buf[pos++] = intToBase64[(x >> 10) & MASK_6BITS]; + buf[pos++] = intToBase64[(x >> 4) & MASK_6BITS]; + buf[pos++] = intToBase64[(x << 2) & MASK_6BITS]; + buf[pos++] = PAD; + break; + } + if (lineLength > 0) { + System.arraycopy(lineSeparator, 0, buf, pos, lineSeparator.length); + pos += lineSeparator.length; + } + } else { + for (int i = 0; i < inAvail; i++) { + if (buf == null || buf.length - pos < encodeSize) { + resizeBuf(); + } + modulus = (++modulus) % 3; + int b = in[inPos++]; + if (b < 0) { b += 256; } + x = (x << 8) + b; + if (0 == modulus) { + buf[pos++] = intToBase64[(x >> 18) & MASK_6BITS]; + buf[pos++] = intToBase64[(x >> 12) & MASK_6BITS]; + buf[pos++] = intToBase64[(x >> 6) & MASK_6BITS]; + buf[pos++] = intToBase64[x & MASK_6BITS]; + currentLinePos += 4; + if (lineLength > 0 && lineLength <= currentLinePos) { + System.arraycopy(lineSeparator, 0, buf, pos, lineSeparator.length); + pos += lineSeparator.length; + currentLinePos = 0; + } + } + } + } + } + + /** + * <p> + * Decodes all of the provided data, starting at inPos, for inAvail bytes. + * Should be called at least twice: once with the data to decode, and once + * with inAvail set to "-1" to alert decoder that EOF has been reached. + * The "-1" call is not necessary when decoding, but it doesn't hurt, either. + * </p><p> + * Ignores all non-base64 characters. This is how chunked (e.g. 76 character) + * data is handled, since CR and LF are silently ignored, but has implications + * for other bytes, too. This method subscribes to the garbage-in, garbage-out + * philosophy: it will not check the provided data for validity. + * </p><p> + * Thanks to "commons" project in ws.apache.org for the bitwise operations, + * and general approach. + * http://svn.apache.org/repos/asf/webservices/commons/trunk/modules/util/ + * </p> + + * @param in byte[] array of ascii data to base64 decode. + * @param inPos Position to start reading data from. + * @param inAvail Amount of bytes available from input for encoding. + */ + void decode(byte[] in, int inPos, int inAvail) { + if (eof) { + return; + } + if (inAvail < 0) { + eof = true; + } + for (int i = 0; i < inAvail; i++) { + if (buf == null || buf.length - pos < decodeSize) { + resizeBuf(); + } + byte b = in[inPos++]; + if (b == PAD) { + x = x << 6; + switch (modulus) { + case 2: + x = x << 6; + buf[pos++] = (byte) ((x >> 16) & MASK_8BITS); + break; + case 3: + buf[pos++] = (byte) ((x >> 16) & MASK_8BITS); + buf[pos++] = (byte) ((x >> 8) & MASK_8BITS); + break; + } + // WE'RE DONE!!!! + eof = true; + return; + } else { + if (b >= 0 && b < base64ToInt.length) { + int result = base64ToInt[b]; + if (result >= 0) { + modulus = (++modulus) % 4; + x = (x << 6) + result; + if (modulus == 0) { + buf[pos++] = (byte) ((x >> 16) & MASK_8BITS); + buf[pos++] = (byte) ((x >> 8) & MASK_8BITS); + buf[pos++] = (byte) (x & MASK_8BITS); + } + } + } + } + } + } + + /** + * Returns whether or not the <code>octet</code> is in the base 64 alphabet. + * + * @param octet + * The value to test + * @return <code>true</code> if the value is defined in the the base 64 alphabet, <code>false</code> otherwise. + */ + public static boolean isBase64(byte octet) { + return octet == PAD || (octet >= 0 && octet < base64ToInt.length && base64ToInt[octet] != -1); + } + + /** + * Tests a given byte array to see if it contains only valid characters within the Base64 alphabet. + * Currently the method treats whitespace as valid. + * + * @param arrayOctet + * byte array to test + * @return <code>true</code> if all bytes are valid characters in the Base64 alphabet or if the byte array is + * empty; false, otherwise + */ + public static boolean isArrayByteBase64(byte[] arrayOctet) { + for (int i = 0; i < arrayOctet.length; i++) { + if (!isBase64(arrayOctet[i]) && !isWhiteSpace(arrayOctet[i])) { + return false; + } + } + return true; + } + + /* + * Tests a given byte array to see if it contains only valid characters within the Base64 alphabet. + * + * @param arrayOctet + * byte array to test + * @return <code>true</code> if any byte is a valid character in the Base64 alphabet; false herwise + */ + private static boolean containsBase64Byte(byte[] arrayOctet) { + for (int i = 0; i < arrayOctet.length; i++) { + if (isBase64(arrayOctet[i])) { + return true; + } + } + return false; + } + + /** + * Encodes binary data using the base64 algorithm but does not chunk the output. + * + * @param binaryData + * binary data to encode + * @return Base64 characters + */ + public static byte[] encodeBase64(byte[] binaryData) { + return encodeBase64(binaryData, false); + } + + /** + * Encodes binary data using the base64 algorithm and chunks the encoded output into 76 character blocks + * + * @param binaryData + * binary data to encode + * @return Base64 characters chunked in 76 character blocks + */ + public static byte[] encodeBase64Chunked(byte[] binaryData) { + return encodeBase64(binaryData, true); + } + + /** + * Decodes a byte[] containing containing characters in the Base64 alphabet. + * + * @param pArray + * A byte array containing Base64 character data + * @return a byte array containing binary data + */ + public byte[] decode(byte[] pArray) { + return decodeBase64(pArray); + } + + /** + * Encodes binary data using the base64 algorithm, optionally chunking the output into 76 character blocks. + * + * @param binaryData + * Array containing binary data to encode. + * @param isChunked + * if <code>true</code> this encoder will chunk the base64 output into 76 character blocks + * @return Base64-encoded data. + * @throws IllegalArgumentException + * Thrown when the input array needs an output array bigger than {@link Integer#MAX_VALUE} + */ + public static byte[] encodeBase64(byte[] binaryData, boolean isChunked) { + if (binaryData == null || binaryData.length == 0) { + return binaryData; + } + Base64 b64 = isChunked ? new Base64() : new Base64(0); + + long len = (binaryData.length * 4) / 3; + long mod = len % 4; + if (mod != 0) { + len += 4 - mod; + } + if (isChunked) { + len += (1 + (len / CHUNK_SIZE)) * CHUNK_SEPARATOR.length; + } + + if (len > Integer.MAX_VALUE) { + throw new IllegalArgumentException( + "Input array too big, output array would be bigger than Integer.MAX_VALUE=" + Integer.MAX_VALUE); + } + byte[] buf = new byte[(int) len]; + b64.setInitialBuffer(buf, 0, buf.length); + b64.encode(binaryData, 0, binaryData.length); + b64.encode(binaryData, 0, -1); // Notify encoder of EOF. + + // Encoder might have resized, even though it was unnecessary. + if (b64.buf != buf) { + b64.readResults(buf, 0, buf.length); + } + return buf; + } + + /** + * Decodes Base64 data into octets + * + * @param base64Data Byte array containing Base64 data + * @return Array containing decoded data. + */ + public static byte[] decodeBase64(byte[] base64Data) { + if (base64Data == null || base64Data.length == 0) { + return base64Data; + } + Base64 b64 = new Base64(); + + long len = (base64Data.length * 3) / 4; + byte[] buf = new byte[(int) len]; + b64.setInitialBuffer(buf, 0, buf.length); + b64.decode(base64Data, 0, base64Data.length); + b64.decode(base64Data, 0, -1); // Notify decoder of EOF. + + // We have no idea what the line-length was, so we + // cannot know how much of our array wasn't used. + byte[] result = new byte[b64.pos]; + b64.readResults(result, 0, result.length); + return result; + } + + /** + * Check if a byte value is whitespace or not. + * + * @param byteToCheck the byte to check + * @return true if byte is whitespace, false otherwise + */ + private static boolean isWhiteSpace(byte byteToCheck){ + switch (byteToCheck) { + case ' ' : + case '\n' : + case '\r' : + case '\t' : + return true; + default : + return false; + } + } + + /** + * Discards any characters outside of the base64 alphabet, per the requirements on page 25 of RFC 2045 - "Any + * characters outside of the base64 alphabet are to be ignored in base64 encoded data." + * + * @param data + * The base-64 encoded data to groom + * @return The data, less non-base64 characters (see RFC 2045). + */ + static byte[] discardNonBase64(byte[] data) { + byte groomedData[] = new byte[data.length]; + int bytesCopied = 0; + + for (int i = 0; i < data.length; i++) { + if (isBase64(data[i])) { + groomedData[bytesCopied++] = data[i]; + } + } + + byte packedData[] = new byte[bytesCopied]; + + System.arraycopy(groomedData, 0, packedData, 0, bytesCopied); + + return packedData; + } + + // Implementation of the Encoder Interface + + /** + * Encodes a byte[] containing binary data, into a byte[] containing characters in the Base64 alphabet. + * + * @param pArray + * a byte array containing binary data + * @return A byte array containing only Base64 character data + */ + public byte[] encode(byte[] pArray) { + return encodeBase64(pArray, false); + } + + // Implementation of integer encoding used for crypto + /** + * Decode a byte64-encoded integer according to crypto + * standards such as W3C's XML-Signature + * + * @param pArray a byte array containing base64 character data + * @return A BigInteger + */ + public static BigInteger decodeInteger(byte[] pArray) { + return new BigInteger(1, decodeBase64(pArray)); + } + + /** + * Encode to a byte64-encoded integer according to crypto + * standards such as W3C's XML-Signature + * + * @param bigInt a BigInteger + * @return A byte array containing base64 character data + * @throws NullPointerException if null is passed in + */ + public static byte[] encodeInteger(BigInteger bigInt) { + if(bigInt == null) { + throw new NullPointerException("encodeInteger called with null parameter"); + } + + return encodeBase64(toIntegerBytes(bigInt), false); + } + + /** + * Returns a byte-array representation of a <code>BigInteger</code> + * without sign bit. + * + * @param bigInt <code>BigInteger</code> to be converted + * @return a byte array representation of the BigInteger parameter + */ + static byte[] toIntegerBytes(BigInteger bigInt) { + int bitlen = bigInt.bitLength(); + // round bitlen + bitlen = ((bitlen + 7) >> 3) << 3; + byte[] bigBytes = bigInt.toByteArray(); + + if(((bigInt.bitLength() % 8) != 0) && + (((bigInt.bitLength() / 8) + 1) == (bitlen / 8))) { + return bigBytes; + } + + // set up params for copying everything but sign bit + int startSrc = 0; + int len = bigBytes.length; + + // if bigInt is exactly byte-aligned, just skip signbit in copy + if((bigInt.bitLength() % 8) == 0) { + startSrc = 1; + len--; + } + + int startDst = bitlen / 8 - len; // to pad w/ nulls as per spec + byte[] resizedBytes = new byte[bitlen / 8]; + + System.arraycopy(bigBytes, startSrc, resizedBytes, startDst, len); + + return resizedBytes; + } +} diff --git a/bbb-lti/src/java/net/oauth/signature/HMAC_SHA1.java b/bbb-lti/src/java/net/oauth/signature/HMAC_SHA1.java index 244ea07cc9..ed9dc5c694 100644 --- a/bbb-lti/src/java/net/oauth/signature/HMAC_SHA1.java +++ b/bbb-lti/src/java/net/oauth/signature/HMAC_SHA1.java @@ -1,102 +1,102 @@ -/* - * Copyright 2007 Netflix, Inc. - * - * 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 net.oauth.signature; - -import java.io.UnsupportedEncodingException; -import java.security.GeneralSecurityException; -import java.util.Arrays; - -import javax.crypto.Mac; -import javax.crypto.SecretKey; -import javax.crypto.spec.SecretKeySpec; - -import net.oauth.OAuth; -import net.oauth.OAuthException; - -/** - * @author John Kristian - */ -class HMAC_SHA1 extends OAuthSignatureMethod { - - @Override - protected String getSignature(String baseString) throws OAuthException { - try { - String signature = base64Encode(computeSignature(baseString)); - return signature; - } catch (GeneralSecurityException e) { - throw new OAuthException(e); - } catch (UnsupportedEncodingException e) { - throw new OAuthException(e); - } - } - - @Override - protected boolean isValid(String signature, String baseString) - throws OAuthException { - try { - byte[] expected = computeSignature(baseString); - byte[] actual = decodeBase64(signature); - return Arrays.equals(expected, actual); - } catch (GeneralSecurityException e) { - throw new OAuthException(e); - } catch (UnsupportedEncodingException e) { - throw new OAuthException(e); - } - } - - private byte[] computeSignature(String baseString) - throws GeneralSecurityException, UnsupportedEncodingException { - SecretKey key = null; - synchronized (this) { - if (this.key == null) { - String keyString = OAuth.percentEncode(getConsumerSecret()) - + '&' + OAuth.percentEncode(getTokenSecret()); - byte[] keyBytes = keyString.getBytes(ENCODING); - this.key = new SecretKeySpec(keyBytes, MAC_NAME); - } - key = this.key; - } - Mac mac = Mac.getInstance(MAC_NAME); - mac.init(key); - byte[] text = baseString.getBytes(ENCODING); - return mac.doFinal(text); - } - - /** ISO-8859-1 or US-ASCII would work, too. */ - private static final String ENCODING = OAuth.ENCODING; - - private static final String MAC_NAME = "HmacSHA1"; - - private SecretKey key = null; - - @Override - public void setConsumerSecret(String consumerSecret) { - synchronized (this) { - key = null; - } - super.setConsumerSecret(consumerSecret); - } - - @Override - public void setTokenSecret(String tokenSecret) { - synchronized (this) { - key = null; - } - super.setTokenSecret(tokenSecret); - } - -} +/* + * Copyright 2007 Netflix, Inc. + * + * 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 net.oauth.signature; + +import java.io.UnsupportedEncodingException; +import java.security.GeneralSecurityException; +import java.util.Arrays; + +import javax.crypto.Mac; +import javax.crypto.SecretKey; +import javax.crypto.spec.SecretKeySpec; + +import net.oauth.OAuth; +import net.oauth.OAuthException; + +/** + * @author John Kristian + */ +public class HMAC_SHA1 extends OAuthSignatureMethod { + + @Override + public String getSignature(String baseString) throws OAuthException { + try { + String signature = base64Encode(computeSignature(baseString)); + return signature; + } catch (GeneralSecurityException e) { + throw new OAuthException(e); + } catch (UnsupportedEncodingException e) { + throw new OAuthException(e); + } + } + + @Override + public boolean isValid(String signature, String baseString) + throws OAuthException { + try { + byte[] expected = computeSignature(baseString); + byte[] actual = decodeBase64(signature); + return Arrays.equals(expected, actual); + } catch (GeneralSecurityException e) { + throw new OAuthException(e); + } catch (UnsupportedEncodingException e) { + throw new OAuthException(e); + } + } + + private byte[] computeSignature(String baseString) + throws GeneralSecurityException, UnsupportedEncodingException { + SecretKey key = null; + synchronized (this) { + if (this.key == null) { + String keyString = OAuth.percentEncode(getConsumerSecret()) + + '&' + OAuth.percentEncode(getTokenSecret()); + byte[] keyBytes = keyString.getBytes(ENCODING); + this.key = new SecretKeySpec(keyBytes, MAC_NAME); + } + key = this.key; + } + Mac mac = Mac.getInstance(MAC_NAME); + mac.init(key); + byte[] text = baseString.getBytes(ENCODING); + return mac.doFinal(text); + } + + /** ISO-8859-1 or US-ASCII would work, too. */ + private static final String ENCODING = OAuth.ENCODING; + + private static final String MAC_NAME = "HmacSHA1"; + + private SecretKey key = null; + + @Override + public void setConsumerSecret(String consumerSecret) { + synchronized (this) { + key = null; + } + super.setConsumerSecret(consumerSecret); + } + + @Override + public void setTokenSecret(String tokenSecret) { + synchronized (this) { + key = null; + } + super.setTokenSecret(tokenSecret); + } + +} diff --git a/bbb-lti/src/java/net/oauth/signature/OAuthSignatureMethod.java b/bbb-lti/src/java/net/oauth/signature/OAuthSignatureMethod.java index ec1d5595a7..5b53b07de3 100644 --- a/bbb-lti/src/java/net/oauth/signature/OAuthSignatureMethod.java +++ b/bbb-lti/src/java/net/oauth/signature/OAuthSignatureMethod.java @@ -31,7 +31,6 @@ import net.oauth.OAuthConsumer; import net.oauth.OAuthException; import net.oauth.OAuthMessage; import net.oauth.OAuthProblemException; -import org.apache.commons.codec.binary.Base64; /** * A pair of algorithms for computing and verifying an OAuth digital signature. diff --git a/bbb-lti/src/java/net/oauth/signature/PLAINTEXT.java b/bbb-lti/src/java/net/oauth/signature/PLAINTEXT.java index 1efd94f596..59c94a900e 100644 --- a/bbb-lti/src/java/net/oauth/signature/PLAINTEXT.java +++ b/bbb-lti/src/java/net/oauth/signature/PLAINTEXT.java @@ -1,64 +1,64 @@ -/* - * Copyright 2007 Netflix, Inc. - * - * 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 net.oauth.signature; - -import net.oauth.OAuth; -import net.oauth.OAuthException; - -/** - * @author John Kristian - */ -class PLAINTEXT extends OAuthSignatureMethod { - - @Override - public String getSignature(String baseString) { - return getSignature(); - } - - @Override - protected boolean isValid(String signature, String baseString) - throws OAuthException { - return signature.equals(getSignature()); - } - - private synchronized String getSignature() { - if (signature == null) { - signature = OAuth.percentEncode(getConsumerSecret()) + '&' - + OAuth.percentEncode(getTokenSecret()); - } - return signature; - } - - private String signature = null; - - @Override - public void setConsumerSecret(String consumerSecret) { - synchronized (this) { - signature = null; - } - super.setConsumerSecret(consumerSecret); - } - - @Override - public void setTokenSecret(String tokenSecret) { - synchronized (this) { - signature = null; - } - super.setTokenSecret(tokenSecret); - } - -} +/* + * Copyright 2007 Netflix, Inc. + * + * 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 net.oauth.signature; + +import net.oauth.OAuth; +import net.oauth.OAuthException; + +/** + * @author John Kristian + */ +class PLAINTEXT extends OAuthSignatureMethod { + + @Override + public String getSignature(String baseString) { + return getSignature(); + } + + @Override + protected boolean isValid(String signature, String baseString) + throws OAuthException { + return signature.equals(getSignature()); + } + + private synchronized String getSignature() { + if (signature == null) { + signature = OAuth.percentEncode(getConsumerSecret()) + '&' + + OAuth.percentEncode(getTokenSecret()); + } + return signature; + } + + private String signature = null; + + @Override + public void setConsumerSecret(String consumerSecret) { + synchronized (this) { + signature = null; + } + super.setConsumerSecret(consumerSecret); + } + + @Override + public void setTokenSecret(String tokenSecret) { + synchronized (this) { + signature = null; + } + super.setTokenSecret(tokenSecret); + } + +} diff --git a/bbb-lti/src/java/net/oauth/signature/RSA_SHA1.java b/bbb-lti/src/java/net/oauth/signature/RSA_SHA1.java index f723ecb75d..a0f92a7923 100644 --- a/bbb-lti/src/java/net/oauth/signature/RSA_SHA1.java +++ b/bbb-lti/src/java/net/oauth/signature/RSA_SHA1.java @@ -17,6 +17,8 @@ package net.oauth.signature; import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; import java.io.UnsupportedEncodingException; import java.security.GeneralSecurityException; import java.security.KeyFactory; @@ -26,12 +28,15 @@ import java.security.Signature; import java.security.cert.CertificateFactory; import java.security.cert.X509Certificate; import java.security.spec.EncodedKeySpec; +import java.security.spec.KeySpec; import java.security.spec.PKCS8EncodedKeySpec; import java.security.spec.X509EncodedKeySpec; import net.oauth.OAuth; import net.oauth.OAuthAccessor; import net.oauth.OAuthException; +import net.oauth.signature.pem.PEMReader; +import net.oauth.signature.pem.PKCS1EncodedKeySpec; /** * Class to handle RSA-SHA1 signatures on OAuth requests. A consumer @@ -44,10 +49,9 @@ import net.oauth.OAuthException; * c.setProperty(RSA_SHA1.PRIVATE_KEY, consumer_privateRSAKey); * * consumer_privateRSAKey must be an RSA signing key and - * of type java.security.PrivateKey, String, or byte[]. In the latter two - * cases, the key must be PKCS#8-encoded (byte[]) or PKCS#8-encoded and - * then Base64-encoded (String). - * + * of type java.security.PrivateKey, String, byte[] or InputStream. + * The key must either PKCS#1 or PKCS#8 encoded. + * * A service provider that wishes to verify signatures made by such a * consumer does not need a shared secret with the consumer, but it needs * to know the consumer's public key. You create the necessary @@ -87,73 +91,36 @@ public class RSA_SHA1 extends OAuthSignatureMethod { private PrivateKey privateKey = null; private PublicKey publicKey = null; - + @Override protected void initialize(String name, OAuthAccessor accessor) throws OAuthException { super.initialize(name, accessor); - Object privateKeyObject = accessor.consumer.getProperty(PRIVATE_KEY); + // Due to the support of PEM input stream, the keys must be cached. + // The stream may not be markable so it can't be read again. try { + Object privateKeyObject = accessor.consumer.getProperty(PRIVATE_KEY); if (privateKeyObject != null) { - if (privateKeyObject instanceof PrivateKey) { - privateKey = (PrivateKey)privateKeyObject; - } else if (privateKeyObject instanceof String) { - privateKey = getPrivateKeyFromPem((String)privateKeyObject); - } else if (privateKeyObject instanceof byte[]) { - privateKey = getPrivateKeyFromDer((byte[])privateKeyObject); - } else { - throw new IllegalArgumentException( - "Private key set through RSA_SHA1.PRIVATE_KEY must be of " + - "type PrivateKey, String, or byte[], and not " + - privateKeyObject.getClass().getName()); - } + privateKey = loadPrivateKey(privateKeyObject); } Object publicKeyObject = accessor.consumer.getProperty(PUBLIC_KEY); if (publicKeyObject != null) { - if (publicKeyObject instanceof PublicKey) { - publicKey = (PublicKey)publicKeyObject; - } else if (publicKeyObject instanceof String) { - publicKey = getPublicKeyFromPem((String)publicKeyObject); - } else if (publicKeyObject instanceof byte[]) { - publicKey = getPublicKeyFromDer((byte[])publicKeyObject); - } else { - throw new IllegalArgumentException( - "Public key set through RSA_SHA1.PRIVATE_KEY must be of " + - "type PublicKey, String, or byte[], and not " + - publicKeyObject.getClass().getName()); - } + publicKey = loadPublicKey(publicKeyObject, false); } else { // public key was null. perhaps they gave us a X509 cert. Object certObject = accessor.consumer.getProperty(X509_CERTIFICATE); if (certObject != null) { - if (certObject instanceof X509Certificate) { - publicKey = ((X509Certificate) certObject).getPublicKey(); - } else if (certObject instanceof String) { - publicKey = getPublicKeyFromPemCert((String)certObject); - } else if (certObject instanceof byte[]) { - publicKey = getPublicKeyFromDerCert((byte[])certObject); - } else { - throw new IllegalArgumentException( - "X509Certificate set through RSA_SHA1.X509_CERTIFICATE" + - " must be of type X509Certificate, String, or byte[]," + - " and not " + certObject.getClass().getName()); - } + publicKey = loadPublicKey(certObject, true); } } } catch (GeneralSecurityException e) { throw new OAuthException(e); + } catch (IOException e) { + throw new OAuthException(e); } } - private PublicKey getPublicKeyFromPemCert(String certObject) - throws GeneralSecurityException { - CertificateFactory fac = CertificateFactory.getInstance("X509"); - ByteArrayInputStream in = new ByteArrayInputStream(certObject.getBytes()); - X509Certificate cert = (X509Certificate)fac.generateCertificate(in); - return cert.getPublicKey(); - } - private PublicKey getPublicKeyFromDerCert(byte[] certObject) throws GeneralSecurityException { CertificateFactory fac = CertificateFactory.getInstance("X509"); @@ -169,9 +136,28 @@ public class RSA_SHA1 extends OAuthSignatureMethod { return fac.generatePublic(pubKeySpec); } - private PublicKey getPublicKeyFromPem(String publicKeyObject) - throws GeneralSecurityException { - return getPublicKeyFromDer(decodeBase64(publicKeyObject)); + private PublicKey getPublicKeyFromPem(String pem) + throws GeneralSecurityException, IOException { + + InputStream stream = new ByteArrayInputStream( + pem.getBytes("UTF-8")); + + PEMReader reader = new PEMReader(stream); + byte[] bytes = reader.getDerBytes(); + PublicKey pubKey; + + if (PEMReader.PUBLIC_X509_MARKER.equals(reader.getBeginMarker())) { + KeySpec keySpec = new X509EncodedKeySpec(bytes); + KeyFactory fac = KeyFactory.getInstance("RSA"); + pubKey = fac.generatePublic(keySpec); + } else if (PEMReader.CERTIFICATE_X509_MARKER.equals(reader.getBeginMarker())) { + pubKey = getPublicKeyFromDerCert(bytes); + } else { + throw new IOException("Invalid PEM fileL: Unknown marker for " + + " public key or cert " + reader.getBeginMarker()); + } + + return pubKey; } private PrivateKey getPrivateKeyFromDer(byte[] privateKeyObject) @@ -181,9 +167,27 @@ public class RSA_SHA1 extends OAuthSignatureMethod { return fac.generatePrivate(privKeySpec); } - private PrivateKey getPrivateKeyFromPem(String privateKeyObject) - throws GeneralSecurityException { - return getPrivateKeyFromDer(decodeBase64(privateKeyObject)); + private PrivateKey getPrivateKeyFromPem(String pem) + throws GeneralSecurityException, IOException { + + InputStream stream = new ByteArrayInputStream( + pem.getBytes("UTF-8")); + + PEMReader reader = new PEMReader(stream); + byte[] bytes = reader.getDerBytes(); + KeySpec keySpec; + + if (PEMReader.PRIVATE_PKCS1_MARKER.equals(reader.getBeginMarker())) { + keySpec = (new PKCS1EncodedKeySpec(bytes)).getKeySpec(); + } else if (PEMReader.PRIVATE_PKCS8_MARKER.equals(reader.getBeginMarker())) { + keySpec = new PKCS8EncodedKeySpec(bytes); + } else { + throw new IOException("Invalid PEM file: Unknown marker " + + "for private key " + reader.getBeginMarker()); + } + + KeyFactory fac = KeyFactory.getInstance("RSA"); + return fac.generatePrivate(keySpec); } @Override @@ -235,4 +239,99 @@ public class RSA_SHA1 extends OAuthSignatureMethod { verifier.update(message); return verifier.verify(signature); } + + /** + * Load private key from various sources, including + * <ul> + * <li>A PrivateKey object + * <li>A string buffer for PEM + * <li>A byte array with PKCS#8 encoded key + * </ul> + * @param privateKeyObject + * @return The private key + * @throws IOException + * @throws GeneralSecurityException + */ + private PrivateKey loadPrivateKey(Object privateKeyObject) + throws IOException, GeneralSecurityException { + + PrivateKey privateKey; + + if (privateKeyObject instanceof PrivateKey) { + privateKey = (PrivateKey)privateKeyObject; + } else if (privateKeyObject instanceof String) { + try { + // PEM Reader's native string constructor is for filename. + privateKey = getPrivateKeyFromPem((String)privateKeyObject); + } catch (IOException e) { + // Check if it's PEM with markers stripped + privateKey = getPrivateKeyFromDer( + decodeBase64((String)privateKeyObject)); + } + } else if (privateKeyObject instanceof byte[]) { + privateKey = getPrivateKeyFromDer((byte[])privateKeyObject); + } else { + throw new IllegalArgumentException( + "Private key set through RSA_SHA1.PRIVATE_KEY must be of " + + "type PrivateKey, String or byte[] and not " + + privateKeyObject.getClass().getName()); + } + + return privateKey; + } + + /** + * Load a public key from key file or certificate. It can load from + * different sources depending on the type of the input, + * <ul> + * <li>A PublicKey object + * <li>A X509Certificate object + * <li>A string buffer for PEM + * <li>A byte array with X509 encoded key or certificate + * </ul> + * + * @param publicKeyObject The object for public key or certificate + * @param isCert True if this object is provided as Certificate + * @return The public key + * @throws IOException + * @throws GeneralSecurityException + */ + private PublicKey loadPublicKey(Object publicKeyObject, boolean isCert) + throws IOException, GeneralSecurityException { + + PublicKey publicKey; + + if (publicKeyObject instanceof PublicKey) { + publicKey = (PublicKey)publicKeyObject; + } else if (publicKeyObject instanceof X509Certificate) { + publicKey = ((X509Certificate) publicKeyObject).getPublicKey(); + } else if (publicKeyObject instanceof String) { + try { + publicKey = getPublicKeyFromPem((String)publicKeyObject); + } catch (IOException e) { + // Check if it's marker-stripped PEM for public key + if (isCert) + throw e; + publicKey = getPublicKeyFromDer( + decodeBase64((String)publicKeyObject)); + } + } else if (publicKeyObject instanceof byte[]) { + if (isCert) + publicKey = getPublicKeyFromDerCert((byte[])publicKeyObject); + else + publicKey = getPublicKeyFromDer((byte[])publicKeyObject); + } else { + String source; + if (isCert) + source = "RSA_SHA1.X509_CERTIFICATE"; + else + source = "RSA_SHA1.PUBLIC_KEY"; + throw new IllegalArgumentException( + "Public key or certificate set through " + source + " must be of " + + "type PublicKey, String or byte[], and not " + + publicKeyObject.getClass().getName()); + } + + return publicKey; + } } diff --git a/bbb-lti/src/java/net/oauth/signature/pem/Asn1Object.java b/bbb-lti/src/java/net/oauth/signature/pem/Asn1Object.java index 8a96707583..4994f1f1b3 100644 --- a/bbb-lti/src/java/net/oauth/signature/pem/Asn1Object.java +++ b/bbb-lti/src/java/net/oauth/signature/pem/Asn1Object.java @@ -1,150 +1,150 @@ -/**************************************************************************** - * Copyright (c) 1998-2009 AOL LLC. - * - * 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 net.oauth.signature.pem; - -import java.io.IOException; -import java.math.BigInteger; - -/** - * An ASN.1 TLV. The object is not parsed. It can - * only handle integers and strings. - * - * @author zhang - * - */ -class Asn1Object { - - protected final int type; - protected final int length; - protected final byte[] value; - protected final int tag; - - /** - * Construct a ASN.1 TLV. The TLV could be either a - * constructed or primitive entity. - * - * <p/>The first byte in DER encoding is made of following fields, - * <pre> - *------------------------------------------------- - *|Bit 8|Bit 7|Bit 6|Bit 5|Bit 4|Bit 3|Bit 2|Bit 1| - *------------------------------------------------- - *| Class | CF | + Type | - *------------------------------------------------- - * </pre> - * <ul> - * <li>Class: Universal, Application, Context or Private - * <li>CF: Constructed flag. If 1, the field is constructed. - * <li>Type: This is actually called tag in ASN.1. It - * indicates data type (Integer, String) or a construct - * (sequence, choice, set). - * </ul> - * - * @param tag Tag or Identifier - * @param length Length of the field - * @param value Encoded octet string for the field. - */ - public Asn1Object(int tag, int length, byte[] value) { - this.tag = tag; - this.type = tag & 0x1F; - this.length = length; - this.value = value; - } - - public int getType() { - return type; - } - - public int getLength() { - return length; - } - - public byte[] getValue() { - return value; - } - - public boolean isConstructed() { - return (tag & DerParser.CONSTRUCTED) == DerParser.CONSTRUCTED; - } - - /** - * For constructed field, return a parser for its content. - * - * @return A parser for the construct. - * @throws IOException - */ - public DerParser getParser() throws IOException { - if (!isConstructed()) - throw new IOException("Invalid DER: can't parse primitive entity"); //$NON-NLS-1$ - - return new DerParser(value); - } - - /** - * Get the value as integer - * - * @return BigInteger - * @throws IOException - */ - public BigInteger getInteger() throws IOException { - if (type != DerParser.INTEGER) - throw new IOException("Invalid DER: object is not integer"); //$NON-NLS-1$ - - return new BigInteger(value); - } - - /** - * Get value as string. Most strings are treated - * as Latin-1. - * - * @return Java string - * @throws IOException - */ - public String getString() throws IOException { - - String encoding; - - switch (type) { - - // Not all are Latin-1 but it's the closest thing - case DerParser.NUMERIC_STRING: - case DerParser.PRINTABLE_STRING: - case DerParser.VIDEOTEX_STRING: - case DerParser.IA5_STRING: - case DerParser.GRAPHIC_STRING: - case DerParser.ISO646_STRING: - case DerParser.GENERAL_STRING: - encoding = "ISO-8859-1"; //$NON-NLS-1$ - break; - - case DerParser.BMP_STRING: - encoding = "UTF-16BE"; //$NON-NLS-1$ - break; - - case DerParser.UTF8_STRING: - encoding = "UTF-8"; //$NON-NLS-1$ - break; - - case DerParser.UNIVERSAL_STRING: - throw new IOException("Invalid DER: can't handle UCS-4 string"); //$NON-NLS-1$ - - default: - throw new IOException("Invalid DER: object is not a string"); //$NON-NLS-1$ - } - - return new String(value, encoding); - } -} +/**************************************************************************** + * Copyright (c) 1998-2009 AOL LLC. + * + * 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 net.oauth.signature.pem; + +import java.io.IOException; +import java.math.BigInteger; + +/** + * An ASN.1 TLV. The object is not parsed. It can + * only handle integers and strings. + * + * @author zhang + * + */ +class Asn1Object { + + protected final int type; + protected final int length; + protected final byte[] value; + protected final int tag; + + /** + * Construct a ASN.1 TLV. The TLV could be either a + * constructed or primitive entity. + * + * <p/>The first byte in DER encoding is made of following fields, + * <pre> + *------------------------------------------------- + *|Bit 8|Bit 7|Bit 6|Bit 5|Bit 4|Bit 3|Bit 2|Bit 1| + *------------------------------------------------- + *| Class | CF | + Type | + *------------------------------------------------- + * </pre> + * <ul> + * <li>Class: Universal, Application, Context or Private + * <li>CF: Constructed flag. If 1, the field is constructed. + * <li>Type: This is actually called tag in ASN.1. It + * indicates data type (Integer, String) or a construct + * (sequence, choice, set). + * </ul> + * + * @param tag Tag or Identifier + * @param length Length of the field + * @param value Encoded octet string for the field. + */ + public Asn1Object(int tag, int length, byte[] value) { + this.tag = tag; + this.type = tag & 0x1F; + this.length = length; + this.value = value; + } + + public int getType() { + return type; + } + + public int getLength() { + return length; + } + + public byte[] getValue() { + return value; + } + + public boolean isConstructed() { + return (tag & DerParser.CONSTRUCTED) == DerParser.CONSTRUCTED; + } + + /** + * For constructed field, return a parser for its content. + * + * @return A parser for the construct. + * @throws IOException + */ + public DerParser getParser() throws IOException { + if (!isConstructed()) + throw new IOException("Invalid DER: can't parse primitive entity"); //$NON-NLS-1$ + + return new DerParser(value); + } + + /** + * Get the value as integer + * + * @return BigInteger + * @throws IOException + */ + public BigInteger getInteger() throws IOException { + if (type != DerParser.INTEGER) + throw new IOException("Invalid DER: object is not integer"); //$NON-NLS-1$ + + return new BigInteger(value); + } + + /** + * Get value as string. Most strings are treated + * as Latin-1. + * + * @return Java string + * @throws IOException + */ + public String getString() throws IOException { + + String encoding; + + switch (type) { + + // Not all are Latin-1 but it's the closest thing + case DerParser.NUMERIC_STRING: + case DerParser.PRINTABLE_STRING: + case DerParser.VIDEOTEX_STRING: + case DerParser.IA5_STRING: + case DerParser.GRAPHIC_STRING: + case DerParser.ISO646_STRING: + case DerParser.GENERAL_STRING: + encoding = "ISO-8859-1"; //$NON-NLS-1$ + break; + + case DerParser.BMP_STRING: + encoding = "UTF-16BE"; //$NON-NLS-1$ + break; + + case DerParser.UTF8_STRING: + encoding = "UTF-8"; //$NON-NLS-1$ + break; + + case DerParser.UNIVERSAL_STRING: + throw new IOException("Invalid DER: can't handle UCS-4 string"); //$NON-NLS-1$ + + default: + throw new IOException("Invalid DER: object is not a string"); //$NON-NLS-1$ + } + + return new String(value, encoding); + } +} diff --git a/bbb-lti/src/java/net/oauth/signature/pem/DerParser.java b/bbb-lti/src/java/net/oauth/signature/pem/DerParser.java index b82526b028..fff242ea84 100644 --- a/bbb-lti/src/java/net/oauth/signature/pem/DerParser.java +++ b/bbb-lti/src/java/net/oauth/signature/pem/DerParser.java @@ -1,170 +1,170 @@ -/**************************************************************************** - * Copyright (c) 1998-2009 AOL LLC. - * - * 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 net.oauth.signature.pem; - -import java.io.ByteArrayInputStream; -import java.io.IOException; -import java.io.InputStream; -import java.math.BigInteger; - -/** - * A bare-minimum ASN.1 DER decoder, just having enough functions to - * decode PKCS#1 private keys. Especially, it doesn't handle explicitly - * tagged types with an outer tag. - * - * <p/>This parser can only handle one layer. To parse nested constructs, - * get a new parser for each layer using <code>Asn1Object.getParser()</code>. - * - * <p/>There are many DER decoders in JRE but using them will tie this - * program to a specific JCE/JVM. - * - * @author zhang - * - */ -class DerParser { - - // Classes - public final static int UNIVERSAL = 0x00; - public final static int APPLICATION = 0x40; - public final static int CONTEXT = 0x80; - public final static int PRIVATE = 0xC0; - - // Constructed Flag - public final static int CONSTRUCTED = 0x20; - - // Tag and data types - public final static int ANY = 0x00; - public final static int BOOLEAN = 0x01; - public final static int INTEGER = 0x02; - public final static int BIT_STRING = 0x03; - public final static int OCTET_STRING = 0x04; - public final static int NULL = 0x05; - public final static int OBJECT_IDENTIFIER = 0x06; - public final static int REAL = 0x09; - public final static int ENUMERATED = 0x0a; - public final static int RELATIVE_OID = 0x0d; - - public final static int SEQUENCE = 0x10; - public final static int SET = 0x11; - - public final static int NUMERIC_STRING = 0x12; - public final static int PRINTABLE_STRING = 0x13; - public final static int T61_STRING = 0x14; - public final static int VIDEOTEX_STRING = 0x15; - public final static int IA5_STRING = 0x16; - public final static int GRAPHIC_STRING = 0x19; - public final static int ISO646_STRING = 0x1A; - public final static int GENERAL_STRING = 0x1B; - - public final static int UTF8_STRING = 0x0C; - public final static int UNIVERSAL_STRING = 0x1C; - public final static int BMP_STRING = 0x1E; - - public final static int UTC_TIME = 0x17; - public final static int GENERALIZED_TIME = 0x18; - - protected InputStream in; - - /** - * Create a new DER decoder from an input stream. - * - * @param in - * The DER encoded stream - */ - public DerParser(InputStream in) throws IOException { - this.in = in; - } - - /** - * Create a new DER decoder from a byte array. - * - * @param The - * encoded bytes - * @throws IOException - */ - public DerParser(byte[] bytes) throws IOException { - this(new ByteArrayInputStream(bytes)); - } - - /** - * Read next object. If it's constructed, the value holds - * encoded content and it should be parsed by a new - * parser from <code>Asn1Object.getParser</code>. - * - * @return A object - * @throws IOException - */ - public Asn1Object read() throws IOException { - int tag = in.read(); - - if (tag == -1) - throw new IOException("Invalid DER: stream too short, missing tag"); //$NON-NLS-1$ - - int length = getLength(); - - byte[] value = new byte[length]; - int n = in.read(value); - if (n < length) - throw new IOException("Invalid DER: stream too short, missing value"); //$NON-NLS-1$ - - Asn1Object o = new Asn1Object(tag, length, value); - - return o; - } - - /** - * Decode the length of the field. Can only support length - * encoding up to 4 octets. - * - * <p/>In BER/DER encoding, length can be encoded in 2 forms, - * <ul> - * <li>Short form. One octet. Bit 8 has value "0" and bits 7-1 - * give the length. - * <li>Long form. Two to 127 octets (only 4 is supported here). - * Bit 8 of first octet has value "1" and bits 7-1 give the - * number of additional length octets. Second and following - * octets give the length, base 256, most significant digit first. - * </ul> - * @return The length as integer - * @throws IOException - */ - private int getLength() throws IOException { - - int i = in.read(); - if (i == -1) - throw new IOException("Invalid DER: length missing"); //$NON-NLS-1$ - - // A single byte short length - if ((i & ~0x7F) == 0) - return i; - - int num = i & 0x7F; - - // We can't handle length longer than 4 bytes - if ( i >= 0xFF || num > 4) - throw new IOException("Invalid DER: length field too big (" //$NON-NLS-1$ - + i + ")"); //$NON-NLS-1$ - - byte[] bytes = new byte[num]; - int n = in.read(bytes); - if (n < num) - throw new IOException("Invalid DER: length too short"); //$NON-NLS-1$ - - return new BigInteger(1, bytes).intValue(); - } - -} +/**************************************************************************** + * Copyright (c) 1998-2009 AOL LLC. + * + * 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 net.oauth.signature.pem; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.math.BigInteger; + +/** + * A bare-minimum ASN.1 DER decoder, just having enough functions to + * decode PKCS#1 private keys. Especially, it doesn't handle explicitly + * tagged types with an outer tag. + * + * <p/>This parser can only handle one layer. To parse nested constructs, + * get a new parser for each layer using <code>Asn1Object.getParser()</code>. + * + * <p/>There are many DER decoders in JRE but using them will tie this + * program to a specific JCE/JVM. + * + * @author zhang + * + */ +class DerParser { + + // Classes + public final static int UNIVERSAL = 0x00; + public final static int APPLICATION = 0x40; + public final static int CONTEXT = 0x80; + public final static int PRIVATE = 0xC0; + + // Constructed Flag + public final static int CONSTRUCTED = 0x20; + + // Tag and data types + public final static int ANY = 0x00; + public final static int BOOLEAN = 0x01; + public final static int INTEGER = 0x02; + public final static int BIT_STRING = 0x03; + public final static int OCTET_STRING = 0x04; + public final static int NULL = 0x05; + public final static int OBJECT_IDENTIFIER = 0x06; + public final static int REAL = 0x09; + public final static int ENUMERATED = 0x0a; + public final static int RELATIVE_OID = 0x0d; + + public final static int SEQUENCE = 0x10; + public final static int SET = 0x11; + + public final static int NUMERIC_STRING = 0x12; + public final static int PRINTABLE_STRING = 0x13; + public final static int T61_STRING = 0x14; + public final static int VIDEOTEX_STRING = 0x15; + public final static int IA5_STRING = 0x16; + public final static int GRAPHIC_STRING = 0x19; + public final static int ISO646_STRING = 0x1A; + public final static int GENERAL_STRING = 0x1B; + + public final static int UTF8_STRING = 0x0C; + public final static int UNIVERSAL_STRING = 0x1C; + public final static int BMP_STRING = 0x1E; + + public final static int UTC_TIME = 0x17; + public final static int GENERALIZED_TIME = 0x18; + + protected InputStream in; + + /** + * Create a new DER decoder from an input stream. + * + * @param in + * The DER encoded stream + */ + public DerParser(InputStream in) throws IOException { + this.in = in; + } + + /** + * Create a new DER decoder from a byte array. + * + * @param The + * encoded bytes + * @throws IOException + */ + public DerParser(byte[] bytes) throws IOException { + this(new ByteArrayInputStream(bytes)); + } + + /** + * Read next object. If it's constructed, the value holds + * encoded content and it should be parsed by a new + * parser from <code>Asn1Object.getParser</code>. + * + * @return A object + * @throws IOException + */ + public Asn1Object read() throws IOException { + int tag = in.read(); + + if (tag == -1) + throw new IOException("Invalid DER: stream too short, missing tag"); //$NON-NLS-1$ + + int length = getLength(); + + byte[] value = new byte[length]; + int n = in.read(value); + if (n < length) + throw new IOException("Invalid DER: stream too short, missing value"); //$NON-NLS-1$ + + Asn1Object o = new Asn1Object(tag, length, value); + + return o; + } + + /** + * Decode the length of the field. Can only support length + * encoding up to 4 octets. + * + * <p/>In BER/DER encoding, length can be encoded in 2 forms, + * <ul> + * <li>Short form. One octet. Bit 8 has value "0" and bits 7-1 + * give the length. + * <li>Long form. Two to 127 octets (only 4 is supported here). + * Bit 8 of first octet has value "1" and bits 7-1 give the + * number of additional length octets. Second and following + * octets give the length, base 256, most significant digit first. + * </ul> + * @return The length as integer + * @throws IOException + */ + private int getLength() throws IOException { + + int i = in.read(); + if (i == -1) + throw new IOException("Invalid DER: length missing"); //$NON-NLS-1$ + + // A single byte short length + if ((i & ~0x7F) == 0) + return i; + + int num = i & 0x7F; + + // We can't handle length longer than 4 bytes + if ( i >= 0xFF || num > 4) + throw new IOException("Invalid DER: length field too big (" //$NON-NLS-1$ + + i + ")"); //$NON-NLS-1$ + + byte[] bytes = new byte[num]; + int n = in.read(bytes); + if (n < num) + throw new IOException("Invalid DER: length too short"); //$NON-NLS-1$ + + return new BigInteger(1, bytes).intValue(); + } + +} diff --git a/bbb-lti/src/java/net/oauth/signature/pem/PEMReader.java b/bbb-lti/src/java/net/oauth/signature/pem/PEMReader.java index 54fb2b555b..71e826e15a 100644 --- a/bbb-lti/src/java/net/oauth/signature/pem/PEMReader.java +++ b/bbb-lti/src/java/net/oauth/signature/pem/PEMReader.java @@ -1,134 +1,134 @@ -/**************************************************************************** - * Copyright (c) 1998-2009 AOL LLC. - * - * 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. - * - **************************************************************************** - * - * @author: zhang - * @version: $Revision: 2 $ - * @created: Apr 24, 2009 - * - * Description: A class to decode PEM files - * - ****************************************************************************/ -package net.oauth.signature.pem; - -import java.io.BufferedReader; -import java.io.ByteArrayInputStream; -import java.io.FileInputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; -import net.oauth.signature.OAuthSignatureMethod; - -/** - * This class convert PEM into byte array. The begin marker - * is saved and it can be used to determine the type of the - * PEM file. - * - * @author zhang - */ -public class PEMReader { - - // Begin markers for all supported PEM files - public static final String PRIVATE_PKCS1_MARKER = - "-----BEGIN RSA PRIVATE KEY-----"; - public static final String PRIVATE_PKCS8_MARKER = - "-----BEGIN PRIVATE KEY-----"; - public static final String CERTIFICATE_X509_MARKER = - "-----BEGIN CERTIFICATE-----"; - public static final String PUBLIC_X509_MARKER = - "-----BEGIN PUBLIC KEY-----"; - - private static final String BEGIN_MARKER = "-----BEGIN "; - - private InputStream stream; - private byte[] derBytes; - private String beginMarker; - - public PEMReader(InputStream inStream) throws IOException { - stream = inStream; - readFile(); - } - - public PEMReader(byte[] buffer) throws IOException { - this(new ByteArrayInputStream(buffer)); - } - - public PEMReader(String fileName) throws IOException { - this(new FileInputStream(fileName)); - } - - public byte[] getDerBytes() { - return derBytes; - } - - public String getBeginMarker() { - return beginMarker; - } - - /** - * Read the PEM file and save the DER encoded octet - * stream and begin marker. - * - * @throws IOException - */ - protected void readFile() throws IOException { - - String line; - BufferedReader reader = new BufferedReader( - new InputStreamReader(stream)); - try { - while ((line = reader.readLine()) != null) - { - if (line.indexOf(BEGIN_MARKER) != -1) - { - beginMarker = line.trim(); - String endMarker = beginMarker.replace("BEGIN", "END"); - derBytes = readBytes(reader, endMarker); - return; - } - } - throw new IOException("Invalid PEM file: no begin marker"); - } finally { - reader.close(); - } - } - - - /** - * Read the lines between BEGIN and END marker and convert - * the Base64 encoded content into binary byte array. - * - * @return DER encoded octet stream - * @throws IOException - */ - private byte[] readBytes(BufferedReader reader, String endMarker) throws IOException - { - String line = null; - StringBuffer buf = new StringBuffer(); - - while ((line = reader.readLine()) != null) - { - if (line.indexOf(endMarker) != -1) { - - return OAuthSignatureMethod.decodeBase64(buf.toString()); - } - - buf.append(line.trim()); - } - - throw new IOException("Invalid PEM file: No end marker"); - } -} +/**************************************************************************** + * Copyright (c) 1998-2009 AOL LLC. + * + * 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. + * + **************************************************************************** + * + * @author: zhang + * @version: $Revision: 73016 $ + * @created: Apr 24, 2009 + * + * Description: A class to decode PEM files + * + ****************************************************************************/ +package net.oauth.signature.pem; + +import java.io.BufferedReader; +import java.io.ByteArrayInputStream; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import net.oauth.signature.OAuthSignatureMethod; + +/** + * This class convert PEM into byte array. The begin marker + * is saved and it can be used to determine the type of the + * PEM file. + * + * @author zhang + */ +public class PEMReader { + + // Begin markers for all supported PEM files + public static final String PRIVATE_PKCS1_MARKER = + "-----BEGIN RSA PRIVATE KEY-----"; + public static final String PRIVATE_PKCS8_MARKER = + "-----BEGIN PRIVATE KEY-----"; + public static final String CERTIFICATE_X509_MARKER = + "-----BEGIN CERTIFICATE-----"; + public static final String PUBLIC_X509_MARKER = + "-----BEGIN PUBLIC KEY-----"; + + private static final String BEGIN_MARKER = "-----BEGIN "; + + private InputStream stream; + private byte[] derBytes; + private String beginMarker; + + public PEMReader(InputStream inStream) throws IOException { + stream = inStream; + readFile(); + } + + public PEMReader(byte[] buffer) throws IOException { + this(new ByteArrayInputStream(buffer)); + } + + public PEMReader(String fileName) throws IOException { + this(new FileInputStream(fileName)); + } + + public byte[] getDerBytes() { + return derBytes; + } + + public String getBeginMarker() { + return beginMarker; + } + + /** + * Read the PEM file and save the DER encoded octet + * stream and begin marker. + * + * @throws IOException + */ + protected void readFile() throws IOException { + + String line; + BufferedReader reader = new BufferedReader( + new InputStreamReader(stream)); + try { + while ((line = reader.readLine()) != null) + { + if (line.indexOf(BEGIN_MARKER) != -1) + { + beginMarker = line.trim(); + String endMarker = beginMarker.replace("BEGIN", "END"); + derBytes = readBytes(reader, endMarker); + return; + } + } + throw new IOException("Invalid PEM file: no begin marker"); + } finally { + reader.close(); + } + } + + + /** + * Read the lines between BEGIN and END marker and convert + * the Base64 encoded content into binary byte array. + * + * @return DER encoded octet stream + * @throws IOException + */ + private byte[] readBytes(BufferedReader reader, String endMarker) throws IOException + { + String line = null; + StringBuffer buf = new StringBuffer(); + + while ((line = reader.readLine()) != null) + { + if (line.indexOf(endMarker) != -1) { + + return OAuthSignatureMethod.decodeBase64(buf.toString()); + } + + buf.append(line.trim()); + } + + throw new IOException("Invalid PEM file: No end marker"); + } +} diff --git a/bbb-lti/src/java/net/oauth/signature/pem/PKCS1EncodedKeySpec.java b/bbb-lti/src/java/net/oauth/signature/pem/PKCS1EncodedKeySpec.java index 29f698a2c9..5cb893b102 100644 --- a/bbb-lti/src/java/net/oauth/signature/pem/PKCS1EncodedKeySpec.java +++ b/bbb-lti/src/java/net/oauth/signature/pem/PKCS1EncodedKeySpec.java @@ -1,116 +1,116 @@ -/**************************************************************************** - * Copyright (c) 1998-2009 AOL LLC. - * - * 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. - * - **************************************************************************** - * - * @author: zhang - * @version: $Revision: 2 $ - * @created: Apr 24, 2009 - * - * Description: A KeySpec for PKCS#1 encoded RSA private key - * - ****************************************************************************/ -package net.oauth.signature.pem; - -import java.io.IOException; -import java.math.BigInteger; -import java.security.spec.RSAPrivateCrtKeySpec; - -/** - * PKCS#1 encoded private key is commonly used with OpenSSL. It provides CRT parameters - * so the private key operation can be much faster than using exponent/modulus alone, - * which is the case for PKCS#8 encoded key. - * - * <p/>Unfortunately, JCE doesn't have an API to decode the DER. This class takes DER - * buffer and decoded into CRT key. - * - * @author zhang - */ -public class PKCS1EncodedKeySpec { - - private RSAPrivateCrtKeySpec keySpec; - - /** - * Create a PKCS#1 keyspec from DER encoded buffer - * - * @param keyBytes DER encoded octet stream - * @throws IOException - */ - public PKCS1EncodedKeySpec(byte[] keyBytes) throws IOException { - decode(keyBytes); - } - - /** - * Get the key spec that JCE understands. - * - * @return CRT keyspec defined by JCE - */ - public RSAPrivateCrtKeySpec getKeySpec() { - return keySpec; - } - - /** - * Decode PKCS#1 encoded private key into RSAPrivateCrtKeySpec. - * - * <p/>The ASN.1 syntax for the private key with CRT is - * - * <pre> - * -- - * -- Representation of RSA private key with information for the CRT algorithm. - * -- - * RSAPrivateKey ::= SEQUENCE { - * version Version, - * modulus INTEGER, -- n - * publicExponent INTEGER, -- e - * privateExponent INTEGER, -- d - * prime1 INTEGER, -- p - * prime2 INTEGER, -- q - * exponent1 INTEGER, -- d mod (p-1) - * exponent2 INTEGER, -- d mod (q-1) - * coefficient INTEGER, -- (inverse of q) mod p - * otherPrimeInfos OtherPrimeInfos OPTIONAL - * } - * </pre> - * - * @param keyBytes PKCS#1 encoded key - * @throws IOException - */ - - private void decode(byte[] keyBytes) throws IOException { - - DerParser parser = new DerParser(keyBytes); - - Asn1Object sequence = parser.read(); - if (sequence.getType() != DerParser.SEQUENCE) - throw new IOException("Invalid DER: not a sequence"); //$NON-NLS-1$ - - // Parse inside the sequence - parser = sequence.getParser(); - - parser.read(); // Skip version - BigInteger modulus = parser.read().getInteger(); - BigInteger publicExp = parser.read().getInteger(); - BigInteger privateExp = parser.read().getInteger(); - BigInteger prime1 = parser.read().getInteger(); - BigInteger prime2 = parser.read().getInteger(); - BigInteger exp1 = parser.read().getInteger(); - BigInteger exp2 = parser.read().getInteger(); - BigInteger crtCoef = parser.read().getInteger(); - - keySpec = new RSAPrivateCrtKeySpec( - modulus, publicExp, privateExp, prime1, prime2, - exp1, exp2, crtCoef); - } -} +/**************************************************************************** + * Copyright (c) 1998-2009 AOL LLC. + * + * 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. + * + **************************************************************************** + * + * @author: zhang + * @version: $Revision: 73016 $ + * @created: Apr 24, 2009 + * + * Description: A KeySpec for PKCS#1 encoded RSA private key + * + ****************************************************************************/ +package net.oauth.signature.pem; + +import java.io.IOException; +import java.math.BigInteger; +import java.security.spec.RSAPrivateCrtKeySpec; + +/** + * PKCS#1 encoded private key is commonly used with OpenSSL. It provides CRT parameters + * so the private key operation can be much faster than using exponent/modulus alone, + * which is the case for PKCS#8 encoded key. + * + * <p/>Unfortunately, JCE doesn't have an API to decode the DER. This class takes DER + * buffer and decoded into CRT key. + * + * @author zhang + */ +public class PKCS1EncodedKeySpec { + + private RSAPrivateCrtKeySpec keySpec; + + /** + * Create a PKCS#1 keyspec from DER encoded buffer + * + * @param keyBytes DER encoded octet stream + * @throws IOException + */ + public PKCS1EncodedKeySpec(byte[] keyBytes) throws IOException { + decode(keyBytes); + } + + /** + * Get the key spec that JCE understands. + * + * @return CRT keyspec defined by JCE + */ + public RSAPrivateCrtKeySpec getKeySpec() { + return keySpec; + } + + /** + * Decode PKCS#1 encoded private key into RSAPrivateCrtKeySpec. + * + * <p/>The ASN.1 syntax for the private key with CRT is + * + * <pre> + * -- + * -- Representation of RSA private key with information for the CRT algorithm. + * -- + * RSAPrivateKey ::= SEQUENCE { + * version Version, + * modulus INTEGER, -- n + * publicExponent INTEGER, -- e + * privateExponent INTEGER, -- d + * prime1 INTEGER, -- p + * prime2 INTEGER, -- q + * exponent1 INTEGER, -- d mod (p-1) + * exponent2 INTEGER, -- d mod (q-1) + * coefficient INTEGER, -- (inverse of q) mod p + * otherPrimeInfos OtherPrimeInfos OPTIONAL + * } + * </pre> + * + * @param keyBytes PKCS#1 encoded key + * @throws IOException + */ + + private void decode(byte[] keyBytes) throws IOException { + + DerParser parser = new DerParser(keyBytes); + + Asn1Object sequence = parser.read(); + if (sequence.getType() != DerParser.SEQUENCE) + throw new IOException("Invalid DER: not a sequence"); //$NON-NLS-1$ + + // Parse inside the sequence + parser = sequence.getParser(); + + parser.read(); // Skip version + BigInteger modulus = parser.read().getInteger(); + BigInteger publicExp = parser.read().getInteger(); + BigInteger privateExp = parser.read().getInteger(); + BigInteger prime1 = parser.read().getInteger(); + BigInteger prime2 = parser.read().getInteger(); + BigInteger exp1 = parser.read().getInteger(); + BigInteger exp2 = parser.read().getInteger(); + BigInteger crtCoef = parser.read().getInteger(); + + keySpec = new RSAPrivateCrtKeySpec( + modulus, publicExp, privateExp, prime1, prime2, + exp1, exp2, crtCoef); + } +} -- GitLab