Skip to content

Commit 3055e2c

Browse files
felliottcslzchen
authored andcommitted
Add migration path for users from deactivated institutions
* Add "Can't find my institution" link to institution sign-on page. This links to a new page that allows the user to logon w/ pre-existing OSF credentials or to send a reset-password email to their current email account. [ENG-2448]
1 parent 4c22144 commit 3055e2c

13 files changed

Lines changed: 251 additions & 0 deletions

etc/cas/config/cas.properties

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ cas.authn.osf-url.login-with-next=https://{{ .Values.osfDomain }}/login?next=
6969
cas.authn.osf-url.logout=https://{{ .Values.osfDomain }}/logout/
7070
cas.authn.osf-url.resend-confirmation=https://{{ .Values.osfDomain }}/resend/
7171
cas.authn.osf-url.forgot-password=https://{{ .Values.osfDomain }}/forgotpassword/
72+
cas.authn.osf-url.forgot-password-institution=https://{{ .Values.osfDomain }}/forgotpassword-institution/
7273
cas.authn.osf-url.register=https://{{ .Values.osfDomain }}/register/
7374
########################################################################################################################
7475

src/main/java/io/cos/cas/osf/configuration/model/OsfUrlProperties.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,11 @@ public class OsfUrlProperties implements Serializable {
6161
*/
6262
private String forgotPassword;
6363

64+
/**
65+
* OSF forgot-password-institutions page URL
66+
*/
67+
private String forgotPasswordInstitution;
68+
6469
/**
6570
* Build the default service URL using OSF login endpoint with OSF home page as destination.
6671
*/

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

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import io.cos.cas.osf.dao.JpaOsfDao;
44
import io.cos.cas.osf.web.flow.login.OsfDefaultLoginPreparationAction;
55
import io.cos.cas.osf.web.flow.login.OsfInstitutionLoginPreparationAction;
6+
import io.cos.cas.osf.web.flow.login.OsfUnsupportedInstitutionLoginPreparationAction;
67
import io.cos.cas.osf.web.flow.login.OsfCasPreInitialFlowSetupAction;
78
import io.cos.cas.osf.web.flow.login.OsfPrincipalFromNonInteractiveCredentialsAction;
89

@@ -125,4 +126,18 @@ public Action osfInstitutionLoginCheckAction() {
125126
casProperties.getAuthn().getOsfPostgres().getInstitutionClients()
126127
);
127128
}
129+
130+
/**
131+
* Bean configuration for {@link OsfUnsupportedInstitutionLoginPreparationAction}.
132+
*
133+
* @return the initialized action
134+
*/
135+
@Bean
136+
public Action osfUnsupportedInstitutionLoginCheckAction() {
137+
return new OsfUnsupportedInstitutionLoginPreparationAction(
138+
initialAuthenticationAttemptWebflowEventResolver.getObject(),
139+
serviceTicketRequestWebflowEventResolver.getObject(),
140+
adaptiveAuthenticationPolicy.getObject()
141+
);
142+
}
128143
}

src/main/java/io/cos/cas/osf/web/flow/configurer/OsfCasLoginWebflowConfigurer.java

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@ protected void createDefaultViewStates(final Flow flow) {
8282
createTwoFactorLoginFormView(flow);
8383
createTermsOfServiceConsentLoginFormView(flow);
8484
createInstitutionLoginView(flow);
85+
createUnsupportedInstitutionLoginView(flow);
8586
createOrcidLoginAutoRedirectView(flow);
8687
createDefaultServiceLoginAutoRedirectView(flow);
8788
createOsfCasAuthenticationExceptionViewStates(flow);
@@ -139,6 +140,7 @@ protected void createDefaultActionStates(final Flow flow) {
139140
// Create the customized interactive login check actions
140141
createOsfDefaultLoginCheckAction(flow);
141142
createOsfInstitutionLoginCheckAction(flow);
143+
createOsfUnsupportedInstitutionLoginCheckAction(flow);
142144
// Create the customized non-interactive authentication check action
143145
createOsfNonInteractiveAuthenticationCheckAction(flow);
144146
}
@@ -327,6 +329,11 @@ private void createOsfDefaultLoginCheckAction(final Flow flow) {
327329
OsfCasWebflowConstants.TRANSITION_ID_INSTITUTION_LOGIN,
328330
OsfCasWebflowConstants.STATE_ID_OSF_INSTITUTION_LOGIN_CHECK
329331
);
332+
createTransitionForState(
333+
action,
334+
OsfCasWebflowConstants.TRANSITION_ID_UNSUPPORTED_INSTITUTION_LOGIN,
335+
OsfCasWebflowConstants.STATE_ID_OSF_UNSUPPORTED_INSTITUTION_LOGIN_CHECK
336+
);
330337
createTransitionForState(
331338
action,
332339
OsfCasWebflowConstants.TRANSITION_ID_ORCID_LOGIN_AUTO_REDIRECT,
@@ -367,6 +374,29 @@ private void createOsfInstitutionLoginCheckAction(final Flow flow) {
367374
);
368375
}
369376

377+
/**
378+
* Create the unsupported institution login check / preparation action for OSF CAS.
379+
*
380+
* @param flow the flow
381+
*/
382+
private void createOsfUnsupportedInstitutionLoginCheckAction(final Flow flow) {
383+
ActionState action = createActionState(
384+
flow,
385+
OsfCasWebflowConstants.STATE_ID_OSF_UNSUPPORTED_INSTITUTION_LOGIN_CHECK,
386+
OsfCasWebflowConstants.ACTION_ID_OSF_UNSUPPORTED_INSTITUTION_LOGIN_CHECK
387+
);
388+
createTransitionForState(
389+
action,
390+
CasWebflowConstants.TRANSITION_ID_ERROR,
391+
OsfCasWebflowConstants.VIEW_ID_INSTITUTION_SSO_FAILED
392+
);
393+
createTransitionForState(
394+
action,
395+
CasWebflowConstants.TRANSITION_ID_SUCCESS,
396+
OsfCasWebflowConstants.VIEW_ID_UNSUPPORTED_INSTITUTION_SSO_INIT
397+
);
398+
}
399+
370400
/**
371401
* Create extra authentication exception view states for OSF CAS.
372402
*
@@ -506,4 +536,17 @@ protected void createInstitutionLoginView(final Flow flow) {
506536
OsfCasWebflowConstants.VIEW_ID_INSTITUTION_SSO_INIT
507537
);
508538
}
539+
540+
/**
541+
* Create the institution SSO init view state to support the OSF feature "sign-in via institutions".
542+
*
543+
* @param flow the flow
544+
*/
545+
protected void createUnsupportedInstitutionLoginView(final Flow flow) {
546+
createViewState(
547+
flow,
548+
OsfCasWebflowConstants.VIEW_ID_UNSUPPORTED_INSTITUTION_SSO_INIT,
549+
OsfCasWebflowConstants.VIEW_ID_UNSUPPORTED_INSTITUTION_SSO_INIT
550+
);
551+
}
509552
}

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@ public abstract class OsfAbstractLoginPreparationAction extends AbstractAuthenti
3333

3434
protected static final String PARAMETER_CAMPAIGN_VALUE = "institution";
3535

36+
protected static final String PARAMETER_CAMPAIGN_UNSUPPORTED_INSTITUTION_VALUE = "unsupportedinstitution";
37+
3638
protected static final String PARAMETER_INSTITUTION_ID = "institutionId";
3739

3840
protected static final String PARAMETER_ORCID_CLIENT_TYPE = "orcid";

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

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ protected Event doExecute(RequestContext context) {
5252

5353
final boolean institutionLogin = isInstitutionLogin(context);
5454
final String institutionId = getInstitutionIdFromRequestContext(context);
55+
final boolean unsupportedInstitutionLogin = isUnsupportedInstitutionLogin(context);
5556
final boolean orcidRedirect = isOrcidLoginAutoRedirect(context);
5657
final String orcidLoginUrl = getOrcidLoginUrlFromFlowScope(context);
5758

@@ -74,6 +75,7 @@ protected Event doExecute(RequestContext context) {
7475
encodedServiceUrl,
7576
institutionLogin,
7677
institutionId,
78+
unsupportedInstitutionLogin,
7779
orcidRedirect,
7880
orcidLoginUrl,
7981
defaultService,
@@ -83,6 +85,7 @@ protected Event doExecute(RequestContext context) {
8385
loginContext.setDefaultServiceUrl(encodedServiceUrl);
8486
loginContext.setInstitutionLogin(institutionLogin);
8587
loginContext.setInstitutionId(institutionId);
88+
loginContext.setUnsupportedInstitutionLogin(unsupportedInstitutionLogin);
8689
loginContext.setOrcidLoginUrl(orcidLoginUrl);
8790
loginContext.setOrcidRedirect(false);
8891
loginContext.setDefaultService(defaultService);
@@ -98,6 +101,8 @@ protected Event doExecute(RequestContext context) {
98101
return error();
99102
} else if (loginContext.isInstitutionLogin()) {
100103
return switchToInstitutionLogin();
104+
} else if (loginContext.isUnsupportedInstitutionLogin()) {
105+
return switchToUnsupportedInstitutionLogin();
101106
} else if (loginContext.isOrcidRedirect()) {
102107
if (StringUtils.isNotBlank(orcidLoginUrl)) {
103108
return autoRedirectToOrcidLogin();
@@ -119,6 +124,11 @@ private String getInstitutionIdFromRequestContext(final RequestContext context)
119124
return StringUtils.isNotBlank(institutionId) ? institutionId : null;
120125
}
121126

127+
private boolean isUnsupportedInstitutionLogin(final RequestContext context) {
128+
final String campaign = context.getRequestParameters().get(PARAMETER_CAMPAIGN);
129+
return StringUtils.isNotBlank(campaign) && PARAMETER_CAMPAIGN_UNSUPPORTED_INSTITUTION_VALUE.equals(campaign.toLowerCase());
130+
}
131+
122132
private boolean isOrcidLoginAutoRedirect(final RequestContext context) {
123133
final String orcidRedirect = context.getRequestParameters().get(PARAMETER_ORCID_REDIRECT);
124134
return StringUtils.isNotBlank(orcidRedirect)
@@ -159,6 +169,10 @@ private Event switchToInstitutionLogin() {
159169
return new Event(this, OsfCasWebflowConstants.TRANSITION_ID_INSTITUTION_LOGIN);
160170
}
161171

172+
private Event switchToUnsupportedInstitutionLogin() {
173+
return new Event(this, OsfCasWebflowConstants.TRANSITION_ID_UNSUPPORTED_INSTITUTION_LOGIN);
174+
}
175+
162176
private Event autoRedirectToOrcidLogin() {
163177
return new Event(this, OsfCasWebflowConstants.TRANSITION_ID_ORCID_LOGIN_AUTO_REDIRECT);
164178
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
package io.cos.cas.osf.web.flow.login;
2+
3+
import lombok.extern.slf4j.Slf4j;
4+
5+
import org.apereo.cas.authentication.adaptive.AdaptiveAuthenticationPolicy;
6+
import org.apereo.cas.web.flow.resolver.CasDelegatingWebflowEventResolver;
7+
import org.apereo.cas.web.flow.resolver.CasWebflowEventResolver;
8+
9+
import org.springframework.webflow.execution.Event;
10+
import org.springframework.webflow.execution.RequestContext;
11+
12+
/**
13+
* This is {@link OsfUnsupportedInstitutionLoginPreparationAction}.
14+
*
15+
* Extends {@link OsfAbstractLoginPreparationAction} to prepare the unsupported institution login page.
16+
*
17+
* @author Longze Chen
18+
* @since 20.1.0
19+
*/
20+
@Slf4j
21+
public class OsfUnsupportedInstitutionLoginPreparationAction extends OsfAbstractLoginPreparationAction {
22+
23+
public OsfUnsupportedInstitutionLoginPreparationAction(
24+
final CasDelegatingWebflowEventResolver initialAuthenticationAttemptWebflowEventResolver,
25+
final CasWebflowEventResolver serviceTicketRequestWebflowEventResolver,
26+
final AdaptiveAuthenticationPolicy adaptiveAuthenticationPolicy
27+
) {
28+
super(
29+
initialAuthenticationAttemptWebflowEventResolver,
30+
serviceTicketRequestWebflowEventResolver,
31+
adaptiveAuthenticationPolicy
32+
);
33+
}
34+
35+
@Override
36+
protected Event doExecute(RequestContext context) {
37+
return success();
38+
}
39+
}

src/main/java/io/cos/cas/osf/web/flow/support/OsfCasWebflowConstants.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,14 @@ public interface OsfCasWebflowConstants {
2020

2121
String ACTION_ID_OSF_INSTITUTION_LOGIN_CHECK = "osfInstitutionLoginCheckAction";
2222

23+
String ACTION_ID_OSF_UNSUPPORTED_INSTITUTION_LOGIN_CHECK = "osfUnsupportedInstitutionLoginCheckAction";
24+
2325
String TRANSITION_ID_USERNAME_PASSWORD_LOGIN = "continueToUsernamePasswordLogin";
2426

2527
String TRANSITION_ID_INSTITUTION_LOGIN = "switchToInstitutionLogin";
2628

29+
String TRANSITION_ID_UNSUPPORTED_INSTITUTION_LOGIN = "switchToUnsupportedInstitutionLogin";
30+
2731
String TRANSITION_ID_ORCID_LOGIN_AUTO_REDIRECT = "autoRedirectToOrcidLogin";
2832

2933
String TRANSITION_ID_DEFAULT_SERVICE_LOGIN_AUTO_REDIRECT = "autoRedirectToDefaultServiceLogin";
@@ -34,8 +38,12 @@ public interface OsfCasWebflowConstants {
3438

3539
String STATE_ID_OSF_INSTITUTION_LOGIN_CHECK = "osfInstitutionLoginCheck";
3640

41+
String STATE_ID_OSF_UNSUPPORTED_INSTITUTION_LOGIN_CHECK = "osfUnsupportedInstitutionLoginCheck";
42+
3743
String VIEW_ID_INSTITUTION_SSO_INIT = "casInstitutionLoginView";
3844

45+
String VIEW_ID_UNSUPPORTED_INSTITUTION_SSO_INIT = "casUnsupportedInstitutionLoginView";
46+
3947
String VIEW_ID_ORCID_LOGIN_AUTO_REDIRECT = "casAutoRedirectToOrcidLoginView";
4048

4149
String VIEW_ID_DEFAULT_SERVICE_LOGIN_AUTO_REDIRECT = "casAutoRedirectToDefaultServiceLoginView";

src/main/java/io/cos/cas/osf/web/support/OsfCasLoginContext.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,8 @@ public class OsfCasLoginContext implements Serializable {
4040

4141
private String institutionId;
4242

43+
private boolean unsupportedInstitutionLogin;
44+
4345
private boolean orcidRedirect;
4446

4547
private String orcidLoginUrl;
@@ -57,6 +59,7 @@ public OsfCasLoginContext (
5759
final String encodedServiceUrl,
5860
final boolean institutionLogin,
5961
final String institutionId,
62+
final boolean unsupportedInstitutionLogin,
6063
final boolean orcidRedirect,
6164
final String orcidLoginUrl,
6265
final boolean defaultService,
@@ -66,6 +69,7 @@ public OsfCasLoginContext (
6669
this.handleErrorName = null;
6770
this.institutionLogin = institutionLogin;
6871
this.institutionId = institutionId;
72+
this.unsupportedInstitutionLogin = unsupportedInstitutionLogin;
6973
this.orcidRedirect = orcidRedirect;
7074
this.orcidLoginUrl = orcidLoginUrl;
7175
this.defaultService = defaultService;

src/main/resources/messages.properties

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -582,11 +582,24 @@ screen.institutionlogin.tips=Sign in through your institution
582582
screen.institutionlogin.message.select=If your institution has partnered with OSF, please select its name below and sign in with your institutional credentials. If you do not currently have an OSF account, this will create one for you.
583583
screen.institutionlogin.message.auto=Your institution has partnered with OSF. Please continue to sign in with your institutional credentials. If you do not currently have an OSF account, this will create one for you.
584584
screen.institutionlogin.heading.select=Select your institution
585+
screen.institutionlogin.select.unsupported=I can't find my institution
585586
screen.institutionlogin.heading.auto=Your institution
586587
screen.institutionlogin.link.select=Not your institution?
587588
screen.institutionlogin.button.submit=Sign in
588589
screen.institutionlogin.osf=Sign in with your OSF account
589590
#
591+
# Unsupported Institution
592+
#
593+
screen.unsupportedinstitutionlogin.heading=I can't find my institution
594+
screen.unsupportedinstitutionlogin.message=Signing into the OSF with institutional credentials is enabled for <a href="{0}">OSF Institutions members</a>. If your institution is not yet an OSFI member, choose one of the following sign-in methods.
595+
screen.unsupportedinstitutionlogin.existing.heading=I already have an OSF account
596+
screen.unsupportedinstitutionlogin.existing.osf.message=If you have already have an OSF password, you can sign in to the OSF
597+
screen.unsupportedinstitutionlogin.existing.osf.button=Sign in with OSF
598+
screen.unsupportedinstitutionlogin.existing.institution.message=If you have not yet set an OSF password for your account, create a password
599+
screen.unsupportedinstitutionlogin.existing.institution.button=Set a password
600+
screen.unsupportedinstitutionlogin.existing.institution.label.email=<span class="accesskey">E</span>mail
601+
screen.unsupportedinstitutionlogin.existing.institution.label.email.accesskey=e
602+
#
590603
# Generic login and logout success page
591604
#
592605
screen.generic.logoutsuccess.title=Signed-out

0 commit comments

Comments
 (0)