Announcement Announcement Module
Collapse
No announcement yet.
OAuth 1 RestTemplate POST signature base string includes form parameters? Page Title Module
Move Remove Collapse
X
Conversation Detail Module
Collapse
  • Filter
  • Time
  • Show
Clear All
new posts

  • 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
    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.

    Comment


    • #3
      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.

      Comment


      • #4
        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);
        	}

        Comment


        • #5
          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.

          Comment


          • #6
            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.

            Comment

            Working...
            X