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     */
022    package groovyx.net.http;
023    
024    import java.io.IOException;
025    import java.io.InputStream;
026    import java.net.URI;
027    import java.net.URISyntaxException;
028    import java.net.URL;
029    import java.security.GeneralSecurityException;
030    import java.security.KeyStore;
031    import java.util.HashMap;
032    import java.util.Map;
033    
034    import oauth.signpost.OAuthConsumer;
035    import oauth.signpost.commonshttp.CommonsHttpOAuthConsumer;
036    import oauth.signpost.exception.OAuthException;
037    
038    import org.apache.http.Header;
039    import org.apache.http.HttpEntityEnclosingRequest;
040    import org.apache.http.HttpException;
041    import org.apache.http.HttpHost;
042    import org.apache.http.HttpRequest;
043    import org.apache.http.HttpRequestInterceptor;
044    import org.apache.http.auth.AuthScope;
045    import org.apache.http.auth.UsernamePasswordCredentials;
046    import org.apache.http.conn.scheme.Scheme;
047    import org.apache.http.conn.ssl.SSLSocketFactory;
048    import org.apache.http.protocol.ExecutionContext;
049    import org.apache.http.protocol.HttpContext;
050    
051    /**
052     * Encapsulates all configuration related to HTTP authentication methods.
053     * @see HTTPBuilder#getAuth()
054     * 
055     * @author <a href='mailto:tomstrummer+httpbuilder@gmail.com'>Tom Nichols</a>
056     */
057    public class AuthConfig {
058            protected HTTPBuilder builder;
059            public AuthConfig( HTTPBuilder builder ) {
060                    this.builder = builder;
061            }
062            
063            /**
064             * Set authentication credentials to be used for the current 
065             * {@link HTTPBuilder#getUri() default host}.  This method name is a bit of 
066             * a misnomer, since these credentials will actually work for "digest" 
067             * authentication as well.
068             * @param user
069             * @param pass
070             */
071            public void basic( String user, String pass ) {
072                    URI uri = ((URIBuilder)builder.getUri()).toURI();
073                    if ( uri == null ) throw new IllegalStateException( "a default URI must be set" );
074                    this.basic( uri.getHost(), uri.getPort(), user, pass );
075            }
076            
077            /**
078             * Set authentication credentials to be used for the given host and port. 
079             * @param host
080             * @param port
081             * @param user
082             * @param pass
083             */
084            public void basic( String host, int port, String user, String pass ) {
085                    builder.getClient().getCredentialsProvider().setCredentials( 
086                            new AuthScope( host, port ),
087                            new UsernamePasswordCredentials( user, pass )
088                    );
089            }
090            
091            /**
092             * Sets a certificate to be used for SSL authentication.  See 
093             * {@link Class#getResource(String)} for how to get a URL from a resource 
094             * on the classpath.
095             * @param certURL URL to a JKS keystore where the certificate is stored.  
096             * @param password password to decrypt the keystore
097             */
098            public void certificate( String certURL, String password ) 
099                            throws GeneralSecurityException, IOException {
100                    
101                    KeyStore keyStore = KeyStore.getInstance( KeyStore.getDefaultType() );
102            InputStream jksStream = new URL(certURL).openStream();
103            try {
104                    keyStore.load( jksStream, password.toCharArray() );
105            } finally { jksStream.close(); }
106    
107            SSLSocketFactory ssl = new SSLSocketFactory(keyStore, password);
108            ssl.setHostnameVerifier( SSLSocketFactory.STRICT_HOSTNAME_VERIFIER );
109            
110            builder.getClient().getConnectionManager().getSchemeRegistry()
111                    .register( new Scheme("https", ssl, 443) );
112            }
113    
114            /**
115             * </p>OAuth sign all requests.  Note that this currently does <strong>not</strong>
116             * wait for a <code>WWW-Authenticate</code> challenge before sending the 
117             * the OAuth header.  All requests to all domains will be signed for this
118             * instance.</p>
119             * 
120             * <p>This assumes you've already generated an <code>accessToken</code> and 
121             * <code>secretToken</code> for the site you're targeting.  For More information
122             * on how to achieve this, see the 
123             * <a href='http://code.google.com/p/oauth-signpost/wiki/GettingStarted#Using_Signpost'>Signpost documentation</a>.</p>
124             * @since 0.5.1
125             * @param consumerKey <code>null</code> if you want to <strong>unset</strong>
126             *  OAuth handling and stop signing requests.
127             * @param consumerSecret
128             * @param accessToken
129             * @param secretToken
130             */
131            public void oauth( String consumerKey, String consumerSecret,
132                            String accessToken, String secretToken ) {              
133                    this.builder.client.removeRequestInterceptorByClass( OAuthSigner.class );
134                    if ( consumerKey != null )
135                            this.builder.client.addRequestInterceptor( new OAuthSigner(
136                                    consumerKey, consumerSecret, accessToken, secretToken ) );
137            }
138            
139            /**
140             * This class is used to sign all requests via an {@link HttpRequestInterceptor}
141             * until the context-aware AuthScheme is released in HttpClient 4.1.
142             * @since 0.5.1
143             */
144            static class OAuthSigner implements HttpRequestInterceptor {
145                    protected OAuthConsumer oauth;
146                    public OAuthSigner( String consumerKey, String consumerSecret,
147                            String accessToken, String secretToken ) {              
148                            this.oauth = new CommonsHttpOAuthConsumer( consumerKey, consumerSecret );
149                            oauth.setTokenWithSecret( accessToken, secretToken );
150                    }
151                    
152                    public void process(HttpRequest request, HttpContext ctx) throws HttpException, IOException {
153                            /* The full request URI must be reconstructed between the context and the request URI.  
154                             * Best we can do until AuthScheme supports HttpContext.  See:
155                             * https://issues.apache.org/jira/browse/HTTPCLIENT-901 */
156                            try {
157                                    HttpHost host = (HttpHost) ctx.getAttribute( ExecutionContext.HTTP_TARGET_HOST );
158                                    final URI requestURI = new URI( host.toURI() ).resolve( request.getRequestLine().getUri() );
159                                    
160                                    oauth.signpost.http.HttpRequest oAuthRequest = 
161                                            new OAuthRequestAdapter(request, requestURI);
162                                    this.oauth.sign( oAuthRequest );
163                            }
164                            catch ( URISyntaxException ex ) {
165                                    throw new HttpException( "Error rebuilding request URI", ex );
166                            }
167                            catch (OAuthException e) {
168                                    throw new HttpException( "OAuth signing error", e);
169                            }
170                    }
171                    
172                    static class OAuthRequestAdapter implements oauth.signpost.http.HttpRequest {
173                            
174                            final HttpRequest request;
175                            final URI requestURI;
176                            OAuthRequestAdapter( HttpRequest request, URI requestURI ) {
177                                    this.request = request;
178                                    this.requestURI = requestURI;
179                            }
180                            
181                            public String getRequestUrl() { return requestURI.toString(); }
182                            public void setRequestUrl(String url) {/*ignore*/}
183                            public Map<String, String> getAllHeaders() {
184                                    Map<String,String> headers = new HashMap<String,String>();
185                                    // FIXME this doesn't account for repeated headers,
186                                    // which are allowed by the HTTP spec!!
187                                    for ( Header h : request.getAllHeaders() )
188                                            headers.put(h.getName(), h.getValue()); 
189                                    return headers;
190                            }
191                            public String getContentType() {
192                                    try {
193                                            return request.getFirstHeader("content-type").getValue();
194                                    }
195                                    catch ( Exception ex ) { // NPE or ArrayOOBEx
196                                            return null;
197                                    }
198                            }
199                            public String getHeader(String name) {
200                                    Header h = request.getFirstHeader(name);
201                                    return h != null ? h.getValue() : null;
202                            }
203                            public InputStream getMessagePayload() throws IOException {
204                                    if ( request instanceof HttpEntityEnclosingRequest )
205                                            return ((HttpEntityEnclosingRequest)request).getEntity().getContent();
206                                    return null;
207                            }
208                            public String getMethod() {
209                                    return request.getRequestLine().getMethod();
210                            }
211                            public void setHeader(String key, String val) {
212                                    request.setHeader(key, val);
213                            }
214                            public Object unwrap() {
215                                    return request;
216                            }
217                    };
218            }
219    }