diff --git a/docker-compose.yml b/docker-compose.yml index bffd75ddc..20019445a 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -39,6 +39,8 @@ services: - APPLICATION_UI_BASE_URL=${UI_BASE_URL} - APPLICATION_INTERNAL_ASSERTION_SERVICE_ID=assertion-service-internal - APPLICATION_INTERNAL_ASSERTION_SERVICE_CLIENT_SECRET=${ASSERTION_SERVICE_INTERNAL_CLIENT_SECRET} + - APPLICATION_INTERNAL_MEMBER_SERVICE_ID=member-service-internal + - APPLICATION_INTERNAL_MEMBER_SERVICE_CLIENT_SECRET=${MEMBER_SERVICE_INTERNAL_CLIENT_SECRET} - APPLICATION_SECURITY_ISSUER_URL=${ISSUER_URL} - UI_REDIRECT_URI=${UI_BASE_URL}/en/auth/callback - UI_POST_LOGOUT_REDIRECT_URI=${UI_BASE_URL}/en/login @@ -146,6 +148,8 @@ services: - APPLICATION_SALESFORCE_USERNAME=${SALESFORCE_USERNAME} - APPLICATION_SALESFORCE_PASSWORD=${SALESFORCE_PASSWORD} - ENABLE_SCHEDULER=${ENABLE_SCHEDULER:-true} + - APPLICATION_INTERNAL_MEMBER_SERVICE_ID=member-service-internal + - APPLICATION_INTERNAL_MEMBER_SERVICE_CLIENT_SECRET=${MEMBER_SERVICE_INTERNAL_CLIENT_SECRET} volumes: - ${LOG_VOLUME:-/var/tmp/}:/logs diff --git a/member-service-2/pom.xml b/member-service-2/pom.xml index 2bf7234dc..189ce62a7 100644 --- a/member-service-2/pom.xml +++ b/member-service-2/pom.xml @@ -113,6 +113,10 @@ shedlock-spring 5.13.0 + + org.springframework.boot + spring-boot-starter-oauth2-client + diff --git a/member-service-2/src/main/java/org/orcid/mp/member/client/InternalUserServiceClient.java b/member-service-2/src/main/java/org/orcid/mp/member/client/InternalUserServiceClient.java new file mode 100644 index 000000000..f28f39134 --- /dev/null +++ b/member-service-2/src/main/java/org/orcid/mp/member/client/InternalUserServiceClient.java @@ -0,0 +1,22 @@ +package org.orcid.mp.member.client; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.stereotype.Component; +import org.springframework.web.client.RestClient; + +/** + * Client for fetching member data from the MP member service. + */ +@Component +public class InternalUserServiceClient { + + @Autowired + @Qualifier("internalUserServiceRestClient") + private RestClient internalUserServiceRestClient; + + public String updateUsersMemberNames(String memberId, String newMemberName) { + return internalUserServiceRestClient.put().uri("/internal/users/memberName/" + memberId + "/" + newMemberName).retrieve().toEntity(String.class).getBody(); + } + +} diff --git a/member-service-2/src/main/java/org/orcid/mp/member/client/UserServiceClient.java b/member-service-2/src/main/java/org/orcid/mp/member/client/UserServiceClient.java index e3e289691..3389defc6 100644 --- a/member-service-2/src/main/java/org/orcid/mp/member/client/UserServiceClient.java +++ b/member-service-2/src/main/java/org/orcid/mp/member/client/UserServiceClient.java @@ -30,7 +30,4 @@ public String updateUser(User user) { return restClient.put().uri("/users").body(user).retrieve().toEntity(String.class).getBody(); } - public String updateUsersMemberNames(String memberId, String newMemberName) { - return restClient.put().uri("/users/memberName/" + memberId + "/" + newMemberName).retrieve().toEntity(String.class).getBody(); - } } diff --git a/member-service-2/src/main/java/org/orcid/mp/member/config/InternalClientConfig.java b/member-service-2/src/main/java/org/orcid/mp/member/config/InternalClientConfig.java new file mode 100644 index 000000000..b70254221 --- /dev/null +++ b/member-service-2/src/main/java/org/orcid/mp/member/config/InternalClientConfig.java @@ -0,0 +1,80 @@ +package org.orcid.mp.member.config; + +import org.apache.hc.client5.http.impl.classic.CloseableHttpClient; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.http.client.ClientHttpRequestInterceptor; +import org.springframework.http.client.HttpComponentsClientHttpRequestFactory; +import org.springframework.security.oauth2.client.*; +import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository; +import org.springframework.web.client.RestClient; + +@Configuration +public class InternalClientConfig { + + @Value("${application.userService.apiUrl}") + private String userServiceApiUrl; + + @Autowired + private CloseableHttpClient httpClient; + + @Bean + public OAuth2AuthorizedClientManager authorizedClientManager( + ClientRegistrationRepository clientRegistrationRepository, + OAuth2AuthorizedClientService clientService) { + + OAuth2AuthorizedClientProvider authorizedClientProvider = + OAuth2AuthorizedClientProviderBuilder.builder() + .clientCredentials() + .build(); + + AuthorizedClientServiceOAuth2AuthorizedClientManager authorizedClientManager = + new AuthorizedClientServiceOAuth2AuthorizedClientManager( + clientRegistrationRepository, clientService); + authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider); + + return authorizedClientManager; + } + + + @Bean + public ClientHttpRequestInterceptor internalSecurityInterceptor( + OAuth2AuthorizedClientManager authorizedClientManager) { + return (request, body, execution) -> { + // "assertion-service-client" MUST match the registration-id in your YAML + OAuth2AuthorizeRequest authorizeRequest = OAuth2AuthorizeRequest + .withClientRegistrationId("internal-member-client") + .principal("member-service") // Arbitrary string identifying who is running this + .build(); + + // This performs the magic: checks cache, or calls URL to get fresh token + OAuth2AuthorizedClient authorizedClient = authorizedClientManager.authorize(authorizeRequest); + + if (authorizedClient != null) { + String token = authorizedClient.getAccessToken().getTokenValue(); + request.getHeaders().setBearerAuth(token); + } + + return execution.execute(request, body); + }; + } + + /** + * RestClient for making internal requests to the member service with scope 'internal', where no user token is present for authorization. + * + * @param internalSecurityInterceptor + * @return + */ + @Bean("internalUserServiceRestClient") + public RestClient internalUserServiceRestClient(ClientHttpRequestInterceptor internalSecurityInterceptor) { + HttpComponentsClientHttpRequestFactory requestFactory = new HttpComponentsClientHttpRequestFactory(httpClient); + return RestClient.builder() + .baseUrl(userServiceApiUrl) + .requestFactory(requestFactory) + .requestInterceptor(internalSecurityInterceptor) + .build(); + } + +} diff --git a/member-service-2/src/main/java/org/orcid/mp/member/domain/Member.java b/member-service-2/src/main/java/org/orcid/mp/member/domain/Member.java index b72fe5914..6af7f5f07 100644 --- a/member-service-2/src/main/java/org/orcid/mp/member/domain/Member.java +++ b/member-service-2/src/main/java/org/orcid/mp/member/domain/Member.java @@ -58,10 +58,19 @@ public class Member implements Serializable { private String defaultLanguage; @Field - private String type; + private boolean active; + + @Field("last_updated_with_salesforce_data") + private Instant lastUpdatedWithSalesforceData; + + @Field("deactivated_date") + private Instant deactivatedDate; + + @Field("activated_date") + private Instant activatedDate; @Field - private String status; + private String type; public String getId() { return id; @@ -171,14 +180,6 @@ public void setType(String type) { this.type = type; } - public String getStatus() { - return status; - } - - public void setStatus(String status) { - this.status = status; - } - public String getDefaultLanguage() { return defaultLanguage; } @@ -187,6 +188,38 @@ public void setDefaultLanguage(String defaultLanguage) { this.defaultLanguage = defaultLanguage; } + public boolean isActive() { + return active; + } + + public void setActive(boolean active) { + this.active = active; + } + + public Instant getLastUpdatedWithSalesforceData() { + return lastUpdatedWithSalesforceData; + } + + public void setLastUpdatedWithSalesforceData(Instant lastUpdatedWithSalesforceData) { + this.lastUpdatedWithSalesforceData = lastUpdatedWithSalesforceData; + } + + public Instant getDeactivatedDate() { + return deactivatedDate; + } + + public void setDeactivatedDate(Instant deactivatedDate) { + this.deactivatedDate = deactivatedDate; + } + + public Instant getActivatedDate() { + return activatedDate; + } + + public void setActivatedDate(Instant activatedDate) { + this.activatedDate = activatedDate; + } + @Override public int hashCode() { final int prime = 31; @@ -204,8 +237,10 @@ public int hashCode() { result = prime * result + ((parentSalesforceId == null) ? 0 : parentSalesforceId.hashCode()); result = prime * result + ((salesforceId == null) ? 0 : salesforceId.hashCode()); result = prime * result + ((type == null) ? 0 : type.hashCode()); - result = prime * result + ((status == null) ? 0 : status.hashCode()); result = prime * result + ((defaultLanguage == null) ? 0 : defaultLanguage.hashCode()); + result = prime * result + ((deactivatedDate == null) ? 0 : deactivatedDate.hashCode()); + result = prime * result + ((activatedDate == null) ? 0 : activatedDate.hashCode()); + result = prime * result + ((lastUpdatedWithSalesforceData == null) ? 0 : lastUpdatedWithSalesforceData.hashCode()); return result; } @@ -283,16 +318,32 @@ public boolean equals(Object obj) { return false; } else if (!type.equals(other.type)) return false; - if (status == null) { - if (other.status != null) - return false; - } else if (!status.equals(other.status)) - return false; if (defaultLanguage == null) { if (other.defaultLanguage != null) return false; } else if (!defaultLanguage.equals(other.defaultLanguage)) return false; + if (deactivatedDate == null) { + if (other.deactivatedDate != null) + return false; + } else if (!deactivatedDate.equals(other.deactivatedDate)) { + return false; + } + if (activatedDate == null) { + if (other.activatedDate != null) + return false; + } else if (!activatedDate.equals(other.activatedDate)) { + return false; + } + if (lastUpdatedWithSalesforceData == null) { + if (other.lastUpdatedWithSalesforceData != null) + return false; + } else if (!lastUpdatedWithSalesforceData.equals(other.lastUpdatedWithSalesforceData)) { + return false; + } + if (active != other.active) { + return false; + } return true; } @@ -301,6 +352,7 @@ public String toString() { return "Member [id=" + id + ", clientId=" + clientId + ", salesforceId=" + salesforceId + ", parentSalesforceId=" + parentSalesforceId + ", clientName=" + clientName + ", isConsortiumLead=" + isConsortiumLead + ", assertionServiceEnabled=" + assertionServiceEnabled + ", superadminEnabled=" + superadminEnabled + ", createdBy=" + createdBy + ", createdDate=" + createdDate + ", lastModifiedBy=" + lastModifiedBy + ", lastModifiedDate=" - + lastModifiedDate + ", type=" + type + ", status=" + status + ", defaultLanguage=" + defaultLanguage + "]"; + + lastModifiedDate + ", type=" + type + ", defaultLanguage=" + defaultLanguage + ", active=" + active + "]"; } + } \ No newline at end of file diff --git a/member-service-2/src/main/java/org/orcid/mp/member/rest/MemberResource.java b/member-service-2/src/main/java/org/orcid/mp/member/rest/MemberResource.java index c64de8751..200614d03 100644 --- a/member-service-2/src/main/java/org/orcid/mp/member/rest/MemberResource.java +++ b/member-service-2/src/main/java/org/orcid/mp/member/rest/MemberResource.java @@ -11,6 +11,7 @@ import org.orcid.mp.member.pojo.MemberContactUpdate; import org.orcid.mp.member.pojo.MemberContactUpdateResponse; import org.orcid.mp.member.pojo.RemoveConsortiumMember; +import org.orcid.mp.member.security.SecurityUtils; import org.orcid.mp.member.service.SalesforceService; import org.orcid.mp.member.service.UserService; import org.orcid.mp.member.validation.MemberValidation; @@ -67,7 +68,7 @@ public class MemberResource { public ResponseEntity createMember(@Valid @RequestBody Member member) throws URISyntaxException, JSONException { LOG.debug("REST request to save Member : {}", member); - Member created = memberService.createMember(member); + Member created = memberService.createMember(member, SecurityUtils.getCurrentUserLogin().get()); return ResponseEntity.created(new URI("/api/member/" + created.getId())).body(created); } @@ -126,7 +127,7 @@ public ResponseEntity updateMember(@Valid @RequestBody Member member) if (!existentMember.isPresent()) { throw new BadRequestAlertException("Invalid id"); } - member = memberService.updateMember(member); + member = memberService.updateMember(member, SecurityUtils.getCurrentUserLogin().get()); return ResponseEntity.ok().body(member); } diff --git a/member-service-2/src/main/java/org/orcid/mp/member/service/MemberService.java b/member-service-2/src/main/java/org/orcid/mp/member/service/MemberService.java index c53039597..032410f9a 100644 --- a/member-service-2/src/main/java/org/orcid/mp/member/service/MemberService.java +++ b/member-service-2/src/main/java/org/orcid/mp/member/service/MemberService.java @@ -5,18 +5,15 @@ import java.time.Instant; import java.util.List; import java.util.Optional; -import java.util.Set; import org.orcid.mp.member.client.SalesforceClient; import org.orcid.mp.member.domain.Member; -import org.orcid.mp.member.domain.User; import org.orcid.mp.member.error.BadRequestAlertException; import org.orcid.mp.member.repository.MemberRepository; import org.orcid.mp.member.validation.MemberValidation; import org.orcid.mp.member.validation.MemberValidator; import org.orcid.mp.member.salesforce.*; -import org.orcid.mp.member.security.AuthoritiesConstants; import org.orcid.mp.member.security.SecurityUtils; import org.orcid.mp.member.upload.MemberCsvReader; import org.orcid.mp.member.upload.MemberUpload; @@ -47,12 +44,6 @@ public class MemberService { @Autowired private MemberValidator memberValidator; - @Autowired - private SalesforceClient salesforceClient; - - @Autowired - private MailService mailService; - public MemberUpload uploadMemberCSV(InputStream inputStream) { LOG.info("Reading member CSV upload"); MemberUpload upload = null; @@ -76,35 +67,36 @@ public MemberUpload uploadMemberCSV(InputStream inputStream) { public Member createOrUpdateMember(Member member) { Optional optional = memberRepository.findBySalesforceId(member.getSalesforceId()); if (!optional.isPresent()) { - return createMember(member); + return createMember(member, SecurityUtils.getCurrentUserLogin().get()); } else { member.setId(optional.get().getId()); - return updateMember(member); + return updateMember(member, SecurityUtils.getCurrentUserLogin().get()); } } - public Member createMember(Member member) { - MemberValidation validation = memberValidator.validate(member, userService.getLoggedInUser()); + public Member createMember(Member member, String createdBy) { + MemberValidation validation = memberValidator.validate(member, member.getDefaultLanguage() != null ? member.getDefaultLanguage() : "en"); if (!validation.isValid()) { + LOG.warn("Member invalid {}", validation.getErrors().toString()); throw new BadRequestAlertException("Member invalid"); } Instant now = Instant.now(); member.setCreatedDate(now); member.setLastModifiedDate(now); - member.setCreatedBy(SecurityUtils.getCurrentUserLogin().get()); - member.setLastModifiedBy(SecurityUtils.getCurrentUserLogin().get()); + member.setCreatedBy(createdBy); + member.setLastModifiedBy(createdBy); return memberRepository.save(member); } - public Member updateMember(Member member) { + public Member updateMember(Member member, String updatedBy) { Optional optional = memberRepository.findById(member.getId()); validateMemberUpdate(member, optional); Member existingMember = optional.get(); existingMember.setClientId(member.getClientId()); existingMember.setParentSalesforceId(member.getParentSalesforceId()); - existingMember.setLastModifiedBy(SecurityUtils.getCurrentUserLogin().get()); + existingMember.setLastModifiedBy(updatedBy); existingMember.setLastModifiedDate(Instant.now()); existingMember.setAssertionServiceEnabled(member.getAssertionServiceEnabled()); existingMember.setIsConsortiumLead(member.getIsConsortiumLead()); @@ -122,7 +114,7 @@ private void propagateUpdatesAndSave(Member member, Member existingMember) { } private void validateMemberUpdate(Member member, Optional existingMember) { - MemberValidation validation = memberValidator.validate(member, userService.getLoggedInUser()); + MemberValidation validation = memberValidator.validate(member, member.getDefaultLanguage() != null ? member.getDefaultLanguage() : "en"); if (!validation.isValid()) { throw new BadRequestAlertException("Member invalid"); } @@ -148,7 +140,7 @@ private void validateMemberUpdate(Member member, Optional existingMember } public MemberValidation validateMember(Member member) { - return memberValidator.validate(member, userService.getLoggedInUser()); + return memberValidator.validate(member, userService.getLoggedInUser().getLangKey()); } public Page getMembers(Pageable pageable) { diff --git a/member-service-2/src/main/java/org/orcid/mp/member/service/SalesforceService.java b/member-service-2/src/main/java/org/orcid/mp/member/service/SalesforceService.java index fd0ec6cfc..58a063cbf 100644 --- a/member-service-2/src/main/java/org/orcid/mp/member/service/SalesforceService.java +++ b/member-service-2/src/main/java/org/orcid/mp/member/service/SalesforceService.java @@ -12,26 +12,26 @@ import org.orcid.mp.member.pojo.AddConsortiumMember; import org.orcid.mp.member.pojo.MemberContactUpdate; import org.orcid.mp.member.pojo.RemoveConsortiumMember; -import org.orcid.mp.member.repository.MemberRepository; import org.orcid.mp.member.salesforce.*; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; +import java.time.Instant; import java.util.*; @Service public class SalesforceService { + static final String SALESFORCE_SYNC_USERNAME = "salesforce-sync"; + private static final Logger LOG = LoggerFactory.getLogger(SalesforceService.class); private static final List OPPORTUNITY_PUBLIC_STAGE_NAMES = List.of("Invoice Paid", "Agreement Signed", "Invoice Sent", "Partial Payment", "In Collections"); private SalesforceClient salesforceClient; - private MemberRepository memberRepository; - private ObjectMapper objectMapper; private List salesforceCountries; @@ -42,12 +42,15 @@ public class SalesforceService { @Autowired private MailService mailService; - public SalesforceService(UserService userService, MailService mailService, SalesforceClient salesforceClient, MemberRepository memberRepository, ObjectMapper objectMapper) { + @Autowired + private MemberService memberService; + + public SalesforceService(MemberService memberService, UserService userService, MailService mailService, SalesforceClient salesforceClient, ObjectMapper objectMapper) { this.salesforceClient = salesforceClient; - this.memberRepository = memberRepository; this.objectMapper = objectMapper; this.userService = userService; this.mailService = mailService; + this.memberService = memberService; } public void processMemberContact(MemberContactUpdate memberContactUpdate, String salesforceId) { @@ -73,7 +76,7 @@ public void processMemberContact(MemberContactUpdate memberContactUpdate, String public void requestNewConsortiumMember(AddConsortiumMember addConsortiumMember) { User user = userService.getLoggedInUser(); - Optional optionalMember = memberRepository.findById(user.getMemberId()); + Optional optionalMember = memberService.getMember(user.getMemberId()); Member member = optionalMember.get(); if (!member.getIsConsortiumLead()) { throw new RuntimeException("Requesting member is not a consortium lead"); @@ -90,7 +93,7 @@ public void requestRemoveConsortiumMember(RemoveConsortiumMember removeConsortiu User user = userService.getLoggedInUser(); LOG.info("Requesting remove consortium member {} by user {}", removeConsortiumMember.getOrgName(), user.getEmail()); - Optional optionalMember = memberRepository.findById(user.getMemberId()); + Optional optionalMember = memberService.getMember(user.getMemberId()); Member member = optionalMember.get(); if (!member.getIsConsortiumLead()) { throw new RuntimeException("Requesting member is not a consortium lead"); @@ -178,8 +181,57 @@ public void syncMembers() { } } - private void syncMember(MemberDetails member) { - LOG.info("Syncing member {} : {}", member.getId(), member.getName()); + private void syncMember(MemberDetails salesforceMemberData) { + LOG.info("Syncing member {} : {}", salesforceMemberData.getId()); + try { + Optional existingMemberRecord = memberService.getMember(salesforceMemberData.getId()); + if (existingMemberRecord.isPresent()) { + LOG.debug("Found existing member {}", salesforceMemberData.getId()); + updateExistingMemberWithSalesforceData(existingMemberRecord.get(), salesforceMemberData); + } else { + LOG.debug("Member {} not found", salesforceMemberData.getId()); + createNewMemberWithSalesforceData(salesforceMemberData); + } + } catch (Exception e) { + LOG.error("Failed to sync member {} : {}", salesforceMemberData.getId(), e.getMessage()); + } + } + + private void createNewMemberWithSalesforceData(MemberDetails salesforceMemberData) { + Member member = new Member(); + member.setSalesforceId(salesforceMemberData.getId()); + member.setActive(salesforceMemberData.isActiveMember()); + member.setClientName(salesforceMemberData.getName()); + member.setAssertionServiceEnabled(false); + member.setIsConsortiumLead(false); // to be updated... + member = memberService.createMember(member, SALESFORCE_SYNC_USERNAME); + LOG.info("Created new member {}", member.getId()); + } + + private void updateExistingMemberWithSalesforceData(Member member, MemberDetails salesforceMemberData) { + member = updateMemberMetadata(member, salesforceMemberData); + member = updateMemberSalesforceStatus(member, salesforceMemberData); + member.setLastUpdatedWithSalesforceData(Instant.now()); + memberService.updateMember(member, SALESFORCE_SYNC_USERNAME); + } + + private Member updateMemberSalesforceStatus(Member member, MemberDetails salesforceMemberData) { + if (salesforceMemberData.isActiveMember() && !member.isActive()) { + LOG.info("Activating member {}", member.getId()); + member.setActive(true); + member.setActivatedDate(Instant.now()); + } else if (!salesforceMemberData.isActiveMember() && member.isActive()) { + LOG.info("Deactivating member {}", member.getId()); + member.setActive(false); + member.setDeactivatedDate(Instant.now()); + } + return member; + } + + private Member updateMemberMetadata(Member member, MemberDetails salesforceMemberData) { + LOG.info("Updating member {} name to {}", member.getId(), salesforceMemberData.getName()); + member.setClientName(salesforceMemberData.getName()); + return member; } private List getAllMembers() throws JsonProcessingException { diff --git a/member-service-2/src/main/java/org/orcid/mp/member/service/UserService.java b/member-service-2/src/main/java/org/orcid/mp/member/service/UserService.java index 058fc5194..7fa4e4ea0 100644 --- a/member-service-2/src/main/java/org/orcid/mp/member/service/UserService.java +++ b/member-service-2/src/main/java/org/orcid/mp/member/service/UserService.java @@ -2,6 +2,7 @@ import java.util.List; +import org.orcid.mp.member.client.InternalUserServiceClient; import org.orcid.mp.member.client.UserServiceClient; import org.orcid.mp.member.domain.User; import org.orcid.mp.member.security.SecurityUtils; @@ -18,6 +19,9 @@ public class UserService { @Autowired private UserServiceClient userServiceClient; + @Autowired + private InternalUserServiceClient internalUserServiceClient; + public User getLoggedInUser() { String login = SecurityUtils.getCurrentUserLogin().get(); LOG.info("Logged in user is {}", login); @@ -37,6 +41,6 @@ public void updateUser(User user) { } public void updateUsersMemberNames(String salesforceId, String newClientName) { - userServiceClient.updateUsersMemberNames(salesforceId, newClientName); + internalUserServiceClient.updateUsersMemberNames(salesforceId, newClientName); } } diff --git a/member-service-2/src/main/java/org/orcid/mp/member/upload/MemberCsvReader.java b/member-service-2/src/main/java/org/orcid/mp/member/upload/MemberCsvReader.java index 2260f8f43..8984d8f75 100644 --- a/member-service-2/src/main/java/org/orcid/mp/member/upload/MemberCsvReader.java +++ b/member-service-2/src/main/java/org/orcid/mp/member/upload/MemberCsvReader.java @@ -45,7 +45,7 @@ public MemberUpload readMemberUpload(InputStream inputStream, User user) throws Member member = createMemberInstance(user); member = parseLine(line, member); - MemberValidation validation = memberValidator.validate(member, user); + MemberValidation validation = memberValidator.validate(member, user.getLangKey()); if (!validation.isValid()) { for (String error : validation.getErrors()) { upload.addError(line.getRecordNumber(), error); diff --git a/member-service-2/src/main/java/org/orcid/mp/member/validation/MemberValidator.java b/member-service-2/src/main/java/org/orcid/mp/member/validation/MemberValidator.java index 5c6ff61f8..bdbcb7875 100644 --- a/member-service-2/src/main/java/org/orcid/mp/member/validation/MemberValidator.java +++ b/member-service-2/src/main/java/org/orcid/mp/member/validation/MemberValidator.java @@ -25,8 +25,6 @@ public class MemberValidator { private static final Pattern NEW_CLIENT_ID_PATTERN = Pattern.compile(NEW_CLIENT_ID_REGEX); private static final String MEMBER_TYPE_BASIC = "basic"; private static final String MEMBER_TYPE_PREMIUM = "premium"; - private static final String MEMBER_STATUS_ACTIVE = "Active"; - private static final String MEMBER_STATUS_DEACTIVATED = "Deactivated"; @Autowired private MessageSource messageSource; @@ -34,15 +32,14 @@ public class MemberValidator { @Autowired private MemberRepository memberRepository; - public MemberValidation validate(Member member, User user) { + public MemberValidation validate(Member member, String langKey) { List errors = new ArrayList<>(); - validateAssertionServiceEnabled(member, user, errors); - validateSalesforceId(member, user, errors); - validateConsortiumLeadAndParentSalesforceId(member, user, errors); - validateClientId(member, user, errors); - validateClientName(member, user, errors); - validateType(member, user, errors); - validateStatus(member, user, errors); + validateAssertionServiceEnabled(member, langKey, errors); + validateSalesforceId(member, langKey, errors); + validateConsortiumLeadAndParentSalesforceId(member, langKey, errors); + validateClientId(member, langKey, errors); + validateClientName(member, langKey, errors); + validateType(member, langKey, errors); MemberValidation validation = new MemberValidation(); validation.setValid(errors.isEmpty()); @@ -50,85 +47,77 @@ public MemberValidation validate(Member member, User user) { return validation; } - private void validateClientName(Member member, User user, List errors) { + private void validateClientName(Member member, String langKey, List errors) { if (StringUtils.isBlank(member.getClientName())) { - errors.add(getError("missingClientName", user)); + errors.add(getError("missingClientName", langKey)); } else { Optional matchingName = memberRepository.findByClientName(member.getClientName()); if (matchingName.isPresent() && !matchingName.get().getId().equals(member.getId())) { - errors.add(getError("nameAlreadyExists", member.getClientName(), user)); + errors.add(getError("nameAlreadyExists", member.getClientName(), langKey)); } } } - private void validateClientId(Member member, User user, List errors) { + private void validateClientId(Member member, String langKey, List errors) { if (Boolean.FALSE.equals(member.getIsConsortiumLead()) && Boolean.TRUE.equals(member.getAssertionServiceEnabled()) && StringUtils.isBlank(member.getClientId())) { - errors.add(getError("missingClientId", user)); + errors.add(getError("missingClientId", langKey)); } else if (!StringUtils.isBlank(member.getClientId())) { if (member.getClientId().startsWith(NEW_CLIENT_ID_PREFIX)) { Matcher newMatcher = NEW_CLIENT_ID_PATTERN.matcher(member.getClientId()); if (!newMatcher.matches()) { - errors.add(getError("invalidClientId", user)); + errors.add(getError("invalidClientId", langKey)); } } else { Matcher oldMatcher = OLD_CLIENT_ID_PATTERN.matcher(member.getClientId()); if (!oldMatcher.matches()) { - errors.add(getError("invalidClientId", user)); + errors.add(getError("invalidClientId", langKey)); } } } } - private void validateSalesforceId(Member member, User user, List errors) { + private void validateSalesforceId(Member member, String langKey, List errors) { if (StringUtils.isBlank(member.getSalesforceId())) { - errors.add(getError("missingSalesforceId", user)); + errors.add(getError("missingSalesforceId", langKey)); } else { Optional matchingSalesforceId = memberRepository.findBySalesforceId(member.getSalesforceId()); if (matchingSalesforceId.isPresent() && !matchingSalesforceId.get().getId().equals(member.getId())) { - errors.add(getError("salesforceIdAlreadyExists", member.getSalesforceId(), user)); + errors.add(getError("salesforceIdAlreadyExists", member.getSalesforceId(), langKey)); } } } - private void validateConsortiumLeadAndParentSalesforceId(Member member, User user, List errors) { + private void validateConsortiumLeadAndParentSalesforceId(Member member, String langKey, List errors) { if (member.getIsConsortiumLead() == null) { - errors.add(getError("missingConsortiumLead", user)); + errors.add(getError("missingConsortiumLead", langKey)); } else if (member.getIsConsortiumLead() && !StringUtils.isBlank(member.getParentSalesforceId()) && !member.getParentSalesforceId().equals(member.getSalesforceId())) { - errors.add(getError("parentSalesforceIdNotAllowed", user)); + errors.add(getError("parentSalesforceIdNotAllowed", langKey)); } } - private void validateAssertionServiceEnabled(Member member, User user, List errors) { + private void validateAssertionServiceEnabled(Member member, String langKey, List errors) { if (member.getAssertionServiceEnabled() == null) { - errors.add(getError("missingAssertionsEnabled", user)); + errors.add(getError("missingAssertionsEnabled", langKey)); } else if (member.getAssertionServiceEnabled() && Boolean.TRUE.equals(member.getIsConsortiumLead()) && StringUtils.isBlank(member.getClientId())) { - errors.add(getError("invalidAssertionsEnabled", user)); + errors.add(getError("invalidAssertionsEnabled", langKey)); } } - private void validateType(Member member, User user, List errors) { + private void validateType(Member member, String langKey, List errors) { if (member.getType() != null) { if (!MEMBER_TYPE_BASIC.equals(member.getType()) && !MEMBER_TYPE_PREMIUM.equals(member.getType())) { - errors.add(getError("invalidMemberType", user)); + errors.add(getError("invalidMemberType", langKey)); } } } - private void validateStatus(Member member, User user, List errors) { - if (member.getStatus() != null) { - if (!MEMBER_STATUS_ACTIVE.equals(member.getStatus()) && !MEMBER_STATUS_DEACTIVATED.equals(member.getStatus())) { - errors.add(getError("invalidMemberStatus", user)); - } - } - } - - private String getError(String code, User user) { - return getError(code, null, user); + private String getError(String code, String langKey) { + return getError(code, null, langKey); } - private String getError(String code, String arg, User user) { - return messageSource.getMessage("member.validation.error." + code, arg != null ? new Object[] { arg } : null, Locale.forLanguageTag(user.getLangKey())); + private String getError(String code, String arg, String langKey) { + return messageSource.getMessage("member.validation.error." + code, arg != null ? new Object[] { arg } : null, Locale.forLanguageTag(langKey)); } } diff --git a/member-service-2/src/main/resources/application.yaml b/member-service-2/src/main/resources/application.yaml index 2146621bf..28b40d2d7 100644 --- a/member-service-2/src/main/resources/application.yaml +++ b/member-service-2/src/main/resources/application.yaml @@ -10,6 +10,21 @@ spring: jwt: issuer-uri: ${APPLICATION_SECURITY_ISSUER_URL} jwk-set-uri: ${APPLICATION_USER_SERVICE_URL}/oauth2/jwks + client: + registration: + # This ID is used in your Java RestClient/WebClient code in the Member Service + internal-member-client: + provider: user-service-provider + client-id: ${APPLICATION_INTERNAL_MEMBER_SERVICE_ID} + client-secret: ${APPLICATION_INTERNAL_MEMBER_SERVICE_CLIENT_SECRET} + authorization-grant-type: client_credentials + scope: internal + provider: + user-service-provider: + # The URL of your User Service (Auth Server) + token-uri: ${APPLICATION_USER_SERVICE_URL}/oauth2/token + # (Optional for client_credentials flow, but good to have) + authorization-uri: ${APPLICATION_USER_SERVICE_URL}/oauth2/authorize data: mongodb: database: memberservice diff --git a/member-service-2/src/test/java/org/orcid/mp/member/service/MemberServiceTest.java b/member-service-2/src/test/java/org/orcid/mp/member/service/MemberServiceTest.java index bfe0f2397..01f5357f3 100644 --- a/member-service-2/src/test/java/org/orcid/mp/member/service/MemberServiceTest.java +++ b/member-service-2/src/test/java/org/orcid/mp/member/service/MemberServiceTest.java @@ -29,6 +29,7 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; class MemberServiceTest { @@ -67,8 +68,8 @@ public void setUp() { @Test void testCreateMember() { - Mockito.when(memberValidator.validate(Mockito.any(Member.class), Mockito.any(User.class))).thenReturn(getValidValidation()); - Mockito.when(memberRepository.findBySalesforceId(Mockito.anyString())).thenReturn(Optional.empty()); + Mockito.when(memberValidator.validate(Mockito.any(Member.class), anyString())).thenReturn(getValidValidation()); + Mockito.when(memberRepository.findBySalesforceId(anyString())).thenReturn(Optional.empty()); Mockito.when(memberRepository.save(Mockito.any(Member.class))).thenAnswer(new Answer() { @Override public Member answer(InvocationOnMock invocation) throws Throwable { @@ -77,7 +78,7 @@ public Member answer(InvocationOnMock invocation) throws Throwable { }); Member member = getMember(); - Member created = memberService.createMember(member); + Member created = memberService.createMember(member, "me"); assertNotNull(created.getCreatedBy()); assertNotNull(created.getCreatedDate()); assertNotNull(created.getLastModifiedBy()); @@ -87,23 +88,25 @@ public Member answer(InvocationOnMock invocation) throws Throwable { assertEquals(member.getSalesforceId(), created.getSalesforceId()); assertEquals(member.getAssertionServiceEnabled(), created.getAssertionServiceEnabled()); assertEquals(member.getIsConsortiumLead(), created.getIsConsortiumLead()); + assertEquals("me", member.getCreatedBy()); + assertEquals("me", created.getLastModifiedBy()); } @Test void testCreateMemberWhenMemberExists() { - Mockito.when(memberValidator.validate(Mockito.any(Member.class), Mockito.any(User.class))).thenReturn(getInvalidValidation("member-exists")); - Mockito.when(memberRepository.findBySalesforceId(Mockito.anyString())).thenReturn(Optional.of(getMember())); + Mockito.when(memberValidator.validate(Mockito.any(Member.class), anyString())).thenReturn(getInvalidValidation("member-exists")); + Mockito.when(memberRepository.findBySalesforceId(anyString())).thenReturn(Optional.of(getMember())); Member member = getMember(); Assertions.assertThrows(BadRequestAlertException.class, () -> { - memberService.createMember(member); + memberService.createMember(member, "me"); }); } @Test void testUpdateMember() { - Mockito.when(memberValidator.validate(Mockito.any(Member.class), Mockito.any(User.class))).thenReturn(getValidValidation()); - Mockito.when(memberRepository.findById(Mockito.anyString())).thenReturn(Optional.of(getMember())); + Mockito.when(memberValidator.validate(Mockito.any(Member.class), anyString())).thenReturn(getValidValidation()); + Mockito.when(memberRepository.findById(anyString())).thenReturn(Optional.of(getMember())); Mockito.when(memberRepository.save(Mockito.any(Member.class))).thenAnswer(new Answer() { @Override public Member answer(InvocationOnMock invocation) throws Throwable { @@ -114,7 +117,7 @@ public Member answer(InvocationOnMock invocation) throws Throwable { Member member = getMember(); member.setClientName("new name"); member.setId("id"); - Member updated = memberService.updateMember(member); + Member updated = memberService.updateMember(member, "user"); assertNotNull(updated.getLastModifiedBy()); assertNotNull(updated.getLastModifiedDate()); assertEquals(member.getClientName(), updated.getClientName()); @@ -127,11 +130,11 @@ public Member answer(InvocationOnMock invocation) throws Throwable { @Test void testUpdateMemberWithDuplicateName() { - Mockito.when(memberValidator.validate(Mockito.any(Member.class), Mockito.any(User.class))).thenReturn(getValidValidation()); - Mockito.when(memberRepository.findById(Mockito.anyString())).thenReturn(Optional.of(getMember())); + Mockito.when(memberValidator.validate(Mockito.any(Member.class), anyString())).thenReturn(getValidValidation()); + Mockito.when(memberRepository.findById(anyString())).thenReturn(Optional.of(getMember())); // return a record when name is checked against db - Mockito.when(memberRepository.findByClientName(Mockito.anyString())).thenReturn(Optional.of(getMember())); + Mockito.when(memberRepository.findByClientName(anyString())).thenReturn(Optional.of(getMember())); Member member = getMember(); member.setId("id"); @@ -139,7 +142,7 @@ void testUpdateMemberWithDuplicateName() { member.setClientName("new client name"); Exception e = Assertions.assertThrows(BadRequestAlertException.class, () -> { - memberService.updateMember(member); + memberService.updateMember(member, "user"); }); assertThat(e.getMessage()).isEqualTo("Invalid member name"); @@ -149,8 +152,8 @@ void testUpdateMemberWithDuplicateName() { @Test void testUpdateMemberWithMemberName() { - Mockito.when(memberValidator.validate(Mockito.any(Member.class), Mockito.any(User.class))).thenReturn(getValidValidation()); - Mockito.when(memberRepository.findById(Mockito.anyString())).thenReturn(Optional.of(getMember())); + Mockito.when(memberValidator.validate(Mockito.any(Member.class), anyString())).thenReturn(getValidValidation()); + Mockito.when(memberRepository.findById(anyString())).thenReturn(Optional.of(getMember())); Mockito.when(memberRepository.save(Mockito.any(Member.class))).thenAnswer(new Answer() { @Override public Member answer(InvocationOnMock invocation) throws Throwable { @@ -159,7 +162,7 @@ public Member answer(InvocationOnMock invocation) throws Throwable { }); Member member = getMember(); member.setClientName("a new member name"); - memberService.updateMember(member); + memberService.updateMember(member, "user"); // check assertion and user changes rolled back Mockito.verify(userService, Mockito.times(1)).updateUsersMemberNames(eq("memberId"), eq("a new member name")); @@ -171,13 +174,14 @@ public Member answer(InvocationOnMock invocation) throws Throwable { @Test void testUpdateMemberWithAssertionEnabledUpdate() { - Mockito.when(memberValidator.validate(Mockito.any(Member.class), Mockito.any(User.class))).thenReturn(getValidValidation()); + Mockito.when(memberValidator.validate(Mockito.any(Member.class), anyString())).thenReturn(getValidValidation()); Member existingMember = getMember(); existingMember.setAssertionServiceEnabled(false); existingMember.setSalesforceId("salesforce-id"); + existingMember.setDefaultLanguage("en"); - Mockito.when(memberRepository.findById(Mockito.anyString())).thenReturn(Optional.of(existingMember)); + Mockito.when(memberRepository.findById(anyString())).thenReturn(Optional.of(existingMember)); Mockito.when(memberRepository.save(Mockito.any(Member.class))).thenAnswer(new Answer() { @Override public Member answer(InvocationOnMock invocation) throws Throwable { @@ -189,21 +193,21 @@ public Member answer(InvocationOnMock invocation) throws Throwable { member.setId("id"); member.setSalesforceId("salesforce-id"); member.setAssertionServiceEnabled(true); - memberService.updateMember(member); + memberService.updateMember(member, "user"); Mockito.verify(memberRepository, Mockito.times(1)).save(Mockito.any(Member.class)); } @Test void testUpdateMemberWithCLUpdate() { - Mockito.when(memberValidator.validate(Mockito.any(Member.class), Mockito.any(User.class))).thenReturn(getValidValidation()); + Mockito.when(memberValidator.validate(Mockito.any(Member.class), anyString())).thenReturn(getValidValidation()); Member existingMember = getMember(); existingMember.setAssertionServiceEnabled(false); existingMember.setIsConsortiumLead(false); existingMember.setSalesforceId("salesforce-id"); - Mockito.when(memberRepository.findById(Mockito.anyString())).thenReturn(Optional.of(existingMember)); + Mockito.when(memberRepository.findById(anyString())).thenReturn(Optional.of(existingMember)); Mockito.when(memberRepository.save(Mockito.any(Member.class))).thenAnswer(new Answer() { @Override public Member answer(InvocationOnMock invocation) throws Throwable { @@ -216,15 +220,15 @@ public Member answer(InvocationOnMock invocation) throws Throwable { member.setSalesforceId("salesforce-id"); member.setAssertionServiceEnabled(false); member.setIsConsortiumLead(true); - memberService.updateMember(member); + memberService.updateMember(member, "user"); Mockito.verify(memberRepository, Mockito.times(1)).save(Mockito.any(Member.class)); } @Test void testUpdateNonExistentMember() { - Mockito.when(memberValidator.validate(Mockito.any(Member.class), Mockito.any(User.class))).thenReturn(getValidValidation()); - Mockito.when(memberRepository.findById(Mockito.anyString())).thenReturn(Optional.empty()); + Mockito.when(memberValidator.validate(Mockito.any(Member.class), anyString())).thenReturn(getValidValidation()); + Mockito.when(memberRepository.findById(anyString())).thenReturn(Optional.empty()); Mockito.when(memberRepository.save(Mockito.any(Member.class))).thenAnswer(new Answer() { @Override public Member answer(InvocationOnMock invocation) throws Throwable { @@ -237,14 +241,14 @@ public Member answer(InvocationOnMock invocation) throws Throwable { member.setId("id"); Assertions.assertThrows(BadRequestAlertException.class, () -> { - memberService.updateMember(member); + memberService.updateMember(member, "user"); }); } @Test void testUpdateInvalidMember() { - Mockito.when(memberValidator.validate(Mockito.any(Member.class), Mockito.any(User.class))).thenReturn(getInvalidValidation("some-error")); - Mockito.when(memberRepository.findById(Mockito.anyString())).thenReturn(Optional.of(getMember())); + Mockito.when(memberValidator.validate(Mockito.any(Member.class), anyString())).thenReturn(getInvalidValidation("some-error")); + Mockito.when(memberRepository.findById(anyString())).thenReturn(Optional.of(getMember())); Member member = getMember(); member.setClientName("new name"); @@ -252,14 +256,14 @@ void testUpdateInvalidMember() { member.setSalesforceId(null); Assertions.assertThrows(BadRequestAlertException.class, () -> { - memberService.updateMember(member); + memberService.updateMember(member, "user"); }); } @Test void testUploadMemberCSV() throws IOException { Mockito.when(membersUploadReader.readMemberUpload(Mockito.any(), Mockito.any(User.class))).thenReturn(getMemberUpload()); - Mockito.when(memberValidator.validate(Mockito.any(Member.class), Mockito.any(User.class))).thenReturn(getValidValidation()); + Mockito.when(memberValidator.validate(Mockito.any(Member.class), anyString())).thenReturn(getValidValidation()); Mockito.when(memberRepository.findBySalesforceId(eq("one"))).thenReturn(Optional.empty()); Member existing = getMember(); existing.setId("two"); @@ -278,13 +282,13 @@ void testGetMembers() { assertEquals(3, page.getTotalElements()); Mockito.verify(memberRepository, Mockito.times(1)).findAll(Mockito.any(Pageable.class)); - Mockito.when(memberRepository.findByClientNameContainingIgnoreCaseOrSalesforceIdContainingIgnoreCaseOrParentSalesforceIdContainingIgnoreCase(Mockito.anyString(), - Mockito.anyString(), Mockito.anyString(), Mockito.any(Pageable.class))).thenReturn(new PageImpl<>(Arrays.asList(getMember(), getMember(), getMember()))); + Mockito.when(memberRepository.findByClientNameContainingIgnoreCaseOrSalesforceIdContainingIgnoreCaseOrParentSalesforceIdContainingIgnoreCase(anyString(), + anyString(), anyString(), Mockito.any(Pageable.class))).thenReturn(new PageImpl<>(Arrays.asList(getMember(), getMember(), getMember()))); page = memberService.getMembers(Mockito.mock(Pageable.class), "test"); assertNotNull(page); assertEquals(3, page.getTotalElements()); Mockito.verify(memberRepository, Mockito.times(1)).findByClientNameContainingIgnoreCaseOrSalesforceIdContainingIgnoreCaseOrParentSalesforceIdContainingIgnoreCase( - Mockito.anyString(), Mockito.anyString(), Mockito.anyString(), Mockito.any(Pageable.class)); + anyString(), anyString(), anyString(), Mockito.any(Pageable.class)); } @Test diff --git a/member-service-2/src/test/java/org/orcid/mp/member/service/SalesforceServiceTest.java b/member-service-2/src/test/java/org/orcid/mp/member/service/SalesforceServiceTest.java index a66d2d227..e9cc6f8ff 100644 --- a/member-service-2/src/test/java/org/orcid/mp/member/service/SalesforceServiceTest.java +++ b/member-service-2/src/test/java/org/orcid/mp/member/service/SalesforceServiceTest.java @@ -13,13 +13,14 @@ import org.orcid.mp.member.pojo.AddConsortiumMember; import org.orcid.mp.member.pojo.MemberContactUpdate; import org.orcid.mp.member.pojo.RemoveConsortiumMember; -import org.orcid.mp.member.repository.MemberRepository; import org.orcid.mp.member.salesforce.*; import org.orcid.mp.member.security.MockSecurityContext; +import org.springframework.core.io.ClassPathResource; import org.springframework.security.core.context.SecurityContextHolder; import java.io.File; import java.io.IOException; +import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; import java.util.Arrays; @@ -31,8 +32,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; +import static org.mockito.Mockito.*; public class SalesforceServiceTest { @@ -42,15 +42,18 @@ public class SalesforceServiceTest { @Mock private UserService userService; - @Mock - private MemberRepository memberRepository; - @Mock private MailService mailService; @Captor private ArgumentCaptor publicMemberDetailsCaptor; + @Mock + private MemberService memberService; + + @Captor + private ArgumentCaptor salesforceUpdateCaptor; + private SalesforceService salesforceService; private ObjectMapper objectMapper = new ObjectMapper(); @@ -58,7 +61,7 @@ public class SalesforceServiceTest { @BeforeEach public void setUp() { MockitoAnnotations.initMocks(this); - salesforceService = new SalesforceService(userService, mailService, salesforceClient, memberRepository, objectMapper); + salesforceService = new SalesforceService(memberService, userService, mailService, salesforceClient, objectMapper); SecurityContextHolder.setContext(new MockSecurityContext("me")); Mockito.when(userService.getLoggedInUser()).thenReturn(getUser()); } @@ -66,7 +69,6 @@ public void setUp() { @Test void testGetMemberDetails() throws IOException, UnauthorizedMemberAccessException { Mockito.when(userService.getLoggedInUser()).thenReturn(getUser()); - Mockito.when(memberRepository.findBySalesforceId(Mockito.eq("salesforceId"))).thenReturn(Optional.of(getMember())); Mockito.when(salesforceClient.getMemberDetails(Mockito.eq("salesforceId"))).thenReturn(getFileContent("src/test/resources/salesforce/member.json")); MemberDetails memberDetails = salesforceService.getMemberDetails("salesforceId"); @@ -147,7 +149,6 @@ void testGetMemberOrgIds() throws IOException, UnauthorizedMemberAccessException @Test void testGetMemberOrgIds_forConsortiumMemberByConsortiumLead() throws IOException, UnauthorizedMemberAccessException { Mockito.when(userService.getLoggedInUser()).thenReturn(getCLUser()); - Mockito.when(memberRepository.findBySalesforceId(Mockito.eq("salesforceId"))).thenReturn(Optional.of(getMember())); Mockito.when(salesforceClient.getMemberOrgIds(Mockito.eq("salesforceId"))).thenReturn(objectMapper.writeValueAsString(getMemberOrgIds())); MemberOrgIds memberOrgIds = salesforceService.getMemberOrgIds("salesforceId"); @@ -185,7 +186,6 @@ void testGetConsortiumLeadDetails() throws IOException { @Test void testUpdatePublicMemberDetails() throws IOException, UnauthorizedMemberAccessException { Mockito.when(userService.getLoggedInUser()).thenReturn(getUser()); - Mockito.when(memberRepository.findBySalesforceId(Mockito.eq("salesforceId"))).thenReturn(Optional.of(getConsortiumLeadMember())); Mockito.doNothing().when(salesforceClient).updatePublicMemberDetails(Mockito.any(MemberUpdateData.class)); MemberUpdateData memberUpdateData = getPublicMemberDetails(); @@ -206,7 +206,6 @@ void testUpdatePublicMemberDetails() throws IOException, UnauthorizedMemberAcces void testUpdatePublicMemberDetails_forConsortiumMemberByConsortiumLead() throws IOException, UnauthorizedMemberAccessException { Mockito.when(userService.getLoggedInUser()).thenReturn(getCLUser()); Mockito.doNothing().when(salesforceClient).updatePublicMemberDetails(Mockito.any(MemberUpdateData.class)); - Mockito.when(memberRepository.findBySalesforceId(Mockito.eq("salesforceId"))).thenReturn(Optional.of(getMember())); MemberUpdateData memberUpdateData = getPublicMemberDetails(); salesforceService.updatePublicMemberDetails(memberUpdateData); @@ -242,7 +241,7 @@ void testProcessMemberContact_update() throws UnauthorizedMemberAccessException void testAddConsortiumMember() { Mockito.doNothing().when(mailService).sendAddConsortiumMemberEmail(Mockito.any(AddConsortiumMember.class)); Mockito.when(userService.getLoggedInUser()).thenReturn(getUser()); - Mockito.when(memberRepository.findById(Mockito.eq("memberId"))).thenReturn(Optional.of(getConsortiumLeadMember())); + Mockito.when(memberService.getMember(Mockito.eq("memberId"))).thenReturn(Optional.of(getConsortiumLeadMember())); AddConsortiumMember addConsortiumMember = new AddConsortiumMember(); addConsortiumMember.setOrgName("new org name"); @@ -251,14 +250,13 @@ void testAddConsortiumMember() { Mockito.verify(mailService).sendAddConsortiumMemberEmail(Mockito.any(AddConsortiumMember.class)); Mockito.verify(userService).getLoggedInUser(); - Mockito.verify(memberRepository).findById(Mockito.eq("memberId")); + Mockito.verify(memberService).getMember(Mockito.eq("memberId")); } @Test void testRemoveConsortiumMember_nonCL() { Mockito.doNothing().when(mailService).sendRemoveConsortiumMemberEmail(Mockito.any(RemoveConsortiumMember.class)); Mockito.when(userService.getLoggedInUser()).thenReturn(getUser()); - Mockito.when(memberRepository.findBySalesforceId(Mockito.eq("salesforceId"))).thenReturn(Optional.of(getMember())); RemoveConsortiumMember removeConsortiumMember = new RemoveConsortiumMember(); removeConsortiumMember.setOrgName("old org name"); @@ -272,7 +270,7 @@ void testRemoveConsortiumMember_nonCL() { void testRemoveConsortiumMember() { Mockito.doNothing().when(mailService).sendRemoveConsortiumMemberEmail(Mockito.any(RemoveConsortiumMember.class)); Mockito.when(userService.getLoggedInUser()).thenReturn(getUser()); - Mockito.when(memberRepository.findById(Mockito.eq("memberId"))).thenReturn(Optional.of(getConsortiumLeadMember())); + Mockito.when(memberService.getMember(Mockito.eq("memberId"))).thenReturn(Optional.of(getConsortiumLeadMember())); RemoveConsortiumMember removeConsortiumMember = new RemoveConsortiumMember(); removeConsortiumMember.setOrgName("old org name"); @@ -281,14 +279,13 @@ void testRemoveConsortiumMember() { Mockito.verify(mailService).sendRemoveConsortiumMemberEmail(Mockito.any(RemoveConsortiumMember.class)); Mockito.verify(userService).getLoggedInUser(); - Mockito.verify(memberRepository).findById(Mockito.eq("memberId")); + Mockito.verify(memberService).getMember(Mockito.eq("memberId")); } @Test void testAddConsortiumMember_nonCL() { Mockito.doNothing().when(mailService).sendAddConsortiumMemberEmail(Mockito.any(AddConsortiumMember.class)); Mockito.when(userService.getLoggedInUser()).thenReturn(getUser()); - Mockito.when(memberRepository.findBySalesforceId(Mockito.eq("salesforceId"))).thenReturn(Optional.of(getMember())); AddConsortiumMember addConsortiumMember = new AddConsortiumMember(); addConsortiumMember.setOrgName("new org name"); @@ -347,6 +344,104 @@ void testGetSalesforceCountries() throws Exception { assertEquals(26, ireland.getStates().size()); } + @Test + void testSyncMembers_updates() { + when(salesforceClient.getMembers()).thenReturn(getSalesforceMembersResponse()); + doAnswer(invocation -> invocation.getArgument(0)).when(memberService).updateMember(Mockito.any(), anyString()); + when(memberService.getMember(Mockito.eq("0011000001XYZ01"))).thenReturn(Optional.of(getMember("0011000001XYZ01", false))); + when(memberService.getMember(Mockito.eq("0011000002XYZ02"))).thenReturn(Optional.of(getMember("0011000002XYZ02", true))); + when(memberService.getMember(Mockito.eq("0011000003XYZ03"))).thenReturn(Optional.of(getMember("0011000003XYZ03", true))); + when(memberService.getMember(Mockito.eq("0011000004XYZ04"))).thenReturn(Optional.of(getMember("0011000004XYZ04", true))); + when(memberService.getMember(Mockito.eq("0011000005XYZ05"))).thenReturn(Optional.of(getMember("0011000005XYZ05", false))); + + salesforceService.syncMembers(); + + verify(memberService, times(5)).updateMember(salesforceUpdateCaptor.capture(), anyString()); + + List updatedMembers = salesforceUpdateCaptor.getAllValues(); + assertThat(updatedMembers.get(0)).isNotNull(); + assertThat(updatedMembers.get(0).getSalesforceId()).isEqualTo("0011000001XYZ01"); + assertThat(updatedMembers.get(0).getClientName()).isEqualTo("Global Research University"); + assertThat(updatedMembers.get(0).isActive()).isTrue(); + assertThat(updatedMembers.get(0).getActivatedDate()).isNotNull(); + assertThat(updatedMembers.get(0).getDeactivatedDate()).isNull(); + assertThat(updatedMembers.get(1)).isNotNull(); + assertThat(updatedMembers.get(1).getSalesforceId()).isEqualTo("0011000002XYZ02"); + assertThat(updatedMembers.get(1).getClientName()).isEqualTo("Consortium Sub-Member A"); + assertThat(updatedMembers.get(1).isActive()).isTrue(); + assertThat(updatedMembers.get(1).getActivatedDate()).isNull(); // hasn't just been activated + assertThat(updatedMembers.get(1).getDeactivatedDate()).isNull(); + assertThat(updatedMembers.get(2)).isNotNull(); + assertThat(updatedMembers.get(2).getSalesforceId()).isEqualTo("0011000003XYZ03"); + assertThat(updatedMembers.get(2).getClientName()).isEqualTo("Consortium Sub-Member B"); + assertThat(updatedMembers.get(2).isActive()).isTrue(); + assertThat(updatedMembers.get(2).getActivatedDate()).isNull(); + assertThat(updatedMembers.get(2).getDeactivatedDate()).isNull(); + assertThat(updatedMembers.get(3)).isNotNull(); + assertThat(updatedMembers.get(3).getSalesforceId()).isEqualTo("0011000004XYZ04"); + assertThat(updatedMembers.get(3).getClientName()).isEqualTo("Legacy Research Lab"); + assertThat(updatedMembers.get(3).isActive()).isFalse(); + assertThat(updatedMembers.get(3).getDeactivatedDate()).isNotNull(); + assertThat(updatedMembers.get(3).getActivatedDate()).isNull(); + assertThat(updatedMembers.get(4)).isNotNull(); + assertThat(updatedMembers.get(4).getSalesforceId()).isEqualTo("0011000005XYZ05"); + assertThat(updatedMembers.get(4).getClientName()).isEqualTo("New Horizon Publisher"); + assertThat(updatedMembers.get(4).isActive()).isTrue(); + assertThat(updatedMembers.get(4).getActivatedDate()).isNotNull(); + assertThat(updatedMembers.get(4).getDeactivatedDate()).isNull(); + } + + @Test + void testSyncMembers_creates() { + when(salesforceClient.getMembers()).thenReturn(getSalesforceMembersResponse()); + doAnswer(invocation -> invocation.getArgument(0)).when(memberService).createMember(Mockito.any(), anyString()); + + salesforceService.syncMembers(); + + verify(memberService, times(5)).createMember(salesforceUpdateCaptor.capture(), anyString()); + + List updatedMembers = salesforceUpdateCaptor.getAllValues(); + assertThat(updatedMembers.get(0)).isNotNull(); + assertThat(updatedMembers.get(0).getSalesforceId()).isEqualTo("0011000001XYZ01"); + assertThat(updatedMembers.get(0).getClientName()).isEqualTo("Global Research University"); + assertThat(updatedMembers.get(0).isActive()).isTrue(); + assertThat(updatedMembers.get(0).getActivatedDate()).isNull(); + assertThat(updatedMembers.get(0).getDeactivatedDate()).isNull(); + assertThat(updatedMembers.get(1)).isNotNull(); + assertThat(updatedMembers.get(1).getSalesforceId()).isEqualTo("0011000002XYZ02"); + assertThat(updatedMembers.get(1).getClientName()).isEqualTo("Consortium Sub-Member A"); + assertThat(updatedMembers.get(1).isActive()).isTrue(); + assertThat(updatedMembers.get(1).getActivatedDate()).isNull(); // hasn't just been activated + assertThat(updatedMembers.get(1).getDeactivatedDate()).isNull(); + assertThat(updatedMembers.get(2)).isNotNull(); + assertThat(updatedMembers.get(2).getSalesforceId()).isEqualTo("0011000003XYZ03"); + assertThat(updatedMembers.get(2).getClientName()).isEqualTo("Consortium Sub-Member B"); + assertThat(updatedMembers.get(2).isActive()).isTrue(); + assertThat(updatedMembers.get(2).getActivatedDate()).isNull(); + assertThat(updatedMembers.get(2).getDeactivatedDate()).isNull(); + assertThat(updatedMembers.get(3)).isNotNull(); + assertThat(updatedMembers.get(3).getSalesforceId()).isEqualTo("0011000004XYZ04"); + assertThat(updatedMembers.get(3).getClientName()).isEqualTo("Legacy Research Lab"); + assertThat(updatedMembers.get(3).isActive()).isFalse(); + assertThat(updatedMembers.get(3).getDeactivatedDate()).isNull(); + assertThat(updatedMembers.get(3).getActivatedDate()).isNull(); + assertThat(updatedMembers.get(4)).isNotNull(); + assertThat(updatedMembers.get(4).getSalesforceId()).isEqualTo("0011000005XYZ05"); + assertThat(updatedMembers.get(4).getClientName()).isEqualTo("New Horizon Publisher"); + assertThat(updatedMembers.get(4).isActive()).isTrue(); + assertThat(updatedMembers.get(4).getActivatedDate()).isNull(); + assertThat(updatedMembers.get(4).getDeactivatedDate()).isNull(); + } + + private String getSalesforceMembersResponse() { + try { + ClassPathResource resource = new ClassPathResource("salesforce/members.json"); + return Files.readString(resource.getFile().toPath(), StandardCharsets.UTF_8); + } catch (IOException e) { + throw new RuntimeException("Failed to read members.json test resource file", e); + } + } + private List getSalesforceCountries() { State state1 = new State(); state1.setName("state1"); @@ -384,14 +479,10 @@ private User getUser() { return user; } - private Member getMember() { + private Member getMember(String salesforceId, boolean active) { Member member = new Member(); - member.setAssertionServiceEnabled(true); - member.setClientId("XXXX-XXXX-XXXX-XXXX"); - member.setClientName("clientname"); - member.setIsConsortiumLead(false); - member.setSalesforceId("two"); - member.setParentSalesforceId("parentSalesforceId"); + member.setSalesforceId(salesforceId); + member.setActive(active); return member; } diff --git a/member-service-2/src/test/java/org/orcid/mp/member/upload/MemberCsvReaderTest.java b/member-service-2/src/test/java/org/orcid/mp/member/upload/MemberCsvReaderTest.java index f036ba443..ceca6507e 100644 --- a/member-service-2/src/test/java/org/orcid/mp/member/upload/MemberCsvReaderTest.java +++ b/member-service-2/src/test/java/org/orcid/mp/member/upload/MemberCsvReaderTest.java @@ -18,6 +18,7 @@ import static org.junit.jupiter.api.Assertions.*; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.ArgumentMatchers.anyString; class MemberCsvReaderTest { @@ -37,7 +38,7 @@ public void setUp() { @Test void testReadMembersUpload() throws IOException { - Mockito.when(memberValidator.validate(Mockito.any(Member.class), Mockito.any(User.class))).thenReturn(getValidValidation()); + Mockito.when(memberValidator.validate(Mockito.any(Member.class), anyString())).thenReturn(getValidValidation()); InputStream inputStream = getClass().getResourceAsStream("/members.csv"); MemberUpload upload = reader.readMemberUpload(inputStream, getUser()); diff --git a/member-service-2/src/test/java/org/orcid/mp/member/validation/MemberValidatorTest.java b/member-service-2/src/test/java/org/orcid/mp/member/validation/MemberValidatorTest.java index ba5712235..f23917b1a 100644 --- a/member-service-2/src/test/java/org/orcid/mp/member/validation/MemberValidatorTest.java +++ b/member-service-2/src/test/java/org/orcid/mp/member/validation/MemberValidatorTest.java @@ -41,7 +41,7 @@ public void setUp() { @Test public void testValidateWithMissingAssertionsEnabled() { Member member = getMemberWithMissingAssertionsEnabled(); - MemberValidation validation = memberValidator.validate(member, getUser()); + MemberValidation validation = memberValidator.validate(member, "en"); List errors = validation.getErrors(); assertFalse(validation.isValid()); assertEquals(1, errors.size()); @@ -54,7 +54,7 @@ public void testValidateWithMissingAssertionsEnabled() { public void testValidateWithInvalidAssertionsEnabled() { Member member = getConsortiaLeadWithNoClientId(); member.setAssertionServiceEnabled(true); // invalid due to no client id - MemberValidation validation = memberValidator.validate(member, getUser()); + MemberValidation validation = memberValidator.validate(member, "en"); List errors = validation.getErrors(); assertFalse(validation.isValid()); assertEquals(1, errors.size()); @@ -66,7 +66,7 @@ public void testValidateWithInvalidAssertionsEnabled() { @Test public void testValidateWithMissingSalesforceId() { Member member = getMemberWithMissingSalesforceId(); - MemberValidation validation = memberValidator.validate(member, getUser()); + MemberValidation validation = memberValidator.validate(member, "en"); List errors = validation.getErrors(); assertFalse(validation.isValid()); assertEquals(1, errors.size()); @@ -78,7 +78,7 @@ public void testValidateWithMissingSalesforceId() { @Test public void testValidateWithMissingClientId() { Member member = getMemberWithMissingClientId(); - MemberValidation validation = memberValidator.validate(member, getUser()); + MemberValidation validation = memberValidator.validate(member, "en"); List errors = validation.getErrors(); assertFalse(validation.isValid()); assertEquals(1, errors.size()); @@ -88,7 +88,7 @@ public void testValidateWithMissingClientId() { // shouldn't be required if assertion service not enabled member.setAssertionServiceEnabled(false); - validation = memberValidator.validate(member, getUser()); + validation = memberValidator.validate(member, "en"); errors = validation.getErrors(); assertTrue(validation.isValid()); assertEquals(0, errors.size()); @@ -97,7 +97,7 @@ public void testValidateWithMissingClientId() { @Test public void testValidateWithMissingClientName() { Member member = getMemberWithMissingClientName(); - MemberValidation validation = memberValidator.validate(member, getUser()); + MemberValidation validation = memberValidator.validate(member, "en"); List errors = validation.getErrors(); assertFalse(validation.isValid()); assertEquals(1, errors.size()); @@ -109,7 +109,7 @@ public void testValidateWithMissingClientName() { @Test public void testValidateWithInvalidClientId() { Member member = getMemberWithInvalidClientId(); - MemberValidation validation = memberValidator.validate(member, getUser()); + MemberValidation validation = memberValidator.validate(member, "en"); List errors = validation.getErrors(); assertFalse(validation.isValid()); assertEquals(1, errors.size()); @@ -121,7 +121,7 @@ public void testValidateWithInvalidClientId() { @Test public void testValidateWithValidOldClientId() { Member member = getMemberWithValidOldClientId(); - MemberValidation validation = memberValidator.validate(member, getUser()); + MemberValidation validation = memberValidator.validate(member, "en"); List errors = validation.getErrors(); assertTrue(validation.isValid()); assertEquals(0, errors.size()); @@ -131,7 +131,7 @@ public void testValidateWithValidOldClientId() { @Test public void testValidateWithValidNewClientId() { Member member = getMemberWithValidNewClientId(); - MemberValidation validation = memberValidator.validate(member, getUser()); + MemberValidation validation = memberValidator.validate(member, "en"); List errors = validation.getErrors(); assertTrue(validation.isValid()); assertEquals(0, errors.size()); @@ -141,7 +141,7 @@ public void testValidateWithValidNewClientId() { @Test public void testValidateWithMissingConsortiumLead() { Member member = getMemberWithMissingConsortiumLead(); - MemberValidation validation = memberValidator.validate(member, getUser()); + MemberValidation validation = memberValidator.validate(member, "en"); List errors = validation.getErrors(); assertFalse(validation.isValid()); assertEquals(1, errors.size()); @@ -153,7 +153,7 @@ public void testValidateWithMissingConsortiumLead() { @Test public void testValidateNonConsortiumLeadWithNoParentSalesforceId() { Member member = getNonConsortiumLeadWithNoParentSalesforceId(); - MemberValidation validation = memberValidator.validate(member, getUser()); + MemberValidation validation = memberValidator.validate(member, "en"); assertTrue(validation.isValid()); } @@ -162,7 +162,7 @@ public void testValidateConsortiumLeadWithParentSalesforceId() { Member member = getConsortiaLeadWithNoClientId(); member.setParentSalesforceId("illegal"); member.setAssertionServiceEnabled(false); - MemberValidation validation = memberValidator.validate(member, getUser()); + MemberValidation validation = memberValidator.validate(member, "en"); assertFalse(validation.isValid()); List errors = validation.getErrors(); assertEquals(1, errors.size()); @@ -172,13 +172,13 @@ public void testValidateConsortiumLeadWithParentSalesforceId() { member.setSalesforceId("legal"); member.setParentSalesforceId("legal"); - validation = memberValidator.validate(member, getUser()); + validation = memberValidator.validate(member, "en"); assertTrue(validation.isValid()); errors = validation.getErrors(); assertEquals(0, errors.size()); member.setParentSalesforceId(null); - validation = memberValidator.validate(member, getUser()); + validation = memberValidator.validate(member, "en"); assertTrue(validation.isValid()); errors = validation.getErrors(); assertEquals(0, errors.size()); @@ -191,7 +191,7 @@ public void testValidateCreateMemberWhereSalesforceIdExists() { Mockito.when(memberRepository.findBySalesforceId(Mockito.anyString())).thenReturn(Optional.of(existingMember)); Member invalidNewMemberWithClashingSalesforceId = getMemberWithValidNewClientId(); - MemberValidation validation = memberValidator.validate(invalidNewMemberWithClashingSalesforceId, getUser()); + MemberValidation validation = memberValidator.validate(invalidNewMemberWithClashingSalesforceId, "en"); List errors = validation.getErrors(); assertFalse(validation.isValid()); @@ -208,7 +208,7 @@ public void testValidateCreateMemberWhereNameExists() { Mockito.when(memberRepository.findByClientName(Mockito.anyString())).thenReturn(Optional.of(existingMember)); Member invalidNewMemberDueToClashingClientName = getMemberWithValidNewClientId(); - MemberValidation validation = memberValidator.validate(invalidNewMemberDueToClashingClientName, getUser()); + MemberValidation validation = memberValidator.validate(invalidNewMemberDueToClashingClientName, "en"); List errors = validation.getErrors(); assertFalse(validation.isValid()); @@ -226,7 +226,7 @@ public void testValidateUpdateMemberWhereSalesforceIdExists() { Member invalidUpdatingMemberWithClashingSalesforceId = getMemberWithValidNewClientId(); invalidUpdatingMemberWithClashingSalesforceId.setId("some-id"); - MemberValidation validation = memberValidator.validate(invalidUpdatingMemberWithClashingSalesforceId, getUser()); + MemberValidation validation = memberValidator.validate(invalidUpdatingMemberWithClashingSalesforceId, "en"); List errors = validation.getErrors(); assertFalse(validation.isValid()); @@ -244,7 +244,7 @@ public void testValidateUpdateMemberWhereNameExists() { Member invalidUpdatingMemberDueToClashingClientName = getMemberWithValidNewClientId(); invalidUpdatingMemberDueToClashingClientName.setId("some-id"); - MemberValidation validation = memberValidator.validate(invalidUpdatingMemberDueToClashingClientName, getUser()); + MemberValidation validation = memberValidator.validate(invalidUpdatingMemberDueToClashingClientName, "en"); List errors = validation.getErrors(); assertFalse(validation.isValid()); @@ -258,7 +258,7 @@ public void testValidateUpdateMemberWhereNameExists() { public void testValidateMemberWithInvalidMemberType() { Member member = getMemberWithValidNewClientId(); member.setType("invalid member type"); - MemberValidation validation = memberValidator.validate(member, getUser()); + MemberValidation validation = memberValidator.validate(member, "en"); List errors = validation.getErrors(); assertFalse(validation.isValid()); @@ -272,37 +272,11 @@ public void testValidateMemberWithInvalidMemberType() { public void testValidateMemberWithValidMemberType() { Member member = getMemberWithValidNewClientId(); member.setType("basic"); - MemberValidation validation = memberValidator.validate(member, getUser()); + MemberValidation validation = memberValidator.validate(member, "en"); assertTrue(validation.isValid()); member.setType("premium"); - validation = memberValidator.validate(member, getUser()); - assertTrue(validation.isValid()); - } - - @Test - public void testValidateMemberWithInvalidMemberStatus() { - Member member = getMemberWithValidNewClientId(); - member.setStatus("invalid member status"); - MemberValidation validation = memberValidator.validate(member, getUser()); - - List errors = validation.getErrors(); - assertFalse(validation.isValid()); - assertEquals(1, errors.size()); - Mockito.verify(messageSource, Mockito.times(1)).getMessage(errorMessagePropertyCaptor.capture(), Mockito.any(), Mockito.any()); - String propertyName = errorMessagePropertyCaptor.getValue(); - assertEquals("member.validation.error.invalidMemberStatus", propertyName); - } - - @Test - public void testValidateMemberWithValidMemberStatus() { - Member member = getMemberWithValidNewClientId(); - member.setStatus("Active"); - MemberValidation validation = memberValidator.validate(member, getUser()); - assertTrue(validation.isValid()); - - member.setStatus("Deactivated"); - validation = memberValidator.validate(member, getUser()); + validation = memberValidator.validate(member, "en"); assertTrue(validation.isValid()); } @@ -356,12 +330,6 @@ private Member getMemberWithMissingConsortiumLead() { return member; } - private User getUser() { - User user = new User(); - user.setLangKey("en"); - return user; - } - private Member getMemberWithMissingAssertionsEnabled() { Member member = getMember(); member.setAssertionServiceEnabled(null); diff --git a/member-service-2/src/test/resources/application.yaml b/member-service-2/src/test/resources/application.yaml index 48122ecb2..1584763a4 100644 --- a/member-service-2/src/test/resources/application.yaml +++ b/member-service-2/src/test/resources/application.yaml @@ -7,6 +7,16 @@ spring: resourceserver: jwt: issuer-uri: http://localhost:9000 + client: + registration: + internal-member-client: + provider: dummy-provider + client-id: test-id + client-secret: test-secret + authorization-grant-type: client_credentials + provider: + dummy-provider: + token-uri: http://localhost:9000/oauth2/token application: encrypt: salt: 5a8b9f1d2c6e7a403b5d8e9c0f4a1b32 diff --git a/member-service-2/src/test/resources/salesforce/members.json b/member-service-2/src/test/resources/salesforce/members.json new file mode 100644 index 000000000..bbd70aca2 --- /dev/null +++ b/member-service-2/src/test/resources/salesforce/members.json @@ -0,0 +1,112 @@ +{ + "totalSize": 5, + "done": true, + "nextRecordsUrl": null, + "records": [ + { + "Id": "0011000001XYZ01", + "Consortia_Member__c": false, + "Consortium_Lead__c": null, + "Name": "Global Research University", + "Public_Display_Name__c": "Global Res Uni", + "Website": "https://www.globalresuni.edu", + "BillingCountry": "US", + "Research_Community__c": "Academic", + "Public_Display_Description__c": "

Leading global institution.

", + "Logo_Description__c": "https://logos.com/gru.png", + "Public_Display_Email__c": "info@globalresuni.edu", + "Last_membership_start_date__c": "2025-01-01", + "Last_membership_end_date__c": "2026-12-31", + "Trademark_License__c": "Approved", + "BillingAddress": { + "city": "Boston", + "country": "US" + }, + "Active_Member__c": true + }, + { + "Id": "0011000002XYZ02", + "Consortia_Member__c": true, + "Consortium_Lead__c": "0011000001XYZ01", + "Name": "Consortium Sub-Member A", + "Public_Display_Name__c": "Sub-Member A", + "Website": "https://sub-a.org", + "BillingCountry": "UK", + "Research_Community__c": "Healthcare", + "Public_Display_Description__c": null, + "Logo_Description__c": null, + "Public_Display_Email__c": "contact@sub-a.org", + "Last_membership_start_date__c": "2024-06-15", + "Last_membership_end_date__c": "2026-06-14", + "Trademark_License__c": "Pending", + "BillingAddress": { + "city": "London", + "country": "UK" + }, + "Active_Member__c": true + }, + { + "Id": "0011000003XYZ03", + "Consortia_Member__c": true, + "Consortium_Lead__c": "0011000001XYZ01", + "Name": "Consortium Sub-Member B", + "Public_Display_Name__c": "Sub-Member B", + "Website": "https://sub-b.org", + "BillingCountry": "FR", + "Research_Community__c": "Government", + "Public_Display_Description__c": "

National laboratory.

", + "Logo_Description__c": "https://logos.com/sub-b.png", + "Public_Display_Email__c": null, + "Last_membership_start_date__c": "2025-03-01", + "Last_membership_end_date__c": "2027-02-28", + "Trademark_License__c": "Approved", + "BillingAddress": { + "city": "Paris", + "country": "FR" + }, + "Active_Member__c": true + }, + { + "Id": "0011000004XYZ04", + "Consortia_Member__c": false, + "Consortium_Lead__c": null, + "Name": "Legacy Research Lab", + "Public_Display_Name__c": "Legacy Lab", + "Website": "https://legacylab.com", + "BillingCountry": "CA", + "Research_Community__c": "Corporate", + "Public_Display_Description__c": null, + "Logo_Description__c": null, + "Public_Display_Email__c": "admin@legacylab.com", + "Last_membership_start_date__c": "2020-01-01", + "Last_membership_end_date__c": "2023-12-31", + "Trademark_License__c": "Expired", + "BillingAddress": { + "city": "Toronto", + "country": "CA" + }, + "Active_Member__c": false + }, + { + "Id": "0011000005XYZ05", + "Consortia_Member__c": false, + "Consortium_Lead__c": null, + "Name": "New Horizon Publisher", + "Public_Display_Name__c": "New Horizon", + "Website": "https://newhorizonpub.com", + "BillingCountry": "AU", + "Research_Community__c": "Publisher", + "Public_Display_Description__c": "

Open access publisher.

", + "Logo_Description__c": null, + "Public_Display_Email__c": "oa@newhorizonpub.com", + "Last_membership_start_date__c": "2026-01-01", + "Last_membership_end_date__c": "2026-12-31", + "Trademark_License__c": "Approved", + "BillingAddress": { + "city": "Sydney", + "country": "AU" + }, + "Active_Member__c": true + } + ] +} \ No newline at end of file diff --git a/ui-2/src/app/member/member-detail.component.html b/ui-2/src/app/member/member-detail.component.html index 8b2fe45f6..710729945 100644 --- a/ui-2/src/app/member/member-detail.component.html +++ b/ui-2/src/app/member/member-detail.component.html @@ -59,7 +59,7 @@

M
Status
- {{ member()?.status }} + {{ member()?.active ? 'Active' : 'Deactivated' }}
Created Date
diff --git a/ui-2/src/app/member/model/member.model.ts b/ui-2/src/app/member/model/member.model.ts index a330f5c60..98c643280 100644 --- a/ui-2/src/app/member/model/member.model.ts +++ b/ui-2/src/app/member/model/member.model.ts @@ -15,7 +15,7 @@ export interface IMember { lastModifiedBy?: string lastModifiedDate?: Moment | undefined | null type?: string - status?: string + active?: boolean defaultLanguage?: string } @@ -35,8 +35,8 @@ export class Member implements IMember { public lastModifiedBy?: string, public lastModifiedDate?: Moment | undefined | null, public type?: string, - public status?: string, - public defaultLaungage?: string + public active?: boolean, + public defaultLanguage?: string ) { this.isConsortiumLead = this.isConsortiumLead || false this.superadminEnabled = this.superadminEnabled || false diff --git a/user-service-2/src/main/java/org/orcid/mp/user/config/SecurityConfig.java b/user-service-2/src/main/java/org/orcid/mp/user/config/SecurityConfig.java index e5f019a13..f02fad9ea 100644 --- a/user-service-2/src/main/java/org/orcid/mp/user/config/SecurityConfig.java +++ b/user-service-2/src/main/java/org/orcid/mp/user/config/SecurityConfig.java @@ -75,6 +75,12 @@ public class SecurityConfig { @Value("${application.internal.assertionServiceClientSecret}") private String internalAssertionServiceClientSecret; + @Value("${application.internal.memberServiceClientId}") + private String internalMemberServiceClientId; + + @Value("${application.internal.memberServiceClientSecret}") + private String internalMemberServiceClientSecret; + @Value("${application.security.jwt.private-key}") private RSAPrivateKey privateKey; @@ -210,7 +216,18 @@ public RegisteredClientRepository registeredClientRepository(PasswordEncoder enc .build()) .build(); - return new InMemoryRegisteredClientRepository(oidcClient, userServiceClient, assertionServiceClient); + RegisteredClient memberServiceClient = RegisteredClient.withId(UUID.randomUUID().toString()) + .clientId(internalMemberServiceClientId) + .clientSecret(encoder.encode(internalMemberServiceClientSecret)) + .clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC) + .authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS) + .scope("internal") + .tokenSettings(TokenSettings.builder() + .accessTokenTimeToLive(Duration.ofMinutes(5)) // Keep internal tokens short-lived + .build()) + .build(); + + return new InMemoryRegisteredClientRepository(oidcClient, userServiceClient, assertionServiceClient, memberServiceClient); } @Bean diff --git a/user-service-2/src/main/java/org/orcid/mp/user/rest/InternalResource.java b/user-service-2/src/main/java/org/orcid/mp/user/rest/InternalResource.java index 03d5b3d41..07256ad3b 100644 --- a/user-service-2/src/main/java/org/orcid/mp/user/rest/InternalResource.java +++ b/user-service-2/src/main/java/org/orcid/mp/user/rest/InternalResource.java @@ -8,10 +8,7 @@ import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.PathVariable; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.bind.annotation.*; import java.util.Optional; @@ -37,8 +34,8 @@ public class InternalResource { * body the user, or with status {@code 404 (Not Found)}. */ @GetMapping("/{loginOrId}") - public ResponseEntity getUserByLogin(@PathVariable String loginOrId) { - LOG.debug("REST request to get User : {}", loginOrId); + public ResponseEntity getUserByLoginOrId(@PathVariable String loginOrId) { + LOG.debug("Internal request to get User : {}", loginOrId); Optional user = userService.getUserByLogin(loginOrId); if (!user.isPresent()) { user = userService.getUser(loginOrId); @@ -49,4 +46,23 @@ public ResponseEntity getUserByLogin(@PathVariable String loginOrId) { return ResponseEntity.ok(userMapper.toUserDTO(user.get())); } + /** + * {@code PUT /users/memberName/:oldMemberName/:newMemberName} : Updates memberName + * for existing Users. + * + * @param memberId the memberId for finding users to update + * @param newMemberName the new Value of the memberName to update + * @return the {@link ResponseEntity} with status {@code 200 (OK)}. + */ + @PutMapping("/memberName/{memberId}/{newMemberName}") + public ResponseEntity updateUsersMemberName(@PathVariable String memberId, @PathVariable String newMemberName) { + LOG.info("Internal request to update users' member names id to {}", newMemberName); + boolean success = userService.updateUsersMemberName(memberId, newMemberName); + if (success) { + return ResponseEntity.ok().build(); + } else { + return ResponseEntity.status(500).build(); + } + } + } diff --git a/user-service-2/src/main/java/org/orcid/mp/user/rest/UserResource.java b/user-service-2/src/main/java/org/orcid/mp/user/rest/UserResource.java index 6f502db98..3f469a5ee 100644 --- a/user-service-2/src/main/java/org/orcid/mp/user/rest/UserResource.java +++ b/user-service-2/src/main/java/org/orcid/mp/user/rest/UserResource.java @@ -313,26 +313,6 @@ public ResponseEntity resendActivation(@PathVariable S return ResponseEntity.ok(response); } - /** - * {@code PUT /users/memberName/:oldMemberName/:newMemberName} : Updates memberName - * for existing Users. - * - * @param memberId the memberId for finding users to update - * @param newMemberName the new Value of the memberName to update - * @return the {@link ResponseEntity} with status {@code 200 (OK)}. - */ - @PutMapping("/memberName/{memberId}/{newMemberName}") - @PreAuthorize("hasRole(\"" + AuthoritiesConstants.ADMIN + "\")") - public ResponseEntity updateUsersMemberName(@PathVariable String memberId, @PathVariable String newMemberName) { - LOG.debug("REST request to update users' member names id to {}", newMemberName); - boolean success = userService.updateUsersMemberName(memberId, newMemberName); - if (success) { - return ResponseEntity.ok().headers(JHipsterAlerts.createEntityUpdateAlert(applicationName, true, "user", memberId)).build(); - } else { - return ResponseEntity.status(500).build(); - } - } - /** * {@code GET /users/:saleforceId}/owner : get the "login" user. * diff --git a/user-service-2/src/main/resources/application.yml b/user-service-2/src/main/resources/application.yml index 5afdf12b7..8393760de 100644 --- a/user-service-2/src/main/resources/application.yml +++ b/user-service-2/src/main/resources/application.yml @@ -92,6 +92,8 @@ application: clientSecret: ${APPLICATION_INTERNAL_CLIENT_SECRET} assertionServiceClientId: ${APPLICATION_INTERNAL_ASSERTION_SERVICE_ID} assertionServiceClientSecret: ${APPLICATION_INTERNAL_ASSERTION_SERVICE_CLIENT_SECRET} + memberServiceClientId: ${APPLICATION_INTERNAL_MEMBER_SERVICE_ID} + memberServiceClientSecret: ${APPLICATION_INTERNAL_MEMBER_SERVICE_CLIENT_SECRET} security: issuerUrl: ${APPLICATION_SECURITY_ISSUER_URL} jwt: diff --git a/user-service-2/src/test/java/org/orcid/mp/user/rest/InternalResourceTest.java b/user-service-2/src/test/java/org/orcid/mp/user/rest/InternalResourceTest.java new file mode 100644 index 000000000..c2815b139 --- /dev/null +++ b/user-service-2/src/test/java/org/orcid/mp/user/rest/InternalResourceTest.java @@ -0,0 +1,100 @@ +package org.orcid.mp.user.rest; + + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.MockitoAnnotations; +import org.orcid.mp.user.domain.User; +import org.orcid.mp.user.dto.UserDTO; +import org.orcid.mp.user.mapper.UserMapper; +import org.orcid.mp.user.service.UserService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.mock.web.MockHttpServletRequest; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContext; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.web.context.request.RequestContextHolder; +import org.springframework.web.context.request.ServletRequestAttributes; + +import java.util.Optional; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.Mockito.when; + +public class InternalResourceTest { + + @Mock + private UserService userService; + + @Mock + private UserMapper userMapper; + + @InjectMocks + private InternalResource internalResource; + + @BeforeEach + public void setUp() { + MockitoAnnotations.initMocks(this); + + Authentication authentication = Mockito.mock(Authentication.class); + SecurityContext securityContext = Mockito.mock(SecurityContext.class); + when(securityContext.getAuthentication()).thenReturn(authentication); + SecurityContextHolder.setContext(securityContext); + when(authentication.getPrincipal()).thenReturn("some@email.com"); + + + MockHttpServletRequest request = new MockHttpServletRequest(); + RequestContextHolder.setRequestAttributes(new ServletRequestAttributes(request)); + } + + @Test + void testGetUserByLoginOrId_login() { + when(userService.getUserByLogin(Mockito.eq("some@email.com"))).thenReturn(getUser()); + when(userMapper.toUserDTO(Mockito.any())).thenReturn(new UserDTO()); + + ResponseEntity response = internalResource.getUserByLoginOrId("some@email.com"); + assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK); + } + + + @Test + void testGetUserByLoginOrId_notFound() { + when(userService.getUserByLogin(Mockito.eq("some@email.com"))).thenReturn(Optional.empty()); + when(userService.getUser(Mockito.eq("some@email.com"))).thenReturn(Optional.empty()); + + ResponseEntity response = internalResource.getUserByLoginOrId("some@email.com"); + assertThat(response.getStatusCode()).isEqualTo(HttpStatus.NOT_FOUND); + } + + @Test + void testGetUserByLoginOrId_id() { + when(userService.getUserByLogin(Mockito.eq("some@email.com"))).thenReturn(Optional.empty()); + when(userService.getUser(Mockito.eq("some@email.com"))).thenReturn(getUser()); + when(userMapper.toUserDTO(Mockito.any())).thenReturn(new UserDTO()); + + ResponseEntity response = internalResource.getUserByLoginOrId("some@email.com"); + assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK); + } + + @Test + void testUpdateUsersMemberName() { + when(userService.updateUsersMemberName(Mockito.eq("member-id"), Mockito.eq("newName"))).thenReturn(true); + ResponseEntity response = internalResource.updateUsersMemberName("member-id", "newName"); + assertTrue(response.getStatusCode().is2xxSuccessful()); + } + + private Optional getUser() { + User user = new User(); + user.setEmail("some@email.com"); + user.setLangKey("en"); + user.setMemberId("some-memberId"); + user.setMainContact(true); + return Optional.of(user); + } +} diff --git a/user-service-2/src/test/java/org/orcid/mp/user/rest/UserResourceTest.java b/user-service-2/src/test/java/org/orcid/mp/user/rest/UserResourceTest.java index 30beaa0e9..66f5196bd 100644 --- a/user-service-2/src/test/java/org/orcid/mp/user/rest/UserResourceTest.java +++ b/user-service-2/src/test/java/org/orcid/mp/user/rest/UserResourceTest.java @@ -43,9 +43,6 @@ public class UserResourceTest { @Mock private UserValidator userValidator; - @Mock - private UserMapper userMapper; - @InjectMocks private UserResource userResource; @@ -64,13 +61,6 @@ public void setUp() { RequestContextHolder.setRequestAttributes(new ServletRequestAttributes(request)); } - @Test - void testUpdateUsersMemberName() { - Mockito.when(userService.updateUsersMemberName(Mockito.eq("member-id"), Mockito.eq("newName"))).thenReturn(true); - ResponseEntity response = userResource.updateUsersMemberName("member-id", "newName"); - assertTrue(response.getStatusCode().is2xxSuccessful()); - } - @Test public void testResendActivation() { Mockito.doNothing().when(userService).resendActivationEmail(Mockito.anyString()); diff --git a/user-service-2/src/test/resources/application.yml b/user-service-2/src/test/resources/application.yml index cab884918..2ff9581ee 100644 --- a/user-service-2/src/test/resources/application.yml +++ b/user-service-2/src/test/resources/application.yml @@ -26,6 +26,8 @@ application: clientSecret: userserviceSecret assertionServiceClientId: assertionServiceClientId assertionServiceClientSecret: assertionServiceClientSecret + memberServiceClientId: memberServiceClientId + memberServiceClientSecret: memberServiceClientSecret encrypt: salt: 5a8b9f1d2c6e7a403b5d8e9c0f4a1b32 key: user-service