Skip to content

Commit

Permalink
Remove the ability to compute login urls directly
Browse files Browse the repository at this point in the history
  • Loading branch information
leleuj committed Jan 8, 2016
1 parent b49a720 commit 4ad705e
Show file tree
Hide file tree
Showing 32 changed files with 108 additions and 284 deletions.
5 changes: 0 additions & 5 deletions pac4j-cas/src/main/java/org/pac4j/cas/client/CasClient.java
Expand Up @@ -435,11 +435,6 @@ public String toString() {
"allowedProxyChains", this.allowedProxyChains, "casProxyReceptor", this.casProxyReceptor); "allowedProxyChains", this.allowedProxyChains, "casProxyReceptor", this.casProxyReceptor);
} }


@Override
protected boolean isDirectRedirection() {
return true;
}

@Override @Override
public ClientType getClientType() { public ClientType getClientType() {
return ClientType.CAS_PROTOCOL; return ClientType.CAS_PROTOCOL;
Expand Down
Expand Up @@ -148,11 +148,6 @@ protected CasProfile retrieveUserProfile(final CasCredentials credentials, final
throw new TechnicalException("Not supported by the CAS proxy receptor"); throw new TechnicalException("Not supported by the CAS proxy receptor");
} }


@Override
protected boolean isDirectRedirection() {
return true;
}

@Override @Override
public ClientType getClientType() { public ClientType getClientType() {
return ClientType.CAS_PROTOCOL; return ClientType.CAS_PROTOCOL;
Expand Down
Expand Up @@ -73,12 +73,12 @@ public void testRenew() throws RequiresHttpAction {
casClient.setCallbackUrl(CALLBACK_URL); casClient.setCallbackUrl(CALLBACK_URL);
casClient.setCasLoginUrl(LOGIN_URL); casClient.setCasLoginUrl(LOGIN_URL);
MockWebContext context = MockWebContext.create(); MockWebContext context = MockWebContext.create();
casClient.redirect(context, false); casClient.redirect(context);
assertFalse(context.getResponseLocation().indexOf("renew=true") >= 0); assertFalse(context.getResponseLocation().indexOf("renew=true") >= 0);
casClient.setRenew(true); casClient.setRenew(true);
casClient.reinit(null); casClient.reinit(null);
context = MockWebContext.create(); context = MockWebContext.create();
casClient.redirect(context, false); casClient.redirect(context);
assertTrue(context.getResponseLocation().indexOf("renew=true") >= 0); assertTrue(context.getResponseLocation().indexOf("renew=true") >= 0);
} }


Expand All @@ -87,11 +87,11 @@ public void testGateway() throws RequiresHttpAction {
casClient.setCallbackUrl(CALLBACK_URL); casClient.setCallbackUrl(CALLBACK_URL);
casClient.setCasLoginUrl(LOGIN_URL); casClient.setCasLoginUrl(LOGIN_URL);
final MockWebContext context = MockWebContext.create(); final MockWebContext context = MockWebContext.create();
casClient.redirect(context, false); casClient.redirect(context);
assertFalse(context.getResponseLocation().indexOf("gateway=true") >= 0); assertFalse(context.getResponseLocation().indexOf("gateway=true") >= 0);
casClient.setGateway(true); casClient.setGateway(true);
casClient.reinit(null); casClient.reinit(null);
casClient.redirect(context, false); casClient.redirect(context);
assertTrue(context.getResponseLocation().indexOf("gateway=true") >= 0); assertTrue(context.getResponseLocation().indexOf("gateway=true") >= 0);
final CasCredentials credentials = casClient.getCredentials(context); final CasCredentials credentials = casClient.getCredentials(context);
assertNull(credentials); assertNull(credentials);
Expand Down
Expand Up @@ -29,7 +29,7 @@
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;


/** /**
* <p>This class is the default implementation of an authentication client (whatever the protocol). It has the core concepts:</p> * <p>This class is the default implementation of an authentication client (whatever the mechanism). It has the core concepts:</p>
* <ul> * <ul>
* <li>The initialization process is handled by the {@link InitializableWebObject} inheritance, the {@link #internalInit(WebContext)} must be implemented * <li>The initialization process is handled by the {@link InitializableWebObject} inheritance, the {@link #internalInit(WebContext)} must be implemented
* in sub-classes. The {@link #init(WebContext)} method must be called implicitly by the main methods of the {@link Client} interface, so that no explicit call is * in sub-classes. The {@link #init(WebContext)} method must be called implicitly by the main methods of the {@link Client} interface, so that no explicit call is
Expand Down Expand Up @@ -85,9 +85,6 @@ public String getName() {
return this.name; return this.name;
} }


/**
* {@inheritDoc}
*/
@Override @Override
public final U getUserProfile(final C credentials, final WebContext context) { public final U getUserProfile(final C credentials, final WebContext context) {
init(context); init(context);
Expand Down
17 changes: 8 additions & 9 deletions pac4j-core/src/main/java/org/pac4j/core/client/Client.java
Expand Up @@ -21,16 +21,16 @@
import org.pac4j.core.profile.UserProfile; import org.pac4j.core.profile.UserProfile;


/** /**
* <p>This interface is the core of <code>pac4j</code>. It represents an authentication mechanism to validate user's credentials and * <p>This interface is the core class of the library. It represents an authentication mechanism to validate user's credentials and
* retrieve the user profile.</p> * retrieve his user profile.</p>
* <p>Clients can be "indirect": in that case, credentials are not provided with the HTTP request, but the user must be redirected to * <p>Clients can be "indirect": in that case, credentials are not provided with the HTTP request, but the user must be redirected to
* an identity provider to perform login, the original requested url being saved and restored after the authentication process is done.</p> * an identity provider to perform login, the original requested url being saved and restored after the authentication process is done.</p>
* <p>The {@link #redirect(WebContext, boolean)} method is called to redirect the user to the identity provider, * <p>The {@link #redirect(WebContext)} method is called to redirect the user to the identity provider,
* the {@link #getCredentials(WebContext)} method is used to retrieve the credentials provided by the remote identity provider and * the {@link #getCredentials(WebContext)} method is used to retrieve the credentials provided by the remote identity provider and
* the {@link #getUserProfile(Credentials, WebContext)} method is called to get the user profile from the identity provider and based * the {@link #getUserProfile(Credentials, WebContext)} method is called to get the user profile from the identity provider and based
* on the provided credentials.</p> * on the provided credentials.</p>
* <p>Clients can be "direct": in that case, credentials are provided along with the HTTP request and validated by the application.</p> * <p>Clients can be "direct": in that case, credentials are provided along with the HTTP request and validated by the application.</p>
* <p>The {@link #redirect(WebContext, boolean)} method is not used, the {@link #getCredentials(WebContext)} method is used to retrieve * <p>The {@link #redirect(WebContext)} method is not used, the {@link #getCredentials(WebContext)} method is used to retrieve
* and validate the credentials provided and the {@link #getUserProfile(Credentials, WebContext)} method is called to get the user profile from * and validate the credentials provided and the {@link #getUserProfile(Credentials, WebContext)} method is called to get the user profile from
* the appropriate system.</p> * the appropriate system.</p>
* *
Expand All @@ -44,16 +44,15 @@ public interface Client<C extends Credentials, U extends UserProfile> {
* *
* @return the name of the client * @return the name of the client
*/ */
public String getName(); String getName();


/** /**
* <p>Redirect to the authentication provider for an indirect client.</p> * <p>Redirect to the authentication provider for an indirect client.</p>
* *
* @param context the current web context * @param context the current web context
* @param protectedTarget whether the target url is protected
* @throws RequiresHttpAction whether an additional HTTP action is required * @throws RequiresHttpAction whether an additional HTTP action is required
*/ */
public void redirect(WebContext context, boolean protectedTarget) throws RequiresHttpAction; void redirect(WebContext context) throws RequiresHttpAction;


/** /**
* <p>Get the credentials from the web context. If no validation was made remotely (direct client), credentials must be validated at this step.</p> * <p>Get the credentials from the web context. If no validation was made remotely (direct client), credentials must be validated at this step.</p>
Expand All @@ -63,7 +62,7 @@ public interface Client<C extends Credentials, U extends UserProfile> {
* @return the credentials * @return the credentials
* @throws RequiresHttpAction whether an additional HTTP action is required * @throws RequiresHttpAction whether an additional HTTP action is required
*/ */
public C getCredentials(WebContext context) throws RequiresHttpAction; C getCredentials(WebContext context) throws RequiresHttpAction;


/** /**
* Get the user profile based on the provided credentials. * Get the user profile based on the provided credentials.
Expand All @@ -72,5 +71,5 @@ public interface Client<C extends Credentials, U extends UserProfile> {
* @param context web context * @param context web context
* @return the user profile * @return the user profile
*/ */
public U getUserProfile(C credentials, WebContext context); U getUserProfile(C credentials, WebContext context);
} }
Expand Up @@ -204,7 +204,7 @@ public List<Client> getClients() {


@Override @Override
public String toString() { public String toString() {
return CommonHelper.toString(this.getClass(), "callbackUrl", this.callbackUrl, "clientTypeParameter", return CommonHelper.toString(this.getClass(), "callbackUrl", this.callbackUrl, "clientNameParameter",
this.clientNameParameter, "clients", getClients()); this.clientNameParameter, "clients", getClients());
} }
} }
Expand Up @@ -21,16 +21,16 @@
import org.pac4j.core.profile.CommonProfile; import org.pac4j.core.profile.CommonProfile;


/** /**
* <p>This class is the default direct (stateless) implementation of an authentication client (whatever the protocol). * <p>This class is the default direct (stateless) implementation of an authentication client (whatever the mechanism).
* In that case, redirecting does not have any sense.</p> * In that case, redirecting does not make any sense.</p>
* *
* @author Jerome Leleu * @author Jerome Leleu
* @since 1.8.0 * @since 1.8.0
*/ */
public abstract class DirectClient<C extends Credentials, U extends CommonProfile> extends BaseClient<C, U> { public abstract class DirectClient<C extends Credentials, U extends CommonProfile> extends BaseClient<C, U> {


@Override @Override
public final void redirect(final WebContext context, final boolean protectedTarget) { public final void redirect(final WebContext context) {
throw new TechnicalException("direct clients do not support redirections"); throw new TechnicalException("direct clients do not support redirections");
} }
} }
124 changes: 33 additions & 91 deletions pac4j-core/src/main/java/org/pac4j/core/client/IndirectClient.java
Expand Up @@ -29,26 +29,15 @@
import org.pac4j.core.util.CommonHelper; import org.pac4j.core.util.CommonHelper;


/** /**
* <p>This class is the default indirect (with redirection, stateful) implementation of an authentication client (whatever the protocol). * <p>This class is the default indirect (with redirection, stateful) implementation of an authentication client (whatever the mechanism).</p>
* It has the core concepts:</p> * <p>The callback url is managed via the {@link #setCallbackUrl(String)} and {@link #getCallbackUrl()} methods. The way the callback url
* <ul> * is finally computed is done by the {@link #callbackUrlResolver} which returns by default the provided {@link #callbackUrl}.</p>
* <li>The callback url is handled through the {@link #setCallbackUrl(String)} and {@link #getCallbackUrl()} methods</li>
* <li>The concept of "direct" redirection is defined through the {@link #isDirectRedirection()} method: if true, the
* {@link #redirect(WebContext, boolean)} method will always return the redirection to the provider where as if it's false, the
* redirection url will be the callback url with an additional parameter: {@link #NEEDS_CLIENT_REDIRECTION_PARAMETER} to require the
* redirection, which will be handled <b>later</b> in the {@link #getCredentials(WebContext)} method.
* To force a direct redirection, the {@link #getRedirectAction(WebContext, boolean)} must be used with <code>true</code> for the
* <code>protectedTarget</code> parameter</li>
* <li>The way the callback url is finally computed is handled by the {@link #callbackUrlResolver} which is by default the provided {@link #callbackUrl}.</li>
* </ul>
* *
* @author Jerome Leleu * @author Jerome Leleu
* @since 1.8.0 * @since 1.8.0
*/ */
public abstract class IndirectClient<C extends Credentials, U extends CommonProfile> extends BaseClient<C, U> { public abstract class IndirectClient<C extends Credentials, U extends CommonProfile> extends BaseClient<C, U> {


public final static String NEEDS_CLIENT_REDIRECTION_PARAMETER = "needs_client_redirection";

public final static String ATTEMPTED_AUTHENTICATION_SUFFIX = "$attemptedAuthentication"; public final static String ATTEMPTED_AUTHENTICATION_SUFFIX = "$attemptedAuthentication";


protected String callbackUrl; protected String callbackUrl;
Expand All @@ -59,30 +48,10 @@ public abstract class IndirectClient<C extends Credentials, U extends CommonProf


protected CallbackUrlResolver callbackUrlResolver = new DefaultCallbackUrlResolver(); protected CallbackUrlResolver callbackUrlResolver = new DefaultCallbackUrlResolver();


/**
* Define if this client has a direct redirection.
*
* @return if this client has a direct redirection
*/
protected abstract boolean isDirectRedirection();

/**
* <p>Redirect to the authentication provider by updating the WebContext accordingly.</p>
* <p>Though, if this client requires an indirect redirection, it will return a redirection to the callback url (with an additionnal parameter requesting a
* redirection). Whatever the kind of client's redirection, the <code>protectedTarget</code> parameter set to <code>true</code> enforces
* a direct redirection.
* <p>If an authentication has already been tried for this client and has failed (previous <code>null</code> credentials) and if the target
* is protected (<code>protectedTarget</code> set to <code>true</code>), a forbidden response (403 HTTP status code) is returned.</p>
* <p>If the request is an AJAX one, an authorized response (401 HTTP status code) is returned instead of a redirection.</p>
*
* @param context the current web context
* @param protectedTarget whether the target url is protected
* @throws RequiresHttpAction whether an additional HTTP action is required
*/
@Override @Override
public final void redirect(final WebContext context, final boolean protectedTarget) public final void redirect(final WebContext context)
throws RequiresHttpAction { throws RequiresHttpAction {
final RedirectAction action = getRedirectAction(context, protectedTarget); final RedirectAction action = getRedirectAction(context);
if (action.getType() == RedirectType.REDIRECT) { if (action.getType() == RedirectType.REDIRECT) {
context.setResponseStatus(HttpConstants.TEMP_REDIRECT); context.setResponseStatus(HttpConstants.TEMP_REDIRECT);
context.setResponseHeader(HttpConstants.LOCATION_HEADER, action.getLocation()); context.setResponseHeader(HttpConstants.LOCATION_HEADER, action.getLocation());
Expand All @@ -93,41 +62,32 @@ public final void redirect(final WebContext context, final boolean protectedTarg
} }


/** /**
* Get the redirectAction computed for this client. All the logic is encapsulated here. It should not be called be directly, the * <p>Get the redirectAction computed for this client. All the logic is encapsulated here. It should not be called be directly, the
* {@link #redirect(WebContext, boolean)} should be generally called instead. * {@link #redirect(WebContext)} should be generally called instead.</p>
* * <p>If an authentication has already been tried for this client and has failed (<code>null</code> credentials), a forbidden response (403 HTTP status code) is returned.</p>
* <p>If the request is an AJAX one, an authorized response (401 HTTP status code) is returned instead of a redirection.</p>
*
* @param context context * @param context context
* @param protectedTarget requires authentication
* @return the redirection action * @return the redirection action
* @throws RequiresHttpAction requires an additional HTTP action * @throws RequiresHttpAction requires an additional HTTP action
*/ */
public final RedirectAction getRedirectAction(final WebContext context, final boolean protectedTarget) throws RequiresHttpAction { public final RedirectAction getRedirectAction(final WebContext context) throws RequiresHttpAction {
// it's an AJAX request -> unauthorized (instead of a redirection) // it's an AJAX request -> unauthorized (instead of a redirection)
if (ajaxRequestResolver.isAjax(context)) { if (ajaxRequestResolver.isAjax(context)) {
logger.info("AJAX request detected -> returning 401"); logger.info("AJAX request detected -> returning 401");
cleanRequestedUrl(context); cleanRequestedUrl(context);
throw RequiresHttpAction.unauthorized("AJAX request -> 401", context, null); throw RequiresHttpAction.unauthorized("AJAX request -> 401", context, null);
} }
// authentication has already been tried // authentication has already been tried -> forbidden
final String attemptedAuth = (String) context.getSessionAttribute(getName() + ATTEMPTED_AUTHENTICATION_SUFFIX); final String attemptedAuth = (String) context.getSessionAttribute(getName() + ATTEMPTED_AUTHENTICATION_SUFFIX);
if (CommonHelper.isNotBlank(attemptedAuth)) { if (CommonHelper.isNotBlank(attemptedAuth)) {
cleanAttemptedAuthentication(context); cleanAttemptedAuthentication(context);
// protected target -> forbidden cleanRequestedUrl(context);
if (protectedTarget) { throw RequiresHttpAction.forbidden("authentication already tried -> forbidden", context);
cleanRequestedUrl(context);
throw RequiresHttpAction.forbidden("authentication already tried -> forbidden", context);
}
}
// it's a direct redirection or force the redirection because the target is protected -> return the real redirection
if (isDirectRedirection() || protectedTarget) {
init(context);
return retrieveRedirectAction(context);
} else {
// return an intermediate url which is the callback url with a specific parameter requiring redirection
final String intermediateUrl = CommonHelper.addParameter(computeFinalCallbackUrl(context),
NEEDS_CLIENT_REDIRECTION_PARAMETER, "true");
return RedirectAction.redirect(intermediateUrl);
} }

init(context);
return retrieveRedirectAction(context);
} }


private void cleanRequestedUrl(final WebContext context) { private void cleanRequestedUrl(final WebContext context) {
Expand All @@ -143,26 +103,16 @@ public String computeFinalCallbackUrl(final WebContext context) {
} }


/** /**
* Return the redirection url to the provider, requested from an anonymous page. * Retrieve the redirect action.
* *
* @param context the current web context * @param context the web context
* @return the redirection url to the provider. * @return the redirection action
*/ */
public String getRedirectionUrl(final WebContext context) {
try {
return getRedirectAction(context, false).getLocation();
} catch (final RequiresHttpAction e) {
return null;
}
}

protected abstract RedirectAction retrieveRedirectAction(final WebContext context); protected abstract RedirectAction retrieveRedirectAction(final WebContext context);


/** /**
* <p>Get the credentials from the web context. In some cases, a {@link RequiresHttpAction} may be thrown instead:</p> * <p>Get the credentials from the web context. In some cases, a {@link RequiresHttpAction} may be thrown:</p>
* <ul> * <ul>
* <li>if this client requires an indirect redirection, the redirection will be actually performed by these method and not by the
* {@link #redirect(WebContext, boolean)} one (302 HTTP status code)</li>
* <li>if the <code>CasClient</code> receives a logout request, it returns a 200 HTTP status code</li> * <li>if the <code>CasClient</code> receives a logout request, it returns a 200 HTTP status code</li>
* <li>for the <code>IndirectBasicAuthClient</code>, if no credentials are sent to the callback url, an unauthorized response (401 HTTP status * <li>for the <code>IndirectBasicAuthClient</code>, if no credentials are sent to the callback url, an unauthorized response (401 HTTP status
* code) is returned to request credentials through a popup.</li> * code) is returned to request credentials through a popup.</li>
Expand All @@ -175,30 +125,22 @@ public String getRedirectionUrl(final WebContext context) {
@Override @Override
public final C getCredentials(final WebContext context) throws RequiresHttpAction { public final C getCredentials(final WebContext context) throws RequiresHttpAction {
init(context); init(context);
final String value = context.getRequestParameter(NEEDS_CLIENT_REDIRECTION_PARAMETER); final C credentials = retrieveCredentials(context);
// needs redirection -> return the redirection url // no credentials -> save this authentication has already been tried and failed
if (CommonHelper.isNotBlank(value)) { if (credentials == null) {
final RedirectAction action = retrieveRedirectAction(context); context.setSessionAttribute(getName() + ATTEMPTED_AUTHENTICATION_SUFFIX, "true");
final String message = "Needs client redirection";
if (action.getType() == RedirectType.SUCCESS) {
throw RequiresHttpAction.ok(message, context, action.getContent());
} else {
// it's a redirect
throw RequiresHttpAction.redirect(message, context, action.getLocation());
}
} else { } else {
// else get the credentials cleanAttemptedAuthentication(context);
final C credentials = retrieveCredentials(context);
// no credentials -> save this authentication has already been tried and failed
if (credentials == null) {
context.setSessionAttribute(getName() + ATTEMPTED_AUTHENTICATION_SUFFIX, "true");
} else {
cleanAttemptedAuthentication(context);
}
return credentials;
} }
return credentials;
} }


/**
* Retrieve the credentials.
*
* @param context the web context
* @return the credentials.
*/
protected abstract C retrieveCredentials(final WebContext context) throws RequiresHttpAction; protected abstract C retrieveCredentials(final WebContext context) throws RequiresHttpAction;


/** /**
Expand Down
3 changes: 1 addition & 2 deletions pac4j-core/src/test/java/org/pac4j/core/client/ClientIT.java
Expand Up @@ -166,8 +166,7 @@ protected boolean isJavascriptEnabled() {
protected HtmlPage getRedirectionPage(final WebClient webClient, final Client<?, ?> client, final J2EContext context) protected HtmlPage getRedirectionPage(final WebClient webClient, final Client<?, ?> client, final J2EContext context)
throws Exception { throws Exception {
final BaseClient baseClient = (BaseClient) client; final BaseClient baseClient = (BaseClient) client;
// force immediate redirection for tests baseClient.redirect(context);
baseClient.redirect(context, true);
final String redirectionUrl = context.getResponse().getHeader(HttpConstants.LOCATION_HEADER); final String redirectionUrl = context.getResponse().getHeader(HttpConstants.LOCATION_HEADER);
logger.debug("redirectionUrl : {}", redirectionUrl); logger.debug("redirectionUrl : {}", redirectionUrl);
final HtmlPage loginPage = webClient.getPage(redirectionUrl); final HtmlPage loginPage = webClient.getPage(redirectionUrl);
Expand Down

0 comments on commit 4ad705e

Please sign in to comment.