/* * Copyright (C) 2007-2014 Crafter Software Corporation. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package org.craftercms.profile.services.impl; import java.io.ByteArrayInputStream; import java.io.FileNotFoundException; import java.io.InputStream; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.Date; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import java.util.regex.Pattern; import javax.activation.MimetypesFileTypeMap; import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.collections4.MapUtils; import org.apache.commons.collections4.Predicate; import org.apache.commons.io.FileExistsException; import org.apache.commons.io.FilenameUtils; import org.apache.commons.lang3.ArrayUtils; import org.apache.commons.lang3.StringUtils; import org.bson.types.ObjectId; import org.craftercms.commons.collections.IterableUtils; import org.craftercms.commons.crypto.CipherUtils; import org.craftercms.commons.i10n.I10nLogger; import org.craftercms.commons.logging.Logged; import org.craftercms.commons.mail.EmailUtils; import org.craftercms.commons.mongo.DuplicateKeyException; import org.craftercms.commons.mongo.FileInfo; import org.craftercms.commons.mongo.MongoDataException; import org.craftercms.commons.mongo.UpdateHelper; import org.craftercms.commons.security.exception.ActionDeniedException; import org.craftercms.commons.security.exception.PermissionException; import org.craftercms.commons.security.permissions.PermissionEvaluator; import org.craftercms.profile.api.AccessToken; import org.craftercms.profile.api.AttributeAction; import org.craftercms.profile.api.AttributeDefinition; import org.craftercms.profile.api.Profile; import org.craftercms.profile.api.SortOrder; import org.craftercms.profile.api.Tenant; import org.craftercms.profile.api.TenantAction; import org.craftercms.profile.api.Ticket; import org.craftercms.profile.api.VerificationToken; import org.craftercms.profile.api.exceptions.I10nProfileException; import org.craftercms.profile.api.exceptions.ProfileException; import org.craftercms.profile.api.services.AuthenticationService; import org.craftercms.profile.api.services.ProfileAttachment; import org.craftercms.profile.api.services.ProfileService; import org.craftercms.profile.api.services.TenantService; import org.craftercms.profile.exceptions.AttributeNotDefinedException; import org.craftercms.profile.exceptions.InvalidEmailAddressException; import org.craftercms.profile.exceptions.InvalidQueryException; import org.craftercms.profile.exceptions.NoSuchProfileException; import org.craftercms.profile.exceptions.NoSuchTenantException; import org.craftercms.profile.exceptions.NoSuchTicketException; import org.craftercms.profile.exceptions.NoSuchVerificationTokenException; import org.craftercms.profile.exceptions.ProfileExistsException; import org.craftercms.profile.repositories.ProfileRepository; import org.craftercms.profile.services.VerificationService; import org.craftercms.profile.utils.db.ProfileUpdater; import org.springframework.beans.factory.annotation.Required; /** * Default implementation of {@link org.craftercms.profile.api.services.ProfileService}. * * @author avasquez */ @Logged public class ProfileServiceImpl implements ProfileService { private static final I10nLogger logger = new I10nLogger(ProfileServiceImpl.class, "crafter.profile.messages.logging"); public static final String LOG_KEY_PROFILE_CREATED = "profile.profile.profileCreated"; public static final String LOG_KEY_PROFILE_UPDATED = "profile.profile.profileUpdated"; public static final String LOG_KEY_PROFILE_VERIFIED = "profile.profile.profileVerified"; public static final String LOG_KEY_PROFILE_ENABLED = "profile.profile.profileEnabled"; public static final String LOG_KEY_PROFILE_DISABLED = "profile.profile.profileDisabled"; public static final String LOG_KEY_PROFILE_ROLES_ADDED = "profile.profile.rolesAdded"; public static final String LOG_KEY_PROFILE_ROLES_REMOVED = "profile.profile.rolesRemoved"; public static final String LOG_KEY_PROFILE_ATTRIBS_UPDATED = "profile.profile.attributesUpdated"; public static final String LOG_KEY_PROFILE_ATTRIBS_REMOVED = "profile.profile.attributesRemoved"; public static final String LOG_KEY_PROFILE_DELETED = "profile.profile.profileDeleted"; public static final String LOG_KEY_PASSWORD_CHANGED = "profile.profile.passwordChanged"; public static final String ERROR_KEY_CREATE_PROFILE_ERROR = "profile.profile.createProfileError"; public static final String ERROR_KEY_GET_PROFILE_BY_QUERY_ERROR = "profile.profile.getProfileByQueryError"; public static final String ERROR_KEY_GET_PROFILE_ERROR = "profile.profile.getProfileError"; public static final String ERROR_KEY_UPDATE_PROFILE_ERROR = "profile.profile.updateProfileError"; public static final String ERROR_KEY_DELETE_PROFILE_ERROR = "profile.profile.deleteProfileError"; public static final String ERROR_KEY_GET_PROFILE_COUNT_BY_QUERY_ERROR = "profile.profile" + ".getProfileCountByQueryError"; public static final String ERROR_KEY_GET_PROFILES_BY_QUERY_ERROR = "profile.profile.getProfilesByQueryError"; public static final String ERROR_KEY_GET_PROFILE_BY_USERNAME_ERROR = "profile.profile.getProfileByUsernameError"; public static final String ERROR_KEY_GET_PROFILE_COUNT_ERROR = "profile.profile.getProfileCountError"; public static final String ERROR_KEY_GET_PROFILES_ERROR = "profile.profile.getProfilesError"; public static final String ERROR_KEY_GET_PROFILE_RANGE_ERROR = "profile.profile.getProfileRangeError"; public static final String ERROR_KEY_GET_PROFILES_BY_ROLE_ERROR = "profile.profile.getProfilesByRoleError"; public static final String ERROR_KEY_GET_PROFILES_BY_EXISTING_ATTRIB_ERROR = "profile.profile" + ".getProfilesByExistingAttributeError"; public static final String ERROR_KEY_GET_PROFILES_BY_ATTRIB_VALUE_ERROR = "profile.profile" + ".getProfilesByAttributeValueError"; public static final String ERROR_KEY_TENANT_NOT_ALLOWED = "profile.profile.query.tenantNotAllowed"; public static final String ERROR_KEY_WHERE_NOT_ALLOWED = "profile.profile.query.whereNotAllowed"; public static final String ERROR_KEY_ATTRIBUTE_NOT_ALLOWED = "profile.profile.query.attributeNotAllowed"; public static final Pattern QUERY_TENANT_PATTERN = Pattern.compile("['\"]?tenant['\"]?\\s*:"); public static final Pattern QUERY_WHERE_PATTERN = Pattern.compile("['\"]?\\$where['\"]?\\s*:"); public static final String QUERY_ATTRIBUTE_PATTERN_FORMAT = "['\"]?attributes\\.%s(\\.[^'\":]+)?['\"]?\\s*:"; public static final String QUERY_FINAL_FORMAT = "{$and: [{tenant: '%s'}, %s]}"; protected PermissionEvaluator<AccessToken, String> tenantPermissionEvaluator; protected PermissionEvaluator<AccessToken, AttributeDefinition> attributePermissionEvaluator; protected ProfileRepository profileRepository; protected TenantService tenantService; protected AuthenticationService authenticationService; protected VerificationService verificationService; protected List<String> validAttachmentMimeTypes; protected String newProfileEmailFromAddress; protected String newProfileEmailSubject; protected String newProfileEmailTemplateName; protected String resetPwdEmailFromAddress; protected String resetPwdEmailSubject; protected String resetPwdEmailTemplateName; @Required public void setTenantPermissionEvaluator(PermissionEvaluator<AccessToken, String> tenantPermissionEvaluator) { this.tenantPermissionEvaluator = tenantPermissionEvaluator; } @Required public void setAttributePermissionEvaluator( PermissionEvaluator<AccessToken, AttributeDefinition> attributePermissionEvaluator) { this.attributePermissionEvaluator = attributePermissionEvaluator; } @Required public void setProfileRepository(ProfileRepository profileRepository) { this.profileRepository = profileRepository; } @Required public void setTenantService(TenantService tenantService) { this.tenantService = tenantService; } @Required public void setAuthenticationService(AuthenticationService authenticationService) { this.authenticationService = authenticationService; } @Required public void setVerificationService(final VerificationService verificationService) { this.verificationService = verificationService; } @Required public void setNewProfileEmailFromAddress(final String newProfileEmailFromAddress) { this.newProfileEmailFromAddress = newProfileEmailFromAddress; } @Required public void setNewProfileEmailSubject(final String newProfileEmailSubject) { this.newProfileEmailSubject = newProfileEmailSubject; } @Required public void setNewProfileEmailTemplateName(final String newProfileEmailTemplateName) { this.newProfileEmailTemplateName = newProfileEmailTemplateName; } @Required public void setResetPwdEmailFromAddress(final String resetPwdEmailFromAddress) { this.resetPwdEmailFromAddress = resetPwdEmailFromAddress; } @Required public void setResetPwdEmailSubject(final String resetPwdEmailSubject) { this.resetPwdEmailSubject = resetPwdEmailSubject; } @Required public void setResetPwdEmailTemplateName(final String resetPwdEmailTemplateName) { this.resetPwdEmailTemplateName = resetPwdEmailTemplateName; } @Override public Profile createProfile(String tenantName, String username, String password, String email, boolean enabled, Set<String> roles, Map<String, Object> attributes, String verificationUrl) throws ProfileException { checkIfManageProfilesIsAllowed(tenantName); if (!EmailUtils.validateEmail(email)) { throw new InvalidEmailAddressException(email); } try { Tenant tenant = getTenant(tenantName); Date now = new Date(); Profile profile = new Profile(); profile.setTenant(tenantName); profile.setUsername(username); profile.setPassword(CipherUtils.hashPassword(password)); profile.setEmail(email); profile.setCreatedOn(now); profile.setLastModified(now); profile.setVerified(false); boolean emailNewProfiles = tenant.isVerifyNewProfiles(); if (!emailNewProfiles || StringUtils.isEmpty(verificationUrl)) { profile.setEnabled(enabled); } if (CollectionUtils.isNotEmpty(roles)) { profile.setRoles(roles); } for (AttributeDefinition definition : tenant.getAttributeDefinitions()) { if (definition.getDefaultValue() != null) { profile.setAttribute(definition.getName(), definition.getDefaultValue()); } } if (MapUtils.isNotEmpty(attributes)) { rejectAttributesIfActionNotAllowed(tenant, attributes.keySet(), AttributeAction.WRITE_ATTRIBUTE); profile.getAttributes().putAll(attributes); } profileRepository.insert(profile); logger.debug(LOG_KEY_PROFILE_CREATED, profile); if (emailNewProfiles && StringUtils.isNotEmpty(verificationUrl)) { VerificationToken token = verificationService.createToken(profile); verificationService.sendEmail(token, profile, verificationUrl, newProfileEmailFromAddress, newProfileEmailSubject, newProfileEmailTemplateName); } return profile; } catch (DuplicateKeyException e) { throw new ProfileExistsException(tenantName, username); } catch (MongoDataException e) { throw new I10nProfileException(ERROR_KEY_CREATE_PROFILE_ERROR, e, username, tenantName); } } @Override public Profile updateProfile(final String profileId, final String username, final String password, final String email, final Boolean enabled, final Set<String> roles, final Map<String, Object> attributes, String... attributesToReturn) throws ProfileException { if (StringUtils.isNotEmpty(email) && !EmailUtils.validateEmail(email)) { throw new InvalidEmailAddressException(email); } Profile profile = updateProfile(profileId, new UpdateCallback() { @Override public void doWithProfile(ProfileUpdater profileUpdater) throws ProfileException { if (StringUtils.isNotEmpty(username)) { profileUpdater.setUsername(username); } if (StringUtils.isNotEmpty(password)) { profileUpdater.setPassword(CipherUtils.hashPassword(password)); } if (StringUtils.isNotEmpty(email)) { profileUpdater.setEmail(email); } if (enabled != null) { profileUpdater.setEnabled(enabled); } if (roles != null) { profileUpdater.setRoles(roles); } if (MapUtils.isNotEmpty(attributes)) { String tenantName = profileUpdater.getProfile().getTenant(); rejectAttributesIfActionNotAllowed(tenantName, attributes.keySet(), AttributeAction.WRITE_ATTRIBUTE); profileUpdater.addAttributes(attributes); } } }, attributesToReturn); logger.debug(LOG_KEY_PROFILE_UPDATED, profile); return profile; } @Override public Profile verifyProfile(String verificationTokenId, final String... attributesToReturn) throws ProfileException { VerificationToken token = verificationService.getToken(verificationTokenId); if (token == null) { throw new NoSuchVerificationTokenException(verificationTokenId); } Profile profile = updateProfile(token.getProfileId(), new UpdateCallback() { @Override public void doWithProfile(ProfileUpdater profileUpdater) throws ProfileException { profileUpdater.setEnabled(true); profileUpdater.setVerified(true); } }, attributesToReturn); verificationService.deleteToken(verificationTokenId); logger.debug(LOG_KEY_PROFILE_VERIFIED, profile.getId()); return profile; } @Override public Profile enableProfile(String profileId, String... attributesToReturn) throws ProfileException { Profile profile = updateProfile(profileId, new UpdateCallback() { @Override public void doWithProfile(ProfileUpdater profileUpdater) throws ProfileException { profileUpdater.setEnabled(true); } }, attributesToReturn); logger.debug(LOG_KEY_PROFILE_ENABLED, profileId); return profile; } @Override public Profile setLastFailedLogin(String profileId, final Date lastFailedLogin, String... attributesToReturn) throws ProfileException { Profile profile = updateProfile(profileId, new UpdateCallback() { @Override public void doWithProfile(ProfileUpdater profileUpdater) throws ProfileException { profileUpdater.setLastFailedLogin(lastFailedLogin); } }, attributesToReturn); logger.debug(LOG_KEY_PROFILE_ENABLED, profileId); return profile; } @Override public Profile setFailedLoginAttempts(String profileId, final int failedAttempts, String... attributesToReturn) throws ProfileException { Profile profile = updateProfile(profileId, new UpdateCallback() { @Override public void doWithProfile(ProfileUpdater profileUpdater) throws ProfileException { profileUpdater.setFailedLoginAttempts(failedAttempts); } }, attributesToReturn); logger.debug(LOG_KEY_PROFILE_ENABLED, profileId); return profile; } @Override public Profile disableProfile(String profileId, String... attributesToReturn) throws ProfileException { Profile profile = updateProfile(profileId, new UpdateCallback() { @Override public void doWithProfile(ProfileUpdater profileUpdater) throws ProfileException { profileUpdater.setEnabled(false); } }, attributesToReturn); logger.debug(LOG_KEY_PROFILE_DISABLED, profileId); return profile; } @Override public Profile addRoles(String profileId, final Collection<String> roles, String... attributesToReturn) throws ProfileException { Profile profile = updateProfile(profileId, new UpdateCallback() { @Override public void doWithProfile(ProfileUpdater profileUpdater) throws ProfileException { profileUpdater.addRoles(roles); } }, attributesToReturn); logger.debug(LOG_KEY_PROFILE_ROLES_ADDED, roles, profileId); return profile; } @Override public Profile removeRoles(String profileId, final Collection<String> roles, String... attributesToReturn) throws ProfileException { Profile profile = updateProfile(profileId, new UpdateCallback() { @Override public void doWithProfile(ProfileUpdater profileUpdater) throws ProfileException { profileUpdater.removeRoles(roles); } }, attributesToReturn); logger.debug(LOG_KEY_PROFILE_ROLES_REMOVED, roles, profileId); return profile; } @Override public Map<String, Object> getAttributes(String profileId, String... attributesToReturn) throws ProfileException { return getNonNullProfile(profileId, attributesToReturn).getAttributes(); } @Override public Profile updateAttributes(String profileId, final Map<String, Object> attributes, String... attributesToReturn) throws ProfileException { Profile profile = updateProfile(profileId, new UpdateCallback() { @Override public void doWithProfile(ProfileUpdater profileUpdater) throws ProfileException { String tenantName = profileUpdater.getProfile().getTenant(); rejectAttributesIfActionNotAllowed(tenantName, attributes.keySet(), AttributeAction.WRITE_ATTRIBUTE); profileUpdater.addAttributes(attributes); } }, attributesToReturn); if (logger.isDebugEnabled()) { logger.debug(LOG_KEY_PROFILE_ATTRIBS_UPDATED, attributes.keySet(), profileId); } return profile; } @Override public Profile removeAttributes(String profileId, final Collection<String> attributeNames, String... attributesToReturn) throws ProfileException { Profile profile = updateProfile(profileId, new UpdateCallback() { @Override public void doWithProfile(ProfileUpdater profileUpdater) throws ProfileException { String tenantName = profileUpdater.getProfile().getTenant(); rejectAttributesIfActionNotAllowed(tenantName, attributeNames, AttributeAction.REMOVE_ATTRIBUTE); profileUpdater.removeAttributes(attributeNames); } }, attributesToReturn); logger.debug(LOG_KEY_PROFILE_ATTRIBS_REMOVED, attributeNames, profileId); return profile; } @Override public void deleteProfile(String profileId) throws ProfileException { try { Profile profile = getProfile(profileId); if (profile != null) { profileRepository.removeById(profileId); } logger.debug(LOG_KEY_PROFILE_DELETED, profileId); } catch (MongoDataException e) { throw new I10nProfileException(ERROR_KEY_DELETE_PROFILE_ERROR, e, profileId); } } @Override public Profile getProfileByQuery(String tenantName, String query, String... attributesToReturn) throws ProfileException { checkIfManageProfilesIsAllowed(tenantName); Tenant tenant = getTenant(tenantName); try { Profile profile = profileRepository.findOneByQuery(getFinalQuery(tenant, query), attributesToReturn); filterNonReadableAttributes(tenant, profile); return profile; } catch (MongoDataException e) { throw new I10nProfileException(ERROR_KEY_GET_PROFILE_BY_QUERY_ERROR, e, query); } } @Override public Profile getProfile(String profileId, String... attributesToReturn) throws ProfileException { try { Profile profile = profileRepository.findById(profileId, attributesToReturn); if (profile != null) { checkIfManageProfilesIsAllowed(profile.getTenant()); filterNonReadableAttributes(profile); } return profile; } catch (MongoDataException e) { throw new I10nProfileException(ERROR_KEY_GET_PROFILE_ERROR, e, profileId); } } @Override public Profile getProfileByUsername(String tenantName, String username, String... attributesToReturn) throws ProfileException { checkIfManageProfilesIsAllowed(tenantName); try { Profile profile = profileRepository.findByTenantAndUsername(tenantName, username, attributesToReturn); filterNonReadableAttributes(profile); return profile; } catch (MongoDataException e) { throw new I10nProfileException(ERROR_KEY_GET_PROFILE_BY_USERNAME_ERROR, e, username, tenantName); } } @Override public Profile getProfileByTicket(String ticketId, String... attributesToReturn) throws ProfileException { Ticket ticket = authenticationService.getTicket(ticketId); if (ticket != null) { return getProfile(ticket.getProfileId(), attributesToReturn); } else { throw new NoSuchTicketException(ticketId); } } @Override public long getProfileCount(String tenantName) throws ProfileException { checkIfManageProfilesIsAllowed(tenantName); try { return profileRepository.countByTenant(tenantName); } catch (MongoDataException e) { throw new I10nProfileException(ERROR_KEY_GET_PROFILE_COUNT_ERROR, e, tenantName); } } @Override public long getProfileCountByQuery(String tenantName, String query) throws ProfileException { checkIfManageProfilesIsAllowed(tenantName); Tenant tenant = getTenant(tenantName); try { return profileRepository.count(getFinalQuery(tenant, query)); } catch (MongoDataException e) { throw new I10nProfileException(ERROR_KEY_GET_PROFILE_COUNT_BY_QUERY_ERROR, e, tenant, query); } } @Override public List<Profile> getProfilesByQuery(String tenantName, String query, String sortBy, SortOrder sortOrder, Integer start, Integer count, String... attributesToReturn) throws ProfileException { checkIfManageProfilesIsAllowed(tenantName); Tenant tenant = getTenant(tenantName); try { List<Profile> profiles = IterableUtils.toList(profileRepository.findByQuery(getFinalQuery(tenant, query), sortBy, sortOrder, start, count, attributesToReturn)); filterNonReadableAttributes(tenant, profiles); return profiles; } catch (MongoDataException e) { throw new I10nProfileException(ERROR_KEY_GET_PROFILES_BY_QUERY_ERROR, e, tenant, query); } } @Override public List<Profile> getProfilesByIds(List<String> profileIds, String sortBy, SortOrder sortOrder, String... attributesToReturn) throws ProfileException { try { List<Profile> profiles = IterableUtils.toList(profileRepository.findByIds(profileIds, sortBy, sortOrder, attributesToReturn)); if (profiles != null) { for (Profile profile : profiles) { checkIfManageProfilesIsAllowed(profile.getTenant()); filterNonReadableAttributes(profile); } } return profiles; } catch (MongoDataException e) { throw new I10nProfileException(ERROR_KEY_GET_PROFILES_ERROR, e, profileIds); } } @Override public List<Profile> getProfileRange(String tenantName, String sortBy, SortOrder sortOrder, Integer start, Integer count, String... attributesToReturn) throws ProfileException { checkIfManageProfilesIsAllowed(tenantName); try { List<Profile> profiles = IterableUtils.toList(profileRepository.findRange(tenantName, sortBy, sortOrder, start, count, attributesToReturn)); filterNonReadableAttributes(profiles); return profiles; } catch (MongoDataException e) { throw new I10nProfileException(ERROR_KEY_GET_PROFILE_RANGE_ERROR, e, start, count, tenantName); } } @Override public List<Profile> getProfilesByRole(String tenantName, String role, String sortBy, SortOrder sortOrder, String... attributesToReturn) throws ProfileException { checkIfManageProfilesIsAllowed(tenantName); try { List<Profile> profiles = IterableUtils.toList(profileRepository.findByTenantAndRole(tenantName, role, sortBy, sortOrder, attributesToReturn)); filterNonReadableAttributes(profiles); return profiles; } catch (MongoDataException e) { throw new I10nProfileException(ERROR_KEY_GET_PROFILES_BY_ROLE_ERROR, e, role, tenantName); } } @Override public List<Profile> getProfilesByExistingAttribute(String tenantName, String attributeName, String sortBy, SortOrder sortOrder, String... attributesToReturn) throws ProfileException { checkIfManageProfilesIsAllowed(tenantName); try { List<Profile> profiles = IterableUtils.toList(profileRepository.findByTenantAndExistingAttribute( tenantName, attributeName, sortBy, sortOrder, attributesToReturn)); filterNonReadableAttributes(profiles); return profiles; } catch (MongoDataException e) { throw new I10nProfileException(ERROR_KEY_GET_PROFILES_BY_EXISTING_ATTRIB_ERROR, e, attributeName, tenantName); } } @Override public List<Profile> getProfilesByAttributeValue(String tenantName, String attributeName, String attributeValue, String sortBy, SortOrder sortOrder, String... attributesToReturn) throws ProfileException { checkIfManageProfilesIsAllowed(tenantName); try { List<Profile> profiles = IterableUtils.toList( profileRepository.findByTenantAndAttributeValue(tenantName, attributeName, attributeValue, sortBy, sortOrder, attributesToReturn)); filterNonReadableAttributes(profiles); return profiles; } catch (MongoDataException e) { throw new I10nProfileException(ERROR_KEY_GET_PROFILES_BY_ATTRIB_VALUE_ERROR, e, attributeName, attributeValue, tenantName); } } @Override public Profile resetPassword(String profileId, String resetPasswordUrl, String... attributesToReturn) throws ProfileException { Profile profile = getNonNullProfile(profileId, attributesToReturn); VerificationToken token = verificationService.createToken(profile); verificationService.sendEmail(token, profile, resetPasswordUrl, resetPwdEmailFromAddress, resetPwdEmailSubject, resetPwdEmailTemplateName); return profile; } @Override public Profile changePassword(String resetTokenId, final String newPassword, final String... attributesToReturn) throws ProfileException { VerificationToken token = verificationService.getToken(resetTokenId); if (token == null) { throw new NoSuchVerificationTokenException(resetTokenId); } Profile profile = updateProfile(token.getProfileId(), new UpdateCallback() { @Override public void doWithProfile(ProfileUpdater profileUpdater) throws ProfileException { profileUpdater.setPassword(CipherUtils.hashPassword(newPassword)); } }, attributesToReturn); verificationService.deleteToken(resetTokenId); logger.debug(LOG_KEY_PASSWORD_CHANGED, profile.getId().toString()); return profile; } @Override public VerificationToken createVerificationToken(String profileId) throws ProfileException { return verificationService.createToken(getNonNullProfile(profileId)); } @Override public VerificationToken getVerificationToken(String tokenId) throws ProfileException { return verificationService.getToken(tokenId); } @Override public void deleteVerificationToken(String tokenId) throws ProfileException { verificationService.deleteToken(tokenId); } @Override public ProfileAttachment addProfileAttachment(final String profileId, final String attachmentName, final InputStream file) throws ProfileException { final String storeName = "/"+profileId+"/"+ FilenameUtils.removeExtension(attachmentName); try { ObjectId currentId=checkIfAttchmentExist(storeName); final String mimeType = getAttachmentContentType(attachmentName); //Just final FileInfo fileInfo; if(currentId!=null) { // Update !!! fileInfo = profileRepository.updateFile(currentId,file,storeName,mimeType,true); }else { fileInfo = profileRepository.saveFile(file, storeName, getAttachmentContentType(attachmentName)); } return fileInfoToProfileAttachment(fileInfo); }catch(MongoDataException | FileExistsException |FileNotFoundException /*This should not happen*/ |SecurityException e ){ if(e instanceof SecurityException){ throw new ProfileException("The given ContentType is not allowed",e); }else { throw new ProfileException("Unable to Attach file to Profile",e); } } } private ObjectId checkIfAttchmentExist(final String storeName) { ObjectId toReturn = null; try { final FileInfo fileInfo = profileRepository.getFileInfo(storeName); toReturn=fileInfo.getFileId(); }catch (FileNotFoundException ex){ // Nothing since files should be New !!!! } return toReturn; } private ProfileAttachment fileInfoToProfileAttachment(final FileInfo fileInfo) { if(fileInfo==null){ return new ProfileAttachment(); } final ProfileAttachment toReturn = new ProfileAttachment(); toReturn.setContentType(fileInfo.getContentType());; toReturn.setMd5(fileInfo.getMd5()); toReturn.setFileName(fileInfo.getStoreName().substring(fileInfo.getStoreName().lastIndexOf("/")+1)); toReturn.setFileSize(fileInfo.getFileSize()); toReturn.setFileSizeBytes(fileInfo.getFileSizeBytes()); toReturn.setId(fileInfo.getFileId().toString()); return toReturn; } private String getAttachmentContentType(final String attachmentName) { String mimeType= new MimetypesFileTypeMap().getContentType(attachmentName); if(validAttachmentMimeTypes.contains(mimeType)){ return mimeType; } throw new SecurityException("File "+attachmentName+" if of content Type "+mimeType+" which is not allowed"); } @Override public ProfileAttachment getProfileAttachmentInformation(final String profileId, final String attachmentId) throws ProfileException { final FileInfo fileInfo; try { fileInfo = profileRepository.getFileInfo("/" + profileId + "/" + attachmentId); return fileInfoToProfileAttachment(fileInfo); } catch (FileNotFoundException e) { return new ProfileAttachment(); } } @Override public InputStream getProfileAttachment(final String attachmentId, final String profileId) throws ProfileException { try { final FileInfo fileInfo = profileRepository.readFile("/" + profileId + "/" + attachmentId); return fileInfo.getInputStream(); } catch (FileNotFoundException e) { return new ByteArrayInputStream(new byte[0]); } } @Override public List<ProfileAttachment> getProfileAttachments(final String profileId) throws ProfileException { final List<FileInfo> files = profileRepository.listFilesByName(profileId); List<ProfileAttachment>toReturn= new ArrayList<>(); for (FileInfo file : files) { toReturn.add(fileInfoToProfileAttachment(file)); } return toReturn; } protected void checkIfManageProfilesIsAllowed(String tenantName) { if (!tenantPermissionEvaluator.isAllowed(tenantName, TenantAction.MANAGE_PROFILES.toString())) { throw new ActionDeniedException(TenantAction.MANAGE_PROFILES.toString(), "tenant \"" + tenantName + "\""); } } protected Profile getNonNullProfile(String id, String... attributesToReturn) throws ProfileException { Profile profile = getProfile(id, attributesToReturn); if (profile != null) { return profile; } else { throw new NoSuchProfileException(id); } } protected Tenant getTenant(String name) throws ProfileException { Tenant tenant = tenantService.getTenant(name); if (tenant != null) { return tenant; } else { throw new NoSuchTenantException(name); } } protected Profile updateProfile(String profileId, UpdateCallback callback, String... attributesToReturn) throws ProfileException { // We need to filter the attributes after save, if not, the attributes to return will replace all the // attributes Profile profile = getNonNullProfile(profileId); UpdateHelper updateHelper = new UpdateHelper(); ProfileUpdater profileUpdater = new ProfileUpdater(profile, updateHelper, profileRepository); callback.doWithProfile(profileUpdater); profileUpdater.setLastModified(new Date()); try { profileUpdater.update(); } catch (MongoDataException e) { throw new I10nProfileException(ERROR_KEY_UPDATE_PROFILE_ERROR, e, profileId); } return filterAttributes(profile, attributesToReturn); } protected boolean isAttributeActionAllowed(AttributeDefinition definition, AttributeAction action) { return attributePermissionEvaluator.isAllowed(definition, action.toString()); } protected Profile filterAttributes(Profile profile, String[] attributesToReturn) { if (ArrayUtils.isNotEmpty(attributesToReturn) && MapUtils.isNotEmpty(profile.getAttributes())) { Iterator<String> keyIter = profile.getAttributes().keySet().iterator(); while (keyIter.hasNext()) { String key = keyIter.next(); if (!ArrayUtils.contains(attributesToReturn, key)) { keyIter.remove(); } } } return profile; } protected void filterNonReadableAttributes(Profile profile) throws ProfileException { if (profile != null) { filterNonReadableAttributes(getTenant(profile.getTenant()), profile); } } protected void filterNonReadableAttributes(Tenant tenant, Profile profile) throws ProfileException { if (profile != null) { List<AttributeDefinition> attributeDefinitions = tenant.getAttributeDefinitions(); Iterator<String> attributeNamesIter = profile.getAttributes().keySet().iterator(); while (attributeNamesIter.hasNext()) { filterAttributeIfReadNotAllowed(tenant, attributeNamesIter, attributeDefinitions); } } } protected void filterNonReadableAttributes(Iterable<Profile> profiles) throws ProfileException { if (profiles != null) { for (Profile profile : profiles) { filterNonReadableAttributes(profile); } } } protected void filterNonReadableAttributes(Tenant tenant, Iterable<Profile> profiles) throws ProfileException { if (profiles != null) { for (Profile profile : profiles) { filterNonReadableAttributes(tenant, profile); } } } protected void rejectAttributesIfActionNotAllowed(String tenantName, Collection<String> attributeNames, AttributeAction action) throws ProfileException { rejectAttributesIfActionNotAllowed(getTenant(tenantName), attributeNames, action); } protected void rejectAttributesIfActionNotAllowed(Tenant tenant, Collection<String> attributeNames, AttributeAction action) throws ProfileException { List<AttributeDefinition> attributeDefinitions = tenant.getAttributeDefinitions(); for (String attributeName : attributeNames) { rejectAttributeIfActionNotAllowed(tenant, attributeName, action, attributeDefinitions); } } protected void filterAttributeIfReadNotAllowed(Tenant tenant, Iterator<String> attributeNamesIter, List<AttributeDefinition> attributeDefinitions) throws PermissionException, AttributeNotDefinedException { String tenantName = tenant.getName(); String attributeName = attributeNamesIter.next(); AttributeDefinition definition = findAttributeDefinition(attributeDefinitions, attributeName); if (definition != null) { if (!isAttributeActionAllowed(definition, AttributeAction.READ_ATTRIBUTE)) { attributeNamesIter.remove(); } } else { throw new AttributeNotDefinedException(attributeName, tenantName); } } protected void rejectAttributeIfActionNotAllowed(Tenant tenant, String attributeName, AttributeAction action, List<AttributeDefinition> attributeDefinitions) throws PermissionException, AttributeNotDefinedException { AttributeDefinition definition = findAttributeDefinition(attributeDefinitions, attributeName); if (definition != null) { if (!isAttributeActionAllowed(definition, action)) { throw new ActionDeniedException(action.toString(), "attribute \"" + attributeName + "\""); } } else { throw new AttributeNotDefinedException(attributeName, tenant.getName()); } } protected String getFinalQuery(Tenant tenant, String query) throws ProfileException { validateQuery(tenant, query); return String.format(QUERY_FINAL_FORMAT, tenant.getName(), query); } protected void validateQuery(Tenant tenant, String query) throws ProfileException { if (QUERY_TENANT_PATTERN.matcher(query).find()) { throw new InvalidQueryException(ERROR_KEY_TENANT_NOT_ALLOWED); } if (QUERY_WHERE_PATTERN.matcher(query).find()) { throw new InvalidQueryException(ERROR_KEY_WHERE_NOT_ALLOWED); } for (AttributeDefinition definition : tenant.getAttributeDefinitions()) { if (!attributePermissionEvaluator.isAllowed(definition, AttributeAction.READ_ATTRIBUTE.toString())) { String attributeName = definition.getName(); Pattern pattern = Pattern.compile(String.format(QUERY_ATTRIBUTE_PATTERN_FORMAT, attributeName)); if (pattern.matcher(query).find()) { throw new InvalidQueryException(ERROR_KEY_ATTRIBUTE_NOT_ALLOWED, attributeName); } } } } public void setValidAttachmentMimeTypes(final String validAttachmentMimeTypes) { if(StringUtils.isNotBlank(validAttachmentMimeTypes)){ this.validAttachmentMimeTypes=Arrays.asList(validAttachmentMimeTypes.split(",")); }else{ this.validAttachmentMimeTypes = Collections.EMPTY_LIST; } } protected AttributeDefinition findAttributeDefinition(final List<AttributeDefinition> attributeDefinitions, final String name) { return CollectionUtils.find(attributeDefinitions, new Predicate<AttributeDefinition>() { @Override public boolean evaluate(AttributeDefinition definition) { return definition.getName().equals(name); } }); } protected interface UpdateCallback { void doWithProfile(ProfileUpdater profileUpdater) throws ProfileException; } }