/* * Copyright (c) 2014, WSO2 Inc. (http://www.wso2.org) All Rights Reserved. * * WSO2 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 org.wso2.carbon.identity.mgt.services; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.wso2.carbon.base.MultitenantConstants; import org.wso2.carbon.captcha.mgt.beans.CaptchaInfoBean; import org.wso2.carbon.captcha.mgt.util.CaptchaUtil; import org.wso2.carbon.identity.base.IdentityException; import org.wso2.carbon.identity.mgt.ChallengeQuestionProcessor; import org.wso2.carbon.identity.mgt.IdentityMgtConfig; import org.wso2.carbon.identity.mgt.IdentityMgtServiceException; import org.wso2.carbon.identity.mgt.RecoveryProcessor; import org.wso2.carbon.identity.mgt.beans.UserIdentityMgtBean; import org.wso2.carbon.identity.mgt.beans.VerificationBean; import org.wso2.carbon.identity.mgt.constants.IdentityMgtConstants; import org.wso2.carbon.identity.mgt.dto.NotificationDataDTO; import org.wso2.carbon.identity.mgt.dto.UserChallengesDTO; import org.wso2.carbon.identity.mgt.dto.UserDTO; import org.wso2.carbon.identity.mgt.dto.UserIdentityClaimDTO; import org.wso2.carbon.identity.mgt.dto.UserRecoveryDTO; import org.wso2.carbon.identity.mgt.dto.UserRecoveryDataDO; import org.wso2.carbon.identity.mgt.internal.IdentityMgtServiceComponent; import org.wso2.carbon.identity.mgt.util.UserIdentityManagementUtil; import org.wso2.carbon.identity.mgt.util.Utils; import org.wso2.carbon.user.api.UserStoreException; import org.wso2.carbon.user.api.UserStoreManager; import org.wso2.carbon.utils.multitenancy.MultitenantUtils; import java.util.UUID; // TODO: User Account Recovery Service public class UserIdentityManagementService { Log log = LogFactory.getLog(UserIdentityManagementService.class); /** * Authenticates the user with the temporary credentials and returns user * identity recovery data such as primary email address, telephone number * and all other identity claims of the user including the identity property * "isUserMustChangePassword". These claims are useful when the user is * recovering the identity using a temporary credential may be after * forgetting their password or after the identity being stolen. Then they * can update the values for these identity claims to keep their identity * safe. * TODO : Captcha must be considered * * @param userName * @param tempCredential * @return * @throws IdentityMgtServiceException */ public UserIdentityClaimDTO[] authenticateWithTemporaryCredentials(String userName, String tempCredential) throws IdentityMgtServiceException { try { int tenantId = Utils.getTenantId(MultitenantUtils.getTenantDomain(userName)); boolean isValid = UserIdentityManagementUtil.isValidIdentityMetadata(userName, tenantId, UserRecoveryDataDO.METADATA_TEMPORARY_CREDENTIAL, tempCredential); if (!isValid) { log.warn("WARNING: Invalidated temporary credential provided by " + userName); throw new IdentityMgtServiceException("Invalid temporary credential provided"); } UserStoreManager userStoreManager = IdentityMgtServiceComponent.getRealmService() .getTenantUserRealm(tenantId) .getUserStoreManager(); userStoreManager.authenticate(userName, tempCredential); // this credential should not be used again UserIdentityManagementUtil.invalidateUserIdentityMetadata(userName, tenantId, UserRecoveryDataDO.METADATA_TEMPORARY_CREDENTIAL, tempCredential); return UserIdentityManagementUtil.getAllUserIdentityClaims(userName); } catch (UserStoreException e) { log.error("Error while authenticating", e); throw new IdentityMgtServiceException("Error while authenticating the user"); } catch (IdentityException e) { log.error("Error while authenticating", e); throw new IdentityMgtServiceException("Error while authenticating the user"); } } /** * Validates the confirmation code and then unlock the user account * * @param userName * @param confirmationCode * @return * @throws IdentityMgtServiceException */ //TODO : expiration of confirmation code (1 time, 24hrs). Use only UserName public UserIdentityClaimDTO[] confirmUserRegistration(String userName, String confirmationCode) throws IdentityMgtServiceException { try { int tenantId = Utils.getTenantId(MultitenantUtils.getTenantDomain(userName)); // throws an exception if invalid boolean isValid = UserIdentityManagementUtil.isValidIdentityMetadata(userName, tenantId, UserRecoveryDataDO.METADATA_CONFIRMATION_CODE, confirmationCode); if (!isValid) { log.warn("WARNING: Invalid confirmation code provided by " + userName); throw new IdentityMgtServiceException("Invalid confirmation code provided"); } UserStoreManager userStoreManager = IdentityMgtServiceComponent.getRealmService() .getTenantUserRealm(tenantId) .getUserStoreManager(); // update the user identity claim UserIdentityManagementUtil.unlockUserAccount(userName, userStoreManager); // invalidate the confirmation code UserIdentityManagementUtil.invalidateUserIdentityMetadata(userName, tenantId, UserRecoveryDataDO.METADATA_CONFIRMATION_CODE, confirmationCode); return UserIdentityManagementUtil.getAllUserIdentityClaims(userName); } catch (UserStoreException e) { log.error("Error while confirming the account", e); throw new IdentityMgtServiceException("Error while confirming the account"); } catch (IdentityException e) { log.error("Error while confirming the account", e); throw new IdentityMgtServiceException("Error while confirming the account"); } } /** * Checks the security questions and their answerers against the user's * stored questions and answerers. If not all security questions of the user * are answered, an exception will be thrown. After all security questions * are answered properly, then the system will generate a random password, * and reset the user password with it and then will be returned the * resulting DTO containing the temporary password. * TODO : Re-think * * @param userName * @param secQuesAnsweres * @return * @throws IdentityMgtServiceException */ public void recoverUserIdentityWithSecurityQuestions(String userName, UserIdentityClaimDTO[] secQuesAnsweres) throws IdentityMgtServiceException { try { int tenantId = Utils.getTenantId(MultitenantUtils.getTenantDomain(userName)); UserStoreManager userStoreManager = IdentityMgtServiceComponent.getRealmService() .getTenantUserRealm(tenantId) .getUserStoreManager(); UserIdentityClaimDTO[] storedSecQuesAnswers = UserIdentityManagementUtil.getUserSecurityQuestions(userName, userStoreManager); // have not answered all questions of the user if (secQuesAnsweres.length < storedSecQuesAnswers.length) { throw new IdentityMgtServiceException("All questions must be answered"); } // NOW check the answer for every question int numberOfAnsweredQuestions = 0; // // for every stored security question for (UserIdentityClaimDTO storedSecQues : storedSecQuesAnswers) { // for every answered security question for (UserIdentityClaimDTO answredSecQues : secQuesAnsweres) { // when the questions are equal, check for the answer if (answredSecQues.getClaimUri().trim().equals(storedSecQues.getClaimUri().trim())) { // if answerers are not equal, throw an exception if (!answredSecQues.getClaimValue().trim() .equals(storedSecQues.getClaimValue().trim())) { throw new IdentityMgtServiceException( "Invalid answeres. Identity recovery failed"); } numberOfAnsweredQuestions++; } } } // not all USER's security questions has been answered if (numberOfAnsweredQuestions < storedSecQuesAnswers.length) { throw new IdentityMgtServiceException("All questions must be answered"); } // now okay to recover // reset the password with a random value char[] tempPassword = UserIdentityManagementUtil.generateTemporaryPassword(); userStoreManager.updateCredentialByAdmin(userName, tempPassword); // store the temp password as a Metadata UserRecoveryDataDO metadataDO = new UserRecoveryDataDO(); metadataDO.setUserName(userName).setTenantId(tenantId).setCode(new String(tempPassword)); UserIdentityManagementUtil.storeUserIdentityMetadata(metadataDO); // sending an email to the user UserIdentityMgtBean bean = new UserIdentityMgtBean(); String email = userStoreManager.getUserClaimValue(userName, IdentityMgtConfig.getInstance() .getAccountRecoveryClaim(), null); log.debug("Sending email to " + email); bean.setUserId(userName).setUserTemporaryPassword(new String(tempPassword)).setEmail(email); UserIdentityManagementUtil.notifyViaEmail(bean); } catch (UserStoreException e) { log.error("Error while recovering user identity", e); throw new IdentityMgtServiceException("Error while recovering user identity"); } catch (IdentityException e) { log.error("Error while recovering user identity", e); throw new IdentityMgtServiceException("Error while recovering user identity"); } } /** * Recovers the account with user email * TODO : what if the user name is invalid, send the error code over mail. TODO : store the temp in metadata, DONOT update. * * @param userName * @throws IdentityMgtServiceException */ public void recoverUserIdentityWithEmail(String userName) throws IdentityMgtServiceException { int tenantId; try { tenantId = Utils.getTenantId(MultitenantUtils.getTenantDomain(userName)); UserStoreManager userStoreManager = IdentityMgtServiceComponent.getRealmService() .getTenantUserRealm(tenantId) .getUserStoreManager(); // reset the password with a random value char[] tempPassword = UserIdentityManagementUtil.generateTemporaryPassword(); userStoreManager.updateCredentialByAdmin(userName, new String(tempPassword)); // sending email UserIdentityMgtBean bean = new UserIdentityMgtBean(); String email = userStoreManager.getUserClaimValue(userName, IdentityMgtConfig.getInstance() .getAccountRecoveryClaim(), null); log.debug("Sending email to " + email); bean.setUserId(userName).setUserTemporaryPassword(new String(tempPassword)).setEmail(email); UserIdentityManagementUtil.notifyViaEmail(bean); } catch (UserStoreException e) { log.error("Error while recovering user identity", e); throw new IdentityMgtServiceException("Error while recovering user identity"); } catch (IdentityException e) { log.error("Error while recovering user identity", e); throw new IdentityMgtServiceException("Error while recovering user identity"); } } /** * Returns an array of primary security questions. Primary security * questions are the security questions which were configured by the admin * and every user will have to answer selected set of questions from this. * * @return * @throws IdentityMgtServiceException */ public String[] getPrimarySecurityQuestions() throws IdentityMgtServiceException { try { return UserIdentityManagementUtil.getPrimaryQuestions(MultitenantConstants.SUPER_TENANT_ID); } catch (IdentityException e) { throw new IdentityMgtServiceException("Error while reading security questions", e); } } ///////////////////////////////////// new methods /////////////////////////////////////////////// /** * verifies user account w.r.t confirmation key * * @param confirmationKey key * @return verified result as a bean */ public VerificationBean confirmUserAccount(String confirmationKey) { return IdentityMgtServiceComponent.getRecoveryProcessor().verifyConfirmationKey(confirmationKey); } /** * process password recovery for given user * * @return recovery process success or not * @throws IdentityException if fails */ public boolean processPasswordRecovery(String userId, String confirmationCode, String notificationType) throws IdentityMgtServiceException { UserDTO userDTO = null; try { userDTO = Utils.processUserId(userId); } catch (IdentityException e) { throw new IdentityMgtServiceException("invalid user name", e); } RecoveryProcessor processor = IdentityMgtServiceComponent.getRecoveryProcessor(); VerificationBean bean = processor.verifyConfirmationKey(confirmationCode); if (!bean.isVerified()) { log.warn("Invalid user is trying to recover the password : " + userId); return false; } UserRecoveryDTO dto = new UserRecoveryDTO(userDTO); dto.setNotification(IdentityMgtConstants.Notification.PASSWORD_RESET_RECOVERY); dto.setNotificationType(notificationType); NotificationDataDTO dataDTO = null; try { dataDTO = processor.recoverWithNotification(dto); } catch (IdentityException e) { throw new IdentityMgtServiceException("Error while password recovery.", e); } return dataDTO.isNotificationSent(); } /** * get challenges of user * * @return array of challenges if null, return empty array * @throws IdentityException if fails */ public UserChallengesDTO[] getChallengeQuestionsForUser(String userName, String confirmation) throws IdentityMgtServiceException { UserDTO userDTO = null; try { userDTO = Utils.processUserId(userName); } catch (IdentityException e) { throw new IdentityMgtServiceException("Invalid user name.", e); } RecoveryProcessor processor = IdentityMgtServiceComponent.getRecoveryProcessor(); VerificationBean bean = processor.verifyConfirmationKey(confirmation); if (bean.isVerified()) { try { processor.createConfirmationCode(userDTO, confirmation); } catch (IdentityException e) { log.error("Error in creating confirmation code.", e); } return processor.getQuestionProcessor(). getChallengeQuestionsOfUser(userDTO.getUserId(), userDTO.getTenantId(), false); } return new UserChallengesDTO[0]; } /** * verify challenge questions * * @return verification results as been * @throws IdentityException if any error occurs */ public VerificationBean verifyChallengeQuestion(String userName, String confirmation, UserChallengesDTO[] userChallengesDTOs) throws IdentityMgtServiceException { VerificationBean bean = new VerificationBean(); bean.setVerified(false); RecoveryProcessor recoveryProcessor = IdentityMgtServiceComponent.getRecoveryProcessor(); if (userChallengesDTOs == null || userChallengesDTOs.length < 1) { log.error("no challenges provided by user for verifications."); bean.setError("no challenges provided by user for verifications."); return bean; } UserDTO userDTO = null; try { userDTO = Utils.processUserId(userName); } catch (IdentityException e) { throw new IdentityMgtServiceException("Invalid user name.", e); } if (recoveryProcessor.verifyConfirmationKey(confirmation).isVerified()) { log.warn("Invalid user is trying to verify user challenges."); bean.setError("Invalid user is trying to verify user challenges."); return bean; } ChallengeQuestionProcessor processor = recoveryProcessor.getQuestionProcessor(); boolean verification = processor.verifyChallengeQuestion(userDTO.getUserId(), userDTO.getTenantId(), userChallengesDTOs); if (verification) { String code = UUID.randomUUID().toString(); try { recoveryProcessor.createConfirmationCode(userDTO, code); } catch (IdentityException e) { log.error("Error while creating confirmation code.", e); } bean = new VerificationBean(userName, code); } return bean; } /** * proceed updating credentials of user * * @param captchaInfoBean bean class that contains captcha information * @return True, if successful in verifying and hence updating the credentials. */ public VerificationBean updateCredential(String userName, String confirmation, String password, CaptchaInfoBean captchaInfoBean) { RecoveryProcessor recoveryProcessor = IdentityMgtServiceComponent.getRecoveryProcessor(); if (IdentityMgtConfig.getInstance().isCaptchaVerificationInternallyManaged()) { try { CaptchaUtil.processCaptchaInfoBean(captchaInfoBean); } catch (Exception e) { log.error("Error while processing captcha bean.", e); return new VerificationBean(VerificationBean.ERROR_CODE_INVALID_CAPTCHA); } } try { UserDTO userDTO = Utils.processUserId(userName); if (recoveryProcessor.verifyConfirmationKey(confirmation).isVerified()) { Utils.updatePassword(userDTO.getUserId(), userDTO.getTenantId(), password); log.info("Credential is updated for user : " + userDTO.getUserId() + " and tenant domain : " + userDTO.getTenantDomain()); return new VerificationBean(true); } else { log.warn("Invalid user tried to update credential with user Id : " + userDTO.getUserId() + " and tenant domain : " + userDTO.getTenantDomain()); } } catch (Exception e) { log.error("Error while updating credential for user : " + userName, e); } return new VerificationBean(VerificationBean.ERROR_CODE_UNEXPECTED); } }