Skip to content

Commit

Permalink
add API endpoint to convert OAuth users to local #3338
Browse files Browse the repository at this point in the history
  • Loading branch information
pdurbin committed Nov 21, 2016
1 parent 4e5a141 commit 2f46105
Show file tree
Hide file tree
Showing 5 changed files with 383 additions and 10 deletions.
186 changes: 185 additions & 1 deletion src/main/java/edu/harvard/iq/dataverse/api/Admin.java
Original file line number Diff line number Diff line change
Expand Up @@ -284,9 +284,14 @@ public Response listAuthenticatedUsers() {
/**
* curl -X PUT -d "shib@mailinator.com"
* http://localhost:8080/api/admin/authenticatedUsers/id/11/convertShibToBuiltIn
*
* @deprecated We have documented this API endpoint so we'll keep in around
* for a while but we should encourage everyone to switch to the
* "convertRemoteToBuiltIn" endpoint and then remove this Shib-specfic one.
*/
@PUT
@Path("authenticatedUsers/id/{id}/convertShibToBuiltIn")
@Deprecated
public Response convertShibUserToBuiltin(@PathParam("id") Long id, String newEmailAddress) {
try {
AuthenticatedUser user = findAuthenticatedUserOrDie();
Expand All @@ -297,7 +302,7 @@ public Response convertShibUserToBuiltin(@PathParam("id") Long id, String newEma
return error(Response.Status.FORBIDDEN, "Superusers only.");
}
try {
BuiltinUser builtinUser = authSvc.convertShibToBuiltIn(id, newEmailAddress);
BuiltinUser builtinUser = authSvc.convertRemoteToBuiltIn(id, newEmailAddress);
if (builtinUser == null) {
return error(Response.Status.BAD_REQUEST, "User id " + id + " could not be converted from Shibboleth to BuiltIn. An Exception was not thrown.");
}
Expand All @@ -318,6 +323,39 @@ public Response convertShibUserToBuiltin(@PathParam("id") Long id, String newEma
}
}

@PUT
@Path("authenticatedUsers/id/{id}/convertRemoteToBuiltIn")
public Response convertOAuthUserToBuiltin(@PathParam("id") Long id, String newEmailAddress) {
try {
AuthenticatedUser user = findAuthenticatedUserOrDie();
if (!user.isSuperuser()) {
return error(Response.Status.FORBIDDEN, "Superusers only.");
}
} catch (WrappedResponse ex) {
return error(Response.Status.FORBIDDEN, "Superusers only.");
}
try {
BuiltinUser builtinUser = authSvc.convertRemoteToBuiltIn(id, newEmailAddress);
if (builtinUser == null) {
return error(Response.Status.BAD_REQUEST, "User id " + id + " could not be converted from remote to BuiltIn. An Exception was not thrown.");
}
JsonObjectBuilder output = Json.createObjectBuilder();
output.add("email", builtinUser.getEmail());
output.add("username", builtinUser.getUserName());
return ok(output);
} catch (Throwable ex) {
StringBuilder sb = new StringBuilder();
sb.append(ex + " ");
while (ex.getCause() != null) {
ex = ex.getCause();
sb.append(ex + " ");
}
String msg = "User id " + id + " could not be converted from remote to BuiltIn. Details from Exception: " + sb;
logger.info(msg);
return error(Response.Status.BAD_REQUEST, msg);
}
}

/**
* This is used in testing via AdminIT.java but we don't expect sysadmins to
* use this.
Expand Down Expand Up @@ -457,6 +495,152 @@ public Response builtin2shib(String content) {
return ok(response);
}

/**
* This is used in testing via AdminIT.java but we don't expect sysadmins to
* use this.
*/
@Path("authenticatedUsers/convert/builtin2oauth")
@PUT
public Response builtin2oauth(String content) {
logger.info("entering builtin2oauth...");
try {
AuthenticatedUser userToRunThisMethod = findAuthenticatedUserOrDie();
if (!userToRunThisMethod.isSuperuser()) {
return error(Response.Status.FORBIDDEN, "Superusers only.");
}
} catch (WrappedResponse ex) {
return error(Response.Status.FORBIDDEN, "Superusers only.");
}
boolean disabled = false;
if (disabled) {
return error(Response.Status.BAD_REQUEST, "API endpoint disabled.");
}
AuthenticatedUser builtInUserToConvert = null;
String emailToFind;
String password;
String authuserId = "0"; // could let people specify id on authuser table. probably better to let them tell us their
String newEmailAddressToUse;
String newProviderId;
String newPersistentUserIdInLookupTable;
logger.info("content: " + content);
try {
String[] args = content.split(":");
emailToFind = args[0];
password = args[1];
newEmailAddressToUse = args[2];
newProviderId = args[3];
newPersistentUserIdInLookupTable = args[4];
// authuserId = args[666];
} catch (ArrayIndexOutOfBoundsException ex) {
return error(Response.Status.BAD_REQUEST, "Problem with content <<<" + content + ">>>: " + ex.toString());
}
AuthenticatedUser existingAuthUserFoundByEmail = shibService.findAuthUserByEmail(emailToFind);
String existing = "NOT FOUND";
if (existingAuthUserFoundByEmail != null) {
builtInUserToConvert = existingAuthUserFoundByEmail;
existing = existingAuthUserFoundByEmail.getIdentifier();
} else {
long longToLookup = Long.parseLong(authuserId);
AuthenticatedUser specifiedUserToConvert = authSvc.findByID(longToLookup);
if (specifiedUserToConvert != null) {
builtInUserToConvert = specifiedUserToConvert;
} else {
return error(Response.Status.BAD_REQUEST, "No user to convert. We couldn't find a *single* existing user account based on " + emailToFind + " and no user was found using specified id " + longToLookup);
}
}
// String shibProviderId = ShibAuthenticationProvider.PROVIDER_ID;
Map<String, String> randomUser = shibService.getRandomUser();
// String eppn = UUID.randomUUID().toString().substring(0, 8);
String eppn = randomUser.get("eppn");
String idPEntityId = randomUser.get("idp");
String notUsed = null;
String separator = "|";
// UserIdentifier newUserIdentifierInLookupTable = new UserIdentifier(idPEntityId + separator + eppn, notUsed);
UserIdentifier newUserIdentifierInLookupTable = new UserIdentifier(newPersistentUserIdInLookupTable, notUsed);
String overwriteFirstName = randomUser.get("firstName");
String overwriteLastName = randomUser.get("lastName");
String overwriteEmail = randomUser.get("email");
overwriteEmail = newEmailAddressToUse;
logger.info("overwriteEmail: " + overwriteEmail);
boolean validEmail = EMailValidator.isEmailValid(overwriteEmail, null);
if (!validEmail) {
// See https://github.com/IQSS/dataverse/issues/2998
return error(Response.Status.BAD_REQUEST, "invalid email: " + overwriteEmail);
}
/**
* @todo If affiliation is not null, put it in RoleAssigneeDisplayInfo
* constructor.
*/
/**
* Here we are exercising (via an API test) shibService.getAffiliation
* with the TestShib IdP and a non-production DevShibAccountType.
*/
// idPEntityId = ShibUtil.testShibIdpEntityId;
// String overwriteAffiliation = shibService.getAffiliation(idPEntityId, ShibServiceBean.DevShibAccountType.RANDOM);
String overwriteAffiliation = null;
logger.info("overwriteAffiliation: " + overwriteAffiliation);
/**
* @todo Find a place to put "position" in the authenticateduser table:
* https://github.com/IQSS/dataverse/issues/1444#issuecomment-74134694
*/
String overwritePosition = "staff;student";
AuthenticatedUserDisplayInfo displayInfo = new AuthenticatedUserDisplayInfo(overwriteFirstName, overwriteLastName, overwriteEmail, overwriteAffiliation, overwritePosition);
JsonObjectBuilder response = Json.createObjectBuilder();
JsonArrayBuilder problems = Json.createArrayBuilder();
if (password != null) {
response.add("password supplied", password);
boolean knowsExistingPassword = false;
BuiltinUser oldBuiltInUser = builtinUserService.findByUserName(builtInUserToConvert.getUserIdentifier());
if (oldBuiltInUser != null) {
String usernameOfBuiltinAccountToConvert = oldBuiltInUser.getUserName();
response.add("old username", usernameOfBuiltinAccountToConvert);
AuthenticatedUser authenticatedUser = shibService.canLogInAsBuiltinUser(usernameOfBuiltinAccountToConvert, password);
if (authenticatedUser != null) {
knowsExistingPassword = true;
AuthenticatedUser convertedUser = authSvc.convertBuiltInUserToRemoteUser(builtInUserToConvert, newProviderId, newUserIdentifierInLookupTable);
if (convertedUser != null) {
/**
* @todo Display name is not being overwritten. Logic
* must be in Shib backing bean
*/
AuthenticatedUser updatedInfoUser = authSvc.updateAuthenticatedUser(convertedUser, displayInfo);
if (updatedInfoUser != null) {
response.add("display name overwritten with", updatedInfoUser.getName());
} else {
problems.add("couldn't update display info");
}
} else {
problems.add("unable to convert user");
}
}
} else {
problems.add("couldn't find old username");
}
if (!knowsExistingPassword) {
String message = "User doesn't know password.";
problems.add(message);
/**
* @todo Someday we should make a errorResponse method that
* takes JSON arrays and objects.
*/
return error(Status.BAD_REQUEST, problems.build().toString());
}
// response.add("knows existing password", knowsExistingPassword);
}

response.add("user to convert", builtInUserToConvert.getIdentifier());
response.add("existing user found by email (prompt to convert)", existing);
response.add("changing to this provider", newProviderId);
response.add("value to overwrite old first name", overwriteFirstName);
response.add("value to overwrite old last name", overwriteLastName);
response.add("value to overwrite old email address", overwriteEmail);
if (overwriteAffiliation != null) {
response.add("affiliation", overwriteAffiliation);
}
response.add("problems", problems);
return ok(response);
}

@DELETE
@Path("authenticatedUsers/id/{id}/")
public Response deleteAuthenticatedUserById(@PathParam("id") Long id) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -586,6 +586,11 @@ public Timestamp getCurrentTimestamp() {

// TODO should probably be moved to the Shib provider - this is a classic Shib-specific
// use case. This class should deal with general autnetications.
@Deprecated
/**
* @deprecated. Switch to convertBuiltInUserToRemoteUser instead.
* @todo. Switch to convertBuiltInUserToRemoteUser instead.
*/
public AuthenticatedUser convertBuiltInToShib(AuthenticatedUser builtInUserToConvert, String shibProviderId, UserIdentifier newUserIdentifierInLookupTable) {
logger.info("converting user " + builtInUserToConvert.getId() + " from builtin to shib");
String builtInUserIdentifier = builtInUserToConvert.getIdentifier();
Expand Down Expand Up @@ -635,6 +640,55 @@ public AuthenticatedUser convertBuiltInToShib(AuthenticatedUser builtInUserToCon
return null;
}

public AuthenticatedUser convertBuiltInUserToRemoteUser(AuthenticatedUser builtInUserToConvert, String newProviderId, UserIdentifier newUserIdentifierInLookupTable) {
logger.info("converting user " + builtInUserToConvert.getId() + " from builtin to remote");
String builtInUserIdentifier = builtInUserToConvert.getIdentifier();
logger.info("builtin user identifier: " + builtInUserIdentifier);
TypedQuery<AuthenticatedUserLookup> typedQuery = em.createQuery("SELECT OBJECT(o) FROM AuthenticatedUserLookup AS o WHERE o.authenticatedUser = :auid", AuthenticatedUserLookup.class);
typedQuery.setParameter("auid", builtInUserToConvert);
AuthenticatedUserLookup authuserLookup;
try {
authuserLookup = typedQuery.getSingleResult();
} catch (NoResultException | NonUniqueResultException ex) {
logger.info("exception caught: " + ex);
return null;
}
if (authuserLookup == null) {
return null;
}

String oldProviderId = authuserLookup.getAuthenticationProviderId();
logger.info("we expect this to be 'builtin': " + oldProviderId);
authuserLookup.setAuthenticationProviderId(newProviderId);
String oldUserLookupIdentifier = authuserLookup.getPersistentUserId();
logger.info("this should be 'pete' or whatever the old builtin username was: " + oldUserLookupIdentifier);
String perUserShibIdentifier = newUserIdentifierInLookupTable.getLookupStringPerAuthProvider();
authuserLookup.setPersistentUserId(perUserShibIdentifier);
/**
* @todo this should be a transaction of some kind. We want to update
* the authenticateduserlookup and also delete the row from the
* builtinuser table in a single transaction.
*/
em.persist(authuserLookup);
String builtinUsername = builtInUserIdentifier.replaceFirst(AuthenticatedUser.IDENTIFIER_PREFIX, "");
BuiltinUser builtin = builtinUserServiceBean.findByUserName(builtinUsername);
if (builtin != null) {
// These were created by AuthenticationResponse.Status.BREAKOUT in ShibServiceBean.canLogInAsBuiltinUser
List<PasswordResetData> oldTokens = passwordResetServiceBean.findPasswordResetDataByDataverseUser(builtin);
for (PasswordResetData oldToken : oldTokens) {
em.remove(oldToken);
}
em.remove(builtin);
} else {
logger.info("Couldn't delete builtin user because could find it based on username " + builtinUsername);
}
AuthenticatedUser nonBuiltinUser = lookupUser(newProviderId, perUserShibIdentifier);
if (nonBuiltinUser != null) {
return nonBuiltinUser;
}
return null;
}

/**
* @param idOfAuthUserToConvert The id of the AuthenticatedUser (Shibboleth
* user) to convert to a BuiltinUser.
Expand All @@ -644,14 +698,14 @@ public AuthenticatedUser convertBuiltInToShib(AuthenticatedUser builtInUserToCon
* @throws java.lang.Exception You must catch and report back to the user (a
* superuser) any Exceptions.
*/
public BuiltinUser convertShibToBuiltIn(Long idOfAuthUserToConvert, String newEmailAddress) throws Exception {
public BuiltinUser convertRemoteToBuiltIn(Long idOfAuthUserToConvert, String newEmailAddress) throws Exception {
AuthenticatedUser authenticatedUser = findByID(idOfAuthUserToConvert);
if (authenticatedUser == null) {
throw new Exception("User id " + idOfAuthUserToConvert + " not found.");
}
AuthenticatedUser existingUserWithSameEmail = getAuthenticatedUserByEmail(newEmailAddress);
if (existingUserWithSameEmail != null) {
throw new Exception("User id " + idOfAuthUserToConvert + " (" + authenticatedUser.getIdentifier() + ") cannot be converted from Shibboleth to BuiltIn because the email address " + newEmailAddress + " is already in use by user id " + existingUserWithSameEmail.getId() + " (" + existingUserWithSameEmail.getIdentifier() + ").");
throw new Exception("User id " + idOfAuthUserToConvert + " (" + authenticatedUser.getIdentifier() + ") cannot be converted from remote to BuiltIn because the email address " + newEmailAddress + " is already in use by user id " + existingUserWithSameEmail.getId() + " (" + existingUserWithSameEmail.getIdentifier() + ").");
}
BuiltinUser builtinUser = new BuiltinUser();
builtinUser.setUserName(authenticatedUser.getUserIdentifier());
Expand All @@ -668,12 +722,12 @@ public BuiltinUser convertShibToBuiltIn(Long idOfAuthUserToConvert, String newEm
for (ConstraintViolation<?> violation : violations) {
logMsg.append(" Invalid value: <<<").append(violation.getInvalidValue()).append(">>> for ").append(violation.getPropertyPath()).append(" at ").append(violation.getLeafBean()).append(" - ").append(violation.getMessage());
}
throw new Exception("User id " + idOfAuthUserToConvert + " cannot be converted from Shibboleth to BuiltIn because of constraint violations on the BuiltIn user that would be created: " + numViolations + ". Details: " + logMsg);
throw new Exception("User id " + idOfAuthUserToConvert + " cannot be converted from remote to BuiltIn because of constraint violations on the BuiltIn user that would be created: " + numViolations + ". Details: " + logMsg);
}
try {
builtinUser = builtinUserServiceBean.save(builtinUser);
} catch (IllegalArgumentException ex) {
throw new Exception("User id " + idOfAuthUserToConvert + " cannot be converted from Shibboleth to BuiltIn because of an IllegalArgumentException creating the row in the builtinuser table: " + ex);
throw new Exception("User id " + idOfAuthUserToConvert + " cannot be converted from remote to BuiltIn because of an IllegalArgumentException creating the row in the builtinuser table: " + ex);
}
AuthenticatedUserLookup lookup = authenticatedUser.getAuthenticatedUserLookup();
if (lookup == null) {
Expand All @@ -683,9 +737,9 @@ public BuiltinUser convertShibToBuiltIn(Long idOfAuthUserToConvert, String newEm
if (providerId == null) {
throw new Exception("User id " + idOfAuthUserToConvert + " provider id is null.");
}
String shibProviderId = ShibAuthenticationProvider.PROVIDER_ID;
if (!providerId.equals(shibProviderId)) {
throw new Exception("User id " + idOfAuthUserToConvert + " cannot be converted from Shibboleth to BuiltIn because current provider id is '" + providerId + "' rather than '" + shibProviderId + "'.");
String builtinProviderId = BuiltinAuthenticationProvider.PROVIDER_ID;
if (providerId.equals(builtinProviderId)) {
throw new Exception("User id " + idOfAuthUserToConvert + " cannot be converted from remote to BuiltIn because current provider id is '" + providerId + "' which is the same as '" + builtinProviderId + "'. This user is already a BuiltIn user.");
}
lookup.setAuthenticationProviderId(BuiltinAuthenticationProvider.PROVIDER_ID);
lookup.setPersistentUserId(authenticatedUser.getUserIdentifier());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -325,10 +325,13 @@ private void mutateRequestForDevRandom(HttpServletRequest request) {

/**
* For testing, don't expect this to work well.
*
* @todo Move this method to somewhere that makes more sense for OAuth as
* well as Shib.
*/
public Map<String, String> getRandomUser() throws JsonSyntaxException, JsonIOException {
Map<String, String> fakeUser = new HashMap<>();
String sURL = "http://api.randomuser.me/0.8";
String sURL = "https://api.randomuser.me/0.8";
URL url = null;
try {
url = new URL(sURL);
Expand Down
Loading

0 comments on commit 2f46105

Please sign in to comment.