/* See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * Esri Inc. licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.esri.gpt.control.identity; import java.util.logging.Level; import com.esri.gpt.control.ResourceKeys; import com.esri.gpt.framework.context.ApplicationConfiguration; import com.esri.gpt.framework.context.RequestContext; import com.esri.gpt.framework.jsf.BaseActionListener; import com.esri.gpt.framework.jsf.MessageBroker; import com.esri.gpt.framework.mail.FeedbackMessage; import com.esri.gpt.framework.mail.MailRequest; import com.esri.gpt.framework.security.codec.PC1_Encryptor; import com.esri.gpt.framework.security.credentials.ChangePasswordCriteria; import com.esri.gpt.framework.security.credentials.CredentialsDeniedException; import com.esri.gpt.framework.security.credentials.EmailPolicyException; import com.esri.gpt.framework.security.credentials.PasswordConfirmationException; import com.esri.gpt.framework.security.credentials.PasswordPolicyException; import com.esri.gpt.framework.security.credentials.RecoverPasswordCriteria; import com.esri.gpt.framework.security.credentials.UsernamePasswordCredentials; import com.esri.gpt.framework.security.credentials.UsernamePolicyException; import com.esri.gpt.framework.security.identity.IdentityAdapter; import com.esri.gpt.framework.security.identity.IdentityConfiguration; import com.esri.gpt.framework.security.identity.IdentitySupport; import com.esri.gpt.framework.security.identity.NotAuthorizedException; import com.esri.gpt.framework.security.principal.User; import com.esri.gpt.framework.security.principal.UserAttributeMap; import com.esri.gpt.framework.util.Val; import javax.faces.component.UIComponent; import javax.faces.event.AbortProcessingException; import javax.faces.event.ActionEvent; import javax.servlet.http.HttpServletRequest; /** * Handles actions associated with user self care. * <p> * The action executed is based upon a supplied "command" attribute * associated with the UIComponent that triggers the processAction * event. Command values:<br/> * <li>changePassword - executes a password change</li> * <li>recoverPassword - recovers a forgotten password</li> * <li>registerUser - registers a user</li> * <li>sendFeedback - sends a feedback message </li> * <li>updateProfile - executes a user profile update</li> */ public class SelfCareController extends BaseActionListener { // class variables ============================================================= // instance variables ========================================================== private UserAttributeMap _activeUserAttributes = null; private ChangePasswordCriteria _changePasswordCriteria; private FeedbackMessage _feedbackMessage; private HasAttributeMap _hasAttributeMap = null; private User _newUser; private RecoverPasswordCriteria _recoverPasswordCriteria; // constructors ================================================================ /** Default constructor. */ public SelfCareController() {} // properties ================================================================== /** * Gets the active user profile attributes. * <p> * This method is intended for use from a JSP page only. * @return the active user profile attributes */ public UserAttributeMap getActiveUserAttributes() { if (_activeUserAttributes == null) { RequestContext rc = extractRequestContext(); User user = rc.getUser(); if (user.getAuthenticationStatus().getWasAuthenticated()) { _activeUserAttributes = new UserAttributeMap(user.getProfile()); // attempt to reload the user's profile IdentityAdapter idAdapter = rc.newIdentityAdapter(); User userUpdate = new User(); userUpdate.setKey(user.getKey()); userUpdate.setLocalID(user.getLocalID()); userUpdate.setDistinguishedName(user.getDistinguishedName()); userUpdate.setProfile(_activeUserAttributes); try { idAdapter.readUserProfile(userUpdate); } catch (Throwable t) { getLogger().log(Level.SEVERE,"Error reading user profile.",t); } } else { _activeUserAttributes = getNewUser().getProfile(); } } return _activeUserAttributes; } /** * Gets the active username. * <br/>This is for display only. * @return the active username */ public String getActiveUsername() { return extractRequestContext().getUser().getProfile().getUsername(); } /** * Setter for the active username. * <br/>This is for display only, the set is ignored * @param ignored */ public void setActiveUsername(String ignored) {} /** * Gets the change password criteria. * @return the change password criteria */ public ChangePasswordCriteria getChangePasswordCriteria() { if (_changePasswordCriteria == null) { _changePasswordCriteria = new ChangePasswordCriteria(); } return _changePasswordCriteria; } /** * Gets the message associated with user feedback. * @return the feedback message */ public FeedbackMessage getFeedbackMessage() { if (_feedbackMessage == null) { RequestContext context = extractRequestContext(); _feedbackMessage = new FeedbackMessage(); //_feedbackMessage.setFromName(context.getUser().getName()); _feedbackMessage.setFromAddress(context.getUser().getProfile().getEmailAddress()); } return _feedbackMessage; } /** * Returns a Map interface of configured user attributes to aid in * determining if a user attribute should be rendered. * <br/Example:<br/> * rendered="#{SelfCareController.hasUserAttribute['firstName']}" * @return the configured attribute map interface */ public HasAttributeMap getHasUserAttribute() { if (_hasAttributeMap == null) { _hasAttributeMap = new HasAttributeMap(getActiveUserAttributes()); } return _hasAttributeMap; } /** * Gets the identity configuration associated with the application. * @return the identity configuration */ private IdentityConfiguration getIdentityConfiguration() { RequestContext rc = extractRequestContext(); return rc.getApplicationConfiguration().getIdentityConfiguration(); } /** * Gets the user associated with new registration requests. * @return the user associated with new registration requests */ public User getNewUser() { if (_newUser == null) { IdentityConfiguration idConfig = getIdentityConfiguration(); _newUser = new User(); _newUser.setCredentials(new UsernamePasswordCredentials()); _newUser.setProfile(new UserAttributeMap(idConfig.getUserAttributeMap())); } return _newUser; } /** * Gets the credentials associated with the new registration requests. * @return the credentials associated with new registration requests */ public UsernamePasswordCredentials getNewUserCredentials() { return (UsernamePasswordCredentials)getNewUser().getCredentials(); } /** * Encrypts a password for configuration file storage. * @return the enscypted password */ public String getEncryptedPwd() { String sPwd = getChangePasswordCriteria().getNewCredentials().getPassword(); String sEncrypted = PC1_Encryptor.encrypt(sPwd); //String sDecrypted = PC1_Encryptor.decrypt(sEncrypted); //return sPwd+" "+sEncrypted+" "+sDecrypted; return sEncrypted; } /** This method does nothing, JSF chokes if it doesn't exist. */ public void setEncryptedPwd(String ignore) {} /** * Gets the recover password criteria. * @return the recover password criteria */ public RecoverPasswordCriteria getRecoverPasswordCriteria() { if (_recoverPasswordCriteria == null) { _recoverPasswordCriteria = new RecoverPasswordCriteria(); } return _recoverPasswordCriteria; } // methods ===================================================================== /** * Executes a change password action. * @param event the associated JSF action event * @param context the context associated with the active request * @throws Exception if an exception occurs */ private void executeChangePassword(ActionEvent event, RequestContext context) throws Exception { MessageBroker msgBroker = extractMessageBroker(); try { // execute the password change, // set success massage and navigation outcome IdentityAdapter idAdapter = context.newIdentityAdapter(); idAdapter.changePassword(context.getUser(),getChangePasswordCriteria()); msgBroker.addSuccessMessage("identity.changePassword.success"); setNavigationOutcome(ResourceKeys.NAVIGATIONOUTCOME_HOME_DIRECT); } catch (CredentialsDeniedException e) { msgBroker.addErrorMessage("identity.changePassword.err.old"); } catch (PasswordPolicyException e) { msgBroker.addErrorMessage("identity.changePassword.err.new"); } catch (PasswordConfirmationException e) { msgBroker.addErrorMessage("identity.changePassword.err.confirm"); } } /** * Executes a recover password action. * @param event the associated JSF action event * @param context the context associated with the active request * @throws Exception if an exception occurs */ private void executeRecoverPassword(ActionEvent event, RequestContext context) throws Exception { MessageBroker msgBroker = extractMessageBroker(); try { // initialize parameters, recover the password String sUsername = getRecoverPasswordCriteria().getUsername(); String sEmail = getRecoverPasswordCriteria().getEmailAddress(); IdentityAdapter idAdapter = context.newIdentityAdapter(); User user = idAdapter.recoverPassword(getRecoverPasswordCriteria()); if (user != null) { // get the new password UsernamePasswordCredentials upCred; upCred = user.getCredentials().getUsernamePasswordCredentials(); String sPassword = upCred.getPassword(); // send mail with the new password String[] args = new String[2]; args[0] = sUsername; args[1] = sPassword; String sSubject = msgBroker.retrieveMessage("identity.forgotPassword.email.subject"); String sBody = msgBroker.retrieveMessage("identity.forgotPassword.email.body",args); ApplicationConfiguration appConfig = context.getApplicationConfiguration(); MailRequest mailReq = appConfig.getMailConfiguration().newOutboundRequest(); mailReq.setToAddress(sEmail); mailReq.setSubject(sSubject); mailReq.setBody(sBody); mailReq.send(); // add the success message, set the navigation outcome msgBroker.addSuccessMessage("identity.forgotPassword.success"); setNavigationOutcome(ResourceKeys.NAVIGATIONOUTCOME_HOME_DIRECT); } else { // add the error message msgBroker.addErrorMessage("identity.forgotPassword.err.denied"); } } finally { } } /** * Executes a user registration action. * @param event the associated JSF action event * @param context the context associated with the active request * @throws Exception if an exception occurs */ private void executeRegisterUser(ActionEvent event, RequestContext context) throws Exception { MessageBroker msgBroker = extractMessageBroker(); try { // register the user, // add the success message, set navigation outcome to the home page IdentityAdapter idAdapter = context.newIdentityAdapter(); idAdapter.registerUser(getNewUser()); msgBroker.addSuccessMessage("catalog.identity.userRegistration.success"); setNavigationOutcome(ResourceKeys.NAVIGATIONOUTCOME_HOME_DIRECT); // attempt to login if not single sign-on mode boolean bSingleSignOn = false; if (!bSingleSignOn) { // authenticate the user, add the successful login message User user = extractRequestContext().getUser(); user.reset(); user.setCredentials(getNewUser().getCredentials()); idAdapter.authenticate(user); String[] args = new String[1]; args[0] = user.getName(); extractMessageBroker().addSuccessMessage("identity.login.success",args); } else { // navigate to login page if single sign-on mode setNavigationOutcome("catalog.identity.login"); } } catch (UsernamePolicyException e) { msgBroker.addErrorMessage("identity.profile.err.username"); } catch (PasswordPolicyException e) { msgBroker.addErrorMessage("identity.profile.err.password"); } catch (PasswordConfirmationException e) { msgBroker.addErrorMessage("identity.profile.err.confirm"); } catch (EmailPolicyException e) { msgBroker.addErrorMessage("identity.profile.err.email"); } catch (javax.naming.NameAlreadyBoundException e) { msgBroker.addErrorMessage("identity.profile.err.userExists"); } } /** * Executes the sending of a user feedback message. * @param event the associated JSF action event * @param context the context associated with the active request * @throws Exception if an exception occurs */ private void executeSendFeedback(ActionEvent event, RequestContext context) throws Exception { MessageBroker msgBroker = extractMessageBroker(); ApplicationConfiguration appConfig = context.getApplicationConfiguration(); FeedbackMessage msg = getFeedbackMessage(); // validate parameters boolean bOk = true; String sName = msg.getFromName(); String sEmail = msg.getFromAddress(); String sBody = msg.getBody(); String sSender = sEmail; if (!Val.chkEmail(sEmail)) { bOk = false; msgBroker.addErrorMessage("identity.feedback.err.email"); } else if (sBody.length() == 0) { bOk = false; msgBroker.addErrorMessage("identity.feedback.err.body"); } else if (sName.length() > 0) { sSender = sName; } // send mail if ok if (bOk) { // try to filter out mischievous content sSender = sSender.replaceAll("<", "<"); sBody = sBody.replaceAll("<", "<"); // build the message subject and body String[] args = new String[3]; args[0] = sSender; args[1] = sBody; args[2] = RequestContext.resolveBaseContextPath((HttpServletRequest) context.getServletRequest()); String sSubject = msgBroker.retrieveMessage("identity.feedback.email.subject"); sBody = msgBroker.retrieveMessage("identity.feedback.email.body",args); // send the message to the site MailRequest mailReq = appConfig.getMailConfiguration().newInboundRequest(); mailReq.setFromAddress(sEmail); mailReq.setSubject(sSubject); mailReq.setBody(sBody); mailReq.send(); // send a copy of the message to the user MailRequest mailReqCopy = appConfig.getMailConfiguration().newOutboundRequest(); mailReqCopy.setToAddress(sEmail); mailReqCopy.setSubject(sSubject); mailReqCopy.setBody(sBody); mailReqCopy.send(); // add the success message, set the navigation outcome msgBroker.addSuccessMessage("identity.feedback.success"); setNavigationOutcome(ResourceKeys.NAVIGATIONOUTCOME_HOME_DIRECT); } } /** * Executes a user profile update action. * @param event the associated JSF action event * @param context the context associated with the active request * @throws Exception if an exception occurs */ private void executeUpdateProfile(ActionEvent event, RequestContext context) throws Exception { MessageBroker msgBroker = extractMessageBroker(); try { // make a temp user to process the update in case of failure User user = extractRequestContext().getUser(); User userUpdate = new User(); userUpdate.setKey(user.getKey()); userUpdate.setLocalID(user.getLocalID()); userUpdate.setDistinguishedName(user.getDistinguishedName()); userUpdate.setProfile(getActiveUserAttributes()); // execute the update, // update the in memory profile for the active user // set the success message and navigation outcome IdentityAdapter idAdapter = context.newIdentityAdapter(); idAdapter.updateUserProfile(userUpdate); user.setProfile(userUpdate.getProfile()); msgBroker.addSuccessMessage("catalog.identity.myProfile.success"); setNavigationOutcome(ResourceKeys.NAVIGATIONOUTCOME_HOME_DIRECT); } catch (EmailPolicyException e) { msgBroker.addErrorMessage("identity.profile.err.email"); } catch (javax.naming.NameAlreadyBoundException e) { msgBroker.addErrorMessage("identity.profile.err.userExists"); } } /** * Handles a user self care action. * <p> * The action executed is based upon a supplied "command" attribute * associated with the UIComponent that triggered the processAction * event. Command values:<br/> * changePassword, recoverPassword, registerUser, sendFeedback, updateProfile * <p> * <br/>This is the default entry point for a sub-class of BaseActionListener. * <br/>This BaseActionListener handles the JSF processAction method and * invokes the processSubAction method of the sub-class. * @param event the associated JSF action event * @param context the context associated with the active request * @throws AbortProcessingException if processing should be aborted * @throws Exception if an exception occurs */ @Override protected void processSubAction(ActionEvent event, RequestContext context) throws AbortProcessingException, Exception { // determine the command IdentitySupport support = context.getIdentityConfiguration().getSupportedFunctions(); UIComponent component = event.getComponent(); String sCommand = Val.chkStr((String)component.getAttributes().get("command")); // execute the command if (sCommand.equals("changePassword")) { if (!support.getSupportsPasswordChange()) throw new NotAuthorizedException("Not authorized."); assertLoggedIn(context); executeChangePassword(event,context); } else if (sCommand.equals("recoverPassword")) { if (!support.getSupportsPasswordRecovery()) throw new NotAuthorizedException("Not authorized."); executeRecoverPassword(event,context); } else if (sCommand.equals("registerUser")) { if (!support.getSupportsUserRegistration()) throw new NotAuthorizedException("Not authorized."); executeRegisterUser(event,context); } else if (sCommand.equals("sendFeedback")) { executeSendFeedback(event,context); } else if (sCommand.equals("updateProfile")) { if (!support.getSupportsUserProfileManagement()) throw new NotAuthorizedException("Not authorized."); assertLoggedIn(context); executeUpdateProfile(event,context); } } }