001: /*
002: * Copyright (c) 2002-2008 Gargoyle Software Inc. All rights reserved.
003: *
004: * Redistribution and use in source and binary forms, with or without
005: * modification, are permitted provided that the following conditions are met:
006: *
007: * 1. Redistributions of source code must retain the above copyright notice,
008: * this list of conditions and the following disclaimer.
009: * 2. Redistributions in binary form must reproduce the above copyright notice,
010: * this list of conditions and the following disclaimer in the documentation
011: * and/or other materials provided with the distribution.
012: * 3. The end-user documentation included with the redistribution, if any, must
013: * include the following acknowledgment:
014: *
015: * "This product includes software developed by Gargoyle Software Inc.
016: * (http://www.GargoyleSoftware.com/)."
017: *
018: * Alternately, this acknowledgment may appear in the software itself, if
019: * and wherever such third-party acknowledgments normally appear.
020: * 4. The name "Gargoyle Software" must not be used to endorse or promote
021: * products derived from this software without prior written permission.
022: * For written permission, please contact info@GargoyleSoftware.com.
023: * 5. Products derived from this software may not be called "HtmlUnit", nor may
024: * "HtmlUnit" appear in their name, without prior written permission of
025: * Gargoyle Software Inc.
026: *
027: * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES,
028: * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
029: * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GARGOYLE
030: * SOFTWARE INC. OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
031: * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
032: * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
033: * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
034: * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
035: * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
036: * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
037: */
038: package com.gargoylesoftware.htmlunit;
039:
040: import java.io.Serializable;
041: import java.util.Collections;
042: import java.util.HashMap;
043: import java.util.Iterator;
044: import java.util.Map;
045: import java.util.Set;
046: import java.util.TreeSet;
047:
048: import org.apache.commons.httpclient.Credentials;
049: import org.apache.commons.httpclient.NTCredentials;
050: import org.apache.commons.httpclient.UsernamePasswordCredentials;
051: import org.apache.commons.httpclient.auth.AuthScheme;
052: import org.apache.commons.httpclient.auth.AuthScope;
053: import org.apache.commons.httpclient.auth.CredentialsNotAvailableException;
054: import org.apache.commons.httpclient.auth.CredentialsProvider;
055: import org.apache.commons.logging.Log;
056: import org.apache.commons.logging.LogFactory;
057:
058: /**
059: * Default HtmlUnit implementation of the <tt>CredentialsProvider</tt> interface. Provides
060: * credentials for both web servers and proxies. Supports NTLM authentication, Digest
061: * authentication, and Basic HTTP authentication.
062: *
063: * @version $Revision: 2132 $
064: * @author Daniel Gredler
065: * @author Vikram Shitole
066: * @author Marc Guillemot
067: * @author Ahmed Ashour
068: */
069: public class DefaultCredentialsProvider implements CredentialsProvider,
070: Serializable {
071:
072: private static final long serialVersionUID = 1036331144926557053L;
073:
074: private final Map credentials_ = new HashMap();
075: private final Map proxyCredentials_ = new HashMap();
076: private final Set answerMarks_ = Collections
077: .synchronizedSortedSet(new TreeSet());
078:
079: /**
080: * Creates a new <tt>DefaultCredentialsProvider</tt> instance.
081: */
082: public DefaultCredentialsProvider() {
083: // nothing
084: }
085:
086: /**
087: * Adds credentials for the specified username/password for any host/port/realm combination.
088: * The credentials may be for any authentication scheme, including NTLM, digest and basic
089: * HTTP authentication. If you are using sensitive username/password information, please do
090: * NOT use this method. If you add credentials using this method, any server that requires
091: * authentication will receive the specified username and password.
092: * @param username The username for the new credentials.
093: * @param password The password for the new credentials.
094: */
095: public void addCredentials(final String username,
096: final String password) {
097: addCredentials(username, password, AuthScope.ANY_HOST,
098: AuthScope.ANY_PORT, AuthScope.ANY_REALM);
099: }
100:
101: /**
102: * Adds credentials for the specified username/password on the specified host/port for the
103: * specified realm. The credentials may be for any authentication scheme, including NTLM,
104: * digest and basic HTTP authentication.
105: * @param username The username for the new credentials.
106: * @param password The password for the new credentials.
107: * @param host The host to which to the new credentials apply (<tt>null</tt> if applicable to any host).
108: * @param port The port to which to the new credentials apply (negative if applicable to any port).
109: * @param realm The realm to which to the new credentials apply (<tt>null</tt> if applicable to any realm).
110: */
111: public void addCredentials(final String username,
112: final String password, final String host, final int port,
113: final String realm) {
114: final AuthScope scope = new AuthScope(host, port, realm,
115: AuthScope.ANY_SCHEME);
116: final Credentials c = new UsernamePasswordCredentials(username,
117: password);
118: credentials_.put(scope, c);
119: clearAnswered(); // don't need to be precise, will cause in worst case one extra request
120: }
121:
122: /**
123: * Adds proxy credentials for the specified username/password for any host/port/realm combination.
124: * @param username The username for the new credentials.
125: * @param password The password for the new credentials.
126: */
127: public void addProxyCredentials(final String username,
128: final String password) {
129: addProxyCredentials(username, password, AuthScope.ANY_HOST,
130: AuthScope.ANY_PORT);
131: }
132:
133: /**
134: * Adds proxy credentials for the specified username/password on the specified host/port.
135: * @param username The username for the new credentials.
136: * @param password The password for the new credentials.
137: * @param host The host to which to the new credentials apply (<tt>null</tt> if applicable to any host).
138: * @param port The port to which to the new credentials apply (negative if applicable to any port).
139: */
140: public void addProxyCredentials(final String username,
141: final String password, final String host, final int port) {
142: final AuthScope scope = new AuthScope(host, port,
143: AuthScope.ANY_REALM, AuthScope.ANY_SCHEME);
144: final Credentials c = new UsernamePasswordCredentials(username,
145: password);
146: proxyCredentials_.put(scope, c);
147: clearAnswered(); // don't need to be precise, will cause in worst case one extra request
148: }
149:
150: /**
151: * Adds NTLM credentials for the specified username/password on the specified host/port.
152: * @param username The username for the new credentials. This should not include the domain to authenticate with.
153: * For example: <tt>"user"</tt> is correct whereas <tt>"DOMAIN\\user"</tt> is not.
154: * @param password The password for the new credentials.
155: * @param host The host to which to the new credentials apply (<tt>null</tt> if applicable to any host).
156: * @param port The port to which to the new credentials apply (negative if applicable to any port).
157: * @param clientHost The host the authentication request is originating from. Essentially, the computer name for
158: * this machine.
159: * @param clientDomain The domain to authenticate within.
160: */
161: public void addNTLMCredentials(final String username,
162: final String password, final String host, final int port,
163: final String clientHost, final String clientDomain) {
164: final AuthScope scope = new AuthScope(host, port,
165: AuthScope.ANY_REALM, AuthScope.ANY_SCHEME);
166: final Credentials c = new NTCredentials(username, password,
167: clientHost, clientDomain);
168: credentials_.put(scope, c);
169: clearAnswered(); // don't need to be precise, will cause in worst case one extra request
170: }
171:
172: /**
173: * Adds NTLM proxy credentials for the specified username/password on the specified host/port.
174: * @param username The username for the new credentials. This should not include the domain to authenticate with.
175: * For example: <tt>"user"</tt> is correct whereas <tt>"DOMAIN\\user"</tt> is not.
176: * @param password The password for the new credentials.
177: * @param host The host to which to the new credentials apply (<tt>null</tt> if applicable to any host).
178: * @param port The port to which to the new credentials apply (negative if applicable to any port).
179: * @param clientHost The host the authentication request is originating from. Essentially, the computer name for
180: * this machine.
181: * @param clientDomain The domain to authenticate within.
182: */
183: public void addNTLMProxyCredentials(final String username,
184: final String password, final String host, final int port,
185: final String clientHost, final String clientDomain) {
186: final AuthScope scope = new AuthScope(host, port,
187: AuthScope.ANY_REALM, AuthScope.ANY_SCHEME);
188: final Credentials c = new NTCredentials(username, password,
189: clientHost, clientDomain);
190: proxyCredentials_.put(scope, c);
191: clearAnswered(); // don't need to be precise, will cause in worst case one extra request
192: }
193:
194: /**
195: * Returns the credentials associated with the specified scheme, host and port
196: * @param scheme The authentication scheme being used (basic, digest, NTLM, etc).
197: * @param host The host we are authenticating for.
198: * @param port The port we are authenticating for.
199: * @param proxy Whether or not we are authenticating using a proxy.
200: * @return The credentials corresponding to the specified scheme, host and port or <code>null</code>
201: * if already asked for it to avoid infinite loop
202: * @throws CredentialsNotAvailableException If the specified credentials cannot be provided due to an error.
203: * @see CredentialsProvider#getCredentials(AuthScheme, String, int, boolean)
204: */
205: public Credentials getCredentials(final AuthScheme scheme,
206: final String host, final int port, final boolean proxy)
207: throws CredentialsNotAvailableException {
208:
209: // it's the responsibility of the CredentialProvider to answer only once with a given Credentials
210: // to avoid infinite loop if it is incorrect
211: // see http://issues.apache.org/bugzilla/show_bug.cgi?id=8140
212: if (alreadyAnswered(scheme, host, port, proxy)) {
213: getLog().debug(
214: "Already answered for "
215: + buildKey(scheme, host, port, proxy)
216: + " returning null");
217: return null;
218: }
219:
220: final Map credentials;
221: if (proxy) {
222: credentials = proxyCredentials_;
223: } else {
224: credentials = credentials_;
225: }
226:
227: for (final Iterator i = credentials.entrySet().iterator(); i
228: .hasNext();) {
229: final Map.Entry entry = (Map.Entry) i.next();
230: final AuthScope scope = (AuthScope) entry.getKey();
231: final Credentials c = (Credentials) entry.getValue();
232: if (matchScheme(scope, scheme) && matchHost(scope, host)
233: && matchPort(scope, port)
234: && matchRealm(scope, scheme)) {
235:
236: markAsAnswered(scheme, host, port, proxy);
237: getLog().debug(
238: "Returning " + c + " for "
239: + buildKey(scheme, host, port, proxy));
240: return c;
241: }
242: }
243:
244: getLog().debug(
245: "No credential found for "
246: + buildKey(scheme, host, port, proxy));
247: return null;
248: }
249:
250: /**
251: * @param scheme the request scheme for which Credentials are asked
252: * @param scope the configured authorization scope
253: * @return <code>true</code> if the scope's realm matches the one of the scheme
254: */
255: protected boolean matchRealm(final AuthScope scope,
256: final AuthScheme scheme) {
257: return scope.getRealm() == AuthScope.ANY_REALM
258: || scope.getRealm().equals(scheme.getRealm());
259: }
260:
261: /**
262: * @param port the request port for which Credentials are asked
263: * @param scope the configured authorization scope
264: * @return <code>true</code> if the scope's port matches the provided one
265: */
266: protected boolean matchPort(final AuthScope scope, final int port) {
267: return scope.getPort() == AuthScope.ANY_PORT
268: || scope.getPort() == port;
269: }
270:
271: /**
272: * @param host the request host for which Credentials are asked
273: * @param scope the configured authorization scope
274: * @return <code>true</code> if the scope's host matches the provided one
275: */
276: protected boolean matchHost(final AuthScope scope, final String host) {
277: return scope.getHost() == AuthScope.ANY_HOST
278: || scope.getHost().equals(host);
279: }
280:
281: /**
282: * @param scheme the request scheme for which Credentials are asked
283: * @param scope the configured authorization scope
284: * @return <code>true</code> if the scope's scheme matches the provided one
285: */
286: protected boolean matchScheme(final AuthScope scope,
287: final AuthScheme scheme) {
288: return scope.getScheme() == AuthScope.ANY_SCHEME
289: || scope.getScheme().equals(scheme.getSchemeName());
290: }
291:
292: /**
293: * Indicates if this provider has already provided an answer for this (scheme, host, port, proxy).
294: * @param scheme The scheme
295: * @param host the server name.
296: * @param port the server port.
297: * @param proxy is proxy
298: * @return true if the provider has already provided an answer for this.
299: */
300: protected boolean alreadyAnswered(final AuthScheme scheme,
301: final String host, final int port, final boolean proxy) {
302: return answerMarks_
303: .contains(buildKey(scheme, host, port, proxy));
304: }
305:
306: /**
307: * @param scheme The scheme
308: * @param host the server name.
309: * @param port the server port.
310: * @param proxy is proxy
311: */
312: protected void markAsAnswered(final AuthScheme scheme,
313: final String host, final int port, final boolean proxy) {
314: answerMarks_.add(buildKey(scheme, host, port, proxy));
315: }
316:
317: /**
318: * Clears the cache of answered (scheme, host, port, proxy) combinations.
319: */
320: protected void clearAnswered() {
321: answerMarks_.clear();
322: getLog().debug("Flushed marked answers");
323: }
324:
325: /**
326: * Build a key with the specified data
327: * @param scheme The scheme
328: * @param host the server name.
329: * @param port the server port.
330: * @param proxy is proxy
331: * @return the new key.
332: */
333: protected Object buildKey(final AuthScheme scheme,
334: final String host, final int port, final boolean proxy) {
335: return scheme.getSchemeName() + " " + scheme.getRealm() + " "
336: + host + ":" + port + " " + proxy;
337: }
338:
339: /**
340: * Return the log object for this class
341: * @return The log object
342: */
343: protected final Log getLog() {
344: return LogFactory.getLog(getClass());
345: }
346:
347: }
|