-
Notifications
You must be signed in to change notification settings - Fork 14
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #1361 from research-software-directory/1359-linked…
…in-auth 1359 linkedin auth
- Loading branch information
Showing
13 changed files
with
286 additions
and
84 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -73,10 +73,10 @@ RSD_ENVIRONMENT=prod | |
# consumed by services: authentication, frontend (api/fe) | ||
# provide a list of supported OpenID auth providers | ||
# the values should be separated by semicolon (;) | ||
# Allowed values are: SURFCONEXT, HELMHOLTZID, ORCID or LOCAL | ||
# Allowed values are: SURFCONEXT, HELMHOLTZID, ORCID, AZURE, LINKEDIN or LOCAL | ||
# if env value is not provided default provider is set to be SURFCONEXT | ||
# if you add the value "LOCAL", then local accounts are enabled, USE THIS FOR TESTING PURPOSES ONLY | ||
RSD_AUTH_PROVIDERS=SURFCONEXT;ORCID;AZURE;LOCAL | ||
RSD_AUTH_PROVIDERS=SURFCONEXT;ORCID;AZURE;LINKEDIN;LOCAL | ||
|
||
# consumed by services: authentication, frontend (api/fe) | ||
# provide a list of supported OpenID auth providers for coupling with the user's RSD account | ||
|
@@ -91,27 +91,27 @@ RSD_AUTH_COUPLE_PROVIDERS=ORCID | |
#[email protected];[email protected] | ||
|
||
# SURFCONEXT - TEST ENVIRONMENT | ||
# consumed by: authentication, frontend/utils/loginHelpers | ||
# consumed by: authentication, frontend/pages/api/fe/auth/ | ||
SURFCONEXT_CLIENT_ID=www.research-software.nl | ||
# consumed by: authentication, frontend/utils/loginHelpers | ||
# consumed by: authentication, frontend/pages/api/fe/auth/ | ||
SURFCONEXT_REDIRECT=http://localhost/auth/login/surfconext | ||
# consumed by: authentication, frontend/utils/loginHelpers | ||
# consumed by: authentication, frontend/pages/api/fe/auth/ | ||
SURFCONEXT_WELL_KNOWN_URL=https://connect.test.surfconext.nl/.well-known/openid-configuration | ||
# consumed by: authentication, frontend/utils/loginHelpers | ||
# consumed by: authentication, frontend/pages/api/fe/auth/ | ||
SURFCONEXT_SCOPES=openid | ||
# consumed by: frontend/utils/loginHelpers | ||
# consumed by: frontend/pages/api/fe/auth/ | ||
SURFCONEXT_RESPONSE_MODE=form_post | ||
|
||
# Helmholtz ID | ||
# consumed by: authentication, frontend/utils/loginHelpers | ||
# consumed by: authentication, frontend/pages/api/fe/auth/ | ||
HELMHOLTZID_CLIENT_ID=rsd-dev | ||
# consumed by: authentication, frontend/utils/loginHelpers | ||
# consumed by: authentication, frontend/pages/api/fe/auth/ | ||
HELMHOLTZID_REDIRECT=http://localhost/auth/login/HELMHOLTZID | ||
# consumed by: authentication, frontend/utils/loginHelpers | ||
# consumed by: authentication, frontend/pages/api/fe/auth/ | ||
HELMHOLTZID_WELL_KNOWN_URL=https://login-dev.helmholtz.de/oauth2/.well-known/openid-configuration | ||
# consumed by: authentication, frontend/utils/loginHelpers | ||
# consumed by: authentication, frontend/pages/api/fe/auth/ | ||
HELMHOLTZID_SCOPES=openid+profile+email+eduperson_principal_name | ||
# consumed by: frontend/utils/loginHelpers | ||
# consumed by: frontend/pages/api/fe/auth/ | ||
HELMHOLTZID_RESPONSE_MODE=query | ||
# consumed by: authentication | ||
# uncomment if you want to allow users from non-Helmholtz centres or social IdPs: | ||
|
@@ -123,29 +123,29 @@ HELMHOLTZID_RESPONSE_MODE=query | |
# HELMHOLTZID_ALLOW_LIST= | ||
|
||
# ORCID | ||
# consumed by: authentication, frontend/utils/loginHelpers | ||
# consumed by: authentication, frontend/pages/api/fe/auth/ | ||
ORCID_CLIENT_ID=APP-4D4D69ASWTYOI9QI | ||
# consumed by: authentication, frontend/utils/loginHelpers | ||
# consumed by: authentication, frontend/pages/api/fe/auth/ | ||
ORCID_REDIRECT=http://www.localhost/auth/login/orcid | ||
# consumed by: authentication, frontend/utils/loginHelpers | ||
# consumed by: authentication, frontend/pages/api/fe/auth/ | ||
ORCID_REDIRECT_COUPLE=http://www.localhost/auth/couple/orcid | ||
# consumed by: authentication, frontend/utils/loginHelpers | ||
# consumed by: authentication, frontend/pages/api/fe/auth/ | ||
ORCID_WELL_KNOWN_URL=https://sandbox.orcid.org/.well-known/openid-configuration | ||
# consumed by: authentication, frontend/utils/loginHelpers | ||
# consumed by: authentication, frontend/pages/api/fe/auth/ | ||
ORCID_SCOPES=openid | ||
# consumed by: frontend/utils/loginHelpers | ||
# consumed by: frontend/pages/api/fe/auth/ | ||
ORCID_RESPONSE_MODE=query | ||
|
||
# AZURE ACTIVE DIRECTORY | ||
# consumed by: authentication, frontend/utils/loginHelpers | ||
# consumed by: authentication, frontend/pages/api/fe/auth/ | ||
AZURE_CLIENT_ID= | ||
# consumed by: authentication, frontend/utils/loginHelpers | ||
# consumed by: authentication, frontend/pages/api/fe/auth/ | ||
AZURE_REDIRECT=http://localhost/auth/login/azure | ||
# consumed by: authentication, frontend/utils/loginHelpers | ||
# consumed by: authentication, frontend/pages/api/fe/auth/ | ||
AZURE_WELL_KNOWN_URL= | ||
# consumed by: authentication, frontend/utils/loginHelpers | ||
# consumed by: authentication, frontend/pages/api/fe/auth/ | ||
AZURE_SCOPES=openid+email+profile | ||
# consumed by: authentication, frontend/utils/loginHelpers | ||
# consumed by: authentication, frontend/pages/api/fe/auth/ | ||
AZURE_LOGIN_PROMPT=select_account | ||
# consumed by: frontend | ||
# the name displayed to users when multiple providers are configured | ||
|
@@ -157,6 +157,17 @@ AZURE_DESCRIPTION_HTML="Sign in with your institutional credentials" | |
# the organisation recorded for users logged in via this provider | ||
AZURE_ORGANISATION= | ||
|
||
# consumed by: authentication, frontend/pages/api/fe/auth/ | ||
LINKEDIN_CLIENT_ID= | ||
# consumed by: authentication, frontend/pages/api/fe/auth/ | ||
LINKEDIN_REDIRECT=http://localhost/auth/login/linkedin | ||
# consumed by: authentication, frontend/pages/api/fe/auth/ | ||
LINKEDIN_WELL_KNOWN_URL=https://www.linkedin.com/oauth/.well-known/openid-configuration | ||
|
||
|
||
# ---- PUBLIC SCRAPER ENV VARIABLES ------------- | ||
|
||
# max requests to the GitHub API per run, runs 10 times per hour | ||
# optional, comment out if not available, a default of 6 will be used | ||
# consumed by: scrapers | ||
|
@@ -211,6 +222,10 @@ AUTH_ORCID_CLIENT_SECRET= | |
# consumed by services: authentication | ||
AUTH_AZURE_CLIENT_SECRET= | ||
|
||
# consumed by services: authentication | ||
AUTH_LINKEDIN_CLIENT_SECRET= | ||
|
||
# consumed by: scrapers | ||
# optional, comment out if not available, should be of the form username:token | ||
# obtain the secret from GITHUB dashboard | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,6 +1,6 @@ | ||
// SPDX-FileCopyrightText: 2022 - 2024 Ewan Cahen (Netherlands eScience Center) <[email protected]> | ||
// SPDX-FileCopyrightText: 2022 - 2024 Helmholtz Centre Potsdam - GFZ German Research Centre for Geosciences | ||
// SPDX-FileCopyrightText: 2022 - 2024 Netherlands eScience Center | ||
// SPDX-FileCopyrightText: 2022 - 2025 Ewan Cahen (Netherlands eScience Center) <[email protected]> | ||
// SPDX-FileCopyrightText: 2022 - 2025 Netherlands eScience Center | ||
// SPDX-FileCopyrightText: 2022 Dusan Mijatovic (dv4all) | ||
// SPDX-FileCopyrightText: 2022 Matthias Rüster (GFZ) <[email protected]> | ||
// SPDX-FileCopyrightText: 2022 dv4all | ||
|
@@ -26,10 +26,10 @@ private Config() { | |
|
||
private static Collection<String> rsdAuthCoupleProviders() { | ||
return Optional.ofNullable(System.getenv("RSD_AUTH_COUPLE_PROVIDERS")) | ||
.map(String::toUpperCase) | ||
.map(s -> s.split(";")) | ||
.map(Set::of) | ||
.orElse(Collections.emptySet()); | ||
.map(String::toUpperCase) | ||
.map(s -> s.split(";")) | ||
.map(Set::of) | ||
.orElse(Collections.emptySet()); | ||
} | ||
|
||
public static boolean isDevEnv() { | ||
|
@@ -42,10 +42,10 @@ public static boolean isDevEnv() { | |
|
||
private static Collection<String> rsdLoginProviders() { | ||
return Optional.ofNullable(System.getenv("RSD_AUTH_PROVIDERS")) | ||
.map(String::toUpperCase) | ||
.map(s -> s.split(";")) | ||
.map(Set::of) | ||
.orElse(Collections.emptySet()); | ||
.map(String::toUpperCase) | ||
.map(s -> s.split(";")) | ||
.map(Set::of) | ||
.orElse(Collections.emptySet()); | ||
} | ||
|
||
public static boolean isLocalLoginEnabled() { | ||
|
@@ -73,6 +73,10 @@ public static boolean isAzureLoginEnabled() { | |
return rsdLoginProviders().contains("AZURE"); | ||
} | ||
|
||
public static boolean isLinkedinLoginEnabled() { | ||
return rsdLoginProviders().contains("LINKEDIN"); | ||
} | ||
|
||
public static String userMailWhitelist() { | ||
return System.getenv("RSD_AUTH_USER_MAIL_WHITELIST"); | ||
} | ||
|
@@ -99,10 +103,6 @@ public static String surfconextClientSecret() { | |
return System.getenv("AUTH_SURFCONEXT_CLIENT_SECRET"); | ||
} | ||
|
||
public static String surfconextScopes() { | ||
return System.getenv("SURFCONEXT_SCOPES"); | ||
} | ||
|
||
|
||
// Helmholtz ID | ||
public static String helmholtzIdRedirect() { | ||
|
@@ -127,7 +127,7 @@ public static String helmholtzIdScopes() { | |
|
||
public static boolean helmholtzIdAllowExternalUsers() { | ||
return Boolean.parseBoolean( | ||
System.getenv("HELMHOLTZID_ALLOW_EXTERNAL_USERS") | ||
System.getenv("HELMHOLTZID_ALLOW_EXTERNAL_USERS") | ||
); | ||
} | ||
|
||
|
@@ -137,7 +137,7 @@ public static String helmholtzIdAllowList() { | |
|
||
public static boolean helmholtzIdUseAllowList() { | ||
return Boolean.parseBoolean( | ||
System.getenv("HELMHOLTZID_USE_ALLOW_LIST") | ||
System.getenv("HELMHOLTZID_USE_ALLOW_LIST") | ||
); | ||
} | ||
|
||
|
@@ -163,9 +163,6 @@ public static String orcidClientSecret() { | |
return System.getenv("AUTH_ORCID_CLIENT_SECRET"); | ||
} | ||
|
||
public static String orcidScopes() { | ||
return System.getenv("ORCID_SCOPES"); | ||
} | ||
|
||
// Azure Active Directory | ||
public static String azureRedirect() { | ||
|
@@ -184,11 +181,25 @@ public static String azureClientSecret() { | |
return System.getenv("AUTH_AZURE_CLIENT_SECRET"); | ||
} | ||
|
||
public static String azureScopes() { | ||
return System.getenv("AZURE_SCOPES"); | ||
} | ||
|
||
public static String azureOrganisation() { | ||
return System.getenv("AZURE_ORGANISATION"); | ||
} | ||
|
||
|
||
public static String linkedinRedirect() { | ||
return System.getenv("LINKEDIN_REDIRECT"); | ||
} | ||
|
||
public static String linkedinClientId() { | ||
return System.getenv("LINKEDIN_CLIENT_ID"); | ||
} | ||
|
||
public static String linkedinWellknown() { | ||
return System.getenv("LINKEDIN_WELL_KNOWN_URL"); | ||
} | ||
|
||
public static String linkedinClientSecret() { | ||
return System.getenv("AUTH_LINKEDIN_CLIENT_SECRET"); | ||
} | ||
} |
69 changes: 69 additions & 0 deletions
69
authentication/src/main/java/nl/esciencecenter/rsd/authentication/LinkedinLogin.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,69 @@ | ||
// SPDX-FileCopyrightText: 2025 Ewan Cahen (Netherlands eScience Center) <[email protected]> | ||
// SPDX-FileCopyrightText: 2025 Netherlands eScience Center | ||
// | ||
// SPDX-License-Identifier: Apache-2.0 | ||
|
||
package nl.esciencecenter.rsd.authentication; | ||
|
||
import com.auth0.jwt.JWT; | ||
import com.auth0.jwt.interfaces.DecodedJWT; | ||
import com.google.gson.JsonParser; | ||
|
||
import java.io.IOException; | ||
import java.net.URI; | ||
import java.util.Collections; | ||
import java.util.HashMap; | ||
import java.util.List; | ||
import java.util.Map; | ||
import java.util.Objects; | ||
|
||
public class LinkedinLogin implements Login { | ||
|
||
private final String code; | ||
private final String redirectUrl; | ||
|
||
public LinkedinLogin(String code, String redirectUrl) { | ||
this.code = Objects.requireNonNull(code); | ||
this.redirectUrl = Objects.requireNonNull(redirectUrl); | ||
} | ||
|
||
@Override | ||
public OpenIdInfo openidInfo() throws IOException, InterruptedException, RsdResponseException { | ||
Map<String, String> form = createForm(); | ||
String tokenResponse = getTokensFromLinkedin(form); | ||
String idToken = extractIdToken(tokenResponse); | ||
|
||
DecodedJWT idJwt = JWT.decode(idToken); | ||
String subject = idJwt.getSubject(); | ||
String name = idJwt.getClaim("name").asString(); | ||
if (name == null) { | ||
String givenName = idJwt.getClaim("given_name").asString(); | ||
String familyName = idJwt.getClaim("family_name").asString(); | ||
if (givenName != null && familyName != null) name = givenName + " " + familyName; | ||
else if (familyName != null) name = familyName; | ||
else if (givenName != null) name = givenName; | ||
} | ||
String email = idJwt.getClaim("email").asString(); | ||
Map<String, List<String>> emptyData = Collections.emptyMap(); | ||
return new OpenIdInfo(subject, name, email, null, emptyData); | ||
} | ||
|
||
private Map<String, String> createForm() { | ||
Map<String, String> form = new HashMap<>(); | ||
form.put("code", code); | ||
form.put("grant_type", "authorization_code"); | ||
form.put("redirect_uri", redirectUrl); | ||
form.put("client_id", Config.linkedinClientId()); | ||
form.put("client_secret", Config.linkedinClientSecret()); | ||
return form; | ||
} | ||
|
||
private String getTokensFromLinkedin(Map<String, String> form) throws IOException, InterruptedException, RsdResponseException { | ||
URI tokenEndpoint = Utils.getTokenUrlFromWellKnownUrl(URI.create(Config.linkedinWellknown())); | ||
return Utils.postForm(tokenEndpoint, form); | ||
} | ||
|
||
private String extractIdToken(String response) { | ||
return JsonParser.parseString(response).getAsJsonObject().getAsJsonPrimitive("id_token").getAsString(); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,5 @@ | ||
// SPDX-FileCopyrightText: 2021 - 2024 Ewan Cahen (Netherlands eScience Center) <[email protected]> | ||
// SPDX-FileCopyrightText: 2021 - 2024 Netherlands eScience Center | ||
// SPDX-FileCopyrightText: 2021 - 2025 Ewan Cahen (Netherlands eScience Center) <[email protected]> | ||
// SPDX-FileCopyrightText: 2021 - 2025 Netherlands eScience Center | ||
// SPDX-FileCopyrightText: 2022 - 2024 Helmholtz Centre Potsdam - GFZ German Research Centre for Geosciences | ||
// SPDX-FileCopyrightText: 2022 Dusan Mijatovic (dv4all) | ||
// SPDX-FileCopyrightText: 2022 Matthias Rüster (GFZ) <[email protected]> | ||
|
@@ -14,6 +14,7 @@ | |
import com.auth0.jwt.interfaces.DecodedJWT; | ||
import io.javalin.Javalin; | ||
import io.javalin.http.Context; | ||
import io.javalin.http.HttpStatus; | ||
import org.slf4j.Logger; | ||
import org.slf4j.LoggerFactory; | ||
|
||
|
@@ -185,6 +186,16 @@ public static void main(String[] args) { | |
}); | ||
} | ||
|
||
if (Config.isLinkedinLoginEnabled()) { | ||
app.get("login/linkedin", ctx -> { | ||
String code = ctx.queryParam("code"); | ||
String redirectUrl = Config.linkedinRedirect(); | ||
OpenIdInfo linkedinInfo = new LinkedinLogin(code, redirectUrl).openidInfo(); | ||
AccountInfo accountInfo = new PostgrestAccount().account(linkedinInfo, OpenidProvider.linkedin); | ||
createAndSetToken(ctx, accountInfo); | ||
}); | ||
} | ||
|
||
app.get("/refresh", ctx -> { | ||
try { | ||
String tokenToVerify = ctx.cookie("rsd_token"); | ||
|
@@ -210,19 +221,19 @@ public static void main(String[] args) { | |
|
||
app.exception(RsdAuthenticationException.class, (ex, ctx) -> { | ||
setLoginFailureCookie(ctx, ex.getMessage()); | ||
ctx.redirect(LOGIN_FAILED_PATH); | ||
ctx.redirect(LOGIN_FAILED_PATH, HttpStatus.SEE_OTHER); | ||
}); | ||
|
||
app.exception(RuntimeException.class, (ex, ctx) -> { | ||
LOGGER.error("RuntimeException", ex); | ||
setLoginFailureCookie(ctx, "Something unexpected went wrong, please try again or contact us."); | ||
ctx.redirect(LOGIN_FAILED_PATH); | ||
ctx.redirect(LOGIN_FAILED_PATH, HttpStatus.SEE_OTHER); | ||
}); | ||
|
||
app.exception(Exception.class, (ex, ctx) -> { | ||
LOGGER.error("Exception", ex); | ||
setLoginFailureCookie(ctx, "Something unexpected went wrong, please try again or contact us."); | ||
ctx.redirect(LOGIN_FAILED_PATH); | ||
ctx.redirect(LOGIN_FAILED_PATH, HttpStatus.SEE_OTHER); | ||
}); | ||
} | ||
|
||
|
@@ -245,9 +256,9 @@ static void setRedirectFromCookie(Context ctx) { | |
String returnPath = ctx.cookie("rsd_pathname"); | ||
if (returnPath != null && !returnPath.isBlank()) { | ||
returnPath = returnPath.trim(); | ||
ctx.redirect(returnPath); | ||
ctx.redirect(returnPath, HttpStatus.SEE_OTHER); | ||
} else { | ||
ctx.redirect("/"); | ||
ctx.redirect("/", HttpStatus.SEE_OTHER); | ||
} | ||
} | ||
|
||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,5 @@ | ||
// SPDX-FileCopyrightText: 2022 Ewan Cahen (Netherlands eScience Center) <[email protected]> | ||
// SPDX-FileCopyrightText: 2022 Netherlands eScience Center | ||
// SPDX-FileCopyrightText: 2022 - 2025 Ewan Cahen (Netherlands eScience Center) <[email protected]> | ||
// SPDX-FileCopyrightText: 2022 - 2025 Netherlands eScience Center | ||
// | ||
// SPDX-License-Identifier: Apache-2.0 | ||
|
||
|
@@ -10,5 +10,6 @@ public enum OpenidProvider { | |
surfconext, | ||
helmholtz, | ||
orcid, | ||
azure | ||
azure, | ||
} |
Oops, something went wrong.