com.rapidclipse.framework.security.authentication.ChainingAuthenticator Maven / Gradle / Ivy
Show all versions of rap-security-auth Show documentation
/*
* Copyright (C) 2013-2023 by XDEV Software, All Rights Reserved.
*
* This file is part of the RapidClipse Application Platform (RAP).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see .
*
* SPDX-License-Identifier: AGPL-3.0-or-later
*
* Contributors:
* XDEV Software - initial API and implementation
*/
package com.rapidclipse.framework.security.authentication;
import static java.util.Objects.requireNonNull;
/**
* Trivial {@link Authenticator} chaining implementation.
*
* See {@link Authenticator#then(Authenticator)}.
*
* @author XDEV Software (TM)
*
* @param
* the type of the credentials instance to be authenticated.
* @param
* the type of the result/response instance to be returned upon an
* authentication attempt.
*/
public final class ChainingAuthenticator implements Authenticator
{
///////////////////////////////////////////////////////////////////////////
// static methods //
///////////////////
/**
* Constructor method that does validation and implementation detail
* encapsulation of the actual constructor. Both passed
* {@link Authenticator} instance may not be null.
*
* Not that while the first authenticator is always evaluated first, any
* {@link AuthenticationFailedException} it might throw will get swallowed
* intentionally to allow the second {@link Authenticator} to be evaluated
* as well.
*
* @param first
* the first or "primary" {@link Authenticator} instance.
* @param second
* the second or "fallback" {@link Authenticator} instance.
* @return a new {@link ChainingAuthenticator} instance wrapping the two
* passed {@link Authenticator} instances.
*/
public static final ChainingAuthenticator New(
final Authenticator first,
final Authenticator second)
{
return new ChainingAuthenticator<>(requireNonNull(first), requireNonNull(second));
}
///////////////////////////////////////////////////////////////////////////
// instance fields //
////////////////////
/**
* The first or "primary" {@link Authenticator} instance to be used in
* {@link #authenticate(Object)}.
*/
private final Authenticator first;
/**
* The second or "fallback" {@link Authenticator} instance to be used in
* {@link #authenticate(Object)}.
*/
private final Authenticator second;
///////////////////////////////////////////////////////////////////////////
// constructors //
/////////////////
/**
* Constructor might change in the future, so it gets hidden as the
* implementation detail it is.
*
* @param first
* the first or "primary" {@link Authenticator} instance.
* @param second
* the second or "fallback" {@link Authenticator} instance.
*/
ChainingAuthenticator(final Authenticator first, final Authenticator second)
{
super();
this.first = first;
this.second = second;
}
///////////////////////////////////////////////////////////////////////////
// override methods //
/////////////////////
/**
* Tries to authenticate the credentials with the first wrapped
* authenticator. If and only if the first attempt fails, the second wrapped
* authenticator is used for a second authentication attempt.
*
* The first attempt is assumed to have failed if any of the following
* conditions are met:
*
* - returned value of null
* - returned value equal to {@link Boolean#FALSE}
* - an {@link AuthenticationFailedException} occured.
*
*
* @param credentials
* the credentials to be used in the authentication attempt.
* @return either the result of an successful first attempt or whatever the
* second attempt returns.
*
* @throws AuthenticationFailedException
* if the second authenticator throws one.
*/
@Override
public final R authenticate(final C credentials) throws AuthenticationFailedException
{
/*
* There are three cases in which the second authentication attempt must
* be made: 1.) The first authenticator returns null Meaning no actual
* result instance could have been created. If the authenticator is
* properly used, is means the authencation has failed 2.) The first
* authenticator returns false This means the authenticator simply uses
* true/false and returns false for a failed attempt. It is kind of
* unclean to cover this case here in a generic piece of logic. However
* as the equals method is safe for such a case and the error
* probability is almost zero (i.e. it is hardly conceivable that an
* authenticator would return false for a successful authentication) it
* is still okay to cover this case in here. The alternative, not
* covering it, would mean that this chaining implementation could not
* be used for authenticators using a simple Boolean. 3.) A
* busines-logical AuthenticationFailedException occured in the first
* exception. Swallowing this exception is okay (more precisely:
* desired) as the second authenticator has the final say, anyway.
*
* If the assumptions made by this implementation prove to cause
* problems in exotic corner cases, it can always be replaced by a
* custom implemented implementation. The required code is rather tiny
* and trivial.
*/
try
{
// first attempt to authenticate the passed credentials
final R firstResult = this.first.authenticate(credentials);
if(firstResult != null && !Boolean.FALSE.equals(firstResult))
{
return firstResult;
}
// otherwise (null or FALSE), fall through to second attempt
}
catch(final AuthenticationFailedException e)
{
// for a business-logical failed authentication, fall through to the
// second attempt.
}
catch(final Throwable t)
{
// pass through any other throwable
throw t;
}
// second attempt (with "final say" characteristics). Returns whatever
// the second authenticator returns
return this.second.authenticate(credentials);
}
}