/* * Copyright 2008-2011 Thomas Nichols. http://blog.thomnichols.org * * 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. * * You are receiving this code free of charge, which represents many hours of * effort from other individuals and corporations. As a responsible member * of the community, you are encouraged (but not required) to donate any * enhancements or improvements back to the community under a similar open * source license. Thank you. -TMN */ package groovyx.net.http; import java.io.IOException; import java.io.InputStream; import java.net.URI; import java.net.URISyntaxException; import java.net.URL; import java.security.GeneralSecurityException; import java.security.KeyStore; import java.util.HashMap; import java.util.Map; import oauth.signpost.OAuthConsumer; import oauth.signpost.commonshttp.CommonsHttpOAuthConsumer; import oauth.signpost.exception.OAuthException; import org.apache.http.Header; import org.apache.http.HttpEntityEnclosingRequest; import org.apache.http.HttpException; import org.apache.http.HttpHost; import org.apache.http.HttpRequest; import org.apache.http.HttpRequestInterceptor; import org.apache.http.auth.AuthScope; import org.apache.http.auth.NTCredentials; import org.apache.http.auth.UsernamePasswordCredentials; import org.apache.http.client.HttpClient; import org.apache.http.conn.scheme.Scheme; import org.apache.http.conn.ssl.SSLSocketFactory; import org.apache.http.impl.client.AbstractHttpClient; import org.apache.http.protocol.ExecutionContext; import org.apache.http.protocol.HttpContext; /** * Encapsulates all configuration related to HTTP authentication methods. * @see HTTPBuilder#getAuth() * * @author <a href='mailto:tomstrummer+httpbuilder@gmail.com'>Tom Nichols</a> */ public class AuthConfig { protected HTTPBuilder builder; public AuthConfig( HTTPBuilder builder ) { this.builder = builder; } /** * Set authentication credentials to be used for the current * {@link HTTPBuilder#getUri() default host}. This method name is a bit of * a misnomer, since these credentials will actually work for "digest" * authentication as well. * @param user * @param pass */ public void basic( String user, String pass ) { URI uri = ((URIBuilder)builder.getUri()).toURI(); if ( uri == null ) throw new IllegalStateException( "a default URI must be set" ); this.basic( uri.getHost(), uri.getPort(), user, pass ); } /** * Set authentication credentials to be used for the given host and port. * @param host * @param port * @param user * @param pass */ public void basic( String host, int port, String user, String pass ) { final HttpClient client = builder.getClient(); if ( !(client instanceof AbstractHttpClient )) { throw new IllegalStateException("client is not an AbstractHttpClient"); } ((AbstractHttpClient)client).getCredentialsProvider().setCredentials( new AuthScope( host, port ), new UsernamePasswordCredentials( user, pass ) ); } /** * Set NTLM authentication credentials to be used for the current * {@link HTTPBuilder#getUri() default host}. * @param user * @param pass * @param workstation * @param domain */ public void ntlm( String user, String pass, String workstation, String domain ) { URI uri = ((URIBuilder)builder.getUri()).toURI(); if ( uri == null ) throw new IllegalStateException( "a default URI must be set" ); this.ntlm( uri.getHost(), uri.getPort(), user, pass, workstation, domain ); } /** * Set NTLM authentication credentials to be used for the given host and port. * @param host * @param port * @param user * @param pass * @param workstation * @param domain */ public void ntlm( String host, int port, String user, String pass, String workstation, String domain ) { final HttpClient client = builder.getClient(); if ( !(client instanceof AbstractHttpClient )) { throw new IllegalStateException("client is not an AbstractHttpClient"); } ((AbstractHttpClient)client).getCredentialsProvider().setCredentials( new AuthScope( host, port ), new NTCredentials( user, pass, workstation, domain ) ); } /** * Sets a certificate to be used for SSL authentication. See * {@link Class#getResource(String)} for how to get a URL from a resource * on the classpath. * @param certURL URL to a JKS keystore where the certificate is stored. * @param password password to decrypt the keystore */ public void certificate( String certURL, String password ) throws GeneralSecurityException, IOException { KeyStore keyStore = KeyStore.getInstance( KeyStore.getDefaultType() ); InputStream jksStream = new URL(certURL).openStream(); try { keyStore.load( jksStream, password.toCharArray() ); } finally { jksStream.close(); } SSLSocketFactory ssl = new SSLSocketFactory(keyStore, password); ssl.setHostnameVerifier( SSLSocketFactory.STRICT_HOSTNAME_VERIFIER ); builder.getClient().getConnectionManager().getSchemeRegistry() .register( new Scheme("https", ssl, 443) ); } /** * </p>OAuth sign all requests. Note that this currently does <strong>not</strong> * wait for a <code>WWW-Authenticate</code> challenge before sending the * the OAuth header. All requests to all domains will be signed for this * instance.</p> * * <p>This assumes you've already generated an <code>accessToken</code> and * <code>secretToken</code> for the site you're targeting. For More information * on how to achieve this, see the * <a href='http://code.google.com/p/oauth-signpost/wiki/GettingStarted#Using_Signpost'>Signpost documentation</a>.</p> * @since 0.5.1 * @param consumerKey <code>null</code> if you want to <strong>unset</strong> * OAuth handling and stop signing requests. * @param consumerSecret * @param accessToken * @param secretToken */ public void oauth( String consumerKey, String consumerSecret, String accessToken, String secretToken ) { final HttpClient client = builder.getClient(); if ( !(client instanceof AbstractHttpClient )) { throw new IllegalStateException("client is not an AbstractHttpClient"); } ((AbstractHttpClient)client).removeRequestInterceptorByClass( OAuthSigner.class ); if ( consumerKey != null ) ((AbstractHttpClient)client).addRequestInterceptor( new OAuthSigner( consumerKey, consumerSecret, accessToken, secretToken ) ); } /** * This class is used to sign all requests via an {@link HttpRequestInterceptor} * until the context-aware AuthScheme is released in HttpClient 4.1. * @since 0.5.1 */ static class OAuthSigner implements HttpRequestInterceptor { protected OAuthConsumer oauth; public OAuthSigner( String consumerKey, String consumerSecret, String accessToken, String secretToken ) { this.oauth = new CommonsHttpOAuthConsumer( consumerKey, consumerSecret ); oauth.setTokenWithSecret( accessToken, secretToken ); } public void process(HttpRequest request, HttpContext ctx) throws HttpException, IOException { /* The full request URI must be reconstructed between the context and the request URI. * Best we can do until AuthScheme supports HttpContext. See: * https://issues.apache.org/jira/browse/HTTPCLIENT-901 */ try { HttpHost host = (HttpHost) ctx.getAttribute( ExecutionContext.HTTP_TARGET_HOST ); final URI requestURI = new URI( host.toURI() ).resolve( request.getRequestLine().getUri() ); oauth.signpost.http.HttpRequest oAuthRequest = new OAuthRequestAdapter(request, requestURI); this.oauth.sign( oAuthRequest ); } catch ( URISyntaxException ex ) { throw new HttpException( "Error rebuilding request URI", ex ); } catch (OAuthException e) { throw new HttpException( "OAuth signing error", e); } } static class OAuthRequestAdapter implements oauth.signpost.http.HttpRequest { final HttpRequest request; final URI requestURI; OAuthRequestAdapter( HttpRequest request, URI requestURI ) { this.request = request; this.requestURI = requestURI; } public String getRequestUrl() { return requestURI.toString(); } public void setRequestUrl(String url) {/*ignore*/} public Map<String, String> getAllHeaders() { Map<String,String> headers = new HashMap<String,String>(); // FIXME this doesn't account for repeated headers, // which are allowed by the HTTP spec!! for ( Header h : request.getAllHeaders() ) headers.put(h.getName(), h.getValue()); return headers; } public String getContentType() { try { return request.getFirstHeader("content-type").getValue(); } catch ( Exception ex ) { // NPE or ArrayOOBEx return null; } } public String getHeader(String name) { Header h = request.getFirstHeader(name); return h != null ? h.getValue() : null; } public InputStream getMessagePayload() throws IOException { if ( request instanceof HttpEntityEnclosingRequest ) return ((HttpEntityEnclosingRequest)request).getEntity().getContent(); return null; } public String getMethod() { return request.getRequestLine().getMethod(); } public void setHeader(String key, String val) { request.setHeader(key, val); } public Object unwrap() { return request; } }; } }