Skip to content

Commit

Permalink
Added: API_BEARER_AUTH_PROVIDE_MISSING_CLAIMS management an different…
Browse files Browse the repository at this point in the history
… logic paths depending on the value to RegisterOIDCUserCommand
  • Loading branch information
GPortas committed Nov 15, 2024
1 parent 0f2cfdc commit 52a5a9e
Show file tree
Hide file tree
Showing 2 changed files with 47 additions and 42 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
import edu.harvard.iq.dataverse.engine.command.exception.IllegalCommandException;
import edu.harvard.iq.dataverse.engine.command.exception.PermissionException;
import edu.harvard.iq.dataverse.engine.command.exception.InvalidFieldsCommandException;
import edu.harvard.iq.dataverse.settings.FeatureFlags;
import edu.harvard.iq.dataverse.util.BundleUtil;

import java.util.HashMap;
Expand Down Expand Up @@ -40,12 +41,9 @@ protected void executeImpl(CommandContext ctxt) throws CommandException {
}

UserInfo userClaimsInfo = oidcUserInfo.getUserClaimsInfo();
boolean provideMissingClaimsEnabled = FeatureFlags.API_BEARER_AUTH_PROVIDE_MISSING_CLAIMS.enabled();

// Update the UserDTO object with available OIDC user claims; keep existing values if claims are absent
userDTO.setUsername(getValueOrDefault(userClaimsInfo.getPreferredUsername(), userDTO.getUsername()));
userDTO.setFirstName(getValueOrDefault(userClaimsInfo.getGivenName(), userDTO.getFirstName()));
userDTO.setLastName(getValueOrDefault(userClaimsInfo.getFamilyName(), userDTO.getLastName()));
userDTO.setEmailAddress(getValueOrDefault(userClaimsInfo.getEmailAddress(), userDTO.getEmailAddress()));
updateUserDTO(userClaimsInfo, provideMissingClaimsEnabled);

AuthenticatedUserDisplayInfo userDisplayInfo = new AuthenticatedUserDisplayInfo(
userDTO.getFirstName(),
Expand All @@ -55,7 +53,7 @@ protected void executeImpl(CommandContext ctxt) throws CommandException {
userDTO.getPosition() != null ? userDTO.getPosition() : ""
);

Map<String, String> fieldErrors = validateUserFields(ctxt);
Map<String, String> fieldErrors = validateUserFields(ctxt, provideMissingClaimsEnabled);
if (!fieldErrors.isEmpty()) {
throw new InvalidFieldsCommandException(
BundleUtil.getStringFromBundle("registerOidcUserCommand.errors.invalidFields"),
Expand All @@ -71,19 +69,34 @@ protected void executeImpl(CommandContext ctxt) throws CommandException {
}
}

private void updateUserDTO(UserInfo userClaimsInfo, boolean provideMissingClaimsEnabled) {
if (provideMissingClaimsEnabled) {
// Update with available OIDC claims, keep existing values if claims are absent
userDTO.setUsername(getValueOrDefault(userClaimsInfo.getPreferredUsername(), userDTO.getUsername()));
userDTO.setFirstName(getValueOrDefault(userClaimsInfo.getGivenName(), userDTO.getFirstName()));
userDTO.setLastName(getValueOrDefault(userClaimsInfo.getFamilyName(), userDTO.getLastName()));
userDTO.setEmailAddress(getValueOrDefault(userClaimsInfo.getEmailAddress(), userDTO.getEmailAddress()));
} else {
// Always use the claims from the IdP provider
userDTO.setUsername(userClaimsInfo.getPreferredUsername());
userDTO.setFirstName(userClaimsInfo.getGivenName());
userDTO.setLastName(userClaimsInfo.getFamilyName());
userDTO.setEmailAddress(userClaimsInfo.getEmailAddress());
}
}

private String getValueOrDefault(String oidcValue, String dtoValue) {
return (oidcValue == null || oidcValue.isEmpty()) ? dtoValue : oidcValue;
}

private Map<String, String> validateUserFields(CommandContext ctxt) {
private Map<String, String> validateUserFields(CommandContext ctxt, boolean provideMissingClaimsEnabled) {
Map<String, String> fieldErrors = new HashMap<>();

validateTermsAccepted(fieldErrors);
validateEmailAddress(ctxt, fieldErrors);
validateUsername(ctxt, fieldErrors);

validateRequiredField("firstName", userDTO.getFirstName(), "registerOidcUserCommand.errors.firstNameFieldRequired", fieldErrors);
validateRequiredField("lastName", userDTO.getLastName(), "registerOidcUserCommand.errors.lastNameFieldRequired", fieldErrors);
validateField(fieldErrors, "emailAddress", userDTO.getEmailAddress(), ctxt, provideMissingClaimsEnabled);
validateField(fieldErrors, "username", userDTO.getUsername(), ctxt, provideMissingClaimsEnabled);
validateField(fieldErrors, "firstName", userDTO.getFirstName(), ctxt, provideMissingClaimsEnabled);
validateField(fieldErrors, "lastName", userDTO.getLastName(), ctxt, provideMissingClaimsEnabled);

return fieldErrors;
}
Expand All @@ -94,35 +107,23 @@ private void validateTermsAccepted(Map<String, String> fieldErrors) {
}
}

private void validateEmailAddress(CommandContext ctxt, Map<String, String> fieldErrors) {
String emailAddress = userDTO.getEmailAddress();
if (emailAddress == null || emailAddress.isEmpty()) {
fieldErrors.put("emailAddress", BundleUtil.getStringFromBundle("registerOidcUserCommand.errors.emailFieldRequired"));
} else if (isEmailInUse(ctxt, emailAddress)) {
fieldErrors.put("emailAddress", BundleUtil.getStringFromBundle("registerOidcUserCommand.errors.emailAddressInUse"));
}
}

private void validateUsername(CommandContext ctxt, Map<String, String> fieldErrors) {
String username = userDTO.getUsername();
if (username == null || username.isEmpty()) {
fieldErrors.put("username", BundleUtil.getStringFromBundle("registerOidcUserCommand.errors.usernameFieldRequired"));
} else if (isUsernameInUse(ctxt, username)) {
fieldErrors.put("username", BundleUtil.getStringFromBundle("registerOidcUserCommand.errors.usernameInUse"));
}
}

private void validateRequiredField(String fieldName, String fieldValue, String bundleKey, Map<String, String> fieldErrors) {
private void validateField(Map<String, String> fieldErrors, String fieldName, String fieldValue, CommandContext ctxt, boolean provideMissingClaimsEnabled) {
if (fieldValue == null || fieldValue.isEmpty()) {
fieldErrors.put(fieldName, BundleUtil.getStringFromBundle(bundleKey));
String errorKey = provideMissingClaimsEnabled ?
"registerOidcUserCommand.errors.provideMissingClaimsEnabled." + fieldName + "FieldRequired" :
"registerOidcUserCommand.errors.provideMissingClaimsDisabled." + fieldName + "FieldRequired";
fieldErrors.put(fieldName, BundleUtil.getStringFromBundle(errorKey));
} else if (isFieldInUse(ctxt, fieldName, fieldValue)) {
fieldErrors.put(fieldName, BundleUtil.getStringFromBundle("registerOidcUserCommand.errors." + fieldName + "InUse"));
}
}

private boolean isEmailInUse(CommandContext ctxt, String emailAddress) {
return ctxt.authentication().getAuthenticatedUserByEmail(emailAddress) != null;
}

private boolean isUsernameInUse(CommandContext ctxt, String username) {
return ctxt.authentication().getAuthenticatedUser(username) != null;
private boolean isFieldInUse(CommandContext ctxt, String fieldName, String value) {
if ("emailAddress".equals(fieldName)) {
return ctxt.authentication().getAuthenticatedUserByEmail(value) != null;
} else if ("username".equals(fieldName)) {
return ctxt.authentication().getAuthenticatedUser(value) != null;
}
return false;
}
}
12 changes: 8 additions & 4 deletions src/main/java/propertyFiles/Bundle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -3072,10 +3072,14 @@ users.api.userRegistered=User registered.
registerOidcUserCommand.errors.userAlreadyRegisteredWithToken=User is already registered with this token.
registerOidcUserCommand.errors.invalidFields=The provided fields are invalid for registering a new user.
registerOidcUserCommand.errors.userShouldAcceptTerms=Terms should be accepted.
registerOidcUserCommand.errors.emailFieldRequired=It is required to include an emailAddress field in the request JSON for registering the user.
registerOidcUserCommand.errors.usernameFieldRequired=It is required to include a username field in the request JSON for registering the user.
registerOidcUserCommand.errors.firstNameFieldRequired=It is required to include a firstName field in the request JSON for registering the user.
registerOidcUserCommand.errors.lastNameFieldRequired=It is required to include a lastName field in the request JSON for registering the user.
registerOidcUserCommand.errors.provideMissingClaimsEnabled.emailAddressFieldRequired=It is required to include an emailAddress field in the request JSON for registering the user.
registerOidcUserCommand.errors.provideMissingClaimsDisabled.emailAddressFieldRequired=The OIDC identity provider does not provide the user claim 'email', which is required for user registration. Please contact your identity provider.
registerOidcUserCommand.errors.provideMissingClaimsEnabled.usernameFieldRequired=It is required to include a username field in the request JSON for registering the user.
registerOidcUserCommand.errors.provideMissingClaimsDisabled.usernameFieldRequired=The OIDC identity provider does not provide the user claim 'preferred_username', which is required for user registration. Please contact your identity provider.
registerOidcUserCommand.errors.provideMissingClaimsEnabled.firstNameFieldRequired=It is required to include a firstName field in the request JSON for registering the user.
registerOidcUserCommand.errors.provideMissingClaimsDisabled.firstNameFieldRequired=The OIDC identity provider does not provide the user claim 'given_name', which is required for user registration. Please contact your identity provider.
registerOidcUserCommand.errors.provideMissingClaimsEnabled.lastNameFieldRequired=It is required to include a lastName field in the request JSON for registering the user.
registerOidcUserCommand.errors.provideMissingClaimsDisabled.lastNameFieldRequired=The OIDC identity provider does not provide the user claim 'family_name', which is required for user registration. Please contact your identity provider.
registerOidcUserCommand.errors.emailAddressInUse=Email already in use.
registerOidcUserCommand.errors.usernameInUse=Username already in use.

Expand Down

0 comments on commit 52a5a9e

Please sign in to comment.