/**
* Copyright 2005-2011 Noelios Technologies.
*
* The contents of this file are subject to the terms of one of the following
* open source licenses: LGPL 3.0 or LGPL 2.1 or CDDL 1.0 or EPL 1.0 (the
* "Licenses"). You can select the license that you prefer but you may not use
* this file except in compliance with one of these Licenses.
*
* You can obtain a copy of the LGPL 3.0 license at
* http://www.opensource.org/licenses/lgpl-3.0.html
*
* You can obtain a copy of the LGPL 2.1 license at
* http://www.opensource.org/licenses/lgpl-2.1.php
*
* You can obtain a copy of the CDDL 1.0 license at
* http://www.opensource.org/licenses/cddl1.php
*
* You can obtain a copy of the EPL 1.0 license at
* http://www.opensource.org/licenses/eclipse-1.0.php
*
* See the Licenses for the specific language governing permissions and
* limitations under the Licenses.
*
* Alternatively, you can obtain a royalty free commercial license with less
* limitations, transferable or non-transferable, directly at
* http://www.noelios.com/products/restlet-engine
*
* Restlet is a registered trademark of Noelios Technologies.
*/
package org.restlet.engine.security;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Level;
import org.restlet.Context;
import org.restlet.Request;
import org.restlet.Response;
import org.restlet.data.AuthenticationInfo;
import org.restlet.data.ChallengeRequest;
import org.restlet.data.ChallengeResponse;
import org.restlet.data.ChallengeScheme;
import org.restlet.data.Digest;
import org.restlet.data.Parameter;
import org.restlet.data.Reference;
import org.restlet.engine.Engine;
import org.restlet.engine.header.ChallengeRequestReader;
import org.restlet.engine.header.ChallengeWriter;
import org.restlet.engine.header.HeaderConstants;
import org.restlet.engine.header.ParameterReader;
import org.restlet.util.Series;
/**
* Authentication utilities.
*
* @author Jerome Louvel
* @author Ray Waldin (ray@waldin.net)
*/
public class AuthenticatorUtils {
/**
* Indicates if any of the objects is null.
*
* @param objects
* The objects to test.
* @return True if any of the objects is null.
*/
public static boolean anyNull(Object... objects) {
for (final Object o : objects) {
if (o == null) {
return true;
}
}
return false;
}
/**
* Formats an authentication information as a HTTP header value. The header
* is {@link HeaderConstants#HEADER_AUTHENTICATION_INFO}.
*
* @param info
* The authentication information to format.
* @return The {@link HeaderConstants#HEADER_AUTHENTICATION_INFO} header
* value.
*/
public static String formatAuthenticationInfo(AuthenticationInfo info) {
ChallengeWriter cw = new ChallengeWriter();
boolean firstParameter = true;
if (info != null) {
if (info.getNextServerNonce() != null
&& info.getNextServerNonce().length() > 0) {
cw.setFirstChallengeParameter(firstParameter);
cw.appendQuotedChallengeParameter("nextnonce",
info.getNextServerNonce());
firstParameter = false;
}
if (info.getQuality() != null && info.getQuality().length() > 0) {
cw.setFirstChallengeParameter(firstParameter);
cw.appendChallengeParameter("qop", info.getQuality());
firstParameter = false;
if (info.getNonceCount() > 0) {
cw.appendChallengeParameter("nc",
formatNonceCount(info.getNonceCount()));
}
}
if (info.getResponseDigest() != null
&& info.getResponseDigest().length() > 0) {
cw.setFirstChallengeParameter(firstParameter);
cw.appendQuotedChallengeParameter("rspauth",
info.getResponseDigest());
firstParameter = false;
}
if (info.getClientNonce() != null
&& info.getClientNonce().length() > 0) {
cw.setFirstChallengeParameter(firstParameter);
cw.appendChallengeParameter("cnonce", info.getClientNonce());
firstParameter = false;
}
}
return cw.toString();
}
/**
* Formats a given nonce count as a HTTP header value. The header is
* {@link HeaderConstants#HEADER_AUTHENTICATION_INFO}.
*
* @param nonceCount
* The given nonce count.
* @return The formatted value of the given nonce count.
*/
public static String formatNonceCount(int nonceCount) {
StringBuilder result = new StringBuilder(
Integer.toHexString(nonceCount));
while (result.length() < 8) {
result.insert(0, '0');
}
return result.toString();
}
/**
* Formats a challenge request as a HTTP header value. The header is
* {@link HeaderConstants#HEADER_WWW_AUTHENTICATE}.
*
* @param challenge
* The challenge request to format.
* @param response
* The parent response.
* @param httpHeaders
* The current response HTTP headers.
* @return The {@link HeaderConstants#HEADER_WWW_AUTHENTICATE} header value.
*/
public static String formatRequest(ChallengeRequest challenge,
Response response, Series<Parameter> httpHeaders) {
String result = null;
if (challenge != null) {
AuthenticatorHelper helper = Engine.getInstance().findHelper(
challenge.getScheme(), false, true);
if (helper != null) {
try {
result = helper.formatRequest(challenge, response,
httpHeaders);
} catch (IOException e) {
Context.getCurrentLogger().log(
Level.WARNING,
"Unable to format the challenge request: "
+ challenge, e);
}
} else {
result = "?";
Context.getCurrentLogger().warning(
"Challenge scheme " + challenge.getScheme()
+ " not supported by the Restlet engine.");
}
}
return result;
}
/**
* Formats a challenge response as a HTTP header value. The header is
* {@link HeaderConstants#HEADER_AUTHORIZATION}.
*
* @param challenge
* The challenge response to format.
* @param request
* The parent request.
* @param httpHeaders
* The current request HTTP headers.
* @return The {@link HeaderConstants#HEADER_AUTHORIZATION} header value.
* @throws IOException
*/
public static String formatResponse(ChallengeResponse challenge,
Request request, Series<Parameter> httpHeaders) {
String result = null;
AuthenticatorHelper helper = Engine.getInstance().findHelper(
challenge.getScheme(), true, false);
if (helper != null) {
result = helper.formatResponse(challenge, request, httpHeaders);
} else {
result = "?";
Context.getCurrentLogger().warning(
"Challenge scheme " + challenge.getScheme()
+ " not supported by the Restlet engine.");
}
return result;
}
/**
* Parses the "Authentication-Info" header.
*
* @param header
* The header value to parse.
* @return The equivalent {@link AuthenticationInfo} instance.
* @throws IOException
*/
public static AuthenticationInfo parseAuthenticationInfo(String header) {
AuthenticationInfo result = null;
ParameterReader hr = new ParameterReader(header);
try {
String nextNonce = null;
String qop = null;
String responseAuth = null;
String cnonce = null;
int nonceCount = 0;
Parameter param = hr.readValue();
while (param != null) {
try {
if ("nextnonce".equals(param.getName())) {
nextNonce = param.getValue();
} else if ("qop".equals(param.getName())) {
qop = param.getValue();
} else if ("rspauth".equals(param.getName())) {
responseAuth = param.getValue();
} else if ("cnonce".equals(param.getName())) {
cnonce = param.getValue();
} else if ("nc".equals(param.getName())) {
nonceCount = Integer.parseInt(param.getValue(), 16);
}
if (hr.skipValueSeparator()) {
param = hr.readValue();
} else {
param = null;
}
} catch (Exception e) {
Context.getCurrentLogger()
.log(Level.WARNING,
"Unable to parse the authentication info header parameter",
e);
}
}
result = new AuthenticationInfo(nextNonce, nonceCount, cnonce, qop,
responseAuth);
} catch (IOException e) {
Context.getCurrentLogger()
.log(Level.WARNING,
"Unable to parse the authentication info header: "
+ header, e);
}
return result;
}
/**
* Parses an authenticate header into a list of challenge request. The
* header is {@link HeaderConstants#HEADER_WWW_AUTHENTICATE}.
*
* @param header
* The HTTP header value to parse.
* @param httpHeaders
* The current response HTTP headers.
* @return The list of parsed challenge request.
*/
public static List<ChallengeRequest> parseRequest(Response response,
String header, Series<Parameter> httpHeaders) {
List<ChallengeRequest> result = new ArrayList<ChallengeRequest>();
if (header != null) {
result = new ChallengeRequestReader(header).readValues();
for (ChallengeRequest cr : result) {
// Give a chance to the authenticator helper to do further
// parsing
AuthenticatorHelper helper = Engine.getInstance().findHelper(
cr.getScheme(), true, false);
if (helper != null) {
helper.parseRequest(cr, response, httpHeaders);
} else {
Context.getCurrentLogger().warning(
"Couldn't find any helper support the "
+ cr.getScheme() + " challenge scheme.");
}
}
}
return result;
}
/**
* Parses an authorization header into a challenge response. The header is
* {@link HeaderConstants#HEADER_AUTHORIZATION}.
*
* @param request
* The parent request.
* @param header
* The authorization header.
* @param httpHeaders
* The current request HTTP headers.
* @return The parsed challenge response.
*/
public static ChallengeResponse parseResponse(Request request,
String header, Series<Parameter> httpHeaders) {
ChallengeResponse result = null;
if (header != null) {
int space = header.indexOf(' ');
if (space != -1) {
String scheme = header.substring(0, space);
String rawValue = header.substring(space + 1);
result = new ChallengeResponse(new ChallengeScheme("HTTP_"
+ scheme, scheme));
result.setRawValue(rawValue);
}
}
if (result != null) {
// Give a chance to the authenticator helper to do further parsing
AuthenticatorHelper helper = Engine.getInstance().findHelper(
result.getScheme(), true, false);
if (helper != null) {
helper.parseResponse(result, request, httpHeaders);
} else {
Context.getCurrentLogger().warning(
"Couldn't find any helper support the "
+ result.getScheme() + " challenge scheme.");
}
}
return result;
}
/**
* Updates a ChallengeResponse object according to given request and
* response.
*
* @param challengeResponse
* The challengeResponse to update.
* @param request
* The request.
* @param response
* The response.
*/
public static void update(ChallengeResponse challengeResponse,
Request request, Response response) {
ChallengeRequest challengeRequest = null;
for (ChallengeRequest c : response.getChallengeRequests()) {
if (challengeResponse.getScheme().equals(c.getScheme())) {
challengeRequest = c;
break;
}
}
String realm = null;
String nonce = null;
if (challengeRequest != null) {
realm = challengeRequest.getRealm();
nonce = challengeRequest.getServerNonce();
challengeResponse.setOpaque(challengeRequest.getOpaque());
}
challengeResponse.setRealm(realm);
challengeResponse.setServerNonce(nonce);
challengeResponse.setDigestRef(new Reference(request.getResourceRef()
.getPath()));
}
/**
* Updates a ChallengeResponse object according to given request and
* response and compute a new secret according to the response sent by the
* server.
*
* @param challengeResponse
* The challengeResponse to update.
* @param request
* The request if available.
* @param response
* The response if available.
* @param identifier
* The identifier.
* @param baseSecret
* The base secret used to compute the secret.
* @param baseSecretAlgorithm
* The digest algorithm of the base secret (@see {@link Digest}
* class).
*/
public static void update(ChallengeResponse challengeResponse,
Request request, Response response, String identifier,
char[] baseSecret, String baseSecretAlgorithm) {
update(challengeResponse, request, response);
// Compute the new secret.
final AuthenticatorHelper helper = Engine.getInstance().findHelper(
challengeResponse.getScheme(), false, true);
challengeResponse
.setSecret(helper.formatSecret(challengeResponse, request,
response, identifier, baseSecret, baseSecretAlgorithm));
}
/**
* Private constructor to ensure that the class acts as a true utility class
* i.e. it isn't instantiable and extensible.
*/
private AuthenticatorUtils() {
}
}