/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
package edu.harvard.iq.dataverse.authorization.providers.builtin;
import edu.harvard.iq.dataverse.DataFile;
import edu.harvard.iq.dataverse.DataFileServiceBean;
import edu.harvard.iq.dataverse.Dataset;
import edu.harvard.iq.dataverse.DatasetServiceBean;
import edu.harvard.iq.dataverse.DatasetVersionServiceBean;
import edu.harvard.iq.dataverse.Dataverse;
import edu.harvard.iq.dataverse.DataverseHeaderFragment;
import edu.harvard.iq.dataverse.DataverseServiceBean;
import edu.harvard.iq.dataverse.DataverseSession;
import edu.harvard.iq.dataverse.DvObject;
import edu.harvard.iq.dataverse.PermissionServiceBean;
import edu.harvard.iq.dataverse.PermissionsWrapper;
import edu.harvard.iq.dataverse.RoleAssignment;
import edu.harvard.iq.dataverse.SettingsWrapper;
import edu.harvard.iq.dataverse.UserNotification;
import static edu.harvard.iq.dataverse.UserNotification.Type.CREATEDV;
import edu.harvard.iq.dataverse.UserNotificationServiceBean;
import edu.harvard.iq.dataverse.authorization.AuthenticationServiceBean;
import edu.harvard.iq.dataverse.authorization.UserRecordIdentifier;
import edu.harvard.iq.dataverse.authorization.groups.Group;
import edu.harvard.iq.dataverse.authorization.groups.GroupServiceBean;
import edu.harvard.iq.dataverse.authorization.users.AuthenticatedUser;
import edu.harvard.iq.dataverse.confirmemail.ConfirmEmailData;
import edu.harvard.iq.dataverse.confirmemail.ConfirmEmailException;
import edu.harvard.iq.dataverse.confirmemail.ConfirmEmailInitResponse;
import edu.harvard.iq.dataverse.confirmemail.ConfirmEmailServiceBean;
import edu.harvard.iq.dataverse.confirmemail.ConfirmEmailUtil;
import edu.harvard.iq.dataverse.mydata.MyDataPage;
import edu.harvard.iq.dataverse.passwordreset.PasswordValidator;
import edu.harvard.iq.dataverse.settings.SettingsServiceBean;
import edu.harvard.iq.dataverse.util.BundleUtil;
import edu.harvard.iq.dataverse.util.JsfHelper;
import static edu.harvard.iq.dataverse.util.JsfHelper.JH;
import edu.harvard.iq.dataverse.util.SystemConfig;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.ejb.EJB;
import javax.faces.application.FacesMessage;
import javax.faces.component.UIComponent;
import javax.faces.component.UIInput;
import javax.faces.context.FacesContext;
import javax.faces.event.ActionEvent;
import javax.faces.view.ViewScoped;
import javax.inject.Inject;
import javax.inject.Named;
import org.apache.commons.lang.StringUtils;
import org.hibernate.validator.constraints.NotBlank;
import org.primefaces.event.TabChangeEvent;
/**
*
* @author xyang
*/
@ViewScoped
@Named("DataverseUserPage")
public class BuiltinUserPage implements java.io.Serializable {
private static final Logger logger = Logger.getLogger(BuiltinUserPage.class.getCanonicalName());
public enum EditMode {
CREATE, EDIT, CHANGE_PASSWORD, FORGOT
};
@Inject
DataverseSession session;
@EJB
DataverseServiceBean dataverseService;
@EJB
UserNotificationServiceBean userNotificationService;
@EJB
DatasetServiceBean datasetService;
@EJB
DataFileServiceBean fileService;
@EJB
DatasetVersionServiceBean datasetVersionService;
@EJB
PermissionServiceBean permissionService;
@EJB
BuiltinUserServiceBean builtinUserService;
@EJB
AuthenticationServiceBean authenticationService;
@EJB
ConfirmEmailServiceBean confirmEmailService;
@EJB
SystemConfig systemConfig;
@EJB
GroupServiceBean groupService;
@Inject
SettingsWrapper settingsWrapper;
@Inject
MyDataPage mydatapage;
@Inject
PermissionsWrapper permissionsWrapper;
@EJB
AuthenticationServiceBean authSvc;
private AuthenticatedUser currentUser;
private BuiltinUser builtinUser;
private EditMode editMode;
private String redirectPage = "dataverse.xhtml";
@NotBlank(message = "Please enter a password for your account.")
private String inputPassword;
@NotBlank(message = "Please enter a password for your account.")
private String currentPassword;
private Long dataverseId;
private List<UserNotification> notificationsList;
private int activeIndex;
private String selectTab = "somedata";
UIInput usernameField;
public EditMode getChangePasswordMode () {
return EditMode.CHANGE_PASSWORD;
}
public AuthenticatedUser getCurrentUser() {
return currentUser;
}
public void setCurrentUser(AuthenticatedUser currentUser) {
this.currentUser = currentUser;
}
public BuiltinUser getBuiltinUser() {
return builtinUser;
}
public void setBuiltinUser(BuiltinUser builtinUser) {
this.builtinUser = builtinUser;
}
public EditMode getEditMode() {
return editMode;
}
public void setEditMode(EditMode editMode) {
this.editMode = editMode;
}
public String getRedirectPage() {
return redirectPage;
}
public void setRedirectPage(String redirectPage) {
this.redirectPage = redirectPage;
}
public String getInputPassword() {
return inputPassword;
}
public void setInputPassword(String inputPassword) {
this.inputPassword = inputPassword;
}
public String getCurrentPassword() {
return currentPassword;
}
public void setCurrentPassword(String currentPassword) {
this.currentPassword = currentPassword;
}
public Long getDataverseId() {
if (dataverseId == null) {
dataverseId = dataverseService.findRootDataverse().getId();
}
return dataverseId;
}
public void setDataverseId(Long dataverseId) {
this.dataverseId = dataverseId;
}
public List getNotificationsList() {
return notificationsList;
}
public void setNotificationsList(List notificationsList) {
this.notificationsList = notificationsList;
}
public int getActiveIndex() {
return activeIndex;
}
public void setActiveIndex(int activeIndex) {
this.activeIndex = activeIndex;
}
public String getSelectTab() {
return selectTab;
}
public void setSelectTab(String selectTab) {
this.selectTab = selectTab;
}
public UIInput getUsernameField() {
return usernameField;
}
public void setUsernameField(UIInput usernameField) {
this.usernameField = usernameField;
}
public String init() {
// prevent creating a user if signup not allowed.
boolean safeDefaultIfKeyNotFound = true;
boolean signupAllowed = settingsWrapper.isTrueForKey(SettingsServiceBean.Key.AllowSignUp.toString(), safeDefaultIfKeyNotFound);
logger.fine("signup is allowed: " + signupAllowed);
if (editMode == EditMode.CREATE && !signupAllowed) {
return "/403.xhtml";
}
if (editMode == EditMode.CREATE) {
if (!session.getUser().isAuthenticated()) { // in create mode for new user
JH.addMessage(FacesMessage.SEVERITY_INFO, BundleUtil.getStringFromBundle("user.signup.tip"));
builtinUser = new BuiltinUser();
return "";
} else {
editMode = null; // we can't be in create mode for an existing user
}
}
if ( session.getUser().isAuthenticated() ) {
currentUser = (AuthenticatedUser) session.getUser();
notificationsList = userNotificationService.findByUser(((AuthenticatedUser)currentUser).getId());
if (currentUser.isBuiltInUser()) {
builtinUser = builtinUserService.findByUserName(currentUser.getUserIdentifier());
}
switch (selectTab) {
case "notifications":
activeIndex = 1;
displayNotification();
break;
case "dataRelatedToMe":
mydatapage.init();
break;
// case "groupsRoles":
// activeIndex = 2;
// break;
case "accountInfo":
activeIndex = 2;
// activeIndex = 3;
break;
case "apiTokenTab":
activeIndex = 3;
break;
default:
activeIndex = 0;
break;
}
} else {
return permissionsWrapper.notAuthorized();
}
return "";
}
public void edit(ActionEvent e) {
editMode = EditMode.EDIT;
}
public void changePassword(ActionEvent e) {
editMode = EditMode.CHANGE_PASSWORD;
}
public void forgotPassword(ActionEvent e) {
editMode = EditMode.FORGOT;
}
public void validateUserName(FacesContext context, UIComponent toValidate, Object value) {
String userName = (String) value;
boolean userNameFound = false;
BuiltinUser user = builtinUserService.findByUserName(userName);
if (editMode == EditMode.CREATE) {
if (user != null) {
userNameFound = true;
}
} else {
if (user != null && !user.getId().equals(builtinUser.getId())) {
userNameFound = true;
}
}
if (userNameFound) {
((UIInput) toValidate).setValid(false);
FacesMessage message = new FacesMessage(FacesMessage.SEVERITY_ERROR, JH.localize("user.username.taken"), null);
context.addMessage(toValidate.getClientId(context), message);
}
}
public void validateUserEmail(FacesContext context, UIComponent toValidate, Object value) {
String userEmail = (String) value;
boolean userEmailFound = false;
BuiltinUser user = builtinUserService.findByEmail(userEmail);
AuthenticatedUser aUser = authenticationService.getAuthenticatedUserByEmail(userEmail);
if (editMode == EditMode.CREATE) {
if (user != null || aUser != null) {
userEmailFound = true;
}
} else {
//In edit mode...
if (user != null || aUser != null){
userEmailFound = true;
}
//if there's a match on edit make sure that the email belongs to the
// user doing the editing by checking ids
if ((user != null && user.getId().equals(builtinUser.getId())) || (aUser!=null && aUser.getId().equals(builtinUser.getId()))){
userEmailFound = false;
}
}
if (userEmailFound) {
((UIInput) toValidate).setValid(false);
FacesMessage message = new FacesMessage(FacesMessage.SEVERITY_ERROR, JH.localize("user.email.taken"), null);
context.addMessage(toValidate.getClientId(context), message);
}
}
public void validateUserNameEmail(FacesContext context, UIComponent toValidate, Object value) {
String userName = (String) value;
boolean userNameFound = false;
BuiltinUser user = builtinUserService.findByUserName(userName);
if (user != null) {
userNameFound = true;
} else {
BuiltinUser user2 = builtinUserService.findByEmail(userName);
if (user2 != null) {
userNameFound = true;
}
}
if (!userNameFound) {
((UIInput) toValidate).setValid(false);
FacesMessage message = new FacesMessage("Username or Email is incorrect.");
context.addMessage(toValidate.getClientId(context), message);
}
}
public void validateCurrentPassword(FacesContext context, UIComponent toValidate, Object value) {
String password = (String) value;
if (StringUtils.isBlank(password)){
logger.log(Level.WARNING, "current password is blank");
((UIInput) toValidate).setValid(false);
FacesMessage message = new FacesMessage(FacesMessage.SEVERITY_ERROR,
"Password Error", "Password is blank: re-type it again.");
context.addMessage(toValidate.getClientId(context), message);
return;
} else {
logger.log(Level.INFO, "current paswword is not blank");
}
if ( ! PasswordEncryption.getVersion(builtinUser.getPasswordEncryptionVersion()).check(password, builtinUser.getEncryptedPassword()) ) {
((UIInput) toValidate).setValid(false);
FacesMessage message = new FacesMessage(FacesMessage.SEVERITY_ERROR, "Password Error", "Password is incorrect.");
context.addMessage(toValidate.getClientId(context), message);
}
}
public void validateNewPassword(FacesContext context, UIComponent toValidate, Object value) {
String password = (String) value;
if (StringUtils.isBlank(password)){
logger.log(Level.WARNING, "new password is blank");
((UIInput) toValidate).setValid(false);
FacesMessage message = new FacesMessage(FacesMessage.SEVERITY_ERROR,
"Password Error", "The new password is blank: re-type it again");
context.addMessage(toValidate.getClientId(context), message);
return;
} else {
logger.log(Level.INFO, "new paswword is not blank");
}
int minPasswordLength = 6;
boolean forceNumber = true;
boolean forceSpecialChar = false;
boolean forceCapitalLetter = false;
int maxPasswordLength = 255;
PasswordValidator validator = PasswordValidator.buildValidator(forceSpecialChar, forceCapitalLetter, forceNumber, minPasswordLength, maxPasswordLength);
boolean passwordIsComplexEnough = password!= null && validator.validatePassword(password);
if (!passwordIsComplexEnough) {
((UIInput) toValidate).setValid(false);
String messageDetail = "Password is not complex enough. The password must have at least one letter, one number and be at least " + minPasswordLength + " characters in length.";
FacesMessage message = new FacesMessage(FacesMessage.SEVERITY_ERROR, "Password Error", messageDetail);
context.addMessage(toValidate.getClientId(context), message);
}
}
public void updatePassword(String userName) {
String plainTextPassword = PasswordEncryption.generateRandomPassword();
BuiltinUser user = builtinUserService.findByUserName(userName);
if (user == null) {
user = builtinUserService.findByEmail(userName);
}
user.updateEncryptedPassword(PasswordEncryption.get().encrypt(plainTextPassword), PasswordEncryption.getLatestVersionNumber());
builtinUserService.save(user);
}
public String save() {
boolean passwordChanged = false;
boolean emailChanged = false;
if (editMode == EditMode.CREATE || editMode == EditMode.CHANGE_PASSWORD) {
if (inputPassword != null) {
builtinUser.updateEncryptedPassword(PasswordEncryption.get().encrypt(inputPassword), PasswordEncryption.getLatestVersionNumber());
passwordChanged = true;
} else {
// just defensive coding: for in case when the validator is not
// working
logger.log(Level.WARNING, "inputPassword is still null");
FacesMessage message = new FacesMessage(FacesMessage.SEVERITY_ERROR, JH.localize("user.noPasswd"), null);
FacesContext context = FacesContext.getCurrentInstance();
context.addMessage(null, message);
return null;
}
}
builtinUser = builtinUserService.save(builtinUser);
if (editMode == EditMode.CREATE) {
AuthenticatedUser au = authSvc.createAuthenticatedUser(
new UserRecordIdentifier(BuiltinAuthenticationProvider.PROVIDER_ID, builtinUser.getUserName()),
builtinUser.getUserName(), builtinUser.getDisplayInfo(), false);
if ( au == null ) {
// username exists
getUsernameField().setValid(false);
FacesMessage message = new FacesMessage(FacesMessage.SEVERITY_ERROR, JH.localize("user.username.taken"), null);
FacesContext context = FacesContext.getCurrentInstance();
context.addMessage(getUsernameField().getClientId(context), message);
return null;
}
session.setUser(au);
userNotificationService.sendNotification(au,
new Timestamp(new Date().getTime()),
UserNotification.Type.CREATEACC, null);
// go back to where user came from
if ("dataverse.xhtml".equals(redirectPage)) {
redirectPage = redirectPage + "&alias=" + dataverseService.findRootDataverse().getAlias();
}
try {
redirectPage = URLDecoder.decode(redirectPage, "UTF-8");
} catch (UnsupportedEncodingException ex) {
Logger.getLogger(BuiltinUserPage.class.getName()).log(Level.SEVERE, null, ex);
redirectPage = "dataverse.xhtml&alias=" + dataverseService.findRootDataverse().getAlias();
}
logger.log(Level.FINE, "Sending user to = {0}", redirectPage);
return redirectPage + (!redirectPage.contains("?") ? "?" : "&") + "faces-redirect=true";
} else {
String emailBeforeUpdate = currentUser.getEmail();
AuthenticatedUser savedUser = authSvc.updateAuthenticatedUser(currentUser, builtinUser.getDisplayInfo());
String emailAfterUpdate = savedUser.getEmail();
if (!emailBeforeUpdate.equals(emailAfterUpdate)) {
emailChanged = true;
}
editMode = null;
String msg = "Your account information has been successfully updated.";
if (passwordChanged) {
msg = "Your account password has been successfully changed.";
}
if (emailChanged) {
ConfirmEmailUtil confirmEmailUtil = new ConfirmEmailUtil();
String expTime = confirmEmailUtil.friendlyExpirationTime(systemConfig.getMinutesUntilConfirmEmailTokenExpires());
msg = msg + " Your email address has changed and must be re-verified. Please check your inbox at " + currentUser.getEmail() + " and follow the link we've sent. \n\nAlso, please note that the link will only work for the next " + expTime + " before it has expired.";
boolean sendEmail = true;
// delete unexpired token, if it exists (clean slate)
confirmEmailService.deleteTokenForUser(currentUser);
try {
ConfirmEmailInitResponse confirmEmailInitResponse = confirmEmailService.beginConfirm(currentUser);
} catch (ConfirmEmailException ex) {
logger.info("Unable to send email confirmation link to user id " + savedUser.getId());
}
session.setUser(currentUser);
JsfHelper.addSuccessMessage(msg);
} else {
JsfHelper.addFlashMessage(msg);
}
return null;
}
}
public String cancel() {
if (editMode == EditMode.CREATE) {
return "/dataverse.xhtml?alias=" + dataverseService.findRootDataverse().getAlias() + "&faces-redirect=true";
}
editMode = null;
return null;
}
public void submit(ActionEvent e) {
updatePassword(builtinUser.getUserName());
editMode = null;
}
public String remove(Long notificationId) {
UserNotification userNotification = userNotificationService.find(notificationId);
userNotificationService.delete(userNotification);
for (UserNotification uNotification : notificationsList) {
if (uNotification.getId() == userNotification.getId()) {
notificationsList.remove(uNotification);
break;
}
}
return null;
}
public void onTabChange(TabChangeEvent event) {
if (event.getTab().getId().equals("notifications")) {
displayNotification();
}
if (event.getTab().getId().equals("dataRelatedToMe")){
mydatapage.init();
}
}
private String getRoleStringFromUser(AuthenticatedUser au, DvObject dvObj) {
// Find user's role(s) for given dataverse/dataset
Set<RoleAssignment> roles = permissionService.assignmentsFor(au, dvObj);
List<String> roleNames = new ArrayList();
// Include roles derived from a user's groups
Set<Group> groupsUserBelongsTo = groupService.groupsFor(au, dvObj);
for (Group g : groupsUserBelongsTo) {
roles.addAll(permissionService.assignmentsFor(g, dvObj));
}
for (RoleAssignment ra : roles) {
roleNames.add(ra.getRole().getName());
}
if (roleNames.isEmpty()){
return "[Unknown]";
}
return StringUtils.join(roleNames, "/");
}
public void displayNotification() {
for (UserNotification userNotification : notificationsList) {
switch (userNotification.getType()) {
case ASSIGNROLE:
case REVOKEROLE:
// Can either be a dataverse or dataset, so search both
Dataverse dataverse = dataverseService.find(userNotification.getObjectId());
if (dataverse != null) {
userNotification.setRoleString(this.getRoleStringFromUser(this.getCurrentUser(), dataverse ));
userNotification.setTheObject(dataverse);
} else {
Dataset dataset = datasetService.find(userNotification.getObjectId());
if (dataset != null){
userNotification.setRoleString(this.getRoleStringFromUser(this.getCurrentUser(), dataset ));
userNotification.setTheObject(dataset);
} else {
DataFile datafile = fileService.find(userNotification.getObjectId());
userNotification.setRoleString(this.getRoleStringFromUser(this.getCurrentUser(), datafile ));
userNotification.setTheObject(datafile);
}
}
break;
case CREATEDV:
userNotification.setTheObject(dataverseService.find(userNotification.getObjectId()));
break;
case REQUESTFILEACCESS:
DataFile file = fileService.find(userNotification.getObjectId());
userNotification.setTheObject(file.getOwner());
break;
case GRANTFILEACCESS:
case REJECTFILEACCESS:
userNotification.setTheObject(datasetService.find(userNotification.getObjectId()));
break;
case MAPLAYERUPDATED:
case CREATEDS:
case SUBMITTEDDS:
case PUBLISHEDDS:
case RETURNEDDS:
userNotification.setTheObject(datasetVersionService.find(userNotification.getObjectId()));
break;
case CREATEACC:
userNotification.setTheObject(userNotification.getUser());
}
userNotification.setDisplayAsRead(userNotification.isReadNotification());
if (userNotification.isReadNotification() == false) {
userNotification.setReadNotification(true);
userNotificationService.save(userNotification);
}
}
}
public void sendConfirmEmail() {
logger.fine("called sendConfirmEmail()");
String userEmail = currentUser.getEmail();
ConfirmEmailUtil confirmEmailUtil = new ConfirmEmailUtil();
try {
confirmEmailService.beginConfirm(currentUser);
List<String> args = Arrays.asList(
userEmail,
confirmEmailUtil.friendlyExpirationTime(systemConfig.getMinutesUntilConfirmEmailTokenExpires()));
JsfHelper.addSuccessMessage(BundleUtil.getStringFromBundle("confirmEmail.submitRequest.success", args));
} catch (ConfirmEmailException ex) {
Logger.getLogger(BuiltinUserPage.class.getName()).log(Level.SEVERE, null, ex);
}
}
public boolean showVerifyEmailButton() {
/**
* Determines whether the button to send a verification email appears on user page
*/
if (confirmEmailService.findSingleConfirmEmailDataByUser(currentUser) == null
&& currentUser.getEmailConfirmed() == null) {
return true;
}
return false;
}
public boolean isEmailIsVerified() {
if (currentUser.getEmailConfirmed() != null && confirmEmailService.findSingleConfirmEmailDataByUser(currentUser) == null) {
return true;
} else return false;
}
public boolean isEmailNotVerified() {
if (currentUser.getEmailConfirmed() == null || confirmEmailService.findSingleConfirmEmailDataByUser(currentUser) != null) {
return true;
} else return false;
}
public boolean isEmailGrandfathered() {
ConfirmEmailUtil confirmEmailUtil = new ConfirmEmailUtil();
if (currentUser.getEmailConfirmed() == confirmEmailUtil.getGrandfatheredTime()) {
return true;
} else return false;
}
}