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 }