/**
* 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.security;
import java.util.logging.Level;
import org.restlet.Context;
import org.restlet.Request;
import org.restlet.Response;
import org.restlet.data.ChallengeRequest;
import org.restlet.data.ChallengeResponse;
import org.restlet.data.ChallengeScheme;
import org.restlet.data.ClientInfo;
import org.restlet.data.Status;
/**
* Authenticator based on a challenge scheme. This is typically used to support
* the HTTP BASIC and DIGEST challenge schemes.
*
* @see ChallengeScheme
* @see ChallengeRequest
* @see ChallengeResponse
* @see <a href="http://wiki.restlet.org/docs_2.1/112-restlet.html">User Guide -
* Authentication</a>
* @author Jerome Louvel
*/
public class ChallengeAuthenticator extends Authenticator {
/** The authentication realm. */
private volatile String realm;
/**
* Indicates if a new challenge should be sent when invalid credentials are
* received (true by default to conform to HTTP recommendations).
*/
private volatile boolean rechallenging;
/** The expected challenge scheme. */
private final ChallengeScheme scheme;
/** The credentials verifier. */
private volatile Verifier verifier;
/**
* Constructor using the context's default verifier.
*
* @param context
* The context.
* @param optional
* Indicates if the authentication success is optional.
* @param challengeScheme
* The authentication scheme to use.
* @param realm
* The authentication realm.
*
* @see #ChallengeAuthenticator(Context, boolean, ChallengeScheme, String,
* Verifier)
*/
public ChallengeAuthenticator(Context context, boolean optional,
ChallengeScheme challengeScheme, String realm) {
this(context, optional, challengeScheme, realm,
(context != null) ? context.getDefaultVerifier() : null);
}
/**
* Constructor.
*
* @param context
* The context.
* @param optional
* Indicates if the authentication success is optional.
* @param challengeScheme
* The authentication scheme to use.
* @param realm
* The authentication realm.
* @param verifier
* The credentials verifier.
*/
public ChallengeAuthenticator(Context context, boolean optional,
ChallengeScheme challengeScheme, String realm, Verifier verifier) {
super(context, optional);
this.realm = realm;
this.rechallenging = true;
this.scheme = challengeScheme;
this.verifier = verifier;
}
/**
* Constructor setting the optional property to false.
*
* @param context
* The context.
* @param challengeScheme
* The authentication scheme to use.
* @param realm
* The authentication realm.
* @see #ChallengeAuthenticator(Context, boolean, ChallengeScheme, String,
* Verifier)
*/
public ChallengeAuthenticator(Context context,
ChallengeScheme challengeScheme, String realm) {
this(context, false, challengeScheme, realm);
}
/**
* Authenticates the call, relying on the verifier to check the credentials
* provided (in general an identifier + secret couple). If the credentials
* are valid, the next Restlet attached is invoked.<br>
* <br>
* If the credentials are missing, then
* {@link #challenge(Response, boolean)} is invoked.<br>
* <br>
* If the credentials are invalid and if the "rechallenge" property is true
* then {@link #challenge(Response, boolean)} is invoked. Otherwise,
* {@link #forbid(Response)} is invoked.<br>
* <br>
* If the credentials are stale, then {@link #challenge(Response, boolean)}
* is invoked with the "stale" parameter to true.<br>
* <br>
* At the end of the process, the
* {@link ClientInfo#setAuthenticated(boolean)} method is invoked.
*/
@Override
protected boolean authenticate(Request request, Response response) {
boolean result = false;
boolean loggable = request.isLoggable()
&& getLogger().isLoggable(Level.FINE);
if (getVerifier() != null) {
switch (getVerifier().verify(request, response)) {
case Verifier.RESULT_VALID:
// Valid credentials provided
result = true;
if (loggable) {
ChallengeResponse challengeResponse = request
.getChallengeResponse();
if (challengeResponse != null) {
getLogger().fine(
"Authentication succeeded. Valid credentials provided for identifier: "
+ request.getChallengeResponse()
.getIdentifier() + ".");
} else {
getLogger()
.fine("Authentication succeeded. Valid credentials provided.");
}
}
break;
case Verifier.RESULT_MISSING:
// No credentials provided
if (loggable) {
getLogger().fine(
"Authentication failed. No credentials provided.");
}
if (!isOptional()) {
challenge(response, false);
}
break;
case Verifier.RESULT_INVALID:
// Invalid credentials provided
if (loggable) {
getLogger()
.fine("Authentication failed. Invalid credentials provided.");
}
if (!isOptional()) {
if (isRechallenging()) {
challenge(response, false);
} else {
forbid(response);
}
}
break;
case Verifier.RESULT_STALE:
if (loggable) {
getLogger()
.fine("Authentication failed. Stale credentials provided.");
}
if (!isOptional()) {
challenge(response, true);
}
break;
case Verifier.RESULT_UNKNOWN:
if (loggable) {
getLogger().fine(
"Authentication failed. Identifier is unknown.");
}
if (!isOptional()) {
if (isRechallenging()) {
challenge(response, false);
} else {
forbid(response);
}
}
break;
}
} else {
getLogger().warning("Authentication failed. No verifier provided.");
}
return result;
}
/**
* Challenges the client by adding a challenge request to the response and
* by setting the status to {@link Status#CLIENT_ERROR_UNAUTHORIZED}.
*
* @param response
* The response to update.
* @param stale
* Indicates if the new challenge is due to a stale response.
*/
public void challenge(Response response, boolean stale) {
response.setStatus(Status.CLIENT_ERROR_UNAUTHORIZED);
response.getChallengeRequests().add(createChallengeRequest(stale));
}
/**
* Creates a new challenge request.
*
* @param stale
* Indicates if the new challenge is due to a stale response.
* @return A new challenge request.
*/
protected ChallengeRequest createChallengeRequest(boolean stale) {
return new ChallengeRequest(getScheme(), getRealm());
}
/**
* Rejects the call due to a failed authentication or authorization. This
* can be overridden to change the default behavior, for example to display
* an error page. By default, if authentication is required, the challenge
* method is invoked, otherwise the call status is set to
* CLIENT_ERROR_FORBIDDEN.
*
* @param response
* The reject response.
*/
public void forbid(Response response) {
response.setStatus(Status.CLIENT_ERROR_FORBIDDEN);
}
/**
* Returns the authentication realm.
*
* @return The authentication realm.
*/
public String getRealm() {
return this.realm;
}
/**
* Returns the authentication challenge scheme.
*
* @return The authentication challenge scheme.
*/
public ChallengeScheme getScheme() {
return scheme;
}
/**
* Returns the credentials verifier.
*
* @return The credentials verifier.
*/
public Verifier getVerifier() {
return verifier;
}
/**
* Indicates if a new challenge should be sent when invalid credentials are
* received (true by default to conform to HTTP recommendations). If set to
* false, upon reception of invalid credentials, the method
* {@link #forbid(Response)} will be called.
*
* @return True if invalid credentials result in a new challenge.
*/
public boolean isRechallenging() {
return this.rechallenging;
}
/**
* Sets the authentication realm.
*
* @param realm
* The authentication realm.
*/
public void setRealm(String realm) {
this.realm = realm;
}
/**
* Indicates if a new challenge should be sent when invalid credentials are
* received.
*
* @param rechallenging
* True if invalid credentials result in a new challenge.
* @see #isRechallenging()
*/
public void setRechallenging(boolean rechallenging) {
this.rechallenging = rechallenging;
}
/**
* Sets the credentials verifier.
*
* @param verifier
* The credentials verifier.
*/
public void setVerifier(Verifier verifier) {
this.verifier = verifier;
}
}