/** * ============================================================================= * * ORCID (R) Open Source * http://orcid.org * * Copyright (c) 2012-2014 ORCID, Inc. * Licensed under an MIT-Style License (MIT) * http://orcid.org/open-source-license * * This copyright and license information (including a link to the full license) * shall be included in its entirety in all copies or substantial portion of * the software. * * ============================================================================= */ package org.orcid.core.manager.impl; import java.security.InvalidParameterException; import java.security.NoSuchAlgorithmException; import java.util.ArrayList; import java.util.Date; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; import javax.annotation.Resource; import org.apache.commons.lang.StringUtils; import org.apache.commons.lang3.tuple.Pair; import org.orcid.core.locale.LocaleManager; import org.orcid.core.manager.AddressManager; import org.orcid.core.manager.AffiliationsManager; import org.orcid.core.manager.BiographyManager; import org.orcid.core.manager.ClientDetailsEntityCacheManager; import org.orcid.core.manager.EmailManager; import org.orcid.core.manager.EncryptionManager; import org.orcid.core.manager.ExternalIdentifierManager; import org.orcid.core.manager.NotificationManager; import org.orcid.core.manager.OtherNameManager; import org.orcid.core.manager.PeerReviewManager; import org.orcid.core.manager.ProfileEntityCacheManager; import org.orcid.core.manager.ProfileEntityManager; import org.orcid.core.manager.ProfileFundingManager; import org.orcid.core.manager.ProfileKeywordManager; import org.orcid.core.manager.RecordNameManager; import org.orcid.core.manager.ResearcherUrlManager; import org.orcid.core.manager.WorkManager; import org.orcid.core.manager.read_only.impl.ProfileEntityManagerReadOnlyImpl; import org.orcid.core.oauth.OrcidOauth2TokenDetailService; import org.orcid.core.security.visibility.OrcidVisibilityDefaults; import org.orcid.jaxb.model.clientgroup.MemberType; import org.orcid.jaxb.model.common_v2.Locale; import org.orcid.jaxb.model.common_v2.OrcidType; import org.orcid.jaxb.model.common_v2.Visibility; import org.orcid.jaxb.model.message.OrcidProfile; import org.orcid.jaxb.model.message.ScopePathType; import org.orcid.jaxb.model.notification.amended_v2.AmendedSection; import org.orcid.jaxb.model.record_v2.Biography; import org.orcid.jaxb.model.record_v2.CreditName; import org.orcid.jaxb.model.record_v2.FamilyName; import org.orcid.jaxb.model.record_v2.GivenNames; import org.orcid.jaxb.model.record_v2.Name; import org.orcid.persistence.dao.OrgAffiliationRelationDao; import org.orcid.persistence.dao.UserConnectionDao; import org.orcid.persistence.jpa.entities.AddressEntity; import org.orcid.persistence.jpa.entities.BiographyEntity; import org.orcid.persistence.jpa.entities.ClientDetailsEntity; import org.orcid.persistence.jpa.entities.EmailEntity; import org.orcid.persistence.jpa.entities.ExternalIdentifierEntity; import org.orcid.persistence.jpa.entities.IndexingStatus; import org.orcid.persistence.jpa.entities.OrcidOauth2TokenDetail; import org.orcid.persistence.jpa.entities.OrgAffiliationRelationEntity; import org.orcid.persistence.jpa.entities.OtherNameEntity; import org.orcid.persistence.jpa.entities.ProfileEntity; import org.orcid.persistence.jpa.entities.ProfileFundingEntity; import org.orcid.persistence.jpa.entities.ProfileKeywordEntity; import org.orcid.persistence.jpa.entities.RecordNameEntity; import org.orcid.persistence.jpa.entities.ResearcherUrlEntity; import org.orcid.pojo.ApplicationSummary; import org.orcid.pojo.ajaxForm.Claim; import org.orcid.pojo.ajaxForm.PojoUtil; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.context.NoSuchMessageException; import org.springframework.transaction.annotation.Transactional; /** * @author Declan Newman (declan) Date: 10/02/2012 */ public class ProfileEntityManagerImpl extends ProfileEntityManagerReadOnlyImpl implements ProfileEntityManager { private static final Logger LOGGER = LoggerFactory.getLogger(ProfileEntityManagerImpl.class); @Resource private AffiliationsManager affiliationsManager; @Resource private ProfileFundingManager fundingManager; @Resource private PeerReviewManager peerReviewManager; @Resource private ProfileEntityCacheManager profileEntityCacheManager; @Resource private WorkManager workManager; @Resource private EncryptionManager encryptionManager; @Resource private AddressManager addressManager; @Resource private ExternalIdentifierManager externalIdentifierManager; @Resource private ProfileKeywordManager profileKeywordManager; @Resource private OtherNameManager otherNameManager; @Resource private ResearcherUrlManager researcherUrlManager; @Resource private EmailManager emailManager; @Resource private OrgAffiliationRelationDao orgAffiliationRelationDao; @Resource private OtherNameManager otherNamesManager; @Resource private BiographyManager biographyManager; @Resource private UserConnectionDao userConnectionDao; @Resource private NotificationManager notificationManager; @Resource private OrcidOauth2TokenDetailService orcidOauth2TokenService; @Resource private ClientDetailsEntityCacheManager clientDetailsEntityCacheManager; @Resource private OrcidUrlManager orcidUrlManager; @Resource private LocaleManager localeManager; @Resource private RecordNameManager recordNameManager; @Override public boolean orcidExists(String orcid) { return profileDao.orcidExists(orcid); } @Override public boolean hasBeenGivenPermissionTo(String giverOrcid, String receiverOrcid) { return profileDao.hasBeenGivenPermissionTo(giverOrcid, receiverOrcid); } @Override public boolean existsAndNotClaimedAndBelongsTo(String messageOrcid, String clientId) { return profileDao.existsAndNotClaimedAndBelongsTo(messageOrcid, clientId); } @Override public String findByCreditName(String creditName) { Name name = recordNameManager.findByCreditName(creditName); if(name == null) { return null; } return name.getPath(); } /** * Deprecates a profile * * @param deprecatedProfile * The profile that want to be deprecated * @param primaryProfile * The primary profile for the deprecated profile * @return true if the account was successfully deprecated, false otherwise */ @Override @Transactional public boolean deprecateProfile(String deprecatedOrcid, String primaryOrcid) { boolean wasDeprecated = profileDao.deprecateProfile(deprecatedOrcid, primaryOrcid); // If it was successfully deprecated if (wasDeprecated) { LOGGER.info("Account {} was deprecated to primary account: {}", deprecatedOrcid, primaryOrcid); ProfileEntity deprecated = profileDao.find(deprecatedOrcid); // Remove works workManager.removeAllWorks(deprecatedOrcid); // Remove funding if (deprecated.getProfileFunding() != null) { for(ProfileFundingEntity funding : deprecated.getProfileFunding()) { fundingManager.removeProfileFunding(funding.getProfile().getId(), funding.getId()); } } // Remove affiliations if (deprecated.getOrgAffiliationRelations() != null) { for(OrgAffiliationRelationEntity affiliation : deprecated.getOrgAffiliationRelations()) { orgAffiliationRelationDao.removeOrgAffiliationRelation(affiliation.getProfile().getId(), affiliation.getId()); } } // Remove external identifiers if (deprecated.getExternalIdentifiers() != null) { for (ExternalIdentifierEntity externalIdentifier : deprecated.getExternalIdentifiers()) { externalIdentifierManager.deleteExternalIdentifier(deprecated.getId(), externalIdentifier.getId(), false); } } // Remove researcher urls if(deprecated.getResearcherUrls() != null) { for(ResearcherUrlEntity rUrl : deprecated.getResearcherUrls()) { researcherUrlManager.deleteResearcherUrl(deprecatedOrcid, rUrl.getId(), false); } } // Remove other names if(deprecated.getOtherNames() != null) { for(OtherNameEntity otherName : deprecated.getOtherNames()) { otherNamesManager.deleteOtherName(deprecatedOrcid, otherName.getId(), false); } } // Remove keywords if(deprecated.getKeywords() != null) { for(ProfileKeywordEntity keyword : deprecated.getKeywords()) { profileKeywordManager.deleteKeyword(deprecatedOrcid, keyword.getId(), false); } } //Remove biography if(biographyManager.exists(deprecatedOrcid)) { Biography deprecatedBio = new Biography(); deprecatedBio.setContent(null); deprecatedBio.setVisibility(Visibility.PRIVATE); biographyManager.updateBiography(deprecatedOrcid, deprecatedBio); } //Set the deactivated names if(recordNameManager.exists(deprecatedOrcid)) { Name name = new Name(); name.setCreditName(new CreditName()); name.setGivenNames(new GivenNames("Given Names Deactivated")); name.setFamilyName(new FamilyName("Family Name Deactivated")); name.setVisibility(org.orcid.jaxb.model.common_v2.Visibility.PRIVATE); name.setPath(deprecatedOrcid); recordNameManager.updateRecordName(deprecatedOrcid, name); } userConnectionDao.deleteByOrcid(deprecatedOrcid); // Move all emails to the primary email Set<EmailEntity> deprecatedAccountEmails = deprecated.getEmails(); if (deprecatedAccountEmails != null) { // For each email in the deprecated profile for (EmailEntity email : deprecatedAccountEmails) { // Delete each email from the deprecated // profile LOGGER.info("About to move email {} from profile {} to profile {}", new Object[] {email.getId(), deprecatedOrcid, primaryOrcid }); emailManager.moveEmailToOtherAccount(email.getId(), deprecatedOrcid, primaryOrcid); } } return true; } return false; } /** * Enable developer tools * * @param profile * The profile to update * @return true if the developer tools where enabled on that profile */ @Override public boolean enableDeveloperTools(OrcidProfile profile) { boolean result = profileDao.updateDeveloperTools(profile.getOrcidIdentifier().getPath(), true); return result; } /** * Disable developer tools * * @param profile * The profile to update * @return true if the developer tools where disabeled on that profile */ @Override public boolean disableDeveloperTools(OrcidProfile profile) { boolean result = profileDao.updateDeveloperTools(profile.getOrcidIdentifier().getPath(), false); return result; } @Override public boolean isProfileClaimed(String orcid) { return profileDao.getClaimedStatus(orcid); } /** * Get the group type of a profile * * @param orcid * The profile to look for * @return the group type, null if it is not a client */ @Override public MemberType getGroupType(String orcid) { return profileDao.getGroupType(orcid); } /** * Updates the DB and the cached value in the request scope. * */ @Override public void updateLastModifed(String orcid) { profileLastModifiedAspect.updateLastModifiedDateAndIndexingStatus(orcid); } @Override public boolean isDeactivated(String orcid) { return profileDao.isDeactivated(orcid); } @Override public boolean reviewProfile(String orcid) { return profileDao.reviewProfile(orcid); } @Override public boolean unreviewProfile(String orcid) { return profileDao.unreviewProfile(orcid); } @Override public void disableApplication(Long tokenId, String userOrcid) { orcidOauth2TokenService.disableAccessToken(tokenId, userOrcid); } @Override public List<ApplicationSummary> getApplications(String orcid) { List<OrcidOauth2TokenDetail> tokenDetails = orcidOauth2TokenService.findByUserName(orcid); List<ApplicationSummary> applications = new ArrayList<ApplicationSummary>(); Map<Pair<String, Set<ScopePathType>>, ApplicationSummary> existingApplications = new HashMap<Pair<String, Set<ScopePathType>>, ApplicationSummary>(); if(tokenDetails != null && !tokenDetails.isEmpty()) { for(OrcidOauth2TokenDetail token : tokenDetails) { if (token.getTokenDisabled() == null || !token.getTokenDisabled()) { ClientDetailsEntity client = clientDetailsEntityCacheManager.retrieve(token.getClientDetailsId()); if(client != null) { ApplicationSummary applicationSummary = new ApplicationSummary(); // Check the scopes Set<ScopePathType> scopesGrantedToClient = ScopePathType.getScopesFromSpaceSeparatedString(token.getScope()); Map<ScopePathType, String> scopePathMap = new HashMap<ScopePathType, String>(); String scopeFullPath = ScopePathType.class.getName() + "."; for (ScopePathType tempScope : scopesGrantedToClient) { try { scopePathMap.put(tempScope, localeManager.resolveMessage(scopeFullPath + tempScope.toString())); } catch (NoSuchMessageException e) { LOGGER.warn("No message to display for scope " + tempScope.toString()); } } //If there is at least one scope in this token, fill the application summary element if(!scopePathMap.isEmpty()) { applicationSummary.setScopePaths(scopePathMap); applicationSummary.setOrcidHost(orcidUrlManager.getBaseHost()); applicationSummary.setOrcidUri(orcidUrlManager.getBaseUriHttp() + "/" + client.getId()); applicationSummary.setOrcidPath(client.getId()); applicationSummary.setName(client.getClientName()); applicationSummary.setWebsiteValue(client.getClientWebsite()); applicationSummary.setApprovalDate(token.getDateCreated()); applicationSummary.setTokenId(String.valueOf(token.getId())); // Add member information if (!PojoUtil.isEmpty(client.getGroupProfileId())) { ProfileEntity member = profileEntityCacheManager.retrieve(client.getGroupProfileId()); applicationSummary.setGroupOrcidPath(member.getId()); applicationSummary.setGroupName(getMemberDisplayName(member)); } if(shouldBeAddedToTheApplicationsList(applicationSummary, scopesGrantedToClient, existingApplications)) { applications.add(applicationSummary); } } } } } } return applications; } private boolean shouldBeAddedToTheApplicationsList(ApplicationSummary application , Set<ScopePathType> scopes, Map<Pair<String, Set<ScopePathType>>, ApplicationSummary> existingApplications) { boolean result = false; Pair<String, Set<ScopePathType>> key = Pair.of(application.getOrcidPath(), scopes); if(!existingApplications.containsKey(key)) { result = true; } else { Date existingAppCreatedDate = existingApplications.get(key).getApprovalDate(); //This case should never happen if(existingAppCreatedDate == null) { result = true; } if(application.getApprovalDate().before(existingAppCreatedDate)) { result = true; } } if(result) { existingApplications.put(key, application); } return result; } private String getMemberDisplayName(ProfileEntity member) { RecordNameEntity recordName = member.getRecordNameEntity(); if(recordName == null) { return StringUtils.EMPTY; } //If it is a member, return the credit name if(OrcidType.GROUP.equals(member.getOrcidType())) { return recordName.getCreditName(); } Visibility namesVisibilty = recordName.getVisibility(); if(Visibility.PUBLIC.equals(namesVisibilty)) { if(!PojoUtil.isEmpty(recordName.getCreditName())) { return recordName.getCreditName(); } else { String displayName = recordName.getGivenNames(); String familyName = recordName.getFamilyName(); if (StringUtils.isNotBlank(familyName)) { displayName += " " + familyName; } return displayName; } } return StringUtils.EMPTY; } @Override public String getOrcidHash(String orcid) throws NoSuchAlgorithmException { if (PojoUtil.isEmpty(orcid)) { return null; } return encryptionManager.sha256Hash(orcid); } @Override public String retrivePublicDisplayName(String orcid) { String publicName = ""; ProfileEntity profile = profileEntityCacheManager.retrieve(orcid); if (profile != null) { RecordNameEntity recordName = profile.getRecordNameEntity(); if(recordName != null) { Visibility namesVisibility = (recordName.getVisibility() != null) ? Visibility.fromValue(recordName.getVisibility().value()) : Visibility.fromValue(OrcidVisibilityDefaults.NAMES_DEFAULT.getVisibility().value()); if (Visibility.PUBLIC.equals(namesVisibility)) { if (!PojoUtil.isEmpty(recordName.getCreditName())) { publicName = recordName.getCreditName(); } else { publicName = PojoUtil.isEmpty(recordName.getGivenNames()) ? "" : recordName.getGivenNames(); publicName += PojoUtil.isEmpty(recordName.getFamilyName()) ? "" : " " + recordName.getFamilyName(); } } } } return publicName; } @Override @Transactional public boolean claimProfileAndUpdatePreferences(String orcid, String email, Locale locale, Claim claim) { //Verify the email boolean emailVerified = emailManager.verifySetCurrentAndPrimary(orcid, email); if(!emailVerified) { throw new InvalidParameterException("Unable to claim and verify email: " + email + " for user: " + orcid); } //Update the profile entity fields ProfileEntity profile = profileDao.find(orcid); profile.setLastModified(new Date()); profile.setIndexingStatus(IndexingStatus.REINDEX); profile.setClaimed(true); profile.setCompletedDate(new Date()); if(locale != null) { profile.setLocale(org.orcid.jaxb.model.common_v2.Locale.fromValue(locale.value())); } if(claim != null) { profile.setSendChangeNotifications(claim.getSendChangeNotifications().getValue()); profile.setSendOrcidNews(claim.getSendOrcidNews().getValue()); profile.setActivitiesVisibilityDefault(claim.getActivitiesVisibilityDefault().getVisibility()); } //Update the visibility for every bio element to the visibility selected by the user //Update the bio org.orcid.jaxb.model.common_v2.Visibility defaultVisibility = org.orcid.jaxb.model.common_v2.Visibility.fromValue(claim.getActivitiesVisibilityDefault().getVisibility().value()); if(profile.getBiographyEntity() != null) { profile.getBiographyEntity().setVisibility(defaultVisibility); } //Update address if(profile.getAddresses() != null) { for(AddressEntity a : profile.getAddresses()) { a.setVisibility(defaultVisibility); } } //Update the keywords if(profile.getKeywords() != null) { for(ProfileKeywordEntity k : profile.getKeywords()) { k.setVisibility(defaultVisibility); } } //Update the other names if(profile.getOtherNames() != null) { for(OtherNameEntity o : profile.getOtherNames()) { o.setVisibility(defaultVisibility); } } //Update the researcher urls if(profile.getResearcherUrls() != null) { for(ResearcherUrlEntity r : profile.getResearcherUrls()) { r.setVisibility(defaultVisibility); } } //Update the external identifiers if(profile.getExternalIdentifiers() != null) { for(ExternalIdentifierEntity e : profile.getExternalIdentifiers()) { e.setVisibility(defaultVisibility); } } profileDao.merge(profile); profileDao.flush(); return true; } @Override @Transactional public boolean deactivateRecord(String orcid) { //Clear the record ProfileEntity toClear = profileDao.find(orcid); toClear.setLastModified(new Date()); toClear.setDeactivationDate(new Date()); toClear.setActivitiesVisibilityDefault(Visibility.PRIVATE); toClear.setIndexingStatus(IndexingStatus.REINDEX); // Remove works workManager.removeAllWorks(orcid); // Remove funding if (toClear.getProfileFunding() != null) { toClear.getProfileFunding().clear(); } // Remove affiliations if (toClear.getOrgAffiliationRelations() != null) { toClear.getOrgAffiliationRelations().clear(); } // Remove external identifiers if (toClear.getExternalIdentifiers() != null) { toClear.getExternalIdentifiers().clear(); } // Remove researcher urls if(toClear.getResearcherUrls() != null) { toClear.getResearcherUrls().clear(); } // Remove other names if(toClear.getOtherNames() != null) { toClear.getOtherNames().clear(); } // Remove keywords if(toClear.getKeywords() != null) { toClear.getKeywords().clear(); } // Remove address if(toClear.getAddresses() != null) { toClear.getAddresses().clear(); } BiographyEntity bioEntity = toClear.getBiographyEntity(); if(bioEntity != null) { bioEntity.setBiography(""); bioEntity.setVisibility(Visibility.PRIVATE); } //Set the deactivated names RecordNameEntity recordName = toClear.getRecordNameEntity(); if(recordName != null) { recordName.setCreditName(null); recordName.setGivenNames("Given Names Deactivated"); recordName.setFamilyName("Family Name Deactivated"); recordName.setVisibility(org.orcid.jaxb.model.common_v2.Visibility.PUBLIC); } Set<EmailEntity> emails = toClear.getEmails(); if (emails != null) { // For each email in the deprecated profile for (EmailEntity email : emails) { email.setVisibility(org.orcid.jaxb.model.common_v2.Visibility.PRIVATE); } } profileDao.merge(toClear); profileDao.flush(); //Delete all connections userConnectionDao.deleteByOrcid(orcid); notificationManager.sendAmendEmail(orcid, AmendedSection.UNKNOWN, null); return true; } @Override @Transactional public boolean reactivateRecord(String orcid) { ProfileEntity toReactivate = profileDao.find(orcid); toReactivate.setLastModified(new Date()); toReactivate.setDeactivationDate(null); profileDao.merge(toReactivate); profileDao.flush(); notificationManager.sendAmendEmail(orcid, AmendedSection.UNKNOWN, null); return true; } @Override public void updateLocale(String orcid, Locale locale) { profileDao.updateLocale(orcid, locale); } @Override public boolean isProfileClaimedByEmail(String email) { return profileDao.getClaimedStatusByEmail(email); } @Override public void reactivate(String orcid, String givenNames, String familyName, String password, Visibility defaultVisibility) { LOGGER.info("About to reactivate record, orcid={}", orcid); ProfileEntity profileEntity = profileEntityCacheManager.retrieve(orcid); profileEntity.setDeactivationDate(null); profileEntity.setClaimed(true); profileEntity.setEncryptedPassword(encryptionManager.hashForInternalUse(password)); profileEntity.setActivitiesVisibilityDefault(defaultVisibility); RecordNameEntity recordNameEntity = profileEntity.getRecordNameEntity(); recordNameEntity.setGivenNames(givenNames); recordNameEntity.setFamilyName(familyName); profileDao.merge(profileEntity); } @Override public void updatePassword(String orcid, String password) { String encryptedPassword = encryptionManager.hashForInternalUse(password); profileDao.updateEncryptedPassword(orcid, encryptedPassword); } @Override public void updateSecurityQuestion(String orcid, Integer questionId, String answer) { String encryptedAnswer = encryptionManager.encryptForInternalUse(answer); profileDao.updateSecurityQuestion(orcid, questionId, questionId != null ? encryptedAnswer : null); } @Override public boolean isProfileDeprecated(String orcid) { return profileDao.isProfileDeprecated(orcid); } @Override public void updateIpAddress(String orcid, String ipAddress) { profileDao.updateIpAddress(orcid, ipAddress); } @Override public Locale retrieveLocale(String orcid) { return profileDao.retrieveLocale(orcid); } /** * Set the locked status of an account to true * * @param orcid * the id of the profile that should be locked * @return true if the account was locked */ @Override public boolean lockProfile(String orcid, String lockReason, String description) { boolean wasLocked = profileDao.lockProfile(orcid, lockReason, description); if (wasLocked) { notificationManager.sendOrcidLockedEmail(orcid); } return wasLocked; } /** * Set the locked status of an account to false * * @param orcid * the id of the profile that should be unlocked * @return true if the account was unlocked */ @Override public boolean unlockProfile(String orcid) { return profileDao.unlockProfile(orcid); } }