Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions etc/cas/config/cas.properties
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,12 @@ cas.server.prefix=${cas.server.name}
# Tomcat Server
#
cas.server.tomcat.server-name=OSF CAS
#
# Dev Mode Options
#
cas.server.dev-mode.allow-force-authn-exception=${ALLOW_FORCE_AUTHN_EXCEPTION:false}
cas.server.dev-mode.allow-force-http-error=${ALLOW_FORCE_HTTP_ERROR:false}
#
########################################################################################################################

########################################################################################################################
Expand Down Expand Up @@ -90,6 +96,12 @@ cas.logout.remove-descendant-tickets=false
#
cas.authn.osf-url.home=https://{{ .Values.osfDomain }}/
cas.authn.osf-url.dashboard=https://{{ .Values.osfDomain }}/dashboard/
cas.authn.osf-url.search=https://{{ .Values.osfDomain }}/search/
cas.authn.osf-url.support=https://help.osf.io
cas.authn.osf-url.registries=https://{{ .Values.osfDomain }}/registries/discover/
cas.authn.osf-url.preprints=https://{{ .Values.osfDomain }}/preprints/discover/
cas.authn.osf-url.meetings=https://{{ .Values.osfDomain }}/meetings/
cas.authn.osf-url.donate=https://www.cos.io/support-cos
cas.authn.osf-url.login-with-next=https://{{ .Values.osfDomain }}/login?next=
cas.authn.osf-url.logout=https://{{ .Values.osfDomain }}/logout/
cas.authn.osf-url.resend-confirmation=https://{{ .Values.osfDomain }}/resend/
Expand Down
11 changes: 11 additions & 0 deletions etc/cas/config/local/cas-local.properties
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,11 @@ cas.server.tomcat.server-name=OSF CAS
# cas.server.tomcat.http.enabled=true
# cas.server.tomcat.http.attributes=
# e.g. cas.server.tomcat.http.attributes.{attribute-name}={attributeValue}
#
# Dev Mode Options
#
cas.server.dev-mode.allow-force-authn-exception=true
cas.server.dev-mode.allow-force-http-error=true
########################################################################################################################

########################################################################################################################
Expand Down Expand Up @@ -97,6 +102,12 @@ cas.logout.remove-descendant-tickets=false
#
cas.authn.osf-url.home=http://localhost:5000/
cas.authn.osf-url.dashboard=http://localhost:5000/dashboard/
cas.authn.osf-url.search=http://localhost:5000/search/
cas.authn.osf-url.support=https://help.osf.io
cas.authn.osf-url.registries=http://localhost:5000/registries/discover/
cas.authn.osf-url.preprints=http://localhost:5000/preprints/discover/
cas.authn.osf-url.meetings=http://localhost:5000/meetings/
cas.authn.osf-url.donate=https://www.cos.io/support-cos
cas.authn.osf-url.login-with-next=http://localhost:5000/login?next=
cas.authn.osf-url.logout=http://localhost:5000/logout/
cas.authn.osf-url.resend-confirmation=http://localhost:5000/resend/
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package io.cos.cas.osf.configuration.model;

import lombok.Getter;
import lombok.Setter;
import lombok.experimental.Accessors;

import java.io.Serializable;

/**
* This is {@link DevModeProperties}.
*
* @author Longze Chen
* @since 25.1.0
*/
@Getter
@Setter
@Accessors(chain = true)
public class DevModeProperties implements Serializable {

/**
* Serialization metadata.
*/
private static final long serialVersionUID = -1725182183570276203L;

/**
* Allow CAS to force throw authentication exceptions and to render respective error pages for testing purpose.
*/
private boolean allowForceAuthnException = Boolean.FALSE;

/**
* Allow CAS to force http errors which have built-in rendering template for rendering and testing.
*/
private boolean allowForceHttpError = Boolean.FALSE;
}
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,36 @@ public class OsfUrlProperties implements Serializable {
*/
private String dashboard;

/**
* OSF search page URL.
*/
private String search;

/**
* OSF support page URL.
*/
private String support;

/**
* OSF registries page URL.
*/
private String registries;

/**
* OSF preprints page URL.
*/
private String preprints;

/**
* OSF meetings page URL.
*/
private String meetings;

/**
* OSF donate page URL.
*/
private String donate;

/**
* OSF sign-up page URL.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,7 @@ public Action osfNonInteractiveAuthenticationCheckAction() {
adaptiveAuthenticationPolicy.getObject(),
centralAuthenticationService.getObject(),
jpaOsfDao.getObject(),
casProperties.getServer().getDevMode(),
casProperties.getAuthn().getOsfUrl(),
casProperties.getAuthn().getOsfApi(),
authnDelegationClients
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ protected Event doExecute(RequestContext context) {
if (institutionId != null) {
institutionLoginUrlMapSorted = institutionLoginUrlMap;
} else {
institutionLoginUrlMap.put("", " -- select an institution -- ");
institutionLoginUrlMap.put("", " Select institution ");
institutionLoginUrlMapSorted = OsfInstitutionUtils.sortByValue(institutionLoginUrlMap);
}
context.getFlowScope().put("institutions", institutionLoginUrlMapSorted);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
import io.cos.cas.osf.authentication.support.DelegationProtocol;
import io.cos.cas.osf.authentication.support.OsfApiPermissionDenied;
import io.cos.cas.osf.authentication.support.OsfInstitutionUtils;
import io.cos.cas.osf.configuration.model.DevModeProperties;
import io.cos.cas.osf.configuration.model.OsfApiProperties;
import io.cos.cas.osf.configuration.model.OsfUrlProperties;
import io.cos.cas.osf.dao.JpaOsfDao;
Expand Down Expand Up @@ -81,6 +82,7 @@
import javax.naming.InvalidNameException;
import javax.naming.ldap.LdapName;
import javax.security.auth.login.AccountException;
import javax.security.auth.login.LoginException;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
Expand Down Expand Up @@ -155,6 +157,8 @@ public class OsfPrincipalFromNonInteractiveCredentialsAction extends AbstractNon

private static final String VERIFICATION_KEY_PARAMETER_NAME = "verification_key";

private static final String FORCE_EXCEPTION_PARAMETER_NAME = "forceException";

private static final String OSF_URL_FLOW_PARAMETER = "osfUrl";

private static final String AUTHENTICATION_EXCEPTION = "authnError";
Expand Down Expand Up @@ -194,6 +198,9 @@ public class OsfPrincipalFromNonInteractiveCredentialsAction extends AbstractNon
@NotNull
private final JpaOsfDao jpaOsfDao;

@NotNull
private DevModeProperties devModeProperties;

@NotNull
private OsfUrlProperties osfUrlProperties;

Expand All @@ -211,6 +218,7 @@ public OsfPrincipalFromNonInteractiveCredentialsAction(
final AdaptiveAuthenticationPolicy adaptiveAuthenticationPolicy,
final CentralAuthenticationService centralAuthenticationService,
final JpaOsfDao jpaOsfDao,
final DevModeProperties devModeProperties,
final OsfUrlProperties osfUrlProperties,
final OsfApiProperties osfApiProperties,
final Map<String, List<String>> authnDelegationClients
Expand All @@ -222,6 +230,7 @@ public OsfPrincipalFromNonInteractiveCredentialsAction(
);
this.centralAuthenticationService = centralAuthenticationService;
this.jpaOsfDao = jpaOsfDao;
this.devModeProperties = devModeProperties;
this.osfUrlProperties = osfUrlProperties;
this.osfApiProperties = osfApiProperties;
this.authnDelegationClients = authnDelegationClients;
Expand Down Expand Up @@ -328,6 +337,27 @@ protected Credential constructCredentialsFromRequest(final RequestContext contex
}
LOGGER.debug("No valid username or verification key found in request parameters.");

// Check 4: check "forceException=" query parameter if in dev mode
if (devModeProperties.isAllowForceAuthnException()) {
final String forcedException = request.getParameter(FORCE_EXCEPTION_PARAMETER_NAME);
if (StringUtils.isNotBlank(forcedException)) {
setSsoErrorContext(
context,
forcedException,
String.format("This exception was thrown on purpose bypassing standard web flow: %s", Class.forName(forcedException).getSimpleName()),
"N/A",
"N/A",
"N/A",
"N/A"
);
try {
throw (LoginException) Class.forName(forcedException).getConstructor(String.class).newInstance(FORCE_EXCEPTION_PARAMETER_NAME);
} catch (java.lang.ClassCastException e) {
throw (RuntimeException) Class.forName(forcedException).getConstructor(String.class).newInstance(FORCE_EXCEPTION_PARAMETER_NAME);
}
}
}

// Default when there is no non-interactive authentication available
// Type 5: return a null credential so that the login webflow will prepare login pages
return null;
Expand Down
59 changes: 57 additions & 2 deletions src/main/java/org/apereo/cas/config/CasWebAppConfiguration.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package org.apereo.cas.config;

import org.apache.http.HttpStatus;

import org.apereo.cas.configuration.CasConfigurationProperties;

import lombok.val;
Expand Down Expand Up @@ -29,7 +31,10 @@
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import java.io.IOException;

import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Optional;

Expand All @@ -44,6 +49,15 @@
@EnableConfigurationProperties(CasConfigurationProperties.class)
public class CasWebAppConfiguration implements WebMvcConfigurer {

private static final List<Integer> HTTP_ERROR_WITH_TEMPLATES = List.of(
HttpStatus.SC_UNAUTHORIZED,
HttpStatus.SC_FORBIDDEN,
HttpStatus.SC_NOT_FOUND,
HttpStatus.SC_METHOD_NOT_ALLOWED,
HttpStatus.SC_LOCKED,
HttpStatus.SC_INTERNAL_SERVER_ERROR
);

@Autowired
private CasConfigurationProperties casProperties;

Expand Down Expand Up @@ -96,8 +110,10 @@ protected UrlFilenameViewController passThroughController() {
protected Controller rootController() {
return new ParameterizableViewController() {
@Override
protected ModelAndView handleRequestInternal(final HttpServletRequest request,
final HttpServletResponse response) {
protected ModelAndView handleRequestInternal(
final HttpServletRequest request,
final HttpServletResponse response
) {
val queryString = request.getQueryString();
val url = request.getContextPath() + "/login"
+ Optional.ofNullable(queryString).map(string -> '?' + string).orElse(StringUtils.EMPTY);
Expand All @@ -108,6 +124,41 @@ protected ModelAndView handleRequestInternal(final HttpServletRequest request,
};
}

/**
* OSF CAS Customization: implement a new controller to support testing error pages with templates in
* "resources/templates/error/" (401, 403, 404, 405 and 423) and with the CAS unavailable template of
* "templates/error.html" (500).
*
* @return {@code null}
*/
@Bean
protected Controller forceHttpErrorController() {
return new ParameterizableViewController() {
@Override
protected ModelAndView handleRequestInternal(
final HttpServletRequest request,
final HttpServletResponse response
) throws IOException {
// TODO: disable this for production environment
var errorCodeString = request.getParameter("code");
if (StringUtils.isNotBlank(errorCodeString)) {
try {
var errorCode = Integer.parseInt(errorCodeString);
if (HTTP_ERROR_WITH_TEMPLATES.contains(errorCode)) {
response.sendError(errorCode);
return null;
}
} catch (NumberFormatException e) {
response.sendError(HttpServletResponse.SC_BAD_REQUEST);
return null;
}
}
response.sendError(HttpServletResponse.SC_BAD_REQUEST);
return null;
}
};
}

@Bean
public SimpleUrlHandlerMapping handlerMapping() {
val mapping = new SimpleUrlHandlerMapping();
Expand All @@ -118,6 +169,10 @@ public SimpleUrlHandlerMapping handlerMapping() {
mapping.setRootHandler(root);
val urls = new HashMap<String, Object>();
urls.put("/", root);
if (casProperties.getServer().getDevMode().isAllowForceHttpError()) {
val forceHttpError = forceHttpErrorController();
urls.put("/forceHttpError", forceHttpError);
}

mapping.setUrlMap(urls);
return mapping;
Expand Down
Loading
Loading