Skip to content

Commit

Permalink
simplified implementation
Browse files Browse the repository at this point in the history
  • Loading branch information
ErykKul committed Oct 13, 2024
1 parent be96092 commit bb70526
Show file tree
Hide file tree
Showing 9 changed files with 68 additions and 132 deletions.
17 changes: 10 additions & 7 deletions src/main/java/edu/harvard/iq/dataverse/api/AbstractApiBean.java
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
import edu.harvard.iq.dataverse.util.json.JsonUtil;
import edu.harvard.iq.dataverse.util.json.NullSafeJsonBuilder;
import edu.harvard.iq.dataverse.validation.PasswordValidatorServiceBean;
import fish.payara.security.openid.api.AccessTokenCallerPrincipal;
import jakarta.ejb.EJB;
import jakarta.ejb.EJBException;
import jakarta.inject.Inject;
Expand Down Expand Up @@ -254,8 +255,7 @@ String getWrappedMessageWhenJson() {
* The main building blocks are:
* - @OpenIdAuthenticationDefinition added on the authentication HttpServlet edu.harvard.iq.dataverse.authorization.providers.oauth2.oidc.OpenIDAuthentication, see https://docs.payara.fish/enterprise/docs/Technical%20Documentation/Public%20API/OpenID%20Connect%20Support.html
* - IdentityStoreHandler and HttpAuthenticationMechanism, as provided on the server (no custom implementation involved here), see https://hantsy.gitbook.io/java-ee-8-by-example/security/security-auth
* - IdentityStore implemented for Bearer tokens in edu.harvard.iq.dataverse.authorization.providers.oauth2.oidc.BearerTokenMechanism, see also https://docs.payara.fish/enterprise/docs/Technical%20Documentation/Public%20API/OpenID%20Connect%20Support.html and https://hantsy.gitbook.io/java-ee-8-by-example/security/security-store
* - SecurityContext injected in AbstractAPIBean to handle authentication, see https://hantsy.gitbook.io/java-ee-8-by-example/security/security-context
* SecurityContext injected in AbstractAPIBean to handle authentication, see https://hantsy.gitbook.io/java-ee-8-by-example/security/security-context
*/
@Inject
OIDCLoginBackingBean oidcLoginBackingBean;
Expand Down Expand Up @@ -347,18 +347,21 @@ protected AuthenticatedUser getRequestAuthenticatedUserOrDie(ContainerRequestCon
} else {
// This is a part of the OpenID Connect solution using security annotations.
// try authenticating with OpenIdContext first
final UserRecordIdentifier userRecordIdentifier = oidcLoginBackingBean.getUserRecordIdentifier();
UserRecordIdentifier userRecordIdentifier = oidcLoginBackingBean.getUserRecordIdentifier();
if (userRecordIdentifier == null) {
// Try SecurityContext and the underlying Bearer token IdentityStore
// See: edu.harvard.iq.dataverse.authorization.providers.oauth2.oidc.BearerTokenMechanism
// Try SecurityContext and the underlying Bearer token
AuthenticationStatus status = securityContext.authenticate(httpRequest, httpResponse, null);
if (AuthenticationStatus.SUCCESS.equals(status)) {
try {
return (AuthenticatedUser) httpRequest.getAttribute(ApiConstants.CONTAINER_REQUEST_CONTEXT_USER);
logger.info(securityContext.getCallerPrincipal().getClass().toString());
userRecordIdentifier = securityContext.getPrincipalsByType(AccessTokenCallerPrincipal.class).stream().map(principal ->
oidcLoginBackingBean.getUserRecordIdentifier(principal.getAccessToken())).filter(userId -> userId != null).findFirst().get();
} catch (Exception e) {
throw new WrappedResponse(authenticatedUserRequired());
// NOOP
}
}
}
if (userRecordIdentifier == null) {
throw new WrappedResponse(authenticatedUserRequired());
}
final AuthenticatedUser authUser = authSvc.lookupUser(userRecordIdentifier);
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
import com.github.scribejava.core.builder.api.DefaultApi20;

import edu.harvard.iq.dataverse.authorization.providers.oauth2.AbstractOAuth2AuthenticationProvider;
import fish.payara.security.openid.api.AccessToken;
import fish.payara.security.openid.api.OpenIdConstant;

/**
* TODO: this should not EXTEND, but IMPLEMENT the contract to be used in
Expand All @@ -15,11 +17,34 @@ public class OIDCAuthProvider extends AbstractOAuth2AuthenticationProvider {
final String aClientId;
final String aClientSecret;
final String issuerEndpointURL;
final String issuerIdentifier;
final String issuerIdentifierField;
final String subjectIdentifierField;

public OIDCAuthProvider(String aClientId, String aClientSecret, String issuerEndpointURL) {
public OIDCAuthProvider(String aClientId, String aClientSecret, String issuerEndpointURL, String issuerIdentifier, String issuerIdentifierField, String subjectIdentifierField) {
this.aClientId = aClientId;
this.aClientSecret = aClientSecret;
this.issuerEndpointURL = issuerEndpointURL;
this.issuerIdentifier = issuerIdentifier == null ? issuerEndpointURL : issuerIdentifier;
this.issuerIdentifierField = issuerIdentifierField == null ? OpenIdConstant.ISSUER_IDENTIFIER : issuerIdentifierField;
this.subjectIdentifierField = subjectIdentifierField == null ? OpenIdConstant.SUBJECT_IDENTIFIER : subjectIdentifierField;
}

public boolean isIssuerOf(AccessToken accessToken) {
try {
final String issuerIdentifierValue = accessToken.getJwtClaims().getStringClaim(issuerIdentifierField).orElse(null);
return issuerIdentifier.equals(issuerIdentifierValue);
} catch (final Exception ignore) {
return false;
}
}

public String getSubject(AccessToken accessToken) {
try {
return accessToken.getJwtClaims().getStringClaim(subjectIdentifierField).orElse(null);
} catch (final Exception ignore) {
return null;
}
}

public String getClientId() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,10 @@ public AuthenticationProvider buildProvider( AuthenticationProviderRow aRow ) th
OIDCAuthProvider oidc = new OIDCAuthProvider(
factoryData.get("clientId"),
factoryData.get("clientSecret"),
factoryData.get("issuer")
factoryData.get("issuer"),
factoryData.get("issuerId"),
factoryData.get("issuerIdField"),
factoryData.get("subjectIdField")
);

oidc.setId(aRow.getId());
Expand All @@ -60,7 +63,10 @@ public static AuthenticationProvider buildFromSettings() throws AuthorizationSet
OIDCAuthProvider oidc = new OIDCAuthProvider(
JvmSettings.OIDC_CLIENT_ID.lookup(),
JvmSettings.OIDC_CLIENT_SECRET.lookup(),
JvmSettings.OIDC_AUTH_SERVER_URL.lookup()
JvmSettings.OIDC_AUTH_SERVER_URL.lookup(),
JvmSettings.OIDC_ISSUER_IDENTIFIER.lookupOptional().orElse(null),
JvmSettings.OIDC_ISSUER_IDENTIFIER_FIELD.lookupOptional().orElse(null),
JvmSettings.OIDC_SUBJECT_IDENTIFIER_FIELD.lookupOptional().orElse(null)
);

oidc.setId("oidc-mpconfig");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.stream.Collectors;

import org.omnifaces.util.Faces;

Expand All @@ -18,22 +17,20 @@
import edu.harvard.iq.dataverse.authorization.providers.oauth2.OAuth2UserRecord;
import edu.harvard.iq.dataverse.authorization.UserRecordIdentifier;
import edu.harvard.iq.dataverse.util.SystemConfig;
import fish.payara.security.openid.api.AccessToken;
import fish.payara.security.openid.api.JwtClaims;
import fish.payara.security.openid.api.OpenIdConstant;
import fish.payara.security.openid.api.OpenIdContext;
import jakarta.ejb.EJB;
import jakarta.ejb.Stateless;
import jakarta.inject.Inject;
import jakarta.inject.Named;
import jakarta.json.JsonString;
import jakarta.json.JsonValue;

/**
* This code is a part of an OpenID Connect solutions using Jakarta security annotations.
* The main building blocks are:
* - @OpenIdAuthenticationDefinition added on the authentication HttpServlet edu.harvard.iq.dataverse.authorization.providers.oauth2.oidc.OpenIDAuthentication, see https://docs.payara.fish/enterprise/docs/Technical%20Documentation/Public%20API/OpenID%20Connect%20Support.html
* - IdentityStoreHandler and HttpAuthenticationMechanism, as provided on the server (no custom implementation involved here), see https://hantsy.gitbook.io/java-ee-8-by-example/security/security-auth
* - IdentityStore implemented for Bearer tokens in edu.harvard.iq.dataverse.authorization.providers.oauth2.oidc.BearerTokenMechanism, see also https://docs.payara.fish/enterprise/docs/Technical%20Documentation/Public%20API/OpenID%20Connect%20Support.html and https://hantsy.gitbook.io/java-ee-8-by-example/security/security-store
* - SecurityContext injected in AbstractAPIBean to handle authentication, see https://hantsy.gitbook.io/java-ee-8-by-example/security/security-context
*/

Expand Down Expand Up @@ -84,21 +81,21 @@ public String getLogInLink(final OIDCAuthProvider oidcAuthProvider) {
*/
public void setUser() {
try {
final String subject = openIdContext.getSubject();
final OIDCAuthProvider provider = getProvider();
final UserRecordIdentifier userRecordIdentifier = new UserRecordIdentifier(provider.getId(), subject);
final JwtClaims claims = openIdContext.getAccessToken().getJwtClaims();
final UserRecordIdentifier userRecordIdentifier = getUserRecordIdentifier();
final String subject = userRecordIdentifier.getUserIdInRepo();
final String providerId = userRecordIdentifier.getUserRepoId();
AuthenticatedUser dvUser = authenticationSvc.lookupUser(userRecordIdentifier);
if (dvUser == null) {
if (!systemConfig.isSignupDisabledForRemoteAuthProvider(provider.getId())) {
final JwtClaims claims = openIdContext.getAccessToken().getJwtClaims();
if (!systemConfig.isSignupDisabledForRemoteAuthProvider(providerId)) {
final String firstName = claims.getStringClaim(OpenIdConstant.GIVEN_NAME).orElse("");
final String lastName = claims.getStringClaim(OpenIdConstant.FAMILY_NAME).orElse("");
final String verifiedEmailAddress = getVerifiedEmail();
final String verifiedEmailAddress = claims.getStringClaim(OpenIdConstant.EMAIL).orElse("");
final String emailAddress = verifiedEmailAddress == null ? "" : verifiedEmailAddress;
final String affiliation = claims.getStringClaim("affiliation").orElse("");
final String position = claims.getStringClaim("position").orElse("");
final OAuth2UserRecord userRecord = new OAuth2UserRecord(
provider.getId(),
providerId,
subject,
claims.getStringClaim(OpenIdConstant.PREFERRED_USERNAME).orElse(subject),
null,
Expand All @@ -119,58 +116,26 @@ public void setUser() {
}

public UserRecordIdentifier getUserRecordIdentifier() {
try {
final String subject = openIdContext.getSubject();
final OIDCAuthProvider provider = getProvider();
return new UserRecordIdentifier(provider.getId(), subject);
} catch (final Exception ignore) {
return null;
}
return getUserRecordIdentifier(openIdContext.getAccessToken());
}

private String getVerifiedEmail() {
public UserRecordIdentifier getUserRecordIdentifier(final AccessToken accessToken) {
try {
final Object emailVerifiedObject = openIdContext.getClaimsJson().get(OpenIdConstant.EMAIL_VERIFIED);
final boolean emailVerified;
if (emailVerifiedObject instanceof JsonValue v) {
emailVerified = JsonValue.TRUE.equals(v)
|| (JsonValue.ValueType.STRING.equals(v.getValueType())
&& Boolean.getBoolean(((JsonString) v).getString()));
} else {
emailVerified = false;
}
if (!emailVerified) {
logger.log(Level.FINE,
"email not verified: " + openIdContext.getClaimsJson().get(OpenIdConstant.EMAIL));
final OIDCAuthProvider provider = getProvider(accessToken);
final String providerId = provider.getId();
final String subject = provider.getSubject(accessToken);
if (subject == null) {
return null;
}
return openIdContext.getClaims().getEmail().orElse(null);
return new UserRecordIdentifier(providerId, subject);
} catch (final Exception ignore) {
return null;
}
}

private OIDCAuthProvider getProvider() {
final String issuerEndpointURL = openIdContext.getAccessToken().getJwtClaims()
.getStringClaim(OpenIdConstant.ISSUER_IDENTIFIER)
.orElse(null);
if (issuerEndpointURL == null) {
logger.log(Level.SEVERE,
"Issuer URL (iss) not found in " + openIdContext.getAccessToken().getJwtClaims().toString());
return null;
}
// Are we sure these values are equal? Does the issuer URL have to be the full qualified one or could it be just the "top" URL from where you can access the .well-known endpoint?
// - No, not sure. This might cause problems in the future.
List<OIDCAuthProvider> providers = authenticationSvc.getAuthenticationProviderIdsOfType(OIDCAuthProvider.class)
.stream()
private OIDCAuthProvider getProvider(AccessToken accessToken) {
return authenticationSvc.getAuthenticationProviderIdsOfType(OIDCAuthProvider.class).stream()
.map(providerId -> (OIDCAuthProvider) authenticationSvc.getAuthenticationProvider(providerId))
.filter(provider -> issuerEndpointURL.equals(provider.getIssuerEndpointURL()))
.collect(Collectors.toUnmodifiableList());
if (providers.isEmpty()) {
logger.log(Level.SEVERE, "OIDC provider not found for URL: " + issuerEndpointURL);
return null;
} else {
return providers.get(0);
}
.filter(provider -> provider.isIssuerOf(accessToken)).findFirst().get();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@
* The main building blocks are:
* - @OpenIdAuthenticationDefinition added on the authentication HttpServlet edu.harvard.iq.dataverse.authorization.providers.oauth2.oidc.OpenIDAuthentication, see https://docs.payara.fish/enterprise/docs/Technical%20Documentation/Public%20API/OpenID%20Connect%20Support.html
* - IdentityStoreHandler and HttpAuthenticationMechanism, as provided on the server (no custom implementation involved here), see https://hantsy.gitbook.io/java-ee-8-by-example/security/security-auth
* - IdentityStore implemented for Bearer tokens in edu.harvard.iq.dataverse.authorization.providers.oauth2.oidc.BearerTokenMechanism, see also https://docs.payara.fish/enterprise/docs/Technical%20Documentation/Public%20API/OpenID%20Connect%20Support.html and https://hantsy.gitbook.io/java-ee-8-by-example/security/security-store
* - SecurityContext injected in AbstractAPIBean to handle authentication, see https://hantsy.gitbook.io/java-ee-8-by-example/security/security-context
*/

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@
* The main building blocks are:
* - @OpenIdAuthenticationDefinition added on the authentication HttpServlet edu.harvard.iq.dataverse.authorization.providers.oauth2.oidc.OpenIDAuthentication, see https://docs.payara.fish/enterprise/docs/Technical%20Documentation/Public%20API/OpenID%20Connect%20Support.html
* - IdentityStoreHandler and HttpAuthenticationMechanism, as provided on the server (no custom implementation involved here), see https://hantsy.gitbook.io/java-ee-8-by-example/security/security-auth
* - IdentityStore implemented for Bearer tokens in edu.harvard.iq.dataverse.authorization.providers.oauth2.oidc.BearerTokenMechanism, see also https://docs.payara.fish/enterprise/docs/Technical%20Documentation/Public%20API/OpenID%20Connect%20Support.html and https://hantsy.gitbook.io/java-ee-8-by-example/security/security-store
* - SecurityContext injected in AbstractAPIBean to handle authentication, see https://hantsy.gitbook.io/java-ee-8-by-example/security/security-context
*/

Expand Down
Loading

0 comments on commit bb70526

Please sign in to comment.