Results 1 to 6 of 6

Thread: OAuth 1 RestTemplate POST signature base string includes form parameters?

  1. #1
    Join Date
    Sep 2012
    Posts
    11

    Default OAuth 1 RestTemplate POST signature base string includes form parameters?

    Hi,

    I have a question. From my reading of the OAuth 1.0 specification the parameters of a application/x-www-form-urlencoded POST request should be used as part of the base string to generate the signature and that is was is expected by the server I am using. However, the base string produced by the RestTemplate object does not include them. Have I mis-read the specfication or are there steps I need to take to put the form parameters into the base string.

    My code:

    Code:
    HttpHeaders headers = new HttpHeaders();
    headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
    
    MultiValueMap<String, String> mapParameters = new LinkedMultiValueMap<String, String>();
    mapParameters.add("data_file[title]", "uploadTestFile");
    mapParameters.add("data_file[filename]", "uploadTestFile.txt");
    
    HttpEntity<MultiValueMap<String, String>> entity = new HttpEntity<MultiValueMap<String, String>>(mapParameters, headers);
    		
    restTemplate.postForLocation(url.toString(), entity);

  2. #2
    Join Date
    Jun 2005
    Posts
    4,241

    Default

    I'd say you were right (but happy to be corrected). In which case it's a bug in CoreOAuthConsumerSupport, or more accurately (and more seriously) in the interface which doesn't take into account the request body at all. It's hard to see how it could with the design the way it is because the authorization header is added before the body is known. I guess the workaround is to send your parameters as a query string.

  3. #3
    Join Date
    Sep 2012
    Posts
    11

    Default

    I have a possible solution that involves subclassing CoreOAuthConsumerSupport and I am testing it now. The subclass takes the form parameters as setter and over ridding the getSignatureBaseString method, this does break the CoreOAuthConsumerSupport interface but once added to RestTemplate the interface methods are sufficent. So not that bad.

    I am still testing this as a solution though.

  4. #4
    Join Date
    Sep 2012
    Posts
    11

    Default

    The solution works. The CoreOAuthConsumerSupport subclass method getSignatureBaseString now adds the form parameters to the base string. I just added a loop to add the form parameters listed in the formParameters map.

    Code:
    public class CoreOAuthConsumerSupportForm extends CoreOAuthConsumerSupport {
    
    	MultiValueMap<String, String> formParameters;
    
    	/**
    	 * Get the signature base string for the specified parameters. It is
    	 * presumed the parameters are NOT OAuth-encoded.
    	 * 
    	 * @param oauthParams
    	 *            The parameters (NOT oauth-encoded).
    	 * @param requestURL
    	 *            The request URL.
    	 * @param httpMethod
    	 *            The http method.
    	 * @return The signature base string.
    	 */
    	protected String getSignatureBaseString(Map<String, Set<CharSequence>> oauthParams, URL requestURL, String httpMethod) {
    		TreeMap<String, TreeSet<String>> sortedParameters = new TreeMap<String, TreeSet<String>>();
    
    		for (Map.Entry<String, Set<CharSequence>> param : oauthParams
    				.entrySet()) {
    			// first encode all parameter names and values (spec section 9.1)
    			String key = oauthEncode(param.getKey());
    
    			// add the encoded parameters sorted according to the spec.
    			TreeSet<String> sortedValues = sortedParameters.get(key);
    			if (sortedValues == null) {
    				sortedValues = new TreeSet<String>();
    				sortedParameters.put(key, sortedValues);
    			}
    			for (CharSequence value : param.getValue()) {
    				sortedValues.add(oauthEncode(value.toString()));
    			}
    		}
                    //new code to add form parameters to base string
    		for (Entry<String, List<String>> entry : formParameters.entrySet()) {
    			String key = oauthEncode(entry.getKey());
    			TreeSet<String> sortedValues = sortedParameters.get(key);
    			if (sortedValues == null) {
    				sortedValues = new TreeSet<String>();
    				sortedParameters.put(key, sortedValues);
    			}
    			for (CharSequence value : entry.getValue()) {
    				sortedValues.add(oauthEncode(value.toString()));
    			}
    		}
    		// now concatenate them into a single query string according to the spec.
    		StringBuilder queryString = new StringBuilder();
    		Iterator<Map.Entry<String, TreeSet<String>>> sortedIt = sortedParameters
    				.entrySet().iterator();
    		while (sortedIt.hasNext()) {
    			Map.Entry<String, TreeSet<String>> sortedParameter = sortedIt
    					.next();
    			for (String parameterValue : sortedParameter.getValue()) {
    				if (parameterValue == null) {
    					parameterValue = "";
    				}
    				queryString.append(sortedParameter.getKey()).append('=').append(parameterValue);
    				if (sortedIt.hasNext()) {
    					queryString.append('&');
    				}
    			}
    		}
    
    		StringBuilder url = new StringBuilder(requestURL.getProtocol()
    				.toLowerCase()).append("://").append(
    				requestURL.getHost().toLowerCase());
    		if ((requestURL.getPort() >= 0)
    				&& (requestURL.getPort() != requestURL.getDefaultPort())) {
    			url.append(":").append(requestURL.getPort());
    		}
    		url.append(requestURL.getPath());
    
    		return new StringBuilder(httpMethod.toUpperCase()).append('&')
    				.append(oauthEncode(url.toString())).append('&')
    				.append(oauthEncode(queryString.toString())).toString();
    	}
    
    	public MultiValueMap<String, String> getFormParameters() {
    		return formParameters;
    	}
    
    	public void setFormParameters(MultiValueMap<String, String> formParameters) {
    		this.formParameters = formParameters;
    	}
    
    }
    This is called by a method in the OAuth client:

    Code:
    	public <T> void seekRestPost(URL url, HttpEntity<T> entity, MultiValueMap<String, String> formParameters) {
    		log.info("URL: " + url);
    		//needed to fix missing functionality in Spring OAuth
    		consumerSupportForm.setFormParameters(formParameters);
    		
    		Map<String, OAuthConsumerToken> tokens = new Hashtable<String, OAuthConsumerToken>();
    		OAuthConsumerToken token = new OAuthConsumerToken();
    		tokens.put(RESOURCE_ID, token);
    		
    		OAuthSecurityContextImpl securityContext = new OAuthSecurityContextImpl();
    		securityContext.setAccessTokens(tokens);
    		
    		OAuthSecurityContextHolder.setContext(securityContext);
    		
    		OAuthRestTemplate restTemplate = new OAuthRestTemplate(protectedResource);
    		restTemplate.setSupport(consumerSupportForm);
    		
    		//If this call is not made then the OAuthSignatureMethodFactoryUrlEncode will not be used in making rest calls. 
    		restTemplate.setRequestFactory(requestFactoryForm);
    		
    		restTemplate.postForLocation(url.toString(), entity);
    	}

  5. #5
    Join Date
    Jun 2005
    Posts
    4,241

    Default

    OK. A pull request with some tests would be great (see README for instructions about contributor's agreement). The patch as it is won't work though I think as it isn't thread safe.

  6. #6
    Join Date
    Sep 2012
    Posts
    11

    Default

    Not sure that will be an issue with the intended use of my Client but I will have a look at the code again.

    I need to read the README and submit this and the decorator for the URL encoding.

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •