com.phloc.web.http.digestauth.DigestAuthServerBuilder Maven / Gradle / Ivy
/**
* Copyright (C) 2006-2015 phloc systems
* http://www.phloc.com
* office[at]phloc[dot]com
*
* 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.
*/
package com.phloc.web.http.digestauth;
import java.io.Serializable;
import java.util.LinkedHashSet;
import java.util.Set;
import javax.annotation.Nonnull;
import javax.annotation.concurrent.NotThreadSafe;
import com.phloc.commons.ValueEnforcer;
import com.phloc.commons.annotations.Nonempty;
import com.phloc.commons.state.ETriState;
import com.phloc.commons.string.StringHelper;
import com.phloc.commons.url.ISimpleURL;
import com.phloc.web.http.HTTPStringHelper;
/**
* Helper class to build the value of the
* {@link com.phloc.web.http.CHTTPHeader#WWW_AUTHENTICATE} value send from the
* server to client.
*
* @author Philip Helger
*/
@NotThreadSafe
public final class DigestAuthServerBuilder implements Serializable
{
private String m_sRealm;
private final Set m_aDomains = new LinkedHashSet ();
private String m_sNonce;
private String m_sOpaque;
private ETriState m_eStale = ETriState.UNDEFINED;
private String m_sAlgorithm;
private final Set m_aQOPs = new LinkedHashSet ();
public DigestAuthServerBuilder ()
{}
/**
* A string to be displayed to users so they know which username and password
* to use. This string should contain at least the name of the host performing
* the authentication and might additionally indicate the collection of users
* who might have access. An example might be
* "[email protected]".
*
* @param sRealm
* The realm to be used. May not be null
and should not be
* empty.
* @return this
*/
@Nonnull
public DigestAuthServerBuilder setRealm (@Nonnull final String sRealm)
{
if (!HTTPStringHelper.isQuotedTextContent (sRealm))
throw new IllegalArgumentException ("realm is invalid: " + sRealm);
m_sRealm = sRealm;
return this;
}
/**
* Add an URIs, as specified in RFC XURI, that define the protection space. If
* a URI is an abs_path, it is relative to the canonical root URL of the
* server being accessed. An absoluteURI in this list may refer to a different
* server than the one being accessed. The client can use this list to
* determine the set of URIs for which the same authentication information may
* be sent: any URI that has a URI in this list as a prefix (after both have
* been made absolute) may be assumed to be in the same protection space. If
* this directive is omitted or its value is empty, the client should assume
* that the protection space consists of all URIs on the responding server.
* This directive is not meaningful in Proxy-Authenticate headers, for which
* the protection space is always the entire proxy; if present it should be
* ignored.
*
* @param aURL
* The absolute or relative path which is protected. May not be
* null
.
* @return this
*/
@Nonnull
public DigestAuthServerBuilder addDomain (@Nonnull final ISimpleURL aURL)
{
ValueEnforcer.notNull (aURL, "Url");
final String sURL = aURL.getAsString ();
// Check for spaces, as all URLs are concatenated with spaces!
if (sURL.indexOf (' ') >= 0)
throw new IllegalArgumentException ("URL may not contain spaces: '" + sURL + "'");
m_aDomains.add (sURL);
return this;
}
/**
* A server-specified data string which should be uniquely generated each time
* a 401 response is made. It is recommended that this string be base64 or
* hexadecimal data. Specifically, since the string is passed in the header
* lines as a quoted string, the double-quote character is not allowed.
* The contents of the nonce are implementation dependent. The quality of the
* implementation depends on a good choice. A nonce might, for example, be
* constructed as the base 64 encoding of
* time-stamp H(time-stamp ":" ETag ":" private-key)
* where time-stamp is a server-generated time or other non-repeating value,
* ETag is the value of the HTTP ETag header associated with the requested
* entity, and private-key is data known only to the server. With a nonce of
* this form a server would recalculate the hash portion after receiving the
* client authentication header and reject the request if it did not match the
* nonce from that header or if the time-stamp value is not recent enough. In
* this way the server can limit the time of the nonce’s validity. The
* inclusion of the ETag prevents a replay request for an updated version of
* the resource. (Note: including the IP address of the client in the nonce
* would appear to offer the server the ability to limit the reuse of the
* nonce to the same client that originally got it. However, that would break
* proxy farms, where requests from a single user often go through different
* proxies in the farm. Also, IP address spoofing is not that hard.)
* An implementation might choose not to accept a previously used nonce or a
* previously used digest, in order to protect against a replay attack. Or, an
* implementation might choose to use one-time nonces or digests for POST or
* PUT requests and a time-stamp for GET requests. For more details on the
* issues involved see section 4. of this document.
* The nonce is opaque to the client.
*
* @param sNonce
* The nonce value to be set. May not be null
.
* @return this
*/
@Nonnull
public DigestAuthServerBuilder setNonce (@Nonnull final String sNonce)
{
if (!HTTPStringHelper.isQuotedTextContent (sNonce))
throw new IllegalArgumentException ("nonce is invalid: " + sNonce);
m_sNonce = sNonce;
return this;
}
/**
* A string of data, specified by the server, which should be returned by the
* client unchanged in the Authorization header of subsequent requests with
* URIs in the same protection space. It is recommended that this string be
* base64 or hexadecimal data.
*
* @param sOpaque
* The opaque value. May not be null
.
* @return this
*/
@Nonnull
public DigestAuthServerBuilder setOpaque (@Nonnull final String sOpaque)
{
if (!HTTPStringHelper.isQuotedTextContent (sOpaque))
throw new IllegalArgumentException ("opaque is invalid: " + sOpaque);
m_sOpaque = sOpaque;
return this;
}
/**
* A flag, indicating that the previous request from the client was rejected
* because the nonce value was stale. If stale is TRUE (case-insensitive), the
* client may wish to simply retry the request with a new encrypted response,
* without reprompting the user for a new username and password. The server
* should only set stale to TRUE if it receives a request for which the nonce
* is invalid but with a valid digest for that nonce (indicating that the
* client knows the correct username/password). If stale is FALSE, or anything
* other than TRUE, or the stale directive is not present, the username and/or
* password are invalid, and new values must be obtained.
*
* @param eStale
* Stale value. May not be null
.
* @return this
*/
@Nonnull
public DigestAuthServerBuilder setStale (@Nonnull final ETriState eStale)
{
m_eStale = ValueEnforcer.notNull (eStale, "Stale");
return this;
}
/**
* A string indicating a pair of algorithms used to produce the digest and a
* checksum. If this is not present it is assumed to be "MD5". If the
* algorithm is not understood, the challenge should be ignored (and a
* different one used, if there is more than one). In this document the string
* obtained by applying the digest algorithm to the data "data" with secret
* "secret" will be denoted by KD(secret, data), and the string obtained by
* applying the checksum algorithm to the data "data" will be denoted H(data).
* The notation unq(X) means the value of the quoted-string X without the
* surrounding quotes.
* For the "MD5" and "MD5-sess" algorithms
* H(data) = MD5(data)
* and
* KD(secret, data) = H(concat(secret, ":", data))
* i.e., the digest is the MD5 of the secret concatenated with a colon
* concatenated with the data. The "MD5-sess" algorithm is intended to allow
* efficient 3rd party authentication servers; for the difference in usage,
* see the description in section 3.2.2.2.
*
* @param sAlgorithm
* @return this
*/
@Nonnull
public DigestAuthServerBuilder setAlgorithm (@Nonnull final String sAlgorithm)
{
if (!HTTPStringHelper.isToken (sAlgorithm))
throw new IllegalArgumentException ("The passed algorithm is not a valid token: " + sAlgorithm);
m_sAlgorithm = sAlgorithm;
return this;
}
/**
* This directive is optional, but is made so only for backward compatibility
* with RFC 2069 [6]; it SHOULD be used by all implementations compliant with
* this version of the Digest scheme. If present, it is a quoted string of one
* or more tokens indicating the "quality of protection" values supported by
* the server. The value "auth" indicates authentication; the value "auth-int"
* indicates authentication with integrity protection; see the descriptions
* below for calculating the response directive value for the application of
* this choice. Unrecognized options MUST be ignored.
*
* @param sQOP
* The qop-option to add. May not be null
.
* @return this
*/
@Nonnull
public DigestAuthServerBuilder addQOP (@Nonnull final String sQOP)
{
if (!HTTPStringHelper.isToken (sQOP))
throw new IllegalArgumentException ("The passed qop-option is not a token: " + sQOP);
m_aQOPs.add (sQOP);
return this;
}
public boolean isValid ()
{
return m_sRealm != null && m_sNonce != null;
}
@Nonnull
@Nonempty
public String build ()
{
if (!isValid ())
throw new IllegalStateException ("Built Digest auth is not valid!");
final StringBuilder ret = new StringBuilder (HTTPDigestAuth.HEADER_VALUE_PREFIX_DIGEST);
// Realm is required
ret.append (" realm=").append (HTTPStringHelper.getQuotedTextString (m_sRealm));
if (!m_aDomains.isEmpty ())
{
ret.append (", domain=")
.append (HTTPStringHelper.getQuotedTextString (StringHelper.getImploded (' ', m_aDomains)));
}
// Nonce is required
ret.append (", nonce=").append (HTTPStringHelper.getQuotedTextString (m_sNonce));
if (m_sOpaque != null)
ret.append (", opaque=").append (HTTPStringHelper.getQuotedTextString (m_sOpaque));
if (!m_eStale.isUndefined ())
ret.append (", stale=").append (m_eStale.isTrue () ? "true" : "false");
if (m_sAlgorithm != null)
ret.append (", algorithm=").append (m_sAlgorithm);
if (!m_aQOPs.isEmpty ())
ret.append (", qop=").append (HTTPStringHelper.getQuotedTextString (StringHelper.getImploded (',', m_aQOPs)));
return ret.toString ();
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy