Open dhulipudi opened 7 months ago
import java.io.IOException; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.stream.Stream;
import javax.jcr.LoginException; import javax.jcr.RepositoryException; import javax.jcr.Session; import javax.jcr.SimpleCredentials; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse;
import org.apache.commons.lang3.StringUtils; import org.apache.jackrabbit.api.security.authentication.token.TokenCredentials; import org.apache.sling.auth.core.spi.AuthenticationFeedbackHandler; import org.apache.sling.auth.core.spi.AuthenticationHandler; import org.apache.sling.auth.core.spi.AuthenticationInfo; import org.apache.sling.auth.core.spi.DefaultAuthenticationFeedbackHandler; import org.apache.sling.jcr.api.SlingRepository; import org.apache.sling.auth.core.AuthUtil; import org.osgi.service.component.annotations.Activate; import org.osgi.service.component.annotations.Component; import org.osgi.service.component.annotations.Reference; import org.osgi.service.metatype.annotations.AttributeDefinition; import org.osgi.service.metatype.annotations.Designate; import org.osgi.service.metatype.annotations.ObjectClassDefinition; import org.slf4j.Logger; import org.slf4j.LoggerFactory;
import com.day.crx.security.token.TokenCookie;
import static org.apache.sling.auth.core.spi.AuthenticationHandler.TYPE_PROPERTY; import static org.apache.sling.auth.core.spi.AuthenticationHandler.PATH_PROPERTY; import static org.osgi.framework.Constants.SERVICE_DESCRIPTION; import static org.osgi.framework.Constants.SERVICE_RANKING; import static org.osgi.service.component.annotations.ReferenceCardinality.MULTIPLE; import static org.osgi.service.component.annotations.ReferencePolicy.DYNAMIC; import static org.apache.commons.lang3.StringUtils.EMPTY;
/**
*/ @Designate(ocd = WFAuthenticationHandler.Config.class)
@Component(service = { AuthenticationHandler.class, AuthenticationFeedbackHandler.class }, // configurationPolicy = REQUIRE, property = { PATH_PROPERTY + "=/content", TYPE_PROPERTY + "= WF_OAUTH", SERVICE_DESCRIPTION + "= WellsFargo Federated Authenticator", SERVICE_RANKING + ":Integer=80000" }) public class WFAuthenticationHandler extends DefaultAuthenticationFeedbackHandler implements AuthenticationHandler {
private static final Logger LOGGER = LoggerFactory.getLogger(WFAuthenticationHandler.class);
@ObjectClassDefinition(name = "CWA AuthenticationHandler Configuration", description = "Configuration for CWA AuthenticationHandler.")
public @interface Config {
@AttributeDefinition(name = "WF AuthenticationHandler Paths")
String[] path() default { "/content/brandcentral" };
@AttributeDefinition(name = "Auth Request Url")
String cwaAuthRequestUrl() default "";
@AttributeDefinition(name = "Callback Url")
String cwaCallbackUrl() default "";
@AttributeDefinition(name = "SessionManager Callback Url")
String sessionManagerCallbackUrl() default "";
@AttributeDefinition(name = "SessionManager Logout Url")
String sessionManagerLogoutUrl() default "";
@AttributeDefinition(name = "SessionManager UserInfo Url")
String sessionManagerUserInfoUrl() default "";
}
private Config config;
private final String REQUEST_METHOD = "GET";
private final String USER_NAME = "j_username";
private final String PASSWORD = "j_password";
private static final String TOKEN_ID = ".token";
private final ConcurrentMap<String, WFLoginCallback> callbacks = new ConcurrentHashMap<>();
@Reference SlingRepository repository;
/**
* This method is used to extract credentials from the request.
* @param request This is the first parameter to extractCredentials method
* @param response This is the second parameter to extractCredentials method
* @return AuthenticationInfo This returns an instance of AuthenticationInfo.
*/
@Override
public AuthenticationInfo extractCredentials(HttpServletRequest request, HttpServletResponse response) {
LOGGER.info("extractCredentials");
AuthenticationInfo authenticationInfo = null;
String j_username = request.getParameter(USER_NAME);
String j_password = request.getParameter(PASSWORD);
if ((request.getRequestURI().contains("j_security_check"))) {
try {
authenticationInfo = basicAuth(request, response, j_username, j_password);
} catch (LoginException ex) {
LOGGER.error(ex.getMessage());
return null;
}
}
return authenticationInfo;
}
private AuthenticationInfo basicAuth(HttpServletRequest request, HttpServletResponse response,
String userName, String password) throws LoginException {
AuthenticationInfo authInfo = null;
if ((userName != null && password != null)) {
authInfo = new AuthenticationInfo("TOKEN", userName);
SimpleCredentials creds = new SimpleCredentials(userName,
password.toCharArray());
Session session = null;
try {
session = this.repository.login(creds);
this.repository.login(creds);
if (session != null) {
// create a new AuthenticationInfo object that makes use of the standard
// AEM token authentication
authInfo = new AuthenticationInfo("TOKEN",
userName);
// and we set a dummy token which will be updated
creds.setAttribute(TOKEN_ID, "testtoken");
// log in using the credentials to record the log in event
repository.login(creds);
// use the associated session to create TokenCredentials
TokenCredentials tc = new TokenCredentials((String) creds.getAttribute(TOKEN_ID));
// and set the token-credentials in authenticationInfo object
authInfo.put("user.jcr.credentials", tc);
// set or update login token cookie
String repoId = repository.getDescriptor("crx.cluster.id");
// TokenCookie.update(request, response, repoId, tc.getToken(),
// repository.getDefaultWorkspace(), true);
updateTokenCookie(request, response, repoId, tc, repository);
if (session.isLive()) {
session.logout();
}
LOGGER.info("authInfo:{}", authInfo);
return authInfo;
}
} catch (LoginException e) {
LOGGER.error(
this.getClass().getName() + " extractCredentials(..) Failed to log user in" + e.getMessage(),
e);
e.printStackTrace();
} catch (RepositoryException e) {
LOGGER.error(
this.getClass().getName() + " extractCredentials(..) Failed to log user in" + e.getMessage(),
e);
}
}
return authInfo;
}
// Helper method to update the token cookie, to make this unit testable
public void updateTokenCookie(HttpServletRequest request, HttpServletResponse response, String repoId, TokenCredentials tc, SlingRepository repository) {
TokenCookie.update(request, response, repoId, tc.getToken(),
repository.getDefaultWorkspace(), true);
}
@Override
public boolean requestCredentials(HttpServletRequest request, HttpServletResponse response) throws IOException {
return false;
}
@Override
public void dropCredentials(HttpServletRequest request, HttpServletResponse response) throws IOException {
String appId = request.getParameter("appId");
LOGGER.info("dropCredentials for appId: [{}]", appId);
if (StringUtils.isNotEmpty(appId)) {
if (Boolean.parseBoolean(request.getParameter("sm"))) {
LOGGER.info("Sending AppId: [{}] to SessionManager for logout.", appId);
this.redirect(response, this.config.sessionManagerLogoutUrl() + "?appId=" + appId, true);
} else {
this.redirect(response, "/bin/public/servlets/cwa/logout?appId=" + appId, true);
}
}
}
// <<----------------------------------- AuthenticationFeedbackHandler
// ----------------------------------->>
@Override
public void authenticationFailed(HttpServletRequest request, HttpServletResponse response,
AuthenticationInfo authInfo) {
LOGGER.info("authenticationFailed");
this.callbacks.forEach((name, callback) -> {
try {
if (callback.canHandle(request)) {
callback.onLoginFailure(request);
}
} catch (Exception ex) {
LOGGER.error(ex.getMessage(), ex);
}
});
}
@Override
public boolean authenticationSucceeded(HttpServletRequest request, HttpServletResponse response,
AuthenticationInfo authInfo) {
LOGGER.info("authenticationSucceeded");
this.callbacks.forEach((name, callback) -> {
try {
LOGGER.info("Name:{}", name);
if (callback.canHandle(request)) {
callback.onLoginSuccess(request);
}
} catch (Exception ex) { // NOSONAR
LOGGER.error(ex.getMessage(), ex);
}
});
// return true so that SlingAuthenticator stops further request processing and
// redirect to the given url instead.
return redirectQuietly(response, AuthUtil.getLoginResource(request, EMPTY), false);
}
@Reference(service = WFLoginCallback.class, cardinality = MULTIPLE, policy = DYNAMIC)
protected void bindCWALoginCallback(WFLoginCallback callback) {
this.callbacks.put(callback.getTenantName(), callback);
LOGGER.info("Added [{}] WFLoginCallback!", callback.getTenantName());
}
protected void unbindCWALoginCallback(WFLoginCallback callback) {
if (this.callbacks.remove(callback.getTenantName()) != null) {
LOGGER.info("Removed [{}] WFLoginCallback!", callback.getTenantName());
}
}
private boolean redirectQuietly(HttpServletResponse resp, String url, boolean encodeUrl) {
boolean outcome = false;
try {
if (resp.isCommitted()) {
LOGGER.error("Response already committed!!");
} else {
LOGGER.debug("Redirecting to url: [{}]", url);
resp.sendRedirect(encodeUrl ? resp.encodeRedirectURL(url) : url);
outcome = true;
}
} catch (IOException ex) {
LOGGER.error(ex.getMessage(), ex);// NOSONAR
}
return outcome;
}
private void redirect(HttpServletResponse resp, String url, boolean encodeUrl) throws IOException {
LOGGER.debug("Redirecting to: [{}]", url);
resp.sendRedirect(encodeUrl ? resp.encodeRedirectURL(url) : url);
}
@Activate
protected void start(Config config) {
this.config = config;
Stream.of(config.path())
.forEach(path -> LOGGER.info("WFAuthenticationHandler listening at path: [{}]", path));
}
}
import org.apache.sling.jcr.api.SlingRepository; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.MockitoAnnotations;
import javax.jcr.Session; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse;
import static org.junit.jupiter.api.Assertions.; import static org.mockito.Mockito.; import javax.jcr.SimpleCredentials; import org.apache.sling.auth.core.spi.AuthenticationInfo; import org.apache.jackrabbit.api.security.authentication.token.TokenCredentials;
public class WFAuthenticationHandlerTest {