Skip to content

Commit ea4fb51

Browse files
authored
[ENG-8927] Add dev mode for forcing errors/exceptions (#93)
* Overlay CasServerProperties as of apereo-cas 6.2.8 * Create a new DevModeProperties to set dev mode options and add it to CasServerProperties * Enable dev mode for SimpleUrlHandlerMapping and OsfPrincipalFromNonInteractiveCredentialsAction * Only force-triggering selected http errors and authentication exceptions in dev mode
1 parent e88d740 commit ea4fb51

File tree

7 files changed

+154
-19
lines changed

7 files changed

+154
-19
lines changed

etc/cas/config/cas.properties

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,12 @@ cas.server.prefix=${cas.server.name}
1515
# Tomcat Server
1616
#
1717
cas.server.tomcat.server-name=OSF CAS
18+
#
19+
# Dev Mode Options
20+
#
21+
cas.server.dev-mode.allow-force-authn-exception=${ALLOW_FORCE_AUTHN_EXCEPTION:false}
22+
cas.server.dev-mode.allow-force-http-error=${ALLOW_FORCE_HTTP_ERROR:false}
23+
#
1824
########################################################################################################################
1925

2026
########################################################################################################################

etc/cas/config/local/cas-local.properties

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,11 @@ cas.server.tomcat.server-name=OSF CAS
2121
# cas.server.tomcat.http.enabled=true
2222
# cas.server.tomcat.http.attributes=
2323
# e.g. cas.server.tomcat.http.attributes.{attribute-name}={attributeValue}
24+
#
25+
# Dev Mode Options
26+
#
27+
cas.server.dev-mode.allow-force-authn-exception=true
28+
cas.server.dev-mode.allow-force-http-error=true
2429
########################################################################################################################
2530

2631
########################################################################################################################
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
package io.cos.cas.osf.configuration.model;
2+
3+
import lombok.Getter;
4+
import lombok.Setter;
5+
import lombok.experimental.Accessors;
6+
7+
import java.io.Serializable;
8+
9+
/**
10+
* This is {@link DevModeProperties}.
11+
*
12+
* @author Longze Chen
13+
* @since 25.1.0
14+
*/
15+
@Getter
16+
@Setter
17+
@Accessors(chain = true)
18+
public class DevModeProperties implements Serializable {
19+
20+
/**
21+
* Serialization metadata.
22+
*/
23+
private static final long serialVersionUID = -1725182183570276203L;
24+
25+
/**
26+
* Allow CAS to force throw authentication exceptions and to render respective error pages for testing purpose.
27+
*/
28+
private boolean allowForceAuthnException = Boolean.FALSE;
29+
30+
/**
31+
* Allow CAS to force http errors which have built-in rendering template for rendering and testing.
32+
*/
33+
private boolean allowForceHttpError = Boolean.FALSE;
34+
}

src/main/java/io/cos/cas/osf/web/config/OsfCasSupportActionsConfiguration.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,7 @@ public Action osfNonInteractiveAuthenticationCheckAction() {
101101
adaptiveAuthenticationPolicy.getObject(),
102102
centralAuthenticationService.getObject(),
103103
jpaOsfDao.getObject(),
104+
casProperties.getServer().getDevMode(),
104105
casProperties.getAuthn().getOsfUrl(),
105106
casProperties.getAuthn().getOsfApi(),
106107
authnDelegationClients

src/main/java/io/cos/cas/osf/web/flow/login/OsfPrincipalFromNonInteractiveCredentialsAction.java

Lines changed: 24 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
import io.cos.cas.osf.authentication.support.DelegationProtocol;
1919
import io.cos.cas.osf.authentication.support.OsfApiPermissionDenied;
2020
import io.cos.cas.osf.authentication.support.OsfInstitutionUtils;
21+
import io.cos.cas.osf.configuration.model.DevModeProperties;
2122
import io.cos.cas.osf.configuration.model.OsfApiProperties;
2223
import io.cos.cas.osf.configuration.model.OsfUrlProperties;
2324
import io.cos.cas.osf.dao.JpaOsfDao;
@@ -197,6 +198,9 @@ public class OsfPrincipalFromNonInteractiveCredentialsAction extends AbstractNon
197198
@NotNull
198199
private final JpaOsfDao jpaOsfDao;
199200

201+
@NotNull
202+
private DevModeProperties devModeProperties;
203+
200204
@NotNull
201205
private OsfUrlProperties osfUrlProperties;
202206

@@ -214,6 +218,7 @@ public OsfPrincipalFromNonInteractiveCredentialsAction(
214218
final AdaptiveAuthenticationPolicy adaptiveAuthenticationPolicy,
215219
final CentralAuthenticationService centralAuthenticationService,
216220
final JpaOsfDao jpaOsfDao,
221+
final DevModeProperties devModeProperties,
217222
final OsfUrlProperties osfUrlProperties,
218223
final OsfApiProperties osfApiProperties,
219224
final Map<String, List<String>> authnDelegationClients
@@ -225,6 +230,7 @@ public OsfPrincipalFromNonInteractiveCredentialsAction(
225230
);
226231
this.centralAuthenticationService = centralAuthenticationService;
227232
this.jpaOsfDao = jpaOsfDao;
233+
this.devModeProperties = devModeProperties;
228234
this.osfUrlProperties = osfUrlProperties;
229235
this.osfApiProperties = osfApiProperties;
230236
this.authnDelegationClients = authnDelegationClients;
@@ -331,23 +337,24 @@ protected Credential constructCredentialsFromRequest(final RequestContext contex
331337
}
332338
LOGGER.debug("No valid username or verification key found in request parameters.");
333339

334-
// Check 4: check "forceException=" query parameter
335-
// TODO: disable this for production environment
336-
final String forcedException = request.getParameter(FORCE_EXCEPTION_PARAMETER_NAME);
337-
if (StringUtils.isNotBlank(forcedException)) {
338-
setSsoErrorContext(
339-
context,
340-
forcedException,
341-
String.format("This exception was thrown on purpose bypassing standard web flow: %s", Class.forName(forcedException).getSimpleName()),
342-
"N/A",
343-
"N/A",
344-
"N/A",
345-
"N/A"
346-
);
347-
try {
348-
throw (LoginException) Class.forName(forcedException).getConstructor(String.class).newInstance(FORCE_EXCEPTION_PARAMETER_NAME);
349-
} catch ( java.lang.ClassCastException e) {
350-
throw (RuntimeException) Class.forName(forcedException).getConstructor(String.class).newInstance(FORCE_EXCEPTION_PARAMETER_NAME);
340+
// Check 4: check "forceException=" query parameter if in dev mode
341+
if (devModeProperties.isAllowForceAuthnException()) {
342+
final String forcedException = request.getParameter(FORCE_EXCEPTION_PARAMETER_NAME);
343+
if (StringUtils.isNotBlank(forcedException)) {
344+
setSsoErrorContext(
345+
context,
346+
forcedException,
347+
String.format("This exception was thrown on purpose bypassing standard web flow: %s", Class.forName(forcedException).getSimpleName()),
348+
"N/A",
349+
"N/A",
350+
"N/A",
351+
"N/A"
352+
);
353+
try {
354+
throw (LoginException) Class.forName(forcedException).getConstructor(String.class).newInstance(FORCE_EXCEPTION_PARAMETER_NAME);
355+
} catch (java.lang.ClassCastException e) {
356+
throw (RuntimeException) Class.forName(forcedException).getConstructor(String.class).newInstance(FORCE_EXCEPTION_PARAMETER_NAME);
357+
}
351358
}
352359
}
353360

src/main/java/org/apereo/cas/config/CasWebAppConfiguration.java

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -164,13 +164,15 @@ public SimpleUrlHandlerMapping handlerMapping() {
164164
val mapping = new SimpleUrlHandlerMapping();
165165

166166
val root = rootController();
167-
val forceHttpError = forceHttpErrorController();
168167
mapping.setOrder(1);
169168
mapping.setAlwaysUseFullPath(true);
170169
mapping.setRootHandler(root);
171170
val urls = new HashMap<String, Object>();
172171
urls.put("/", root);
173-
urls.put("/forceHttpError", forceHttpError);
172+
if (casProperties.getServer().getDevMode().isAllowForceHttpError()) {
173+
val forceHttpError = forceHttpErrorController();
174+
urls.put("/forceHttpError", forceHttpError);
175+
}
174176

175177
mapping.setUrlMap(urls);
176178
return mapping;
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
package org.apereo.cas.configuration.model.core;
2+
3+
import io.cos.cas.osf.configuration.model.DevModeProperties;
4+
5+
import org.apereo.cas.CasProtocolConstants;
6+
import org.apereo.cas.configuration.model.core.web.tomcat.CasEmbeddedApacheTomcatProperties;
7+
import org.apereo.cas.configuration.support.RequiredProperty;
8+
import org.apereo.cas.configuration.support.RequiresModule;
9+
10+
import com.fasterxml.jackson.annotation.JsonIgnore;
11+
import lombok.Getter;
12+
import lombok.Setter;
13+
import lombok.experimental.Accessors;
14+
import org.springframework.boot.context.properties.NestedConfigurationProperty;
15+
16+
import java.io.Serializable;
17+
18+
/**
19+
* This is {@link CasServerProperties}.
20+
*
21+
* <p>OSF CAS Customization: add {@link DevModeProperties devMode} to {@link CasServerProperties}; this allows
22+
* dev mode options to be easily accessed from {@link org.apereo.cas.configuration.CasConfigurationProperties}
23+
* by calling {@code casProperties.getServer().getDevMode()}.</p>
24+
*
25+
* @author Misagh Moayyed
26+
* @since 5.0.0
27+
*
28+
* @author Longze Chen
29+
* @since osf-cas 25.1.0
30+
*/
31+
@RequiresModule(name = "cas-server-core", automated = true)
32+
@Getter
33+
@Setter
34+
@Accessors(chain = true)
35+
public class CasServerProperties implements Serializable {
36+
37+
private static final long serialVersionUID = 7876382696803430817L;
38+
39+
/**
40+
* Full name of the CAS server. This is the public-facing address
41+
* of the CAS deployment and not the individual node address,
42+
* in the event that CAS is clustered.
43+
*/
44+
@RequiredProperty
45+
private String name = "https://cas.example.org:8443";
46+
47+
/**
48+
* A concatenation of the server name plus the CAS context path.
49+
* Deployments at root likely need to blank out this value.
50+
*/
51+
@RequiredProperty
52+
private String prefix = name.concat("/cas");
53+
54+
/**
55+
* The CAS Server scope.
56+
*/
57+
@RequiredProperty
58+
private String scope = "example.org";
59+
60+
/**
61+
* Configuration settings that control the embedded Apache Tomcat container.
62+
*/
63+
@NestedConfigurationProperty
64+
private CasEmbeddedApacheTomcatProperties tomcat = new CasEmbeddedApacheTomcatProperties();
65+
66+
// OSF CAS Customization: a new private field devMode that stores osf-cas customized dev mode options
67+
@NestedConfigurationProperty
68+
private DevModeProperties devMode = new DevModeProperties();
69+
70+
@JsonIgnore
71+
public String getLoginUrl() {
72+
return getPrefix().concat(CasProtocolConstants.ENDPOINT_LOGIN);
73+
}
74+
75+
@JsonIgnore
76+
public String getLogoutUrl() {
77+
return getPrefix().concat(CasProtocolConstants.ENDPOINT_LOGOUT);
78+
}
79+
80+
}

0 commit comments

Comments
 (0)