/* * Copyright 2008 Google, 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.HashSet; import java.util.Collections; import java.util.Set; import java.io.IOException; import java.net.URISyntaxException; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.Map; import net.oauth.signature.OAuthSignatureMethod; /** * A simple OAuthValidator, which checks the version, whether the timestamp * is close to now and the signature is valid. Each check may be overridden. * * @author Dirk Balfanz * @author John Kristian */ public class SimpleOAuthValidator implements OAuthValidator { /** The default window for timestamps is 5 minutes. */ public static final long DEFAULT_TIMESTAMP_WINDOW = 5 * 60 * 1000L; /** * Names of parameters that may not appear twice in a valid message. * This limitation is specified by OAuth Core <a * href="http://oauth.net/core/1.0#anchor7">section 5</a>. */ public static final Set<String> SINGLE_PARAMETERS = constructSingleParameters(); private static Set<String> constructSingleParameters() { Set<String> s = new HashSet<String>(); for (String p : new String[] { OAuth.OAUTH_CONSUMER_KEY, OAuth.OAUTH_TOKEN, OAuth.OAUTH_TOKEN_SECRET, OAuth.OAUTH_CALLBACK, OAuth.OAUTH_SIGNATURE_METHOD, OAuth.OAUTH_SIGNATURE, OAuth.OAUTH_TIMESTAMP, OAuth.OAUTH_NONCE, OAuth.OAUTH_VERSION }) { s.add(p); } return Collections.unmodifiableSet(s); } /** * Construct a validator that rejects messages more than five minutes out * of date, or with a OAuth version other than 1.0, or with an invalid * signature. */ public SimpleOAuthValidator() { this(DEFAULT_TIMESTAMP_WINDOW, Double.parseDouble(OAuth.VERSION_1_0)); } /** * Public constructor. * * @param timestampWindowSec * specifies, in seconds, the windows (into the past and * into the future) in which we'll accept timestamps. * @param maxVersion * the maximum acceptable oauth_version */ public SimpleOAuthValidator(long timestampWindowMsec, double maxVersion) { this.timestampWindow = timestampWindowMsec; this.maxVersion = maxVersion; } protected final double minVersion = 1.0; protected final double maxVersion; protected final long timestampWindow; /** {@inherit} * @throws URISyntaxException */ public void validateMessage(OAuthMessage message, OAuthAccessor accessor) throws OAuthException, IOException, URISyntaxException { checkSingleParameters(message); validateVersion(message); validateTimestampAndNonce(message); validateSignature(message, accessor); } /** Throw an exception if any SINGLE_PARAMETERS occur repeatedly. */ protected void checkSingleParameters(OAuthMessage message) throws IOException, OAuthException { // Check for repeated oauth_ parameters: boolean repeated = false; Map<String, Collection<String>> nameToValues = new HashMap<String, Collection<String>>(); for (Map.Entry<String, String> parameter : message.getParameters()) { String name = parameter.getKey(); if (SINGLE_PARAMETERS.contains(name)) { Collection<String> values = nameToValues.get(name); if (values == null) { values = new ArrayList<String>(); nameToValues.put(name, values); } else { repeated = true; } values.add(parameter.getValue()); } } if (repeated) { Collection<OAuth.Parameter> rejected = new ArrayList<OAuth.Parameter>(); for (Map.Entry<String, Collection<String>> p : nameToValues.entrySet()) { String name = p.getKey(); Collection<String> values = p.getValue(); if (values.size() > 1) { for (String value : values) { rejected.add(new OAuth.Parameter(name, value)); } } } OAuthProblemException problem = new OAuthProblemException(OAuth.Problems.PARAMETER_REJECTED); problem.setParameter(OAuth.Problems.OAUTH_PARAMETERS_REJECTED, OAuth.formEncode(rejected)); throw problem; } } protected void validateVersion(OAuthMessage message) throws OAuthException, IOException { String versionString = message.getParameter(OAuth.OAUTH_VERSION); if (versionString != null) { double version = Double.parseDouble(versionString); if (version < minVersion || maxVersion < version) { OAuthProblemException problem = new OAuthProblemException(OAuth.Problems.VERSION_REJECTED); problem.setParameter(OAuth.Problems.OAUTH_ACCEPTABLE_VERSIONS, minVersion + "-" + maxVersion); throw problem; } } } /** This implementation doesn't check the nonce value. */ protected void validateTimestampAndNonce(OAuthMessage message) throws IOException, OAuthProblemException { message.requireParameters(OAuth.OAUTH_TIMESTAMP, OAuth.OAUTH_NONCE); long timestamp = Long.parseLong(message.getParameter(OAuth.OAUTH_TIMESTAMP)) * 1000L; long now = currentTimeMsec(); long min = now - timestampWindow; long max = now + timestampWindow; if (timestamp < min || max < timestamp) { OAuthProblemException problem = new OAuthProblemException(OAuth.Problems.TIMESTAMP_REFUSED); problem.setParameter(OAuth.Problems.OAUTH_ACCEPTABLE_TIMESTAMPS, min + "-" + max); throw problem; } } protected void validateSignature(OAuthMessage message, OAuthAccessor accessor) throws OAuthException, IOException, URISyntaxException { message.requireParameters(OAuth.OAUTH_CONSUMER_KEY, OAuth.OAUTH_SIGNATURE_METHOD, OAuth.OAUTH_SIGNATURE); OAuthSignatureMethod.newSigner(message, accessor).validate(message); } protected long currentTimeMsec() { return System.currentTimeMillis(); } }