package edu.harvard.iq.dataverse.api; import edu.harvard.iq.dataverse.UserNotification; import edu.harvard.iq.dataverse.actionlogging.ActionLogRecord; import edu.harvard.iq.dataverse.authorization.UserRecordIdentifier; import edu.harvard.iq.dataverse.authorization.providers.builtin.BuiltinAuthenticationProvider; import edu.harvard.iq.dataverse.authorization.providers.builtin.BuiltinUser; import edu.harvard.iq.dataverse.authorization.providers.builtin.BuiltinUserServiceBean; import edu.harvard.iq.dataverse.authorization.providers.builtin.PasswordEncryption; import edu.harvard.iq.dataverse.authorization.users.ApiToken; import edu.harvard.iq.dataverse.authorization.users.AuthenticatedUser; import java.sql.Timestamp; import java.util.Calendar; import java.util.logging.Level; import java.util.logging.Logger; import javax.ejb.EJB; import javax.ejb.EJBException; import javax.json.Json; import javax.json.JsonObjectBuilder; import javax.ws.rs.GET; import javax.ws.rs.POST; import javax.ws.rs.Path; import javax.ws.rs.PathParam; import javax.ws.rs.QueryParam; import javax.ws.rs.core.Response; import javax.ws.rs.core.Response.Status; import static edu.harvard.iq.dataverse.util.json.JsonPrinter.jsonForAuthUser; import static edu.harvard.iq.dataverse.util.json.JsonPrinter.json; import java.util.Date; /** * REST API bean for managing {@link BuiltinUser}s. * * @author michael */ @Path("builtin-users") public class BuiltinUsers extends AbstractApiBean { private static final Logger logger = Logger.getLogger(BuiltinUsers.class.getName()); private static final String API_KEY_IN_SETTINGS = "BuiltinUsers.KEY"; /** * Users have not requested the ability to retrieve their API token using * their email address but we could. Here's the issue for which we are * enabling login via email address: * https://github.com/IQSS/dataverse/issues/2115 */ public static boolean retrievingApiTokenViaEmailEnabled = false; @EJB protected BuiltinUserServiceBean builtinUserSvc; @GET @Path("{username}/api-token") public Response getApiToken( @PathParam("username") String username, @QueryParam("password") String password ) { BuiltinUser u = null; if (retrievingApiTokenViaEmailEnabled) { u = builtinUserSvc.findByUsernameOrEmail(username); } else { u = builtinUserSvc.findByUserName(username); } if ( u == null ) return badRequest("Bad username or password"); boolean passwordOk = PasswordEncryption.getVersion(u.getPasswordEncryptionVersion()) .check(password, u.getEncryptedPassword() ); if ( ! passwordOk ) return badRequest("Bad username or password"); AuthenticatedUser authUser = authSvc.lookupUser(BuiltinAuthenticationProvider.PROVIDER_ID, u.getUserName()); ApiToken t = authSvc.findApiTokenByUser(authUser); return (t != null ) ? ok(t.getTokenString()) : notFound("User " + username + " does not have an API token"); } /** * Created this new API command because the save method could not be run * from the RestAssured API. RestAssured doesn't allow a Post request to * contain both a body and request parameters. TODO: replace current usage * of save() with create? * * @param user * @param password * @param key * @return */ @POST @Path("{password}/{key}") public Response create(BuiltinUser user, @PathParam("password") String password, @PathParam("key") String key) { return internalSave(user, password, key); } @POST public Response save(BuiltinUser user, @QueryParam("password") String password, @QueryParam("key") String key) { return internalSave(user, password, key); } private Response internalSave(BuiltinUser user, String password, String key) { String expectedKey = settingsSvc.get(API_KEY_IN_SETTINGS); if (expectedKey == null) { return error(Status.SERVICE_UNAVAILABLE, "Dataverse config issue: No API key defined for built in user management"); } if (!expectedKey.equals(key)) { return badApiKey(key); } ActionLogRecord alr = new ActionLogRecord(ActionLogRecord.ActionType.BuiltinUser, "create"); try { if (password != null) { user.updateEncryptedPassword(PasswordEncryption.get().encrypt(password), PasswordEncryption.getLatestVersionNumber()); } // Make sure the identifier is unique if ( (builtinUserSvc.findByUserName(user.getUserName()) != null) || ( authSvc.identifierExists(user.getUserName())) ) { return error(Status.BAD_REQUEST, "username '" + user.getUserName() + "' already exists"); } user = builtinUserSvc.save(user); AuthenticatedUser au = authSvc.createAuthenticatedUser( new UserRecordIdentifier(BuiltinAuthenticationProvider.PROVIDER_ID, user.getUserName()), user.getUserName(), user.getDisplayInfo(), false); /** * @todo Move this to * AuthenticationServiceBean.createAuthenticatedUser */ userNotificationSvc.sendNotification(au, new Timestamp(new Date().getTime()), UserNotification.Type.CREATEACC, null); ApiToken token = new ApiToken(); token.setTokenString(java.util.UUID.randomUUID().toString()); token.setAuthenticatedUser(au); Calendar c = Calendar.getInstance(); token.setCreateTime(new Timestamp(c.getTimeInMillis())); c.roll(Calendar.YEAR, 1); token.setExpireTime(new Timestamp(c.getTimeInMillis())); authSvc.save(token); JsonObjectBuilder resp = Json.createObjectBuilder(); resp.add("user", json(user)); resp.add("authenticatedUser", jsonForAuthUser(au)); resp.add("apiToken", token.getTokenString()); alr.setInfo("builtinUser:" + user.getUserName() + " authenticatedUser:" + au.getIdentifier() ); return ok(resp); } catch ( EJBException ejbx ) { alr.setActionResult(ActionLogRecord.Result.InternalError); alr.setInfo( alr.getInfo() + "// " + ejbx.getMessage()); if ( ejbx.getCausedByException() instanceof IllegalArgumentException ) { return error(Status.BAD_REQUEST, "Bad request: can't save user. " + ejbx.getCausedByException().getMessage()); } else { logger.log(Level.WARNING, "Error saving user: ", ejbx); return error(Status.INTERNAL_SERVER_ERROR, "Can't save user: " + ejbx.getMessage()); } } catch (Exception e) { logger.log(Level.WARNING, "Error saving user", e); alr.setActionResult(ActionLogRecord.Result.InternalError); alr.setInfo( alr.getInfo() + "// " + e.getMessage()); return error(Status.INTERNAL_SERVER_ERROR, "Can't save user: " + e.getMessage()); } finally { actionLogSvc.log(alr); } } }