Skip to content

Commit

Permalink
Merge pull request IQSS#6192 from CIMMYT/6155-OAuth-2.0-Microsoft
Browse files Browse the repository at this point in the history
IQSS#6155: OAuth 2.0 - Microsoft
  • Loading branch information
kcondon authored Oct 29, 2019
2 parents bbf398c + ab8714e commit 4470369
Show file tree
Hide file tree
Showing 9 changed files with 92 additions and 15 deletions.
Binary file modified doc/Architecture/update-user-account-info.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"id":"microsoft",
"factoryAlias":"oauth2",
"title":"Microsoft",
"subtitle":"",
"factoryData":"type: microsoft | userEndpoint: NONE | clientId: FIXME | clientSecret: FIXME",
"enabled":true
}
14 changes: 8 additions & 6 deletions doc/sphinx-guides/source/installation/oauth2.rst
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
OAuth Login: ORCID, GitHub, Google
==================================
OAuth Login Options
===================

.. contents:: |toctitle|
:local:
Expand All @@ -11,7 +11,7 @@ As explained under "Auth Modes" in the :doc:`config` section, OAuth2 is one of t

`OAuth2 <https://oauth.net/2/>`_ is an authentication protocol that allows systems to share user data, while letting the users control what data is being shared. When you see buttons stating "login with Google" or "login through Facebook", OAuth2 is probably involved. For the purposes of this section, we will shorten "OAuth2" to just "OAuth." OAuth can be compared and contrasted with :doc:`shibboleth`.

Dataverse supports three OAuth providers: `ORCID <http://orcid.org>`_, `GitHub <https://github.com>`_, and `Google <https://console.developers.google.com>`_.
Dataverse supports four OAuth providers: `ORCID <http://orcid.org>`_, `Microsoft Azure Active Directory (AD) <https://docs.microsoft.com/azure/active-directory/>`_, `GitHub <https://github.com>`_, and `Google <https://console.developers.google.com>`_.

Setup
-----
Expand All @@ -24,18 +24,19 @@ Identity Provider Side
Obtain Client ID and Client Secret
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

Before OAuth providers will release information about their users (first name, last name, etc.) to your Dataverse installation, you must request a "Client ID" and "Client Secret" from them. In the case of GitHub and Google, this is as simple as clicking a few buttons and there is no cost associated with using their authentication service. ORCID, on the other hand, does not have an automated system for requesting these credentials, and it is not free to use the ORCID authentication service.
Before OAuth providers will release information about their users (first name, last name, etc.) to your Dataverse installation, you must request a "Client ID" and "Client Secret" from them. In the case of GitHub and Google, this is as simple as clicking a few buttons and there is no cost associated with using their authentication service. ORCID and Microsoft, on the other hand, do not have an automated system for requesting these credentials, and it is not free to use these authentication services.

URLs to help you request a Client ID and Client Secret from the providers supported by Dataverse are provided below. For all of these providers, it's a good idea to request the Client ID and Client secret using a generic account, perhaps the one that's associated with the ``:SystemEmail`` you've configured for Dataverse, rather than your own personal ORCID, GitHub, or Google account:
URLs to help you request a Client ID and Client Secret from the providers supported by Dataverse are provided below. For all of these providers, it's a good idea to request the Client ID and Client secret using a generic account, perhaps the one that's associated with the ``:SystemEmail`` you've configured for Dataverse, rather than your own personal Microsoft Azure AD, ORCID, GitHub, or Google account:

- ORCID: https://orcid.org/content/register-client-application-production-trusted-party
- Microsoft: https://docs.microsoft.com/en-us/azure/active-directory/develop/v1-protocols-oauth-code
- GitHub: https://github.com/settings/applications/new via https://developer.github.com/v3/oauth/
- Google: https://console.developers.google.com/projectselector/apis/credentials via https://developers.google.com/identity/protocols/OAuth2WebServer (pick "OAuth client ID")

Each of these providers will require the following information from you:

- Basic information about your Dataverse installation such as a name, description, URL, logo, privacy policy, etc.
- OAuth2 Redirect URI (ORCID) or Authorization Callback URL (GitHub) or Authorized Redirect URIs (Google): This is the URL on the Dataverse side to which the user will be sent after successfully authenticating with the identity provider. This should be the advertised URL of your Dataverse installation (the protocol, fully qualified domain name, and optional port configured via the ``dataverse.siteUrl`` JVM option mentioned in the :doc:`config` section) appended with ``/oauth2/callback.xhtml`` such as ``https://dataverse.example.edu/oauth2/callback.xhtml``.
- OAuth2 Redirect URI (ORCID) or Redirect URI (Microsoft Azure AD) or Authorization Callback URL (GitHub) or Authorized Redirect URIs (Google): This is the URL on the Dataverse side to which the user will be sent after successfully authenticating with the identity provider. This should be the advertised URL of your Dataverse installation (the protocol, fully qualified domain name, and optional port configured via the ``dataverse.siteUrl`` JVM option mentioned in the :doc:`config` section) appended with ``/oauth2/callback.xhtml`` such as ``https://dataverse.example.edu/oauth2/callback.xhtml``.

When you are finished you should have a Client ID and Client Secret from the provider. Keep them safe and secret.

Expand All @@ -51,6 +52,7 @@ We will ``POST`` a JSON file containing the Client ID and Client Secret to this
- :download:`orcid.json <../_static/installation/files/root/auth-providers/orcid.json>`
- :download:`github.json <../_static/installation/files/root/auth-providers/github.json>`
- :download:`google.json <../_static/installation/files/root/auth-providers/google.json>`
- :download:`microsoft.json <../_static/installation/files/root/auth-providers/microsoft.json>`

Here's how the JSON template for GitHub looks, for example:

Expand Down
9 changes: 5 additions & 4 deletions doc/sphinx-guides/source/user/account.rst
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,13 @@ Dataverse has been configured for one or more of the following log in options:
- Username/Email and Password
- Institutional Log In
- ORCID
- Microsoft Azure AD
- GitHub
- Google

Please note that once you create your Dataverse account, it will be associated with only one of the log in options above.

The Institutional Log In, ORCID, GitHub, and Google options are described in more detail below under "Remote Authentication."
The Institutional Log In, ORCID, Microsoft, GitHub, and Google options are described in more detail below under "Remote Authentication."

Create Account
~~~~~~~~~~~~~~
Expand Down Expand Up @@ -134,10 +135,10 @@ Convert your Dataverse account away from ORCID for log in

If you don't want to log in to Dataverse using ORCID any more, you will want to convert your Dataverse account to the Dataverse Username/Email log in option. To do this, you will need to contact support for the Dataverse installation you are using. On your account page, there is a link that will open a popup form to contact support for assistance.

GitHub and Google Log In
~~~~~~~~~~~~~~~~~~~~~~~~~
Microsoft Azure AD, GitHub, and Google Log In
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

You can also convert your Dataverse account to use authentication provided by GitHub or Google. These options may be found in the "Other options" section of the log in page, and function similarly to how ORCID is outlined above. If you would like to convert your account away from using one of these services for log in, then you can follow the same steps as listed above for converting away from the ORCID log in.
You can also convert your Dataverse account to use authentication provided by GitHub, Microsoft, or Google. These options may be found in the "Other options" section of the log in page, and function similarly to how ORCID is outlined above. If you would like to convert your account away from using one of these services for log in, then you can follow the same steps as listed above for converting away from the ORCID log in.

My Data
-------
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import edu.harvard.iq.dataverse.authorization.providers.oauth2.impl.GitHubOAuth2AP;
import edu.harvard.iq.dataverse.authorization.providers.oauth2.impl.GoogleOAuth2AP;
import edu.harvard.iq.dataverse.authorization.providers.oauth2.impl.OrcidOAuth2AP;
import edu.harvard.iq.dataverse.authorization.providers.oauth2.impl.MicrosoftOAuth2AP;
import edu.harvard.iq.dataverse.authorization.providers.shib.ShibAuthenticationProvider;
import edu.harvard.iq.dataverse.authorization.providers.shib.ShibAuthenticationProviderFactory;
import edu.harvard.iq.dataverse.authorization.users.ApiToken;
Expand Down Expand Up @@ -880,13 +881,15 @@ public AuthenticatedUser canLogInAsBuiltinUser(String username, String password)
public List<String> getAuthenticationProviderIdsSorted() {
GitHubOAuth2AP github = new GitHubOAuth2AP(null, null);
GoogleOAuth2AP google = new GoogleOAuth2AP(null, null);
MicrosoftOAuth2AP microsoft = new MicrosoftOAuth2AP(null, null);
return Arrays.asList(
BuiltinAuthenticationProvider.PROVIDER_ID,
ShibAuthenticationProvider.PROVIDER_ID,
OrcidOAuth2AP.PROVIDER_ID_PRODUCTION,
OrcidOAuth2AP.PROVIDER_ID_SANDBOX,
github.getId(),
google.getId()
google.getId(),
microsoft.getId()
);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -127,19 +127,20 @@ public OAuth2UserRecord getUserRecord(String code, @NotNull OAuth20Service servi
throws IOException, OAuth2Exception, InterruptedException, ExecutionException {

OAuth2AccessToken accessToken = service.getAccessToken(code);

//final String userEndpoint = getUserEndpoint(accessToken);
// We need to check if scope is null first: GitHub is used without scope, so the responses scope is null.
// Checking scopes via Stream to be independent from order.
if ( ( accessToken.getScope() != null && ! getScope().stream().allMatch(accessToken.getScope()::contains) ) ||
( accessToken.getScope() == null && ! getSpacedScope().isEmpty() ) ) {
// We did not get the permissions on the scope(s) we need. Abort and inform the user.
throw new OAuth2Exception(200, BundleUtil.getStringFromBundle("auth.providers.insufficientScope", Arrays.asList(this.getTitle())), "");
}

OAuthRequest request = new OAuthRequest(Verb.GET, getUserEndpoint(accessToken));
request.setCharset("UTF-8");
if (id.equals("microsoft")) {
request.addHeader("Accept", "application/json");
}
service.signRequest(accessToken, request);

Response response = service.execute(request);
int responseCode = response.getCode();
String body = response.getBody();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import edu.harvard.iq.dataverse.authorization.providers.oauth2.impl.GitHubOAuth2AP;
import edu.harvard.iq.dataverse.authorization.providers.oauth2.impl.GoogleOAuth2AP;
import edu.harvard.iq.dataverse.authorization.providers.oauth2.impl.OrcidOAuth2AP;
import edu.harvard.iq.dataverse.authorization.providers.oauth2.impl.MicrosoftOAuth2AP;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
Expand All @@ -31,6 +32,7 @@ public OAuth2AuthenticationProviderFactory() {
builders.put("github", (row, data) -> readRow(row, new GitHubOAuth2AP(data.get("clientId"), data.get("clientSecret"))));
builders.put("google", (row, data) -> readRow(row, new GoogleOAuth2AP(data.get("clientId"), data.get("clientSecret"))));
builders.put("orcid", (row, data) -> readRow(row, new OrcidOAuth2AP(data.get("clientId"), data.get("clientSecret"), data.get("userEndpoint"))));
builders.put("microsoft", (row, data) -> readRow(row, new MicrosoftOAuth2AP(data.get("clientId"), data.get("clientSecret"))));
}

@Override
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package edu.harvard.iq.dataverse.authorization.providers.oauth2.impl;

import com.github.scribejava.apis.MicrosoftAzureActiveDirectory20Api;
import com.github.scribejava.core.builder.api.DefaultApi20;
import edu.harvard.iq.dataverse.authorization.providers.oauth2.AbstractOAuth2AuthenticationProvider;

import java.util.Arrays;
import java.util.Collections;
import java.util.logging.Logger;
import java.io.StringReader;
import javax.json.Json;
import javax.json.JsonObject;
import javax.json.JsonReader;
import edu.harvard.iq.dataverse.authorization.AuthenticatedUserDisplayInfo;

/**
*
* @author
*/
public class MicrosoftOAuth2AP extends AbstractOAuth2AuthenticationProvider{

private static final Logger logger = Logger.getLogger(MicrosoftOAuth2AP.class.getCanonicalName());

public MicrosoftOAuth2AP(String aClientId, String aClientSecret){
this.id = "microsoft";
this.title = "Microsoft";
this.clientId = aClientId;
this.clientSecret = aClientSecret;
this.scope = Arrays.asList("User.Read");
this.baseUserEndpoint = "https://graph.microsoft.com/v1.0/me";
}

@Override
public DefaultApi20 getApiInstance(){
return MicrosoftAzureActiveDirectory20Api.instance();
}

@Override
protected ParsedUserResponse parseUserResponse(final String responseBody) {
try ( StringReader rdr = new StringReader(responseBody);
JsonReader jrdr = Json.createReader(rdr) ) {
JsonObject response = jrdr.readObject();
AuthenticatedUserDisplayInfo displayInfo = new AuthenticatedUserDisplayInfo(
response.getString("givenName", ""),
response.getString("surname", ""),
response.getString("userPrincipalName", ""),
"", "");
String persistentUserId = response.getString("id");
String username = response.getString("userPrincipalName");
return new ParsedUserResponse(displayInfo, persistentUserId, username,
(displayInfo.getEmailAddress().length() > 0 ? Collections.singletonList(displayInfo.getEmailAddress()) : Collections.emptyList() )
);
}
}

public boolean isDisplayIdentifier()
{
return false;
}
}
2 changes: 1 addition & 1 deletion src/main/java/propertyFiles/Bundle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -393,7 +393,7 @@ oauth2.convertAccount.success=Your Dataverse account is now associated with your

# oauth2/callback.xhtml
oauth2.callback.page.title=OAuth Callback
oauth2.callback.message=<strong>Authentication Error</strong> - Dataverse could not authenticate your ORCID login. Please make sure you authorize your ORCID account to connect with Dataverse. For more details about the information being requested, see the <a href="{0}/{1}/user/account.html#orcid-log-in" title="ORCID Log In - Dataverse User Guide" target="_blank">User Guide</a>.
oauth2.callback.message=<strong>Authentication Error</strong> - Dataverse could not authenticate your login with the provider that you selected. Please make sure you authorize your account to connect with Dataverse. For more details about the information being requested, see the <a href="{0}/{1}/user/account.html#remote-authentication" title="Remote Authentication - Dataverse User Guide" target="_blank">User Guide</a>.

# tab on dataverseuser.xhtml
apitoken.title=API Token
Expand Down

0 comments on commit 4470369

Please sign in to comment.