Skip to content

Commit f0abe94

Browse files
authored
Merge pull request #11502 from IQSS/11404-shib-login-mdq
Login page and Shibboleth auth refactored to work with new InCommon services
2 parents 9dd6f07 + a507380 commit f0abe94

File tree

12 files changed

+475
-55
lines changed

12 files changed

+475
-55
lines changed
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
### For Dataverse instances that use Shibboleth as members of InCommon federation
2+
3+
Please note that most of the known Dataverse instances that support Shibboleth logins do so without being part of InCommon, and therefore are not affected. All such instances will be able to continue using the old login workflow without needing to make any configuration changes.
4+
5+
For the relatively few instances using InCommon: Since InCommon discontinued their old-style federation metadata feed, a new Shibboleth implementation has been added to utilize the recommended replacements: the MDQ protocol and the WayFinder service. In order to continue using InCommon, such instances will need to modify their shibd configuration and their registration with Incommon, plus set a new feature flag. See the upgrade instructions for details.
6+
7+
8+
### New Settings
9+
10+
- dataverse.feature.shibboleth-use-wayfinder
11+
- dataverse.feature.shibboleth-use-localhost
12+
13+
### For the Upgrade Instruction:
14+
15+
[(strip this from the real release note) this should be the very last of the optional upgrade steps; as it's been pointed out to me that there may not be any instances affected by this aside from Harvard and UNC, both of which have been active participants in the development of the underlying code and the upgrade process below. ... which kind of makes including them in the release note somewhat unnecessary (?). but I figure we should include them anyway, in case there's an instance out there we are not aware of. - L.A.]
16+
17+
18+
If your instance is offering institutional Shibboleth logins as part of the InCommon federation, you must make some changes to your service configuration:
19+
20+
Note that if your Dataverse instance is using Shibboleth outside of InCommon, your login workflow should continue working unchanged, so please skip this section.
21+
22+
a. Configure your Service Provider (SP) in the InCommon Federation Manager to use WayFinder following [their instructions](https://spaces.at.internet2.edu/display/federation/how-to-configure-service-to-use-wayfinder).
23+
24+
b. Reconfigure your locally-running `shibd` service to use WayFinder and the new MDQ metadata retrieval protocol.
25+
Download and place the new [production signing key](https://spaces.at.internet2.edu/display/MDQ/production-mdq-signing-key) in `/etc/shibboleth` and name it `inc-md-cert-mdq.pem`.
26+
Change the `SSO` and `MetadataProvider` sections of the `/etc/shibboleth/shibboleth2.xml` configuration file as follows:
27+
28+
```
29+
<SSO discoveryProtocol="SAMLDS" discoveryURL="https://wayf.incommonfederation.org/DS/WAYF">
30+
SAML2 SAML1
31+
</SSO>
32+
```
33+
and
34+
```
35+
<MetadataProvider type="MDQ" id="incommon" ignoreTransport="true" cacheDirectory="inc-mdq-cache"
36+
maxCacheDuration="86400" minCacheDuration="60" baseUrl="https://mdq.incommon.org/">
37+
<MetadataFilter type="Signature" certificate="inc-md-cert-mdq.pem"/>
38+
<MetadataFilter type="RequireValidUntil" maxValidityInterval="1209600"/>
39+
</MetadataProvider>
40+
```
41+
See [How to configure a Shibboleth service provider (SP) to use MDQ](https://spaces.at.internet2.edu/display/MDQ/how-to-configure-shib-sp-to-use-mdq) for more information.
42+
43+
c. Set the feature flag `dataverse.feature.shibboleth-use-wayfinder=true`.
44+

doc/sphinx-guides/source/_static/installation/files/etc/shibboleth/shibboleth2.xml

Lines changed: 9 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,12 @@ https://wiki.shibboleth.net/confluence/display/SHIB2/NativeSPConfiguration
2323
<SSO>
2424
SAML2 SAML1
2525
</SSO>
26+
<!-- If you are planning to use Shibboleth as a member of the InCommon federation, -->
27+
<!-- comment out the section above, and un-comment out the following, in order to -->
28+
<!-- use the new WayFinder service for the login page workflow: -->
29+
<!-- SSO discoveryProtocol="SAMLDS" discoveryURL="https://wayf.incommonfederation.org/DS/WAYF">
30+
SAML2 SAML1
31+
</SSO -->
2632

2733
<!-- SAML and local-only logout. -->
2834
<!-- https://wiki.shibboleth.net/confluence/display/SHIB2/NativeSPServiceLogout -->
@@ -58,19 +64,9 @@ https://wiki.shibboleth.net/confluence/display/SHIB2/NativeSPConfiguration
5864
<MetadataProvider type="XML" path="dataverse-idp-metadata.xml" backingFilePath="local-idp-metadata.xml" legacyOrgNames="true" reloadInterval="7200"/>
5965
<!-- Uncomment to enable all the Research & Scholarship IdPs from InCommon -->
6066
<!--
61-
<MetadataProvider type="XML" url="http://md.incommon.org/InCommon/InCommon-metadata.xml" backingFilePath="InCommon-metadata.xml" maxRefreshDelay="3600">
62-
<DiscoveryFilter type="Whitelist" matcher="EntityAttributes">
63-
<saml:Attribute
64-
Name="http://macedir.org/entity-category-support"
65-
NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:uri">
66-
<saml:AttributeValue>http://id.incommon.org/category/research-and-scholarship</saml:AttributeValue>
67-
</saml:Attribute>
68-
<saml:Attribute
69-
Name="http://macedir.org/entity-category-support"
70-
NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:uri">
71-
<saml:AttributeValue>http://refeds.org/category/research-and-scholarship</saml:AttributeValue>
72-
</saml:Attribute>
73-
</DiscoveryFilter>
67+
<MetadataProvider type="MDQ" id="incommon" ignoreTransport="true" cacheDirectory="inc-mdq-cache" maxCacheDuration="86400" minCacheDuration="60" baseUrl="https://mdq.incommon.org/">
68+
<MetadataFilter type="Signature" certificate="inc-md-cert-mdq.pem"/>
69+
<MetadataFilter type="RequireValidUntil" maxValidityInterval="1209600"/>
7470
</MetadataProvider>
7571
-->
7672

doc/sphinx-guides/source/installation/config.rst

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3791,6 +3791,12 @@ please find all known feature flags below. Any of these flags can be activated u
37913791
* - enable-version-note
37923792
- Turns on the ability to add/view/edit/delete per-dataset-version notes intended to provide :ref:`provenance` information about why the dataset/version was created.
37933793
- ``Off``
3794+
* - shibboleth-use-wayfinder
3795+
- This flag allows an instance to use Shibboleth with InCommon federation services. Our original Shibboleth implementation that relies on DiscoFeed can no longer be used since InCommon discontinued their old-style metadata feed. An alternative mechanism had to be implemented in order to use WayFinder service, their recommended replacements, instead.
3796+
- ``Off``
3797+
* - shibboleth-use-localhost
3798+
- A Shibboleth-using Dataverse instance needs to make network calls to the locally-running ``shibd`` service. The default behavior is to use the address configured via the ``siteUrl`` setting. There are however situations (firewalls, etc.) where localhost would be preferable.
3799+
- ``Off``
37943800

37953801
**Note:** Feature flags can be set via any `supported MicroProfile Config API source`_, e.g. the environment variable
37963802
``DATAVERSE_FEATURE_XXX`` (e.g. ``DATAVERSE_FEATURE_API_SESSION_AUTH=1``). These environment variables can be set in your shell before starting Payara. If you are using :doc:`Docker for development </container/dev-usage>`, you can set them in the `docker compose <https://docs.docker.com/compose/environment-variables/set-environment-variables/>`_ file.

doc/sphinx-guides/source/installation/shibboleth.rst

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,8 @@ Rather than or in addition to specifying individual Identity Providers (see :ref
159159

160160
For example, in the United States, you would register your Dataverse installation with `InCommon <https://incommon.org>`_. For a list of federations around the world, see `REFEDS (the Research and Education FEDerations group) <https://refeds.org/federations>`_. The details of how to register with an identity federation are out of scope for this document.
161161

162+
If you are planning to use InCommon, please note that ``shibd`` needs to be configured to use the new MDQ protocol and WayFinder `service <https://spaces.at.internet2.edu/display/federation/incommon-wayfinder-announcement>`_ `announced <https://lists.incommon.org/sympa/arc/inc-ops-notifications/2024-04/msg00000.html>`_ `by <https://incommon.org/news/incommon-federation-service-enhancements/>`_ InCommon. The sample ``shibboleth2.xml`` provided already contains commented-out sections pre-configured to work with this new InCommon framework. Please see https://spaces.at.internet2.edu/display/MDQ/how-to-configure-shib-sp-to-use-mdq and https://spaces.at.internet2.edu/display/federation/how-to-configure-service-to-use-wayfinder for more information. You will also need to set the feature flag ``dataverse.feature.shibboleth-use-wayfinder=true`` (see :ref:`feature-flags`).
163+
162164
For a successful login to Dataverse, certain :ref:`shibboleth-attributes` must be released by the Identity Provider (IdP). Otherwise, in the federation context, users will have the frustrating experience of selecting their IdP in the list but then getting an error like ``Problem with Identity Provider – The SAML assertion for "eppn" was null``. We definitely want to prevent this! There's even some guidance about this problem in the User Guide under the heading :ref:`fix-shib-login` that links back here.
163165

164166
For InCommon, a decent strategy for ensuring that IdPs release the necessary attributes is to have both the SP (your Dataverse installation) and the IdP (there are many of these around the world) join the Research & Scholarship (R&S) category. The `R&S website <https://incommon.org/federation/research-and-scholarship/>`_ explains the R&S dream well:
@@ -259,14 +261,23 @@ On CentOS 6:
259261

260262
``chkconfig shibd on``
261263

262-
Verify DiscoFeed and Metadata URLs
263-
----------------------------------
264+
Verify the Metadata URL
265+
-----------------------
264266

265-
As a sanity check, visit the following URLs (substituting your hostname) to make sure you see JSON and XML:
267+
Substitute your hostname and verify that you are seeing your service provider metadata in XML format:
266268

267-
- https://dataverse.example.edu/Shibboleth.sso/DiscoFeed
268269
- https://dataverse.example.edu/Shibboleth.sso/Metadata
269270

271+
272+
If Your Instance is Using Discofeed: Verify DiscoFeed URL
273+
---------------------------------------------------------
274+
275+
As another sanity check, substitute your hostname and make sure you see well-formed JSON:
276+
277+
- https://dataverse.example.edu/Shibboleth.sso/DiscoFeed
278+
279+
(Skip this step if you'll be using Shibboleth as a registered member of InCommon federation, since the DiscoFeed will not be part of the workflow.)
280+
270281
The JSON in ``DiscoFeed`` comes from the list of IdPs you configured in the ``MetadataProvider`` section of ``shibboleth2.xml`` and will form a dropdown list on the Login Page.
271282

272283
Add the Shibboleth Authentication Provider to Your Dataverse Installation

src/main/java/edu/harvard/iq/dataverse/LoginPage.java

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,9 @@
99
import edu.harvard.iq.dataverse.authorization.exceptions.AuthenticationFailedException;
1010
import edu.harvard.iq.dataverse.authorization.providers.builtin.BuiltinAuthenticationProvider;
1111
import edu.harvard.iq.dataverse.authorization.providers.builtin.BuiltinUserServiceBean;
12+
import edu.harvard.iq.dataverse.authorization.providers.shib.ShibServiceBean;
1213
import edu.harvard.iq.dataverse.authorization.users.AuthenticatedUser;
14+
import edu.harvard.iq.dataverse.settings.FeatureFlags;
1315
import edu.harvard.iq.dataverse.settings.SettingsServiceBean;
1416
import edu.harvard.iq.dataverse.util.BundleUtil;
1517
import edu.harvard.iq.dataverse.util.JsfHelper;
@@ -92,6 +94,9 @@ public enum EditMode {LOGIN, SUCCESS, FAILED};
9294
@EJB
9395
SystemConfig systemConfig;
9496

97+
@EJB
98+
ShibServiceBean shibService;
99+
95100
@Inject
96101
DataverseRequestServiceBean dvRequestService;
97102

@@ -254,6 +259,37 @@ public String getRedirectPage() {
254259
public void setRedirectPage(String redirectPage) {
255260
this.redirectPage = redirectPage;
256261
}
262+
263+
/*
264+
* Starting v6.7 the default Shibboleth login mechanism is to use the new
265+
* Wayfinder service from InCommon. We no longer use the javascript from the
266+
* idp package to generate the list of participating institutions and then
267+
* generate the redirect url to the auth. service they choose. Under the new
268+
* model the user is redirected to InCommon and the workflow of picking
269+
* their institutional auth. service provider will be handled there.
270+
*/
271+
public String getShibWayfinderRedirect() {
272+
String wayFinderUrl = shibService.getWayfinderRedirectUrl();
273+
logger.fine("wayfinder url provided by the shib service: " + wayFinderUrl);
274+
// In order to produce a complete url, we need to add the final redirect
275+
// parameter (this will be the FOURTH redirect in the shib. authentication
276+
// loop), the redirectPage= pointing to the final destination Dataverse
277+
// page. Note the corresponding multiple-level URL encoding involved.
278+
String finalRedirectUrl = wayFinderUrl
279+
+ "%253FredirectPage%253D"
280+
+ getRedirectPage().replaceAll("/", "%25252F").replaceAll("=", "%25253D").replaceAll("\\?", "%25253F").replaceAll("&", "%252526");
281+
logger.fine("final redirect url: " + finalRedirectUrl);
282+
return finalRedirectUrl;
283+
}
284+
285+
/*
286+
* An instance that uses Shibboleth as part of InCommon can switch to using
287+
* the new login workflow that relies on WayFinder and MDQ.
288+
*/
289+
public boolean isShibbolethUseWayFinder() {
290+
return FeatureFlags.SHIBBOLETH_USE_WAYFINDER.enabled();
291+
}
292+
257293

258294
public AuthenticationProvider getAuthProvider() {
259295
return authProvider;

src/main/java/edu/harvard/iq/dataverse/authorization/AuthenticationProvidersRegistrationServiceBean.java

Lines changed: 113 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,12 @@
1717
import edu.harvard.iq.dataverse.authorization.providers.oauth2.OAuth2AuthenticationProviderFactory;
1818
import edu.harvard.iq.dataverse.authorization.providers.oauth2.impl.OrcidOAuth2AP;
1919
import edu.harvard.iq.dataverse.authorization.providers.oauth2.oidc.OIDCAuthenticationProviderFactory;
20+
import edu.harvard.iq.dataverse.authorization.providers.shib.ShibAuthenticationProvider;
2021
import edu.harvard.iq.dataverse.authorization.providers.shib.ShibAuthenticationProviderFactory;
22+
import edu.harvard.iq.dataverse.settings.FeatureFlags;
2123
import edu.harvard.iq.dataverse.settings.JvmSettings;
24+
import edu.harvard.iq.dataverse.settings.SettingsServiceBean;
25+
import edu.harvard.iq.dataverse.util.SystemConfig;
2226
import edu.harvard.iq.dataverse.validation.PasswordValidatorServiceBean;
2327
import java.util.HashMap;
2428
import java.util.Map;
@@ -33,6 +37,16 @@
3337
import jakarta.inject.Named;
3438
import jakarta.persistence.EntityManager;
3539
import jakarta.persistence.PersistenceContext;
40+
import java.io.IOException;
41+
import java.io.InputStream;
42+
import java.io.InputStreamReader;
43+
import java.net.HttpURLConnection;
44+
import java.net.MalformedURLException;
45+
import java.net.URL;
46+
import javax.xml.stream.XMLInputFactory;
47+
import javax.xml.stream.XMLStreamConstants;
48+
import javax.xml.stream.XMLStreamException;
49+
import javax.xml.stream.XMLStreamReader;
3650

3751
/**
3852
*
@@ -63,6 +77,9 @@ public class AuthenticationProvidersRegistrationServiceBean {
6377
@EJB
6478
AuthenticationServiceBean authenticationService;
6579

80+
@EJB
81+
SettingsServiceBean settingsService;
82+
6683
/**
6784
* The maps below (the objects themselves) are "final", but the
6885
* values will be populated in @PostConstruct (see below) during
@@ -117,7 +134,32 @@ public void startup() {
117134
.getResultList().forEach((row) -> {
118135
if(row.isEnabled()) {
119136
try {
120-
registerProvider( loadProvider(row) );
137+
AuthenticationProvider authProvider = loadProvider(row);
138+
139+
registerProvider( authProvider );
140+
141+
// For production Shibboleth instances that are not using
142+
// the legacy DiscoFeed-based workflow, we need to call
143+
// shibd to look up and cache its entityID, since it will
144+
// be needed in order to issue WayFinder service redirects.
145+
146+
if ("shib".equals(authProvider.getId())
147+
&& FeatureFlags.SHIBBOLETH_USE_WAYFINDER.enabled()) {
148+
// ... is this a prod. shibboleth instance?
149+
String shibTypeSetting = settingsService.getValueForKey(SettingsServiceBean.Key.DebugShibAccountType, null);
150+
boolean isProduction = shibTypeSetting == null || shibTypeSetting.equals("PRODUCTION");
151+
152+
if (isProduction) {
153+
String spEntityId = lookupShibbolethEntityId();
154+
logger.info("Looked up the entityId of the shibboleth service provider (via a call to shibd): "
155+
+ spEntityId);
156+
if (spEntityId == null) {
157+
// we'll make this educated guess - it may or may not help us later on:
158+
spEntityId = SystemConfig.getDataverseSiteUrlStatic() + "/sp";
159+
}
160+
((ShibAuthenticationProvider) authProvider).setServiceProviderEntityId(spEntityId);
161+
}
162+
}
121163

122164
} catch ( AuthenticationProviderFactoryNotFoundException e ) {
123165
logger.log(Level.SEVERE, "Cannot find authentication provider factory with alias '" + e.getFactoryAlias() + "'",e);
@@ -307,5 +349,75 @@ public boolean isOrcidEnabled() {
307349
return oAuth2authenticationProviders.values().stream().anyMatch( s -> s.getId().toLowerCase().contains("orcid") );
308350
}
309351
*/
352+
353+
private String lookupShibbolethEntityId() {
354+
355+
String baseUrl;
356+
if (FeatureFlags.SHIBBOLETH_USE_LOCALHOST.enabled()) {
357+
baseUrl = "http://localhost";
358+
} else {
359+
baseUrl = SystemConfig.getDataverseSiteUrlStatic();
360+
}
361+
362+
String urlString = baseUrl + "/Shibboleth.sso/Metadata";
363+
364+
URL url = null;
365+
try {
366+
url = new URL(urlString);
367+
} catch (MalformedURLException ex) {
368+
logger.warning(ex.toString());
369+
return null;
370+
}
371+
372+
if (url == null) {
373+
logger.warning("url object was null after parsing " + urlString);
374+
return null;
375+
}
376+
377+
HttpURLConnection metadataRequest = null;
378+
try {
379+
metadataRequest = (HttpURLConnection) url.openConnection();
380+
} catch (IOException ex) {
381+
logger.warning(ex.toString());
382+
return null;
383+
}
384+
if (metadataRequest == null) {
385+
logger.warning("http request was null for a local /Shibboleth.sso/Metadata call");
386+
return null;
387+
}
388+
try {
389+
metadataRequest.connect();
390+
} catch (IOException ex) {
391+
logger.warning(ex.toString());
392+
return null;
393+
}
394+
395+
XMLStreamReader xmlr = null;
396+
397+
try {
398+
XMLInputFactory xmlFactory = javax.xml.stream.XMLInputFactory.newInstance();
399+
xmlr = xmlFactory.createXMLStreamReader(new InputStreamReader((InputStream) metadataRequest.getInputStream()));
400+
401+
while ( xmlr.next() == XMLStreamConstants.COMMENT);
402+
xmlr.require(XMLStreamConstants.START_ELEMENT, null, "EntityDescriptor");
403+
404+
return xmlr.getAttributeValue(null, "entityID");
405+
406+
} catch (IOException ioex) {
407+
logger.warning("IOException instantiating a stream reader of the /Shibboleth.sso/Metadata output" + ioex.getMessage());
408+
} catch (XMLStreamException xsex) {
409+
logger.warning("Failed to parse the xml output of the /Shibboleth.sso/Metadata; " + xsex.getMessage());
410+
} finally {
411+
if (xmlr != null) {
412+
try {
413+
logger.fine("closing xml reader");
414+
xmlr.close();
415+
} catch (XMLStreamException xsex) {
416+
// we don't care
417+
}
418+
}
419+
}
420+
return null;
421+
}
310422

311423
}

src/main/java/edu/harvard/iq/dataverse/authorization/providers/builtin/DataverseUserPage.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -213,7 +213,7 @@ public String init() {
213213
} else {
214214
return permissionsWrapper.notAuthorized();
215215
}
216-
216+
217217
return "";
218218
}
219219

0 commit comments

Comments
 (0)