001/* 002 * Copyright 2008-2011 Thomas Nichols. http://blog.thomnichols.org 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 * 016 * You are receiving this code free of charge, which represents many hours of 017 * effort from other individuals and corporations. As a responsible member 018 * of the community, you are encouraged (but not required) to donate any 019 * enhancements or improvements back to the community under a similar open 020 * source license. Thank you. -TMN 021 */ 022package groovyx.net.http; 023 024import java.io.IOException; 025import java.io.InputStream; 026import java.net.URI; 027import java.net.URISyntaxException; 028import java.net.URL; 029import java.security.GeneralSecurityException; 030import java.security.KeyStore; 031import java.util.HashMap; 032import java.util.Map; 033 034import oauth.signpost.OAuthConsumer; 035import oauth.signpost.commonshttp.CommonsHttpOAuthConsumer; 036import oauth.signpost.exception.OAuthException; 037 038import org.apache.http.Header; 039import org.apache.http.HttpEntityEnclosingRequest; 040import org.apache.http.HttpException; 041import org.apache.http.HttpHost; 042import org.apache.http.HttpRequest; 043import org.apache.http.HttpRequestInterceptor; 044import org.apache.http.auth.AuthScope; 045import org.apache.http.auth.NTCredentials; 046import org.apache.http.auth.UsernamePasswordCredentials; 047import org.apache.http.client.HttpClient; 048import org.apache.http.conn.scheme.Scheme; 049import org.apache.http.conn.ssl.SSLSocketFactory; 050import org.apache.http.impl.client.AbstractHttpClient; 051import org.apache.http.protocol.ExecutionContext; 052import org.apache.http.protocol.HttpContext; 053 054/** 055 * Encapsulates all configuration related to HTTP authentication methods. 056 * @see HTTPBuilder#getAuth() 057 * 058 * @author <a href='mailto:tomstrummer+httpbuilder@gmail.com'>Tom Nichols</a> 059 */ 060public class AuthConfig { 061 protected HTTPBuilder builder; 062 public AuthConfig( HTTPBuilder builder ) { 063 this.builder = builder; 064 } 065 066 /** 067 * Set authentication credentials to be used for the current 068 * {@link HTTPBuilder#getUri() default host}. This method name is a bit of 069 * a misnomer, since these credentials will actually work for "digest" 070 * authentication as well. 071 * @param user 072 * @param pass 073 */ 074 public void basic( String user, String pass ) { 075 URI uri = ((URIBuilder)builder.getUri()).toURI(); 076 if ( uri == null ) throw new IllegalStateException( "a default URI must be set" ); 077 this.basic( uri.getHost(), uri.getPort(), user, pass ); 078 } 079 080 /** 081 * Set authentication credentials to be used for the given host and port. 082 * @param host 083 * @param port 084 * @param user 085 * @param pass 086 */ 087 public void basic( String host, int port, String user, String pass ) { 088 final HttpClient client = builder.getClient(); 089 if ( !(client instanceof AbstractHttpClient )) { 090 throw new IllegalStateException("client is not an AbstractHttpClient"); 091 } 092 ((AbstractHttpClient)client).getCredentialsProvider().setCredentials( 093 new AuthScope( host, port ), 094 new UsernamePasswordCredentials( user, pass ) 095 ); 096 } 097 098 /** 099 * Set NTLM authentication credentials to be used for the current 100 * {@link HTTPBuilder#getUri() default host}. 101 * @param user 102 * @param pass 103 * @param workstation 104 * @param domain 105 */ 106 public void ntlm( String user, String pass, String workstation, String domain ) { 107 URI uri = ((URIBuilder)builder.getUri()).toURI(); 108 if ( uri == null ) throw new IllegalStateException( "a default URI must be set" ); 109 this.ntlm( uri.getHost(), uri.getPort(), user, pass, workstation, domain ); 110 } 111 112 /** 113 * Set NTLM authentication credentials to be used for the given host and port. 114 * @param host 115 * @param port 116 * @param user 117 * @param pass 118 * @param workstation 119 * @param domain 120 */ 121 public void ntlm( String host, int port, String user, String pass, String workstation, String domain ) { 122 final HttpClient client = builder.getClient(); 123 if ( !(client instanceof AbstractHttpClient )) { 124 throw new IllegalStateException("client is not an AbstractHttpClient"); 125 } 126 ((AbstractHttpClient)client).getCredentialsProvider().setCredentials( 127 new AuthScope( host, port ), 128 new NTCredentials( user, pass, workstation, domain ) 129 ); 130 } 131 132 /** 133 * Sets a certificate to be used for SSL authentication. See 134 * {@link Class#getResource(String)} for how to get a URL from a resource 135 * on the classpath. 136 * @param certURL URL to a JKS keystore where the certificate is stored. 137 * @param password password to decrypt the keystore 138 */ 139 public void certificate( String certURL, String password ) 140 throws GeneralSecurityException, IOException { 141 142 KeyStore keyStore = KeyStore.getInstance( KeyStore.getDefaultType() ); 143 InputStream jksStream = new URL(certURL).openStream(); 144 try { 145 keyStore.load( jksStream, password.toCharArray() ); 146 } finally { jksStream.close(); } 147 148 SSLSocketFactory ssl = new SSLSocketFactory(keyStore, password); 149 ssl.setHostnameVerifier( SSLSocketFactory.STRICT_HOSTNAME_VERIFIER ); 150 151 builder.getClient().getConnectionManager().getSchemeRegistry() 152 .register( new Scheme("https", ssl, 443) ); 153 } 154 155 /** 156 * </p>OAuth sign all requests. Note that this currently does <strong>not</strong> 157 * wait for a <code>WWW-Authenticate</code> challenge before sending the 158 * the OAuth header. All requests to all domains will be signed for this 159 * instance.</p> 160 * 161 * <p>This assumes you've already generated an <code>accessToken</code> and 162 * <code>secretToken</code> for the site you're targeting. For More information 163 * on how to achieve this, see the 164 * <a href='http://code.google.com/p/oauth-signpost/wiki/GettingStarted#Using_Signpost'>Signpost documentation</a>.</p> 165 * @since 0.5.1 166 * @param consumerKey <code>null</code> if you want to <strong>unset</strong> 167 * OAuth handling and stop signing requests. 168 * @param consumerSecret 169 * @param accessToken 170 * @param secretToken 171 */ 172 public void oauth( String consumerKey, String consumerSecret, 173 String accessToken, String secretToken ) { 174 final HttpClient client = builder.getClient(); 175 if ( !(client instanceof AbstractHttpClient )) { 176 throw new IllegalStateException("client is not an AbstractHttpClient"); 177 } 178 ((AbstractHttpClient)client).removeRequestInterceptorByClass( OAuthSigner.class ); 179 if ( consumerKey != null ) 180 ((AbstractHttpClient)client).addRequestInterceptor( new OAuthSigner( 181 consumerKey, consumerSecret, accessToken, secretToken ) ); 182 } 183 184 /** 185 * This class is used to sign all requests via an {@link HttpRequestInterceptor} 186 * until the context-aware AuthScheme is released in HttpClient 4.1. 187 * @since 0.5.1 188 */ 189 static class OAuthSigner implements HttpRequestInterceptor { 190 protected OAuthConsumer oauth; 191 public OAuthSigner( String consumerKey, String consumerSecret, 192 String accessToken, String secretToken ) { 193 this.oauth = new CommonsHttpOAuthConsumer( consumerKey, consumerSecret ); 194 oauth.setTokenWithSecret( accessToken, secretToken ); 195 } 196 197 public void process(HttpRequest request, HttpContext ctx) throws HttpException, IOException { 198 /* The full request URI must be reconstructed between the context and the request URI. 199 * Best we can do until AuthScheme supports HttpContext. See: 200 * https://issues.apache.org/jira/browse/HTTPCLIENT-901 */ 201 try { 202 HttpHost host = (HttpHost) ctx.getAttribute( ExecutionContext.HTTP_TARGET_HOST ); 203 final URI requestURI = new URI( host.toURI() ).resolve( request.getRequestLine().getUri() ); 204 205 oauth.signpost.http.HttpRequest oAuthRequest = 206 new OAuthRequestAdapter(request, requestURI); 207 this.oauth.sign( oAuthRequest ); 208 } 209 catch ( URISyntaxException ex ) { 210 throw new HttpException( "Error rebuilding request URI", ex ); 211 } 212 catch (OAuthException e) { 213 throw new HttpException( "OAuth signing error", e); 214 } 215 } 216 217 static class OAuthRequestAdapter implements oauth.signpost.http.HttpRequest { 218 219 final HttpRequest request; 220 final URI requestURI; 221 OAuthRequestAdapter( HttpRequest request, URI requestURI ) { 222 this.request = request; 223 this.requestURI = requestURI; 224 } 225 226 public String getRequestUrl() { return requestURI.toString(); } 227 public void setRequestUrl(String url) {/*ignore*/} 228 public Map<String, String> getAllHeaders() { 229 Map<String,String> headers = new HashMap<String,String>(); 230 // FIXME this doesn't account for repeated headers, 231 // which are allowed by the HTTP spec!! 232 for ( Header h : request.getAllHeaders() ) 233 headers.put(h.getName(), h.getValue()); 234 return headers; 235 } 236 public String getContentType() { 237 try { 238 return request.getFirstHeader("content-type").getValue(); 239 } 240 catch ( Exception ex ) { // NPE or ArrayOOBEx 241 return null; 242 } 243 } 244 public String getHeader(String name) { 245 Header h = request.getFirstHeader(name); 246 return h != null ? h.getValue() : null; 247 } 248 public InputStream getMessagePayload() throws IOException { 249 if ( request instanceof HttpEntityEnclosingRequest ) 250 return ((HttpEntityEnclosingRequest)request).getEntity().getContent(); 251 return null; 252 } 253 public String getMethod() { 254 return request.getRequestLine().getMethod(); 255 } 256 public void setHeader(String key, String val) { 257 request.setHeader(key, val); 258 } 259 public Object unwrap() { 260 return request; 261 } 262 }; 263 } 264}