/**
* Copyright 2005-2014 Restlet
*
* The contents of this file are subject to the terms of one of the following
* open source licenses: Apache 2.0 or 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 Apache 2.0 license at
* http://www.opensource.org/licenses/apache-2.0
*
* You can obtain a copy of the EPL 1.0 license at
* http://www.opensource.org/licenses/eclipse-1.0
*
* 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://restlet.com/products/restlet-framework
*
* Restlet is a registered trademark of Restlet S.A.S.
*/
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.ClientInfo;
import org.restlet.data.Status;
import org.restlet.routing.Filter;
/**
* Filter authenticating the client sending the inbound request. Its main role
* is to inspect various credentials provided by the client and to add related
* application roles to the request's {@link ClientInfo} property.
*
* @author Jerome Louvel
*/
public abstract class Authenticator extends Filter {
/**
* Invoked upon successful authentication to update the subject with new
* principals.
*/
private volatile Enroler enroler;
/**
* Indicates if the authenticator should attempt to authenticate an already
* authenticated client. By default, it is set to true.
*/
private volatile boolean multiAuthenticating;
/**
* Indicates if the authenticator is not required to succeed. In those
* cases, the attached Restlet is invoked. Note that authentication will be
* attempted independently of this property unless the client is already
* authenticated and the {@link #isMultiAuthenticating()} prevents multiple
* authentications.
*/
private volatile boolean optional;
/**
* Constructor setting the mode to "required".
*
* @param context
* The context.
* @see #Authenticator(Context, boolean)
*/
public Authenticator(Context context) {
this(context, false);
}
/**
* Constructor using the context's default enroler.
*
* @param context
* The context.
* @param optional
* Indicates if the authenticator is not required to succeed.
* @see #Authenticator(Context, boolean, Enroler)
*/
public Authenticator(Context context, boolean optional) {
this(context, optional, (context != null) ? context.getDefaultEnroler()
: null);
}
/**
* Constructor.
*
* @param context
* The context.
* @param multiAuthenticating
* Indicates if the authenticator should attempt to authenticate
* an already authenticated client.
* @param optional
* Indicates if the authenticator is not required to succeed.
* @param enroler
* The enroler to invoke upon successful authentication.
*/
public Authenticator(Context context, boolean multiAuthenticating,
boolean optional, Enroler enroler) {
super(context);
this.multiAuthenticating = multiAuthenticating;
this.optional = optional;
this.enroler = enroler;
}
/**
* Constructor.
*
* @param context
* The context.
* @param optional
* Indicates if the authenticator is not required to succeed.
* @param enroler
* The enroler to invoke upon successful authentication.
*/
public Authenticator(Context context, boolean optional, Enroler enroler) {
this(context, true, optional, enroler);
}
/**
* Attempts to authenticate the subject sending the request.
*
* @param request
* The request sent.
* @param response
* The response to update.
* @return True if the authentication succeeded.
*/
protected abstract boolean authenticate(Request request, Response response);
/**
* Invoked upon successful authentication. By default, it updates the
* request's clientInfo and challengeResponse "authenticated" properties,
* clears the existing challenge requests on the response, calls the enroler
* and finally returns {@link Filter#CONTINUE}.
*
* @param request
* The request sent.
* @param response
* The response to update.
* @return The filter continuation code.
*/
protected int authenticated(Request request, Response response) {
boolean loggable = request.isLoggable()
&& getLogger().isLoggable(Level.FINE);
if (loggable && request.getChallengeResponse() != null) {
getLogger().log(
Level.FINE,
"The authentication succeeded for the identifer \""
+ request.getChallengeResponse().getIdentifier()
+ "\" using the "
+ request.getChallengeResponse().getScheme()
+ " scheme.");
}
// Update the client info accordingly
if (request.getClientInfo() != null) {
request.getClientInfo().setAuthenticated(true);
}
// Clear previous challenge requests
response.getChallengeRequests().clear();
// Add the roles for the authenticated subject
if (getEnroler() != null) {
getEnroler().enrole(request.getClientInfo());
}
return CONTINUE;
}
/**
* Handles the authentication by first invoking the
* {@link #authenticate(Request, Response)} method, only if
* {@link #isMultiAuthenticating()} returns true and if
* {@link ClientInfo#isAuthenticated()} returns false. If the method is
* invoked and returns true, the {@link #authenticated(Request, Response)}
* is called. Otherwise, if {@link #isOptional()} returns true it continues
* to the next Restlet or if it returns false it calls the
* {@link #unauthenticated(Request, Response)} method.
*/
@Override
protected int beforeHandle(Request request, Response response) {
if (isMultiAuthenticating()
|| !request.getClientInfo().isAuthenticated()) {
if (authenticate(request, response)) {
return authenticated(request, response);
} else if (isOptional()) {
response.setStatus(Status.SUCCESS_OK);
return CONTINUE;
} else {
return unauthenticated(request, response);
}
} else {
return CONTINUE;
}
}
/**
* Returns the enroler invoked upon successful authentication to update the
* subject with new principals. Typically new {@link Role} are added based
* on the available {@link User} instances available.
*
* @return The enroler invoked upon successful authentication
*/
public Enroler getEnroler() {
return enroler;
}
/**
* Indicates if the authenticator should attempt to authenticate an already
* authenticated client. The client is considered authenticated if
* {@link ClientInfo#isAuthenticated()} returns true. By default, it is set
* to true.
*
* @return True if the authenticator should attempt to authenticate an
* already authenticated client.
*/
public boolean isMultiAuthenticating() {
return multiAuthenticating;
}
/**
* Indicates if the authenticator is not required to succeed. In those
* cases, the attached Restlet is invoked. Note that authentication will be
* attempted independently of this property unless the client is already
* authenticated and the {@link #isMultiAuthenticating()} prevents multiple
* authentications.
*
* @return True if the authentication success is optional.
*/
public boolean isOptional() {
return optional;
}
/**
* Sets the enroler invoked upon successful authentication.
*
* @param enroler
* The enroler invoked upon successful authentication.
*/
public void setEnroler(Enroler enroler) {
this.enroler = enroler;
}
/**
* Indicates if the authenticator should attempt to authenticate an already
* authenticated client. The client is considered authenticated if
* {@link ClientInfo#isAuthenticated()} returns true. By default, it is set
* to true.
*
* @param multiAuthenticating
* True if the authenticator should attempt to authenticate an
* already authenticated client.
*/
public void setMultiAuthenticating(boolean multiAuthenticating) {
this.multiAuthenticating = multiAuthenticating;
}
/**
* Indicates if the authenticator is not required to succeed. In those
* cases, the attached Restlet is invoked. Note that authentication will be
* attempted independently of this property unless the client is already
* authenticated and the {@link #isMultiAuthenticating()} prevents multiple
* authentications.
*
* @param optional
* True if the authentication success is optional.
*/
public void setOptional(boolean optional) {
this.optional = optional;
}
/**
* Invoked upon failed authentication. By default, it updates the request's
* clientInfo and challengeResponse "authenticated" properties, and returns
* {@link Filter#STOP}.
*
* @param request
* The request sent.
* @param response
* The response to update.
* @return The filter continuation code.
*/
protected int unauthenticated(Request request, Response response) {
boolean loggable = request.isLoggable()
&& getLogger().isLoggable(Level.FINE);
if (request.getChallengeResponse() != null && loggable) {
getLogger().log(
Level.FINE,
"The authentication failed for the identifer \""
+ request.getChallengeResponse().getIdentifier()
+ "\" using the "
+ request.getChallengeResponse().getScheme()
+ " scheme.");
}
// Update the client info accordingly
if (request.getClientInfo() != null) {
request.getClientInfo().setAuthenticated(false);
}
// Stop the filtering chain
return STOP;
}
}