/* * Copyright (C) 2007-2014 Crafter Software Corporation. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package org.craftercms.profile.services.impl; import java.util.Arrays; import java.util.Collections; import java.util.Date; import java.util.HashSet; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Set; import java.util.UUID; import org.bson.types.ObjectId; import org.craftercms.commons.crypto.CipherUtils; import org.craftercms.commons.security.exception.ActionDeniedException; import org.craftercms.commons.security.permissions.PermissionEvaluator; import org.craftercms.profile.api.AccessToken; import org.craftercms.profile.api.AttributeDefinition; import org.craftercms.profile.api.AttributePermission; import org.craftercms.profile.api.Profile; import org.craftercms.profile.api.SortOrder; import org.craftercms.profile.api.Tenant; import org.craftercms.profile.api.TenantAction; import org.craftercms.profile.api.Ticket; import org.craftercms.profile.api.VerificationToken; import org.craftercms.profile.api.services.AuthenticationService; import org.craftercms.profile.api.services.TenantService; import org.craftercms.profile.exceptions.InvalidEmailAddressException; import org.craftercms.profile.exceptions.InvalidQueryException; import org.craftercms.profile.repositories.ProfileRepository; import org.craftercms.profile.services.VerificationService; import org.junit.Before; import org.junit.Test; import org.mockito.ArgumentMatcher; import org.mockito.Mock; import org.mockito.MockitoAnnotations; import org.mockito.invocation.InvocationOnMock; import org.mockito.stubbing.Answer; import static org.craftercms.profile.api.ProfileConstants.NO_ATTRIBUTE; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import static org.mockito.Matchers.argThat; import static org.mockito.Mockito.any; import static org.mockito.Mockito.anyString; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.eq; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; /** * Unit test for {@link org.craftercms.profile.services.impl.ProfileServiceImpl}. * * @author avasquez */ public class ProfileServiceImplTest { private static final String ATTRIB_NAME_FIRST_NAME = "firstName"; private static final String ATTRIB_NAME_LAST_NAME = "lastName"; private static final String ATTRIB_NAME_GENDER = "gender"; private static final String ATTRIB_NAME_PRIVATE = "private"; private static final String TENANT1_NAME = "tenant1"; private static final String TENANT2_NAME = "tenant2"; private static final ObjectId PROFILE1_ID = new ObjectId(); private static final ObjectId PROFILE2_ID = new ObjectId(); private static final List<String> TENANT1_PROFILE_IDS = Arrays.asList(PROFILE1_ID.toString()); private static final String USERNAME1 = "user1"; private static final String USERNAME2 = "user2"; private static final String PASSWORD1 = "12345"; private static final String PASSWORD2 = "54321"; private static final String EMAIL1 = "user1@craftersoftware.com"; private static final String EMAIL2 = "user2@craftersoftware.com"; private static final String ROLE1 = "role1"; private static final String ROLE2 = "role2"; private static final Set<String> ROLES1 = new HashSet<>(Arrays.asList(ROLE1)); private static final Set<String> ROLES2 = new HashSet<>(Arrays.asList(ROLE2)); private static final String FIRST_NAME = "John"; private static final String LAST_NAME = "Doe"; private static final String GENDER = "male"; private static final String QUERY = "{attributes.firstName: 'John'}"; private static final String INVALID_QUERY1 = "{tenant: 'tenant1'}"; private static final String INVALID_QUERY2 = "{$where: \"this.tenant == 'tenant1'\"}"; private static final String INVALID_QUERY3 = "{attributes.private.sub: 'test'}"; private static final String VERIFICATION_URL = "http://localhost:8080/verifyProfile"; private static final String VERIFICATION_FROM_ADDRESS = "noreply@craftersoftware.com"; private static final String VERIFICATION_SUBJECT = "Verify Account"; private static final String VERIFICATION_TEMPLATE_NAME = "verify-new-profile-email.ftl"; private static final String TICKET_ID = UUID.randomUUID().toString(); private static final String SORT_BY = "username"; private static final int START = 0; private static final int COUNT = 10; private static final String RESET_PASSWORD_URL = "http://localhost:8080/resetPassword"; private static final String RESET_PASSWORD_FROM_ADDRESS = "noreply@craftersoftware.com"; private static final String RESET_PASSWORD_SUBJECT = "Reset Password"; private static final String RESET_PASSWORD_TEMPLATE_NAME = "reset-password-email.ftl"; private static final String VERIFICATION_TOKEN_ID1 = UUID.randomUUID().toString(); private static final String VERIFICATION_TOKEN_ID2 = UUID.randomUUID().toString(); private ProfileServiceImpl profileService; @Mock private PermissionEvaluator<AccessToken, String> tenantPermissionEvaluator; @Mock private PermissionEvaluator<AccessToken, AttributeDefinition> attributePermissionEvaluator; @Mock private ProfileRepository profileRepository; @Mock private TenantService tenantService; @Mock private AuthenticationService authenticationService; @Mock private VerificationService verificationService; @Before public void setUp() throws Exception { MockitoAnnotations.initMocks(this); when(tenantPermissionEvaluator.isAllowed(anyString(), anyString())) .thenReturn(true); when(attributePermissionEvaluator.isAllowed(any(AttributeDefinition.class), anyString())) .thenReturn(true); when(attributePermissionEvaluator.isAllowed(eq(new AttributeDefinition(ATTRIB_NAME_PRIVATE)), anyString())) .thenReturn(false); when(tenantService.getTenant(TENANT1_NAME)).thenReturn(getTenant1()); when(tenantService.getTenant(TENANT2_NAME)).thenReturn(getTenant2()); when(authenticationService.getTicket(TICKET_ID)).thenReturn(getTicket()); doAnswer(new Answer() { @Override public Object answer(final InvocationOnMock invocation) throws Throwable { Profile profile = (Profile)invocation.getArguments()[0]; profile.setId(new ObjectId()); return null; } }).when(profileRepository).insert(any(Profile.class)); when(profileRepository.findOneByQuery(String.format(ProfileServiceImpl.QUERY_FINAL_FORMAT, TENANT1_NAME, QUERY), new String[0])) .thenReturn(getTenant1Profile()); when(profileRepository.findById(PROFILE1_ID.toString(), new String[0])) .thenReturn(getTenant1Profile()); when(profileRepository.findById(PROFILE1_ID.toString(), NO_ATTRIBUTE)) .thenReturn(getTenant1ProfileNoAttributes()); when(profileRepository.findById(PROFILE1_ID.toString(), ATTRIB_NAME_FIRST_NAME)) .thenReturn(getTenant1ProfileNoLastName()); when(profileRepository.findById(PROFILE2_ID.toString(), new String[0])) .thenReturn(getTenant2Profile()); when(profileRepository.findByQuery(String.format(ProfileServiceImpl.QUERY_FINAL_FORMAT, TENANT1_NAME, QUERY), SORT_BY, SortOrder.ASC, START, COUNT, new String[0])) .thenReturn(getAllTenant1Profiles()); when(profileRepository.findByTenantAndUsername(TENANT1_NAME, USERNAME1, new String[0])) .thenReturn(getTenant1Profile()); when(profileRepository.findByIds(TENANT1_PROFILE_IDS, SORT_BY, SortOrder.ASC)) .thenReturn(getAllTenant1Profiles()); when(profileRepository.findRange(TENANT1_NAME, SORT_BY, SortOrder.ASC, START, COUNT)) .thenReturn(getAllTenant1Profiles()); when(profileRepository.findByTenantAndRole(TENANT1_NAME, ROLE1, SORT_BY, SortOrder.ASC)) .thenReturn(getAllTenant1Profiles()); when(profileRepository.findByTenantAndAttributeValue(TENANT1_NAME, ATTRIB_NAME_FIRST_NAME, FIRST_NAME, SORT_BY, SortOrder.ASC)) .thenReturn(getAllTenant1Profiles()); when(profileRepository.countByTenant(TENANT1_NAME)) .thenReturn(10L); when(profileRepository.count(String.format(ProfileServiceImpl.QUERY_FINAL_FORMAT, TENANT1_NAME, QUERY))) .thenReturn(1L); when(verificationService.createToken(any(Profile.class))).then(new Answer<VerificationToken>() { @Override public VerificationToken answer(final InvocationOnMock invocation) throws Throwable { Profile profile = (Profile)invocation.getArguments()[0]; VerificationToken token = new VerificationToken(); token.setId(VERIFICATION_TOKEN_ID1); token.setTenant(profile.getTenant()); token.setProfileId(profile.getId().toString()); token.setTimestamp(new Date()); return token; } }); VerificationToken token1 = new VerificationToken(); token1.setId(VERIFICATION_TOKEN_ID1); token1.setTenant(TENANT1_NAME); token1.setProfileId(PROFILE1_ID.toString()); token1.setTimestamp(new Date()); VerificationToken token2 = new VerificationToken(); token2.setId(VERIFICATION_TOKEN_ID2); token2.setTenant(TENANT2_NAME); token2.setProfileId(PROFILE2_ID.toString()); token2.setTimestamp(new Date()); when(verificationService.getToken(VERIFICATION_TOKEN_ID1)).thenReturn(token1); when(verificationService.getToken(VERIFICATION_TOKEN_ID2)).thenReturn(token2); profileService = new ProfileServiceImpl(); profileService.setTenantPermissionEvaluator(tenantPermissionEvaluator); profileService.setAttributePermissionEvaluator(attributePermissionEvaluator); profileService.setProfileRepository(profileRepository); profileService.setTenantService(tenantService); profileService.setVerificationService(verificationService); profileService.setNewProfileEmailFromAddress(VERIFICATION_FROM_ADDRESS); profileService.setNewProfileEmailSubject(VERIFICATION_SUBJECT); profileService.setNewProfileEmailTemplateName(VERIFICATION_TEMPLATE_NAME); profileService.setResetPwdEmailFromAddress(RESET_PASSWORD_FROM_ADDRESS); profileService.setResetPwdEmailSubject(RESET_PASSWORD_SUBJECT); profileService.setResetPwdEmailTemplateName(RESET_PASSWORD_TEMPLATE_NAME); } @Test public void testCreateProfile() throws Exception { Profile expected = getTenant2Profile(); expected.setTenant(TENANT1_NAME); expected.setAttributes(getAttributesWithoutPrivateAttribute()); expected.setAttribute(ATTRIB_NAME_GENDER, GENDER); Profile actual = profileService.createProfile(TENANT1_NAME, USERNAME2, PASSWORD2, EMAIL2, true, ROLES2, getAttributesWithoutPrivateAttribute(), VERIFICATION_URL); assertEqualProfiles(expected, actual); assertTrue(CipherUtils.matchPassword(actual.getPassword(), PASSWORD2)); assertNotNull(actual.getCreatedOn()); assertNotNull(actual.getLastModified()); VerificationToken token = new VerificationToken(); token.setId(VERIFICATION_TOKEN_ID1); verify(tenantPermissionEvaluator).isAllowed(TENANT1_NAME, TenantAction.MANAGE_PROFILES.toString()); verify(tenantService).getTenant(TENANT1_NAME); verify(profileRepository).insert(actual); verify(verificationService).createToken(actual); verify(verificationService).sendEmail(token, actual, VERIFICATION_URL, VERIFICATION_FROM_ADDRESS, VERIFICATION_SUBJECT, VERIFICATION_TEMPLATE_NAME); } @Test public void testCreateProfileWithUnwritableAttribute() throws Exception { try { profileService.createProfile(TENANT1_NAME, USERNAME2, PASSWORD2, EMAIL2, true, ROLES2, getAttributes(), VERIFICATION_URL); fail("Exception " + ActionDeniedException.class.getName() + " expected"); } catch (ActionDeniedException e) { } } @Test public void testCreateProfileInvalidEmail() throws Exception { try { profileService.createProfile(TENANT1_NAME, USERNAME2, PASSWORD2, "a.com", true, ROLES2, null, VERIFICATION_URL); fail("Exception " + InvalidEmailAddressException.class.getName() + " expected"); } catch (InvalidEmailAddressException e) { } } @Test public void testCreateProfileNotVerify() throws Exception { Profile expected = getTenant2Profile(); expected.setEnabled(true); expected.setAttributes(getAttributesWithoutPrivateAttribute()); expected.setAttribute(ATTRIB_NAME_GENDER, GENDER); Profile actual = profileService.createProfile(TENANT2_NAME, USERNAME2, PASSWORD2, EMAIL2, true, ROLES2, getAttributesWithoutPrivateAttribute(), VERIFICATION_URL); assertEqualProfiles(expected, actual); assertTrue(CipherUtils.matchPassword(actual.getPassword(), PASSWORD2)); assertNotNull(actual.getCreatedOn()); assertNotNull(actual.getLastModified()); verify(tenantPermissionEvaluator).isAllowed(TENANT2_NAME, TenantAction.MANAGE_PROFILES.toString()); verify(tenantService).getTenant(TENANT2_NAME); verify(profileRepository).insert(actual); verify(verificationService, never()).createToken(any(Profile.class)); verify(verificationService, never()).sendEmail(any(VerificationToken.class), any(Profile.class), anyString(), anyString(), anyString(), anyString()); } @Test public void testUpdateProfile() throws Exception { final Profile expected = new Profile(); expected.setId(PROFILE1_ID); expected.setTenant(TENANT1_NAME); expected.setUsername(USERNAME2); expected.setPassword(CipherUtils.hashPassword(PASSWORD2)); expected.setEmail(EMAIL2); expected.setRoles(ROLES2); expected.setVerified(true); expected.setEnabled(false); expected.setAttributes(getAttributesWithoutPrivateAttribute()); expected.getAttributes().put(ATTRIB_NAME_GENDER, GENDER); final Map<String, Object> newAttributes = Collections.<String, Object>singletonMap(ATTRIB_NAME_GENDER, GENDER); Profile actual = profileService.updateProfile(PROFILE1_ID.toString(), USERNAME2, PASSWORD2, EMAIL2, false, ROLES2, newAttributes); assertEqualProfiles(expected, actual); ArgumentMatcher<Object> setParamMatcher = new ArgumentMatcher<Object>() { @Override public boolean matches(Object argument) { Map<String, Object> param = (Map<String, Object>)argument; return param.size() == 7 && param.get("username").equals(USERNAME2) && param.containsKey("password") && param.get("email").equals(EMAIL2) && param.get("roles").equals(ROLES2) && param.get("enabled").equals(false) && param.containsKey("lastModified") && param.get("attributes." + ATTRIB_NAME_GENDER).equals(GENDER); } }; verify(tenantPermissionEvaluator).isAllowed(TENANT1_NAME, TenantAction.MANAGE_PROFILES.toString()); verify(profileRepository).findById(PROFILE1_ID.toString(), new String[0]); verify(profileRepository).update(eq(PROFILE1_ID.toString()), eq("{$set: #}"), eq(false), eq(false), argThat(setParamMatcher)); } @Test public void testUpdateProfileWithUnwritableAttribute() throws Exception { try { profileService.updateProfile(PROFILE1_ID.toString(), USERNAME2, PASSWORD2, EMAIL2, false, ROLES2, Collections.<String, Object>singletonMap(ATTRIB_NAME_PRIVATE, 0)); fail("Exception " + ActionDeniedException.class.getName() + " expected"); } catch (ActionDeniedException e) { } } @Test public void testUpdateProfileInvalidEmail() throws Exception { try { profileService.updateProfile(PROFILE1_ID.toString(), USERNAME2, PASSWORD2, "a.com", false, ROLES2, null); fail("Exception " + InvalidEmailAddressException.class.getName() + " expected"); } catch (InvalidEmailAddressException e) { } } @Test public void testVerifyProfile() throws Exception { Profile expected = getTenant2Profile(); expected.setVerified(true); expected.setEnabled(true); expected.setAttributes(getAttributesWithoutPrivateAttribute()); Profile actual = profileService.verifyProfile(VERIFICATION_TOKEN_ID2); assertEqualProfiles(expected, actual); ArgumentMatcher<Object> setParamMatcher = new ArgumentMatcher<Object>() { @Override public boolean matches(Object argument) { Map<String, Object> param = (Map<String, Object>)argument; return param.size() == 3 && param.get("verified").equals(true) && param.get("enabled").equals(true) && param.containsKey("lastModified"); } }; verify(tenantPermissionEvaluator).isAllowed(TENANT2_NAME, TenantAction.MANAGE_PROFILES.toString()); verify(profileRepository).findById(PROFILE2_ID.toString(), new String[0]); verify(profileRepository).update(eq(PROFILE2_ID.toString()), eq("{$set: #}"), eq(false), eq(false), argThat(setParamMatcher)); verify(verificationService).getToken(VERIFICATION_TOKEN_ID2); verify(verificationService).deleteToken(VERIFICATION_TOKEN_ID2); } @Test public void testEnableProfile() throws Exception { Profile expected = getTenant2Profile(); expected.setEnabled(true); expected.setAttributes(getAttributesWithoutPrivateAttribute()); Profile actual = profileService.enableProfile(PROFILE2_ID.toString()); assertEqualProfiles(expected, actual); ArgumentMatcher<Object> setParamMatcher = new ArgumentMatcher<Object>() { @Override public boolean matches(Object argument) { Map<String, Object> param = (Map<String, Object>)argument; return param.size() == 2 && param.get("enabled").equals(true) && param.containsKey("lastModified"); } }; verify(tenantPermissionEvaluator).isAllowed(TENANT2_NAME, TenantAction.MANAGE_PROFILES.toString()); verify(profileRepository).findById(PROFILE2_ID.toString(), new String[0]); verify(profileRepository).update(eq(PROFILE2_ID.toString()), eq("{$set: #}"), eq(false), eq(false), argThat(setParamMatcher)); } @Test public void testDisableProfile() throws Exception { Profile expected = getTenant1Profile(); expected.setEnabled(false); expected.setAttributes(getAttributesWithoutPrivateAttribute()); Profile actual = profileService.disableProfile(PROFILE1_ID.toString()); assertEqualProfiles(expected, actual); ArgumentMatcher<Object> setParamMatcher = new ArgumentMatcher<Object>() { @Override public boolean matches(Object argument) { Map<String, Object> param = (Map<String, Object>)argument; return param.size() == 2 && param.get("enabled").equals(false) && param.containsKey("lastModified"); } }; verify(tenantPermissionEvaluator).isAllowed(TENANT1_NAME, TenantAction.MANAGE_PROFILES.toString()); verify(profileRepository).findById(PROFILE1_ID.toString(), new String[0]); verify(profileRepository).update(eq(PROFILE1_ID.toString()), eq("{$set: #}"), eq(false), eq(false), argThat(setParamMatcher)); } @Test public void testAddRoles() throws Exception { Profile expected = getTenant1Profile(); expected.getRoles().add(ROLE2); expected.setAttributes(getAttributesWithoutPrivateAttribute()); Profile actual = profileService.addRoles(PROFILE1_ID.toString(), Collections.singletonList(ROLE2)); assertEqualProfiles(expected, actual); ArgumentMatcher<Object> setParamMatcher = new ArgumentMatcher<Object>() { @Override public boolean matches(Object argument) { Map<String, Object> param = (Map<String, Object>)argument; return param.size() == 1 && param.containsKey("lastModified"); } }; ArgumentMatcher<Object> pushParamMatcher = new ArgumentMatcher<Object>() { @Override public boolean matches(Object argument) { Map<String, Object> param = (Map<String, Object>)argument; return param.size() == 1 && param.get("roles").equals(Collections.singletonMap("$each", Collections.singletonList(ROLE2))); } }; verify(tenantPermissionEvaluator).isAllowed(TENANT1_NAME, TenantAction.MANAGE_PROFILES.toString()); verify(profileRepository).findById(PROFILE1_ID.toString(), new String[0]); verify(profileRepository).update(eq(PROFILE1_ID.toString()), eq("{$set: #, $push: #}"), eq(false), eq(false), argThat(setParamMatcher), argThat(pushParamMatcher)); } @Test public void testRemoveRoles() throws Exception { Profile expected = getTenant1Profile(); expected.getRoles().remove(ROLE1); expected.setAttributes(getAttributesWithoutPrivateAttribute()); Profile actual = profileService.removeRoles(PROFILE1_ID.toString(), Collections.singletonList(ROLE1)); assertEqualProfiles(expected, actual); ArgumentMatcher<Object> setParamMatcher = new ArgumentMatcher<Object>() { @Override public boolean matches(Object argument) { Map<String, Object> param = (Map<String, Object>)argument; return param.size() == 1 && param.containsKey("lastModified"); } }; ArgumentMatcher<Object> pullParamMatcher = new ArgumentMatcher<Object>() { @Override public boolean matches(Object argument) { Map<String, Object> param = (Map<String, Object>)argument; return param.size() == 1 && param.get("roles").equals(Collections.singletonMap("$in", Collections.singletonList(ROLE1))); } }; verify(tenantPermissionEvaluator).isAllowed(TENANT1_NAME, TenantAction.MANAGE_PROFILES.toString()); verify(profileRepository).findById(PROFILE1_ID.toString(), new String[0]); verify(profileRepository).update(eq(PROFILE1_ID.toString()), eq("{$set: #, $pull: #}"), eq(false), eq(false), argThat(setParamMatcher), argThat(pullParamMatcher)); } @Test public void testGetAllAttributes() throws Exception { Map<String, Object> attributes = profileService.getAttributes(PROFILE1_ID.toString()); assertNotNull(attributes); assertEquals(getAttributesWithoutPrivateAttribute(), attributes); verify(tenantPermissionEvaluator).isAllowed(TENANT1_NAME, TenantAction.MANAGE_PROFILES.toString()); verify(profileRepository).findById(PROFILE1_ID.toString(), new String[0]); } @Test public void testGetOneAttribute() throws Exception { Map<String, Object> attributes = profileService.getAttributes(PROFILE1_ID.toString(), ATTRIB_NAME_FIRST_NAME); assertNotNull(attributes); assertEquals(1, attributes.size()); assertEquals(FIRST_NAME, attributes.get(ATTRIB_NAME_FIRST_NAME)); verify(tenantPermissionEvaluator).isAllowed(TENANT1_NAME, TenantAction.MANAGE_PROFILES.toString()); verify(profileRepository).findById(PROFILE1_ID.toString(), ATTRIB_NAME_FIRST_NAME); } @Test public void testGetNoAttributes() throws Exception { Map<String, Object> attributes = profileService.getAttributes(PROFILE1_ID.toString(), NO_ATTRIBUTE); assertNotNull(attributes); assertEquals(0, attributes.size()); } @Test public void testUpdateAttributes() throws Exception { Profile expected = getTenant1Profile(); expected.setAttributes(getAttributesWithoutPrivateAttribute()); expected.getAttributes().put(ATTRIB_NAME_GENDER, GENDER); Map<String, Object> newAttributes = Collections.<String, Object>singletonMap(ATTRIB_NAME_GENDER, GENDER); Profile actual = profileService.updateAttributes(PROFILE1_ID.toString(), newAttributes); assertEqualProfiles(expected, actual); ArgumentMatcher<Object> setParamMatcher = new ArgumentMatcher<Object>() { @Override public boolean matches(Object argument) { Map<String, Object> param = (Map<String, Object>)argument; return param.size() == 2 && param.containsKey("lastModified") && param.get("attributes." + ATTRIB_NAME_GENDER).equals(GENDER); } }; verify(tenantPermissionEvaluator).isAllowed(TENANT1_NAME, TenantAction.MANAGE_PROFILES.toString()); verify(profileRepository).findById(PROFILE1_ID.toString(), new String[0]); verify(profileRepository).update(eq(PROFILE1_ID.toString()), eq("{$set: #}"), eq(false), eq(false), argThat(setParamMatcher)); } @Test public void testUpdateUnwritableAttribute() throws Exception { try { Map<String, Object> newAttributes = Collections.<String, Object>singletonMap(ATTRIB_NAME_PRIVATE, 0); profileService.updateAttributes(PROFILE1_ID.toString(), newAttributes); fail("Exception " + ActionDeniedException.class.getName() + " expected"); } catch (ActionDeniedException e) { } } @Test public void testRemoveAttributes() throws Exception { Profile expected = getTenant1Profile(); expected.setAttributes(getAttributesWithoutPrivateAttribute()); expected.getAttributes().remove(ATTRIB_NAME_LAST_NAME); Profile actual = profileService.removeAttributes(PROFILE1_ID.toString(), Arrays.asList(ATTRIB_NAME_LAST_NAME)); assertEqualProfiles(expected, actual); ArgumentMatcher<Object> setParamMatcher = new ArgumentMatcher<Object>() { @Override public boolean matches(Object argument) { Map<String, Object> param = (Map<String, Object>)argument; return param.size() == 1 && param.containsKey("lastModified"); } }; ArgumentMatcher<Object> unsetParamMatcher = new ArgumentMatcher<Object>() { @Override public boolean matches(Object argument) { Map<String, Object> param = (Map<String, Object>)argument; return param.size() == 1 && param.get("attributes." + ATTRIB_NAME_LAST_NAME).equals(""); } }; verify(tenantPermissionEvaluator).isAllowed(TENANT1_NAME, TenantAction.MANAGE_PROFILES.toString()); verify(profileRepository).findById(PROFILE1_ID.toString(), new String[0]); verify(profileRepository).update(eq(PROFILE1_ID.toString()), eq("{$set: #, $unset: #}"), eq(false), eq(false), argThat(setParamMatcher), argThat(unsetParamMatcher)); } @Test public void testRemoveUnremovableAttribute() throws Exception { try { profileService.removeAttributes(PROFILE1_ID.toString(), Arrays.asList(ATTRIB_NAME_PRIVATE)); fail("Exception " + ActionDeniedException.class.getName() + " expected"); } catch (ActionDeniedException e) { } } @Test public void testDeleteProfile() throws Exception { profileService.deleteProfile(PROFILE1_ID.toString()); verify(tenantPermissionEvaluator).isAllowed(TENANT1_NAME, TenantAction.MANAGE_PROFILES.toString()); verify(profileRepository).removeById(PROFILE1_ID.toString()); } @Test public void testGetProfileByQuery() throws Exception { Profile expected = getTenant1Profile(); expected.setAttributes(getAttributesWithoutPrivateAttribute()); Profile actual = profileService.getProfileByQuery(TENANT1_NAME, QUERY); assertEqualProfiles(expected, actual); verify(tenantPermissionEvaluator).isAllowed(TENANT1_NAME, TenantAction.MANAGE_PROFILES.toString()); verify(profileRepository).findOneByQuery(String.format(ProfileServiceImpl.QUERY_FINAL_FORMAT, TENANT1_NAME, QUERY), new String[0]); } @Test public void testGetProfileByQueryWithTenant() throws Exception { try { profileService.getProfileByQuery(TENANT1_NAME, INVALID_QUERY1); fail("Expected " + InvalidQueryException.class.getSimpleName() + " exception"); } catch (InvalidQueryException e) { } } @Test public void testGetProfileByQueryWithWhereOperator() throws Exception { try { profileService.getProfileByQuery(TENANT1_NAME, INVALID_QUERY2); fail("Expected " + InvalidQueryException.class.getSimpleName() + " exception"); } catch (InvalidQueryException e) { } } @Test public void testGetProfileByQueryWithUnreadableAttribute() throws Exception { try { profileService.getProfileByQuery(TENANT1_NAME, INVALID_QUERY3); fail("Expected " + InvalidQueryException.class.getSimpleName() + " exception"); } catch (InvalidQueryException e) { } } @Test public void testGetProfilesByQuery() throws Exception { List<Profile> expected = getAllTenant1Profiles(); for (Profile profile : expected) { profile.setAttributes(getAttributesWithoutPrivateAttribute()); } List<Profile> actual = profileService.getProfilesByQuery(TENANT1_NAME, QUERY, SORT_BY, SortOrder.ASC, START, COUNT); assertEqualProfileLists(expected, actual); verify(tenantPermissionEvaluator).isAllowed(TENANT1_NAME, TenantAction.MANAGE_PROFILES.toString()); verify(profileRepository).findByQuery(String.format(ProfileServiceImpl.QUERY_FINAL_FORMAT, TENANT1_NAME, QUERY), SORT_BY, SortOrder.ASC, START, COUNT, new String[0]); } @Test public void testGetProfile() throws Exception { Profile expected = getTenant1Profile(); expected.setAttributes(getAttributesWithoutPrivateAttribute()); Profile actual = profileService.getProfile(PROFILE1_ID.toString()); assertEqualProfiles(expected, actual); verify(tenantPermissionEvaluator).isAllowed(TENANT1_NAME, TenantAction.MANAGE_PROFILES.toString()); verify(profileRepository).findById(PROFILE1_ID.toString(), new String[0]); } @Test public void testGetProfileByUsername() throws Exception { Profile expected = getTenant1Profile(); expected.setAttributes(getAttributesWithoutPrivateAttribute()); Profile actual = profileService.getProfileByUsername(TENANT1_NAME, USERNAME1); assertEqualProfiles(expected, actual); verify(tenantPermissionEvaluator).isAllowed(TENANT1_NAME, TenantAction.MANAGE_PROFILES.toString()); verify(profileRepository).findByTenantAndUsername(TENANT1_NAME, USERNAME1, new String[0]); } @Test public void testGetProfileCount() throws Exception { long expected = 10L; long actual = profileService.getProfileCount(TENANT1_NAME); assertEquals(expected, actual); verify(tenantPermissionEvaluator).isAllowed(TENANT1_NAME, TenantAction.MANAGE_PROFILES.toString()); verify(profileRepository).countByTenant(TENANT1_NAME); } @Test public void testGetProfileCountByQuery() throws Exception { long expected = 1L; long actual = profileService.getProfileCountByQuery(TENANT1_NAME, QUERY); assertEquals(expected, actual); verify(tenantPermissionEvaluator).isAllowed(TENANT1_NAME, TenantAction.MANAGE_PROFILES.toString()); verify(profileRepository).count(String.format(ProfileServiceImpl.QUERY_FINAL_FORMAT, TENANT1_NAME, QUERY)); } @Test public void testGetProfilesByIds() throws Exception { List<Profile> expected = getAllTenant1Profiles(); for (Profile profile : expected) { profile.setAttributes(getAttributesWithoutPrivateAttribute()); } List<Profile> actual = profileService.getProfilesByIds(TENANT1_PROFILE_IDS, "username", SortOrder.ASC); assertEqualProfileLists(expected, actual); verify(tenantPermissionEvaluator).isAllowed(TENANT1_NAME, TenantAction.MANAGE_PROFILES.toString()); verify(profileRepository).findByIds(TENANT1_PROFILE_IDS, "username", SortOrder.ASC); } @Test public void testGetProfileRange() throws Exception { List<Profile> expected = getAllTenant1Profiles(); for (Profile profile : expected) { profile.setAttributes(getAttributesWithoutPrivateAttribute()); } List<Profile> actual = profileService.getProfileRange(TENANT1_NAME, SORT_BY, SortOrder.ASC, START, COUNT); assertEqualProfileLists(expected, actual); verify(tenantPermissionEvaluator).isAllowed(TENANT1_NAME, TenantAction.MANAGE_PROFILES.toString()); verify(profileRepository).findRange(TENANT1_NAME, SORT_BY, SortOrder.ASC, START, COUNT); } @Test public void testGetProfileByRole() throws Exception { List<Profile> expected = getAllTenant1Profiles(); for (Profile profile : expected) { profile.setAttributes(getAttributesWithoutPrivateAttribute()); } List<Profile> actual = profileService.getProfilesByRole(TENANT1_NAME, ROLE1, SORT_BY, SortOrder.ASC); assertEqualProfileLists(expected, actual); verify(tenantPermissionEvaluator).isAllowed(TENANT1_NAME, TenantAction.MANAGE_PROFILES.toString()); verify(profileRepository).findByTenantAndRole(TENANT1_NAME, ROLE1, SORT_BY, SortOrder.ASC); } @Test public void testGetProfilesByAttribute() throws Exception { List<Profile> expected = getAllTenant1Profiles(); for (Profile profile : expected) { profile.setAttributes(getAttributesWithoutPrivateAttribute()); } List<Profile> actual = profileService.getProfilesByAttributeValue(TENANT1_NAME, ATTRIB_NAME_FIRST_NAME, FIRST_NAME, SORT_BY, SortOrder.ASC); assertEqualProfileLists(expected, actual); verify(tenantPermissionEvaluator).isAllowed(TENANT1_NAME, TenantAction.MANAGE_PROFILES.toString()); verify(profileRepository).findByTenantAndAttributeValue(TENANT1_NAME, ATTRIB_NAME_FIRST_NAME, FIRST_NAME, SORT_BY, SortOrder.ASC); } @Test public void testResetPassword() throws Exception { Profile expected = getTenant1Profile(); expected.setAttributes(getAttributesWithoutPrivateAttribute()); Profile actual = profileService.resetPassword(PROFILE1_ID.toString(), RESET_PASSWORD_URL); assertEqualProfiles(expected, actual); VerificationToken token = new VerificationToken(); token.setId(VERIFICATION_TOKEN_ID1); verify(tenantPermissionEvaluator).isAllowed(TENANT1_NAME, TenantAction.MANAGE_PROFILES.toString()); verify(profileRepository).findById(PROFILE1_ID.toString(), new String[0]); verify(verificationService).createToken(actual); verify(verificationService).sendEmail(token, actual, RESET_PASSWORD_URL, RESET_PASSWORD_FROM_ADDRESS, RESET_PASSWORD_SUBJECT, RESET_PASSWORD_TEMPLATE_NAME); } @Test public void testChangePassword() throws Exception { Profile expected = getTenant1Profile(); expected.setAttributes(getAttributesWithoutPrivateAttribute()); Profile actual = profileService.changePassword(VERIFICATION_TOKEN_ID1, PASSWORD2); assertEqualProfiles(expected, actual); assertTrue(CipherUtils.matchPassword(actual.getPassword(), PASSWORD2)); ArgumentMatcher<Object> setParamMatcher = new ArgumentMatcher<Object>() { @Override public boolean matches(Object argument) { Map<String, Object> param = (Map<String, Object>)argument; return param.size() == 2 && param.containsKey("password") && param.containsKey("lastModified"); } }; verify(tenantPermissionEvaluator).isAllowed(TENANT1_NAME, TenantAction.MANAGE_PROFILES.toString()); verify(profileRepository).findById(PROFILE1_ID.toString(), new String[0]); verify(profileRepository).update(eq(PROFILE1_ID.toString()), eq("{$set: #}"), eq(false), eq(false), argThat(setParamMatcher)); verify(verificationService).getToken(VERIFICATION_TOKEN_ID1); verify(verificationService).deleteToken(VERIFICATION_TOKEN_ID1); } @Test public void testCreateVerificationToken() throws Exception { VerificationToken token = profileService.createVerificationToken(PROFILE1_ID.toString()); assertNotNull(token); assertEquals(VERIFICATION_TOKEN_ID1, token.getId()); assertEquals(TENANT1_NAME, token.getTenant()); assertEquals(PROFILE1_ID.toString(), token.getProfileId()); assertNotNull(token.getTimestamp()); verify(verificationService).createToken(getTenant1Profile()); } @Test public void testGetVerificationToken() throws Exception { VerificationToken token = profileService.getVerificationToken(VERIFICATION_TOKEN_ID1); assertNotNull(token); assertEquals(VERIFICATION_TOKEN_ID1, token.getId()); assertEquals(TENANT1_NAME, token.getTenant()); assertEquals(PROFILE1_ID.toString(), token.getProfileId()); assertNotNull(token.getTimestamp()); verify(verificationService).getToken(VERIFICATION_TOKEN_ID1); } @Test public void deleteVerificationToken() throws Exception { profileService.deleteVerificationToken(VERIFICATION_TOKEN_ID1); verify(verificationService).deleteToken(VERIFICATION_TOKEN_ID1); } private Tenant getTenant1() { Tenant tenant = new Tenant(); tenant.setName(TENANT1_NAME); tenant.setVerifyNewProfiles(true); tenant.setAttributeDefinitions(getAttributeDefinitions()); return tenant; } private Tenant getTenant2() { Tenant tenant = new Tenant(); tenant.setName(TENANT2_NAME); tenant.setVerifyNewProfiles(false); tenant.setAttributeDefinitions(getAttributeDefinitions()); return tenant; } private List<AttributeDefinition> getAttributeDefinitions() { AttributePermission anyAppCanDoAnything = new AttributePermission(AttributePermission.ANY_APPLICATION); anyAppCanDoAnything.allow(AttributePermission.ANY_ACTION); AttributeDefinition firstNameDefinition = new AttributeDefinition(); firstNameDefinition.setName(ATTRIB_NAME_FIRST_NAME); firstNameDefinition.addPermission(anyAppCanDoAnything); AttributeDefinition lastNameDefinition = new AttributeDefinition(); lastNameDefinition.setName(ATTRIB_NAME_LAST_NAME); lastNameDefinition.addPermission(anyAppCanDoAnything); AttributeDefinition genderDefinition = new AttributeDefinition(); genderDefinition.setName(ATTRIB_NAME_GENDER); genderDefinition.addPermission(anyAppCanDoAnything); genderDefinition.setDefaultValue(GENDER); AttributeDefinition privateDefinition = new AttributeDefinition(); privateDefinition.setName(ATTRIB_NAME_PRIVATE); return Arrays.asList(firstNameDefinition, lastNameDefinition, genderDefinition, privateDefinition); } private Profile getTenant1Profile() { Profile profile = new Profile(); profile.setId(PROFILE1_ID); profile.setTenant(TENANT1_NAME); profile.setUsername(USERNAME1); profile.setPassword(CipherUtils.hashPassword(PASSWORD1)); profile.setEmail(EMAIL1); profile.setRoles(new HashSet<>(ROLES1)); profile.setVerified(true); profile.setEnabled(true); profile.setAttributes(getAttributes()); return profile; } private Profile getTenant1ProfileNoAttributes() { Profile profile = getTenant1Profile(); profile.getAttributes().clear(); return profile; } private Profile getTenant1ProfileNoLastName() { Profile profile = getTenant1Profile(); profile.getAttributes().remove(ATTRIB_NAME_LAST_NAME); return profile; } private List<Profile> getAllTenant1Profiles() { return Arrays.asList(getTenant1Profile()); } private Profile getTenant2Profile() { Profile profile = new Profile(); profile.setId(PROFILE2_ID); profile.setTenant(TENANT2_NAME); profile.setUsername(USERNAME2); profile.setPassword(CipherUtils.hashPassword(PASSWORD2)); profile.setEmail(EMAIL2); profile.setRoles(new HashSet<>(ROLES2)); profile.setVerified(false); profile.setEnabled(false); profile.setAttributes(getAttributes()); return profile; } private Map<String, Object> getAttributes() { Map<String, Object> attributes = new LinkedHashMap<>(2); attributes.put(ATTRIB_NAME_FIRST_NAME, FIRST_NAME); attributes.put(ATTRIB_NAME_LAST_NAME, LAST_NAME); attributes.put(ATTRIB_NAME_PRIVATE, Collections.singletonMap("sub", "test")); return attributes; } private Map<String, Object> getAttributesWithoutPrivateAttribute() { Map<String, Object> attributes = new LinkedHashMap<>(2); attributes.put(ATTRIB_NAME_FIRST_NAME, FIRST_NAME); attributes.put(ATTRIB_NAME_LAST_NAME, LAST_NAME); return attributes; } private Ticket getTicket() { Ticket ticket = new Ticket(); ticket.setId(TICKET_ID); ticket.setTenant(TENANT1_NAME); ticket.setProfileId(PROFILE1_ID.toString()); ticket.setLastRequestTime(new Date()); return ticket; } private void assertEqualProfiles(Profile expected, Profile actual) { assertNotNull(actual); assertEquals(expected.getTenant(), actual.getTenant()); assertEquals(expected.getUsername(), actual.getUsername()); assertEquals(expected.getEmail(), actual.getEmail()); assertEquals(expected.getRoles(), actual.getRoles()); assertEquals(expected.isVerified(), actual.isVerified()); assertEquals(expected.isEnabled(), actual.isEnabled()); assertEquals(expected.getAttributes(), actual.getAttributes()); } private void assertEqualProfileLists(List<Profile> expected, List<Profile> actual) { assertNotNull(actual); assertEquals(expected.size(), actual.size()); for (int i = 0; i < expected.size(); i++) { assertEqualProfiles(expected.get(i), actual.get(i)); } } }