Skip to content

Commit a31e1bb

Browse files
authored
Merge pull request #94 from CenterForOpenScience/feature/angular-like-pages
Update CAS FE to match angular OSF * [ENG-8793] [ENG-8785] [ENG-8793] Remove branded login + Update no service pages + Update OAuth workflow (#98) * [ENG-8788] [ENG-8786] Institutions page and TOS page redesign (#99) * [ENG-8789] [ENG-8790] [ENG-8791] Exception Page rework (#95) * [ENG-8787] Username/password and 2FA login page (#97) * [ENG-8791] Inserting CasProperties into Spring Dispatcher and Improve header and left pane UI/UX (#92) * [ENG-8927] Add dev mode for forcing errors/exceptions (#93) * [NOTICKET] Remove OSF Collections URL from cas.properties (#91) * [ENG-8556] Update CAS pages to match Angular update: header, footer and left pane (#88) * [ENG-8754] Force throw a few HTTP errors for testing purpose (#90) * [ENG-8792] Force CAS to throw exceptions and display error pages (#87)
2 parents 545348f + 7fe3cf2 commit a31e1bb

70 files changed

Lines changed: 2951 additions & 600 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

etc/cas/config/cas.properties

Lines changed: 12 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
########################################################################################################################
@@ -90,6 +96,12 @@ cas.logout.remove-descendant-tickets=false
9096
#
9197
cas.authn.osf-url.home=https://{{ .Values.osfDomain }}/
9298
cas.authn.osf-url.dashboard=https://{{ .Values.osfDomain }}/dashboard/
99+
cas.authn.osf-url.search=https://{{ .Values.osfDomain }}/search/
100+
cas.authn.osf-url.support=https://help.osf.io
101+
cas.authn.osf-url.registries=https://{{ .Values.osfDomain }}/registries/discover/
102+
cas.authn.osf-url.preprints=https://{{ .Values.osfDomain }}/preprints/discover/
103+
cas.authn.osf-url.meetings=https://{{ .Values.osfDomain }}/meetings/
104+
cas.authn.osf-url.donate=https://www.cos.io/support-cos
93105
cas.authn.osf-url.login-with-next=https://{{ .Values.osfDomain }}/login?next=
94106
cas.authn.osf-url.logout=https://{{ .Values.osfDomain }}/logout/
95107
cas.authn.osf-url.resend-confirmation=https://{{ .Values.osfDomain }}/resend/

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

Lines changed: 11 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
########################################################################################################################
@@ -97,6 +102,12 @@ cas.logout.remove-descendant-tickets=false
97102
#
98103
cas.authn.osf-url.home=http://localhost:5000/
99104
cas.authn.osf-url.dashboard=http://localhost:5000/dashboard/
105+
cas.authn.osf-url.search=http://localhost:5000/search/
106+
cas.authn.osf-url.support=https://help.osf.io
107+
cas.authn.osf-url.registries=http://localhost:5000/registries/discover/
108+
cas.authn.osf-url.preprints=http://localhost:5000/preprints/discover/
109+
cas.authn.osf-url.meetings=http://localhost:5000/meetings/
110+
cas.authn.osf-url.donate=https://www.cos.io/support-cos
100111
cas.authn.osf-url.login-with-next=http://localhost:5000/login?next=
101112
cas.authn.osf-url.logout=http://localhost:5000/logout/
102113
cas.authn.osf-url.resend-confirmation=http://localhost:5000/resend/
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/configuration/model/OsfUrlProperties.java

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,36 @@ public class OsfUrlProperties implements Serializable {
3636
*/
3737
private String dashboard;
3838

39+
/**
40+
* OSF search page URL.
41+
*/
42+
private String search;
43+
44+
/**
45+
* OSF support page URL.
46+
*/
47+
private String support;
48+
49+
/**
50+
* OSF registries page URL.
51+
*/
52+
private String registries;
53+
54+
/**
55+
* OSF preprints page URL.
56+
*/
57+
private String preprints;
58+
59+
/**
60+
* OSF meetings page URL.
61+
*/
62+
private String meetings;
63+
64+
/**
65+
* OSF donate page URL.
66+
*/
67+
private String donate;
68+
3969
/**
4070
* OSF sign-up page URL.
4171
*/

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/OsfInstitutionLoginPreparationAction.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@ protected Event doExecute(RequestContext context) {
9494
if (institutionId != null) {
9595
institutionLoginUrlMapSorted = institutionLoginUrlMap;
9696
} else {
97-
institutionLoginUrlMap.put("", " -- select an institution -- ");
97+
institutionLoginUrlMap.put("", " Select institution ");
9898
institutionLoginUrlMapSorted = OsfInstitutionUtils.sortByValue(institutionLoginUrlMap);
9999
}
100100
context.getFlowScope().put("institutions", institutionLoginUrlMapSorted);

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

Lines changed: 30 additions & 0 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;
@@ -81,6 +82,7 @@
8182
import javax.naming.InvalidNameException;
8283
import javax.naming.ldap.LdapName;
8384
import javax.security.auth.login.AccountException;
85+
import javax.security.auth.login.LoginException;
8486
import javax.servlet.http.Cookie;
8587
import javax.servlet.http.HttpServletRequest;
8688
import javax.servlet.http.HttpServletResponse;
@@ -155,6 +157,8 @@ public class OsfPrincipalFromNonInteractiveCredentialsAction extends AbstractNon
155157

156158
private static final String VERIFICATION_KEY_PARAMETER_NAME = "verification_key";
157159

160+
private static final String FORCE_EXCEPTION_PARAMETER_NAME = "forceException";
161+
158162
private static final String OSF_URL_FLOW_PARAMETER = "osfUrl";
159163

160164
private static final String AUTHENTICATION_EXCEPTION = "authnError";
@@ -194,6 +198,9 @@ public class OsfPrincipalFromNonInteractiveCredentialsAction extends AbstractNon
194198
@NotNull
195199
private final JpaOsfDao jpaOsfDao;
196200

201+
@NotNull
202+
private DevModeProperties devModeProperties;
203+
197204
@NotNull
198205
private OsfUrlProperties osfUrlProperties;
199206

@@ -211,6 +218,7 @@ public OsfPrincipalFromNonInteractiveCredentialsAction(
211218
final AdaptiveAuthenticationPolicy adaptiveAuthenticationPolicy,
212219
final CentralAuthenticationService centralAuthenticationService,
213220
final JpaOsfDao jpaOsfDao,
221+
final DevModeProperties devModeProperties,
214222
final OsfUrlProperties osfUrlProperties,
215223
final OsfApiProperties osfApiProperties,
216224
final Map<String, List<String>> authnDelegationClients
@@ -222,6 +230,7 @@ public OsfPrincipalFromNonInteractiveCredentialsAction(
222230
);
223231
this.centralAuthenticationService = centralAuthenticationService;
224232
this.jpaOsfDao = jpaOsfDao;
233+
this.devModeProperties = devModeProperties;
225234
this.osfUrlProperties = osfUrlProperties;
226235
this.osfApiProperties = osfApiProperties;
227236
this.authnDelegationClients = authnDelegationClients;
@@ -328,6 +337,27 @@ protected Credential constructCredentialsFromRequest(final RequestContext contex
328337
}
329338
LOGGER.debug("No valid username or verification key found in request parameters.");
330339

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+
}
358+
}
359+
}
360+
331361
// Default when there is no non-interactive authentication available
332362
// Type 5: return a null credential so that the login webflow will prepare login pages
333363
return null;

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

Lines changed: 57 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
package org.apereo.cas.config;
22

3+
import org.apache.http.HttpStatus;
4+
35
import org.apereo.cas.configuration.CasConfigurationProperties;
46

57
import lombok.val;
@@ -29,7 +31,10 @@
2931
import javax.servlet.http.HttpServletRequest;
3032
import javax.servlet.http.HttpServletResponse;
3133

34+
import java.io.IOException;
35+
3236
import java.util.HashMap;
37+
import java.util.List;
3338
import java.util.Locale;
3439
import java.util.Optional;
3540

@@ -44,6 +49,15 @@
4449
@EnableConfigurationProperties(CasConfigurationProperties.class)
4550
public class CasWebAppConfiguration implements WebMvcConfigurer {
4651

52+
private static final List<Integer> HTTP_ERROR_WITH_TEMPLATES = List.of(
53+
HttpStatus.SC_UNAUTHORIZED,
54+
HttpStatus.SC_FORBIDDEN,
55+
HttpStatus.SC_NOT_FOUND,
56+
HttpStatus.SC_METHOD_NOT_ALLOWED,
57+
HttpStatus.SC_LOCKED,
58+
HttpStatus.SC_INTERNAL_SERVER_ERROR
59+
);
60+
4761
@Autowired
4862
private CasConfigurationProperties casProperties;
4963

@@ -96,8 +110,10 @@ protected UrlFilenameViewController passThroughController() {
96110
protected Controller rootController() {
97111
return new ParameterizableViewController() {
98112
@Override
99-
protected ModelAndView handleRequestInternal(final HttpServletRequest request,
100-
final HttpServletResponse response) {
113+
protected ModelAndView handleRequestInternal(
114+
final HttpServletRequest request,
115+
final HttpServletResponse response
116+
) {
101117
val queryString = request.getQueryString();
102118
val url = request.getContextPath() + "/login"
103119
+ Optional.ofNullable(queryString).map(string -> '?' + string).orElse(StringUtils.EMPTY);
@@ -108,6 +124,41 @@ protected ModelAndView handleRequestInternal(final HttpServletRequest request,
108124
};
109125
}
110126

127+
/**
128+
* OSF CAS Customization: implement a new controller to support testing error pages with templates in
129+
* "resources/templates/error/" (401, 403, 404, 405 and 423) and with the CAS unavailable template of
130+
* "templates/error.html" (500).
131+
*
132+
* @return {@code null}
133+
*/
134+
@Bean
135+
protected Controller forceHttpErrorController() {
136+
return new ParameterizableViewController() {
137+
@Override
138+
protected ModelAndView handleRequestInternal(
139+
final HttpServletRequest request,
140+
final HttpServletResponse response
141+
) throws IOException {
142+
// TODO: disable this for production environment
143+
var errorCodeString = request.getParameter("code");
144+
if (StringUtils.isNotBlank(errorCodeString)) {
145+
try {
146+
var errorCode = Integer.parseInt(errorCodeString);
147+
if (HTTP_ERROR_WITH_TEMPLATES.contains(errorCode)) {
148+
response.sendError(errorCode);
149+
return null;
150+
}
151+
} catch (NumberFormatException e) {
152+
response.sendError(HttpServletResponse.SC_BAD_REQUEST);
153+
return null;
154+
}
155+
}
156+
response.sendError(HttpServletResponse.SC_BAD_REQUEST);
157+
return null;
158+
}
159+
};
160+
}
161+
111162
@Bean
112163
public SimpleUrlHandlerMapping handlerMapping() {
113164
val mapping = new SimpleUrlHandlerMapping();
@@ -118,6 +169,10 @@ public SimpleUrlHandlerMapping handlerMapping() {
118169
mapping.setRootHandler(root);
119170
val urls = new HashMap<String, Object>();
120171
urls.put("/", root);
172+
if (casProperties.getServer().getDevMode().isAllowForceHttpError()) {
173+
val forceHttpError = forceHttpErrorController();
174+
urls.put("/forceHttpError", forceHttpError);
175+
}
121176

122177
mapping.setUrlMap(urls);
123178
return mapping;

0 commit comments

Comments
 (0)