mirror of
https://github.com/Alfresco/alfresco-community-repo.git
synced 2025-10-01 14:41:46 +00:00
Merge remote-tracking branch 'origin/master' into feature/ACA-4619-Self-service-User-password-reset-ADW
This commit is contained in:
@@ -25,6 +25,8 @@
|
||||
*/
|
||||
package org.alfresco;
|
||||
|
||||
import org.alfresco.repo.security.authentication.identityservice.LazyInstantiatingIdentityServiceFacadeUnitTest;
|
||||
import org.alfresco.repo.security.authentication.identityservice.SpringBasedIdentityServiceFacadeUnitTest;
|
||||
import org.alfresco.util.testing.category.DBTests;
|
||||
import org.alfresco.util.testing.category.NonBuildTests;
|
||||
import org.junit.experimental.categories.Categories;
|
||||
@@ -84,7 +86,6 @@ import org.junit.runners.Suite;
|
||||
org.alfresco.repo.transfer.HttpClientTransmitterImplTest.class,
|
||||
org.alfresco.repo.transfer.manifest.TransferManifestTest.class,
|
||||
org.alfresco.repo.transfer.TransferVersionCheckerImplTest.class,
|
||||
org.alfresco.repo.urlshortening.BitlyUrlShortenerTest.class,
|
||||
org.alfresco.service.cmr.calendar.CalendarRecurrenceHelperTest.class,
|
||||
org.alfresco.service.cmr.calendar.CalendarTimezoneHelperTest.class,
|
||||
org.alfresco.tools.RenameUserTest.class,
|
||||
@@ -137,6 +138,8 @@ import org.junit.runners.Suite;
|
||||
org.alfresco.repo.search.impl.solr.facet.FacetQNameUtilsTest.class,
|
||||
org.alfresco.util.BeanExtenderUnitTest.class,
|
||||
org.alfresco.repo.solr.SOLRTrackingComponentUnitTest.class,
|
||||
LazyInstantiatingIdentityServiceFacadeUnitTest.class,
|
||||
SpringBasedIdentityServiceFacadeUnitTest.class,
|
||||
org.alfresco.repo.security.authentication.CompositePasswordEncoderTest.class,
|
||||
org.alfresco.repo.security.authentication.PasswordHashingTest.class,
|
||||
org.alfresco.repo.security.authority.script.ScriptAuthorityService_RegExTest.class,
|
||||
@@ -244,7 +247,8 @@ import org.junit.runners.Suite;
|
||||
|
||||
org.alfresco.repo.event2.RepoEvent2UnitSuite.class,
|
||||
|
||||
org.alfresco.util.schemacomp.SchemaDifferenceHelperUnitTest.class
|
||||
org.alfresco.util.schemacomp.SchemaDifferenceHelperUnitTest.class,
|
||||
org.alfresco.repo.tagging.TaggingServiceImplUnitTest.class
|
||||
})
|
||||
public class AllUnitTestsSuite
|
||||
{
|
||||
|
@@ -2,7 +2,7 @@
|
||||
* #%L
|
||||
* Alfresco Repository
|
||||
* %%
|
||||
* Copyright (C) 2005 - 2020 Alfresco Software Limited
|
||||
* Copyright (C) 2005 - 2023 Alfresco Software Limited
|
||||
* %%
|
||||
* This file is part of the Alfresco software.
|
||||
* If the software was purchased under a paid Alfresco license, the terms of
|
||||
@@ -32,6 +32,7 @@ import static org.awaitility.Awaitility.await;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
|
||||
import javax.jms.ConnectionFactory;
|
||||
|
||||
@@ -47,6 +48,7 @@ import org.alfresco.repo.event.v1.model.Resource;
|
||||
import org.alfresco.repo.security.authentication.AuthenticationUtil;
|
||||
import org.alfresco.repo.transaction.RetryingTransactionHelper;
|
||||
import org.alfresco.service.cmr.dictionary.CustomModelService;
|
||||
import org.alfresco.service.cmr.repository.MLText;
|
||||
import org.alfresco.service.cmr.repository.NodeRef;
|
||||
import org.alfresco.service.cmr.repository.NodeService;
|
||||
import org.alfresco.service.cmr.repository.StoreRef;
|
||||
@@ -89,6 +91,11 @@ public abstract class AbstractContextAwareRepoEvent extends BaseSpringTest
|
||||
private static final String CAMEL_ROUTE = "jms:topic:" + TOPIC_NAME;
|
||||
private static final CamelContext CAMEL_CONTEXT = new DefaultCamelContext();
|
||||
|
||||
protected final Locale defaultLocale = new Locale(MLText.getDefaultLocale().getLanguage());
|
||||
protected final Locale germanLocale = new Locale(Locale.GERMAN.getLanguage(), "XX");
|
||||
protected final Locale frenchLocale = new Locale(Locale.FRENCH.getLanguage());
|
||||
protected final Locale japaneseLocale = new Locale(Locale.JAPANESE.getLanguage(), "YY", "ZZ");
|
||||
|
||||
private static boolean isCamelConfigured;
|
||||
private static DataFormat dataFormat;
|
||||
|
||||
@@ -388,6 +395,20 @@ public abstract class AbstractContextAwareRepoEvent extends BaseSpringTest
|
||||
return (T) resource.getProperties().get(propertyName);
|
||||
}
|
||||
|
||||
protected String getLocalizedProperty(NodeResource resource, String propertyName, Locale locale)
|
||||
{
|
||||
assertTrue(containsLocalizedProperty(resource, propertyName, locale));
|
||||
return resource.getLocalizedProperties().get(propertyName).get(locale.toString());
|
||||
}
|
||||
|
||||
protected boolean containsLocalizedProperty(NodeResource resource, String propertyName, Locale locale)
|
||||
{
|
||||
assertNotNull(resource);
|
||||
assertNotNull(resource.getLocalizedProperties());
|
||||
assertNotNull(resource.getLocalizedProperties().get(propertyName));
|
||||
return resource.getLocalizedProperties().get(propertyName).containsKey(locale.toString());
|
||||
}
|
||||
|
||||
/**
|
||||
* Await at most 5 seconds for the condition (ie. {@code numOfEvents})
|
||||
* to be met before throwing a timeout exception.
|
||||
|
@@ -2,7 +2,7 @@
|
||||
* #%L
|
||||
* Alfresco Repository
|
||||
* %%
|
||||
* Copyright (C) 2005 - 2020 Alfresco Software Limited
|
||||
* Copyright (C) 2005 - 2023 Alfresco Software Limited
|
||||
* %%
|
||||
* This file is part of the Alfresco software.
|
||||
* If the software was purchased under a paid Alfresco license, the terms of
|
||||
@@ -38,6 +38,7 @@ import org.alfresco.repo.event.v1.model.EventData;
|
||||
import org.alfresco.repo.event.v1.model.EventType;
|
||||
import org.alfresco.repo.event.v1.model.NodeResource;
|
||||
import org.alfresco.repo.event.v1.model.RepoEvent;
|
||||
import org.alfresco.service.cmr.repository.MLText;
|
||||
import org.alfresco.service.cmr.repository.NodeRef;
|
||||
import org.alfresco.service.namespace.QName;
|
||||
import org.alfresco.util.GUID;
|
||||
@@ -50,7 +51,6 @@ import org.springframework.beans.factory.annotation.Autowired;
|
||||
*/
|
||||
public class CreateRepoEventIT extends AbstractContextAwareRepoEvent
|
||||
{
|
||||
|
||||
@Autowired
|
||||
private NodeDAO nodeDAO;
|
||||
|
||||
@@ -63,6 +63,9 @@ public class CreateRepoEventIT extends AbstractContextAwareRepoEvent
|
||||
PropertyMap propertyMap = new PropertyMap();
|
||||
propertyMap.put(ContentModel.PROP_TITLE, "test title");
|
||||
propertyMap.put(ContentModel.PROP_NAME, name);
|
||||
final MLText localizedDescription = new MLText(germanLocale, "german description");
|
||||
localizedDescription.addValue(defaultLocale, "default description");
|
||||
propertyMap.put(ContentModel.PROP_DESCRIPTION, localizedDescription);
|
||||
final NodeRef nodeRef = createNode(ContentModel.TYPE_CONTENT, localName, propertyMap);
|
||||
|
||||
final RepoEvent<EventData<NodeResource>> resultRepoEvent = getRepoEvent(1);
|
||||
@@ -91,6 +94,10 @@ public class CreateRepoEventIT extends AbstractContextAwareRepoEvent
|
||||
assertNotNull(nodeResource.getPrimaryHierarchy());
|
||||
assertNotNull("Default aspects were not added. ", nodeResource.getAspectNames());
|
||||
assertEquals("test title", getProperty(nodeResource, "cm:title"));
|
||||
assertEquals("test title", getLocalizedProperty(nodeResource, "cm:title", defaultLocale));
|
||||
assertEquals("default description", getProperty(nodeResource, "cm:description"));
|
||||
assertEquals("default description", getLocalizedProperty(nodeResource, "cm:description", defaultLocale));
|
||||
assertEquals("german description", getLocalizedProperty(nodeResource, "cm:description", germanLocale));
|
||||
assertNull("There is no content.", nodeResource.getContent());
|
||||
|
||||
assertNotNull("Missing createdByUser property.", nodeResource.getCreatedByUser());
|
||||
|
@@ -2,7 +2,7 @@
|
||||
* #%L
|
||||
* Alfresco Repository
|
||||
* %%
|
||||
* Copyright (C) 2005 - 2020 Alfresco Software Limited
|
||||
* Copyright (C) 2005 - 2023 Alfresco Software Limited
|
||||
* %%
|
||||
* This file is part of the Alfresco software.
|
||||
* If the software was purchased under a paid Alfresco license, the terms of
|
||||
@@ -31,6 +31,7 @@ import org.alfresco.repo.event.v1.model.EventData;
|
||||
import org.alfresco.repo.event.v1.model.EventType;
|
||||
import org.alfresco.repo.event.v1.model.NodeResource;
|
||||
import org.alfresco.repo.event.v1.model.RepoEvent;
|
||||
import org.alfresco.service.cmr.repository.MLText;
|
||||
import org.alfresco.service.cmr.repository.NodeRef;
|
||||
import org.alfresco.service.namespace.QName;
|
||||
import org.alfresco.util.GUID;
|
||||
@@ -48,6 +49,9 @@ public class DeleteRepoEventIT extends AbstractContextAwareRepoEvent
|
||||
String localName = GUID.generate();
|
||||
PropertyMap propertyMap = new PropertyMap();
|
||||
propertyMap.put(ContentModel.PROP_TITLE, "test title");
|
||||
final MLText localizedDescription = new MLText(germanLocale, "german description");
|
||||
localizedDescription.addValue(defaultLocale, "default description");
|
||||
propertyMap.put(ContentModel.PROP_DESCRIPTION, localizedDescription);
|
||||
NodeRef nodeRef = createNode(ContentModel.TYPE_CONTENT, localName, propertyMap);
|
||||
|
||||
NodeResource createdResource = getNodeResource(1);
|
||||
@@ -62,10 +66,16 @@ public class DeleteRepoEventIT extends AbstractContextAwareRepoEvent
|
||||
|
||||
deleteNode(nodeRef);
|
||||
final RepoEvent<EventData<NodeResource>> resultRepoEvent = getRepoEvent(2);
|
||||
final NodeResource nodeResource = getNodeResource(resultRepoEvent);
|
||||
|
||||
assertEquals("Repo event type:", EventType.NODE_DELETED.getType(), resultRepoEvent.getType());
|
||||
assertEquals(createdResource.getId(), getNodeResource(resultRepoEvent).getId());
|
||||
assertEquals(createdResource.getId(), nodeResource.getId());
|
||||
assertEquals("Wrong primaryAssocQName prefix.", "ce:" + localName, createdResource.getPrimaryAssocQName());
|
||||
assertEquals("test title", getProperty(nodeResource, "cm:title"));
|
||||
assertEquals("test title", getLocalizedProperty(nodeResource, "cm:title", defaultLocale));
|
||||
assertEquals("default description", getProperty(nodeResource, "cm:description"));
|
||||
assertEquals("default description", getLocalizedProperty(nodeResource, "cm:description", defaultLocale));
|
||||
assertEquals("german description", getLocalizedProperty(nodeResource, "cm:description", germanLocale));
|
||||
|
||||
// There should be no resourceBefore
|
||||
EventData<NodeResource> eventData = getEventData(resultRepoEvent);
|
||||
|
@@ -0,0 +1,170 @@
|
||||
/*
|
||||
* #%L
|
||||
* Alfresco Repository
|
||||
* %%
|
||||
* Copyright (C) 2005 - 2020 Alfresco Software Limited
|
||||
* %%
|
||||
* This file is part of the Alfresco software.
|
||||
* If the software was purchased under a paid Alfresco license, the terms of
|
||||
* the paid license agreement will prevail. Otherwise, the software is
|
||||
* provided under the following open source license terms:
|
||||
*
|
||||
* Alfresco is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Alfresco 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 Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
|
||||
* #L%
|
||||
*/
|
||||
package org.alfresco.repo.event2;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import javax.jms.Destination;
|
||||
import javax.jms.JMSException;
|
||||
import javax.jms.Message;
|
||||
import javax.jms.MessageConsumer;
|
||||
import javax.jms.MessageListener;
|
||||
import javax.jms.Session;
|
||||
|
||||
import org.alfresco.model.ContentModel;
|
||||
import org.alfresco.repo.event.v1.model.RepoEvent;
|
||||
import org.apache.activemq.ActiveMQConnection;
|
||||
import org.apache.activemq.ActiveMQConnectionFactory;
|
||||
import org.apache.activemq.command.ActiveMQTextMessage;
|
||||
import org.awaitility.Awaitility;
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
|
||||
public class EventGeneratorDisabledTest extends AbstractContextAwareRepoEvent
|
||||
{
|
||||
private static final String EVENT2_TOPIC_NAME = "alfresco.repo.event2";
|
||||
private static final String BROKER_URL = "tcp://localhost:61616";
|
||||
|
||||
@Autowired @Qualifier("event2ObjectMapper")
|
||||
private ObjectMapper objectMapper;
|
||||
|
||||
@Autowired
|
||||
protected ObjectMapper event2ObjectMapper;
|
||||
|
||||
//private EventGenerator eventGenerator;
|
||||
|
||||
private ActiveMQConnection connection;
|
||||
protected List<RepoEvent<?>> receivedEvents;
|
||||
|
||||
|
||||
@Before
|
||||
public void setup() throws Exception
|
||||
{
|
||||
ActiveMQConnectionFactory connectionFactory = new ActiveMQConnectionFactory(BROKER_URL);
|
||||
connection = (ActiveMQConnection) connectionFactory.createConnection();
|
||||
connection.start();
|
||||
|
||||
Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
|
||||
Destination destination = session.createTopic(EVENT2_TOPIC_NAME);
|
||||
MessageConsumer consumer = session.createConsumer(destination);
|
||||
|
||||
receivedEvents = Collections.synchronizedList(new LinkedList<>());
|
||||
consumer.setMessageListener(new MessageListener()
|
||||
{
|
||||
@Override
|
||||
public void onMessage(Message message)
|
||||
{
|
||||
String text = getText(message);
|
||||
RepoEvent<?> event = toRepoEvent(text);
|
||||
receivedEvents.add(event);
|
||||
}
|
||||
|
||||
private RepoEvent<?> toRepoEvent(String json)
|
||||
{
|
||||
try
|
||||
{
|
||||
return objectMapper.readValue(json, RepoEvent.class);
|
||||
} catch (Exception e)
|
||||
{
|
||||
e.printStackTrace();
|
||||
return null;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@After
|
||||
public void shutdownTopicListener() throws Exception
|
||||
{
|
||||
connection.close();
|
||||
connection = null;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldNotReceiveEvent2EventsOnNodeCreation() throws Exception
|
||||
{
|
||||
if (eventGenerator.isEnabled())
|
||||
{
|
||||
eventGenerator.disable();
|
||||
}
|
||||
|
||||
createNode(ContentModel.TYPE_CONTENT);
|
||||
|
||||
Awaitility.await().pollDelay(6, TimeUnit.SECONDS).until(() -> receivedEvents.size() == 0);
|
||||
|
||||
assertTrue(EVENT_CONTAINER.getEvents().size() == 0);
|
||||
assertTrue(receivedEvents.size() == 0);
|
||||
|
||||
eventGenerator.enable();
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldReceiveEvent2EventsOnNodeCreation() throws Exception
|
||||
{
|
||||
if (!eventGenerator.isEnabled())
|
||||
{
|
||||
eventGenerator.enable();
|
||||
}
|
||||
|
||||
createNode(ContentModel.TYPE_CONTENT);
|
||||
|
||||
Awaitility.await().atMost(6, TimeUnit.SECONDS).until(() -> receivedEvents.size() == 1);
|
||||
|
||||
assertTrue(EVENT_CONTAINER.getEvents().size() == 1);
|
||||
assertTrue(receivedEvents.size() == 1);
|
||||
|
||||
RepoEvent<?> sent = getRepoEvent(1);
|
||||
RepoEvent<?> received = receivedEvents.get(0);
|
||||
assertEventsEquals("Events are different!", sent, received);
|
||||
}
|
||||
|
||||
private void assertEventsEquals(String message, RepoEvent<?> expected, RepoEvent<?> current)
|
||||
{
|
||||
assertEquals(message, expected, current);
|
||||
}
|
||||
|
||||
private static String getText(Message message)
|
||||
{
|
||||
try
|
||||
{
|
||||
ActiveMQTextMessage am = (ActiveMQTextMessage) message;
|
||||
return am.getText();
|
||||
} catch (JMSException e)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,114 @@
|
||||
/*
|
||||
* #%L
|
||||
* Alfresco Repository
|
||||
* %%
|
||||
* Copyright (C) 2005 - 2023 Alfresco Software Limited
|
||||
* %%
|
||||
* This file is part of the Alfresco software.
|
||||
* If the software was purchased under a paid Alfresco license, the terms of
|
||||
* the paid license agreement will prevail. Otherwise, the software is
|
||||
* provided under the following open source license terms:
|
||||
*
|
||||
* Alfresco is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Alfresco 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 Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
|
||||
* #L%
|
||||
*/
|
||||
package org.alfresco.repo.event2;
|
||||
|
||||
import static org.alfresco.repo.event2.NodeResourceHelper.getLocalizedPropertiesBefore;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
public class NodeResourceHelperUnitTest
|
||||
{
|
||||
@Test
|
||||
public void shouldExtractOnlyRelevantPropertiesForBeforeNode()
|
||||
{
|
||||
final Map<String, Map<String, String>> before =
|
||||
Map.of(
|
||||
"unchanged-empty", locValues(),
|
||||
"unchanged-non-empty", locValues("pl", "Kiełbasa", "en", "Sausage"),
|
||||
"changed-added", locValues("pl", "Kiełbasa"),
|
||||
"changed-modified", locValues("pl", "XYZ", "en", "Sausage"),
|
||||
"changed-deleted", locValues("pl", "Kiełbasa", "en", "Sausage"),
|
||||
"changed-added-modified-deleted", locValues("pl", "XYZ", "en", "Sausage"),
|
||||
"changed-to-empty", locValues("pl", "Kiełbasa", "en", "Sausage"),
|
||||
"changed-from-empty", locValues(),
|
||||
"removed-empty", locValues(),
|
||||
"removed-non-empty", locValues("pl", "Kiełbasa", "en", "Sausage")
|
||||
);
|
||||
|
||||
final Map<String, Map<String, String>> after =
|
||||
Map.of(
|
||||
"unchanged-empty", locValues(),
|
||||
"unchanged-non-empty", locValues("pl", "Kiełbasa", "en", "Sausage"),
|
||||
"changed-added", locValues("pl", "Kiełbasa", "en", "Sausage"),
|
||||
"changed-modified", locValues("pl", "Kiełbasa", "en", "Sausage"),
|
||||
"changed-deleted", locValues("en", "Sausage"),
|
||||
"changed-added-modified-deleted", locValues("pl", "Kiełbasa", "de", "Würst"),
|
||||
"changed-to-empty", locValues(),
|
||||
"changed-from-empty", locValues("pl", "Kiełbasa", "en", "Sausage"),
|
||||
"new-empty", locValues(),
|
||||
"new-non-empty", locValues("de", "Würst")
|
||||
);
|
||||
|
||||
final Map<String, Map<String, String>> diff = getLocalizedPropertiesBefore(before, after);
|
||||
|
||||
assertFalse(diff.containsKey("unchanged-empty"));
|
||||
assertFalse(diff.containsKey("unchanged-non-empty"));
|
||||
assertEquals(locValues("en", null), diff.get("changed-added"));
|
||||
assertEquals(locValues("pl", "XYZ"), diff.get("changed-modified"));
|
||||
assertEquals(locValues("pl", "Kiełbasa"), diff.get("changed-deleted"));
|
||||
assertEquals(locValues("pl", "XYZ", "en", "Sausage", "de", null), diff.get("changed-added-modified-deleted"));
|
||||
assertEquals(locValues("pl", "Kiełbasa", "en", "Sausage"), diff.get("changed-to-empty"));
|
||||
assertEquals(locValues("pl", null, "en", null), diff.get("changed-from-empty"));
|
||||
assertFalse(diff.containsKey("removed-empty"));
|
||||
assertEquals(locValues("pl", "Kiełbasa", "en", "Sausage"), diff.get("removed-non-empty"));
|
||||
assertFalse(diff.containsKey("new-empty"));
|
||||
assertEquals(locValues("de", null), diff.get("new-non-empty"));
|
||||
}
|
||||
|
||||
private LocalizedValues locValues(String l1, String v1, String l2, String v2, String l3, String v3)
|
||||
{
|
||||
return locValues(l1, v1, l2, v2).append(l3, v3);
|
||||
}
|
||||
|
||||
private LocalizedValues locValues(String l1, String v1, String l2, String v2)
|
||||
{
|
||||
return locValues(l1, v1).append(l2, v2);
|
||||
}
|
||||
|
||||
private LocalizedValues locValues(String l1, String v1)
|
||||
{
|
||||
return locValues().append(l1, v1);
|
||||
}
|
||||
|
||||
private LocalizedValues locValues()
|
||||
{
|
||||
return new LocalizedValues();
|
||||
}
|
||||
|
||||
private static class LocalizedValues extends HashMap<String, String>
|
||||
{
|
||||
public LocalizedValues append(String language, String value)
|
||||
{
|
||||
this.put(language, value);
|
||||
return this;
|
||||
}
|
||||
}
|
||||
}
|
@@ -35,7 +35,8 @@ import org.junit.runners.Suite.SuiteClasses;
|
||||
DeleteRepoEventIT.class,
|
||||
ChildAssociationRepoEventIT.class,
|
||||
PeerAssociationRepoEventIT.class,
|
||||
EventGeneratorTest.class
|
||||
EventGeneratorTest.class,
|
||||
EventGeneratorDisabledTest.class
|
||||
})
|
||||
public class RepoEvent2ITSuite
|
||||
{
|
||||
|
@@ -2,7 +2,7 @@
|
||||
* #%L
|
||||
* Alfresco Repository
|
||||
* %%
|
||||
* Copyright (C) 2005 - 2020 Alfresco Software Limited
|
||||
* Copyright (C) 2005 - 2023 Alfresco Software Limited
|
||||
* %%
|
||||
* This file is part of the Alfresco software.
|
||||
* If the software was purchased under a paid Alfresco license, the terms of
|
||||
@@ -34,7 +34,8 @@ import org.junit.runners.Suite.SuiteClasses;
|
||||
@SuiteClasses({ EventFilterUnitTest.class,
|
||||
EventConsolidatorUnitTest.class,
|
||||
EventJSONSchemaUnitTest.class,
|
||||
EventGeneratorQueueUnitTest.class
|
||||
EventGeneratorQueueUnitTest.class,
|
||||
NodeResourceHelperUnitTest.class
|
||||
})
|
||||
public class RepoEvent2UnitSuite
|
||||
{
|
||||
|
@@ -2,7 +2,7 @@
|
||||
* #%L
|
||||
* Alfresco Repository
|
||||
* %%
|
||||
* Copyright (C) 2005 - 2020 Alfresco Software Limited
|
||||
* Copyright (C) 2005 - 2023 Alfresco Software Limited
|
||||
* %%
|
||||
* This file is part of the Alfresco software.
|
||||
* If the software was purchased under a paid Alfresco license, the terms of
|
||||
@@ -26,6 +26,8 @@
|
||||
|
||||
package org.alfresco.repo.event2;
|
||||
|
||||
import static org.alfresco.model.ContentModel.PROP_DESCRIPTION;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
@@ -46,6 +48,7 @@ import org.alfresco.service.cmr.dictionary.CustomModelDefinition;
|
||||
import org.alfresco.service.cmr.dictionary.TypeDefinition;
|
||||
import org.alfresco.service.cmr.repository.ContentService;
|
||||
import org.alfresco.service.cmr.repository.ContentWriter;
|
||||
import org.alfresco.service.cmr.repository.MLText;
|
||||
import org.alfresco.service.cmr.repository.NodeRef;
|
||||
import org.alfresco.service.namespace.QName;
|
||||
import org.alfresco.util.GUID;
|
||||
@@ -279,6 +282,60 @@ public class UpdateRepoEventIT extends AbstractContextAwareRepoEvent
|
||||
assertNull(resourceBefore.getPrimaryAssocQName());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUpdateContentWithLocalizedProperties()
|
||||
{
|
||||
final String description = "cm:description";
|
||||
final NodeRef nodeRef = createNode(ContentModel.TYPE_CONTENT);
|
||||
NodeResource resource = getNodeResource(1);
|
||||
|
||||
assertNull(getProperty(resource, description));
|
||||
assertNull(resource.getLocalizedProperties());
|
||||
assertNull(getEventData(1).getResourceBefore());
|
||||
|
||||
retryingTransactionHelper.doInTransaction(() -> {
|
||||
final MLText localizedDescription = new MLText(germanLocale, "german description");
|
||||
localizedDescription.addValue(defaultLocale, "default description");
|
||||
localizedDescription.addValue(japaneseLocale, "japanese description");
|
||||
|
||||
nodeService.setProperty(nodeRef, PROP_DESCRIPTION, localizedDescription);
|
||||
return null;
|
||||
});
|
||||
|
||||
resource = getNodeResource(2);
|
||||
NodeResource resourceBefore = getNodeResourceBefore(2);
|
||||
|
||||
assertEquals("default description", getProperty(resource, description));
|
||||
assertEquals("default description", getLocalizedProperty(resource, description, defaultLocale));
|
||||
assertEquals("german description", getLocalizedProperty(resource, description, germanLocale));
|
||||
assertEquals("japanese description", getLocalizedProperty(resource, description, japaneseLocale));
|
||||
assertNull(getLocalizedProperty(resourceBefore, description, defaultLocale));
|
||||
assertNull(getLocalizedProperty(resourceBefore, description, germanLocale));
|
||||
assertNull(getLocalizedProperty(resourceBefore, description, japaneseLocale));
|
||||
|
||||
retryingTransactionHelper.doInTransaction(() -> {
|
||||
final MLText localizedDescription = new MLText(frenchLocale, "french description added");
|
||||
localizedDescription.addValue(defaultLocale, "default description modified");
|
||||
localizedDescription.addValue(japaneseLocale, "japanese description");
|
||||
|
||||
nodeService.setProperty(nodeRef, PROP_DESCRIPTION, localizedDescription);
|
||||
return null;
|
||||
});
|
||||
|
||||
resource = getNodeResource(3);
|
||||
resourceBefore = getNodeResourceBefore(3);
|
||||
|
||||
assertEquals("default description modified", getProperty(resource, description));
|
||||
assertEquals("default description modified", getLocalizedProperty(resource, description, defaultLocale));
|
||||
assertEquals("french description added", getLocalizedProperty(resource, description, frenchLocale));
|
||||
assertEquals("japanese description", getLocalizedProperty(resource, description, japaneseLocale));
|
||||
assertFalse(containsLocalizedProperty(resource, description, germanLocale));
|
||||
assertEquals("default description", getLocalizedProperty(resourceBefore, description, defaultLocale));
|
||||
assertEquals("german description", getLocalizedProperty(resourceBefore, description, germanLocale));
|
||||
assertNull(getLocalizedProperty(resourceBefore, description, frenchLocale));
|
||||
assertFalse(containsLocalizedProperty(resourceBefore, description, japaneseLocale));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUpdateContentTitle()
|
||||
{
|
||||
@@ -296,8 +353,11 @@ public class UpdateRepoEventIT extends AbstractContextAwareRepoEvent
|
||||
});
|
||||
|
||||
resource = getNodeResource(2);
|
||||
NodeResource resourceBefore = getNodeResourceBefore(2);
|
||||
title = getProperty(resource, "cm:title");
|
||||
assertEquals("test title", title);
|
||||
assertEquals("test title", getLocalizedProperty(resource, "cm:title", defaultLocale));
|
||||
assertNull(getLocalizedProperty(resourceBefore, "cm:title", defaultLocale));
|
||||
|
||||
// update content cm:title property again with "new test title" value
|
||||
retryingTransactionHelper.doInTransaction(() -> {
|
||||
@@ -308,10 +368,13 @@ public class UpdateRepoEventIT extends AbstractContextAwareRepoEvent
|
||||
resource = getNodeResource(3);
|
||||
title = getProperty(resource, "cm:title");
|
||||
assertEquals("new test title", title);
|
||||
assertEquals("new test title", getLocalizedProperty(resource, "cm:title", defaultLocale));
|
||||
|
||||
NodeResource resourceBefore = getNodeResourceBefore(3);
|
||||
|
||||
resourceBefore = getNodeResourceBefore(3);
|
||||
title = getProperty(resourceBefore, "cm:title");
|
||||
assertEquals("Wrong old property.", "test title", title);
|
||||
assertEquals("test title", getLocalizedProperty(resourceBefore, "cm:title", defaultLocale));
|
||||
assertNotNull(resourceBefore.getModifiedAt());
|
||||
}
|
||||
|
||||
@@ -354,7 +417,7 @@ public class UpdateRepoEventIT extends AbstractContextAwareRepoEvent
|
||||
|
||||
// update content cm:description property with "test_description" value
|
||||
retryingTransactionHelper.doInTransaction(() -> {
|
||||
nodeService.setProperty(nodeRef, ContentModel.PROP_DESCRIPTION, "test description");
|
||||
nodeService.setProperty(nodeRef, PROP_DESCRIPTION, "test description");
|
||||
return null;
|
||||
});
|
||||
|
||||
@@ -492,7 +555,7 @@ public class UpdateRepoEventIT extends AbstractContextAwareRepoEvent
|
||||
QName.createQName(TEST_NAMESPACE, GUID.generate()),
|
||||
ContentModel.TYPE_CONTENT).getChildRef();
|
||||
|
||||
nodeService.setProperty(node1, ContentModel.PROP_DESCRIPTION, "test description");
|
||||
nodeService.setProperty(node1, PROP_DESCRIPTION, "test description");
|
||||
return null;
|
||||
});
|
||||
//Create and update node are done in the same transaction so one event is expected
|
||||
|
@@ -2,7 +2,7 @@
|
||||
* #%L
|
||||
* Alfresco Remote API
|
||||
* %%
|
||||
* Copyright (C) 2005 - 2022 Alfresco Software Limited
|
||||
* Copyright (C) 2005 - 2023 Alfresco Software Limited
|
||||
* %%
|
||||
* This file is part of the Alfresco software.
|
||||
* If the software was purchased under a paid Alfresco license, the terms of
|
||||
@@ -26,20 +26,39 @@
|
||||
|
||||
package org.alfresco.repo.search.impl.solr;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertThrows;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.BDDMockito.given;
|
||||
import static org.mockito.BDDMockito.then;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.times;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.IntStream;
|
||||
|
||||
import org.alfresco.model.ContentModel;
|
||||
import org.alfresco.repo.search.impl.noindex.NoIndexCategoryServiceImpl;
|
||||
import org.alfresco.repo.search.IndexerAndSearcher;
|
||||
import org.alfresco.service.cmr.dictionary.AspectDefinition;
|
||||
import org.alfresco.service.cmr.dictionary.DataTypeDefinition;
|
||||
import org.alfresco.service.cmr.dictionary.DictionaryService;
|
||||
import org.alfresco.service.cmr.dictionary.PropertyDefinition;
|
||||
import org.alfresco.service.cmr.repository.ChildAssociationRef;
|
||||
import org.alfresco.service.cmr.repository.NodeRef;
|
||||
import org.alfresco.service.cmr.repository.NodeService;
|
||||
import org.alfresco.service.cmr.repository.StoreRef;
|
||||
import org.alfresco.service.cmr.search.ResultSet;
|
||||
import org.alfresco.service.cmr.search.SearchParameters;
|
||||
import org.alfresco.service.cmr.search.SearchService;
|
||||
import org.alfresco.service.namespace.NamespaceService;
|
||||
import org.alfresco.service.namespace.QName;
|
||||
import org.alfresco.util.Pair;
|
||||
import org.apache.commons.collections.CollectionUtils;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.InjectMocks;
|
||||
@@ -55,6 +74,8 @@ public class SolrCategoryServiceImplTest
|
||||
{
|
||||
private static final String PATH_ROOT = "-root-";
|
||||
private static final String CAT_ROOT_NODE_ID = "cat-root-node-id";
|
||||
private static final String NODE_ID_PREFIX = "node-id-";
|
||||
private static final StoreRef STORE_REF = StoreRef.STORE_REF_WORKSPACE_SPACESSTORE;
|
||||
|
||||
@Mock
|
||||
private NodeService nodeServiceMock;
|
||||
@@ -62,9 +83,19 @@ public class SolrCategoryServiceImplTest
|
||||
private ChildAssociationRef categoryRootChildAssociationRefMock;
|
||||
@Mock
|
||||
private ChildAssociationRef categoryChildAssociationRefMock;
|
||||
@Mock
|
||||
private IndexerAndSearcher indexerAndSearcherMock;
|
||||
@Mock
|
||||
private SearchService searcherMock;
|
||||
@Mock
|
||||
private DictionaryService dictionaryServiceMock;
|
||||
@Mock
|
||||
private AspectDefinition aspectDefinitionMock;
|
||||
@Mock
|
||||
private ResultSet resultSetMock;
|
||||
|
||||
@InjectMocks
|
||||
private NoIndexCategoryServiceImpl objectUnderTest;
|
||||
private SolrCategoryServiceImpl objectUnderTest;
|
||||
|
||||
@Test
|
||||
public void testGetRootCategoryNodeRef()
|
||||
@@ -90,4 +121,150 @@ public class SolrCategoryServiceImplTest
|
||||
assertTrue(rooCategoryNodeRef.isPresent());
|
||||
assertEquals(CAT_ROOT_NODE_ID, rooCategoryNodeRef.get().getId());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetTopCategories()
|
||||
{
|
||||
given(indexerAndSearcherMock.getSearcher(STORE_REF, false)).willReturn(searcherMock);
|
||||
final QName aspectGenClassifiable = ContentModel.ASPECT_GEN_CLASSIFIABLE;
|
||||
mockAspectDefinition(ContentModel.PROP_CATEGORIES, null);
|
||||
given(dictionaryServiceMock.getAspect(aspectGenClassifiable)).willReturn(aspectDefinitionMock);
|
||||
final QName categoryProperty = ContentModel.PROP_CATEGORIES;
|
||||
final String field = getField(categoryProperty);
|
||||
final int count = 100;
|
||||
final SearchParameters searchParameters = prepareSearchParams(STORE_REF, categoryProperty, field, count);
|
||||
final List<Integer> countList = List.of(11, 9, 8);
|
||||
mockResultSet(field, countList);
|
||||
given(searcherMock.query(searchParameters)).willReturn(resultSetMock);
|
||||
given(nodeServiceMock.exists(any(NodeRef.class))).willReturn(true);
|
||||
|
||||
//when
|
||||
final List<Pair<NodeRef, Integer>> topCategories = objectUnderTest.getTopCategories(STORE_REF, aspectGenClassifiable, count);
|
||||
|
||||
then(indexerAndSearcherMock).should().getSearcher(STORE_REF, false);
|
||||
then(indexerAndSearcherMock).shouldHaveNoMoreInteractions();
|
||||
then(dictionaryServiceMock).should().getAspect(aspectGenClassifiable);
|
||||
then(dictionaryServiceMock).shouldHaveNoMoreInteractions();
|
||||
then(searcherMock).should().query(searchParameters);
|
||||
then(searcherMock).shouldHaveNoMoreInteractions();
|
||||
then(nodeServiceMock).should(times(3)).exists(any(NodeRef.class));
|
||||
then(nodeServiceMock).shouldHaveNoMoreInteractions();
|
||||
|
||||
IntStream.range(0, countList.size())
|
||||
.forEach(i -> {
|
||||
final NodeRef nodeRef = new NodeRef(StoreRef.STORE_REF_WORKSPACE_SPACESSTORE, NODE_ID_PREFIX + i);
|
||||
assertEquals(nodeRef, topCategories.get(i).getFirst());
|
||||
assertEquals(countList.get(i), topCategories.get(i).getSecond());
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetTopCategories_nonExistingAspect()
|
||||
{
|
||||
final QName aspectGenClassifiable = ContentModel.ASPECT_GEN_CLASSIFIABLE;
|
||||
given(dictionaryServiceMock.getAspect(aspectGenClassifiable)).willReturn(null);
|
||||
|
||||
//when
|
||||
assertThrows(IllegalStateException.class, () -> objectUnderTest.getTopCategories(STORE_REF, aspectGenClassifiable, 100));
|
||||
|
||||
then(indexerAndSearcherMock).shouldHaveNoInteractions();
|
||||
then(dictionaryServiceMock).should().getAspect(aspectGenClassifiable);
|
||||
then(dictionaryServiceMock).shouldHaveNoMoreInteractions();
|
||||
then(searcherMock).shouldHaveNoInteractions();
|
||||
then(nodeServiceMock).shouldHaveNoInteractions();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetTopCategories_customCategoryAspect()
|
||||
{
|
||||
given(indexerAndSearcherMock.getSearcher(STORE_REF, false)).willReturn(searcherMock);
|
||||
final QName aspectCustomCategories = QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "customcategories");
|
||||
mockAspectDefinition(aspectCustomCategories, DataTypeDefinition.CATEGORY);
|
||||
given(dictionaryServiceMock.getAspect(aspectCustomCategories)).willReturn(aspectDefinitionMock);
|
||||
final String field = getField(aspectCustomCategories);
|
||||
final int count = 100;
|
||||
final SearchParameters searchParameters = prepareSearchParams(STORE_REF, aspectCustomCategories, field, count);
|
||||
final List<Integer> countList = List.of(11, 9, 8);
|
||||
mockResultSet(field, countList);
|
||||
given(searcherMock.query(searchParameters)).willReturn(resultSetMock);
|
||||
given(nodeServiceMock.exists(any(NodeRef.class))).willReturn(true);
|
||||
|
||||
//when
|
||||
final List<Pair<NodeRef, Integer>> topCategories = objectUnderTest.getTopCategories(STORE_REF, aspectCustomCategories, count);
|
||||
|
||||
then(indexerAndSearcherMock).should().getSearcher(STORE_REF, false);
|
||||
then(indexerAndSearcherMock).shouldHaveNoMoreInteractions();
|
||||
then(dictionaryServiceMock).should().getAspect(aspectCustomCategories);
|
||||
then(dictionaryServiceMock).shouldHaveNoMoreInteractions();
|
||||
then(searcherMock).should().query(searchParameters);
|
||||
then(searcherMock).shouldHaveNoMoreInteractions();
|
||||
then(nodeServiceMock).should(times(3)).exists(any(NodeRef.class));
|
||||
then(nodeServiceMock).shouldHaveNoMoreInteractions();
|
||||
|
||||
IntStream.range(0, countList.size())
|
||||
.forEach(i -> {
|
||||
final NodeRef nodeRef = new NodeRef(StoreRef.STORE_REF_WORKSPACE_SPACESSTORE, NODE_ID_PREFIX + i);
|
||||
assertEquals(nodeRef, topCategories.get(i).getFirst());
|
||||
assertEquals(countList.get(i), topCategories.get(i).getSecond());
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetTopCategories_invalidCustomCategoryAspect()
|
||||
{
|
||||
final QName aspectCustomCategories = QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "customcategories");
|
||||
mockAspectDefinition(aspectCustomCategories, DataTypeDefinition.QNAME);
|
||||
given(dictionaryServiceMock.getAspect(aspectCustomCategories)).willReturn(aspectDefinitionMock);
|
||||
final String field = getField(aspectCustomCategories);
|
||||
final int count = 100;
|
||||
|
||||
//when
|
||||
assertThrows(IllegalStateException.class, () -> objectUnderTest.getTopCategories(STORE_REF, aspectCustomCategories, count));
|
||||
|
||||
then(indexerAndSearcherMock).shouldHaveNoInteractions();
|
||||
then(dictionaryServiceMock).should().getAspect(aspectCustomCategories);
|
||||
then(dictionaryServiceMock).shouldHaveNoMoreInteractions();
|
||||
then(searcherMock).shouldHaveNoInteractions();
|
||||
then(nodeServiceMock).shouldHaveNoInteractions();
|
||||
}
|
||||
|
||||
private String getField(QName categoryProperty)
|
||||
{
|
||||
return "@" + categoryProperty;
|
||||
}
|
||||
|
||||
private SearchParameters prepareSearchParams(StoreRef storeRef, QName categoryProperty, String field, int count)
|
||||
{
|
||||
final SearchParameters sp = new SearchParameters();
|
||||
sp.setLanguage(SearchService.LANGUAGE_INDEX_FTS_ALFRESCO);
|
||||
sp.addStore(storeRef);
|
||||
sp.setQuery(categoryProperty + ":*");
|
||||
final SearchParameters.FieldFacet ff = new SearchParameters.FieldFacet(field);
|
||||
ff.setLimitOrNull(count);
|
||||
sp.addFieldFacet(ff);
|
||||
sp.setMaxItems(1);
|
||||
sp.setSkipCount(0);
|
||||
return sp;
|
||||
}
|
||||
|
||||
private void mockResultSet(final String field, List<Integer> countList)
|
||||
{
|
||||
final List<Pair<String, Integer>> facetedResults = IntStream.range(0, countList.size())
|
||||
.mapToObj(i -> new Pair<>(StoreRef.STORE_REF_WORKSPACE_SPACESSTORE + "/" + NODE_ID_PREFIX + i, countList.get(i)))
|
||||
.collect(Collectors.toList());
|
||||
given(resultSetMock.getFieldFacet(field)).willReturn(CollectionUtils.isEmpty(facetedResults) ? null : facetedResults);
|
||||
}
|
||||
|
||||
private void mockAspectDefinition(final QName qName, final QName dataType)
|
||||
{
|
||||
final PropertyDefinition propertyDefinitionMock = mock(PropertyDefinition.class);
|
||||
final DataTypeDefinition dataTypeMock = mock(DataTypeDefinition.class);
|
||||
if (dataType != null)
|
||||
{
|
||||
given(propertyDefinitionMock.getDataType()).willReturn(dataTypeMock);
|
||||
given(dataTypeMock.getName()).willReturn(dataType);
|
||||
}
|
||||
given(aspectDefinitionMock.getProperties()).willReturn(Map.of(qName, propertyDefinitionMock));
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -26,15 +26,12 @@
|
||||
package org.alfresco.repo.security.authentication;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.Arrays;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import javax.transaction.Status;
|
||||
import javax.transaction.UserTransaction;
|
||||
|
||||
@@ -48,7 +45,6 @@ import net.sf.acegisecurity.DisabledException;
|
||||
import net.sf.acegisecurity.LockedException;
|
||||
import net.sf.acegisecurity.UserDetails;
|
||||
import net.sf.acegisecurity.providers.UsernamePasswordAuthenticationToken;
|
||||
|
||||
import org.alfresco.error.AlfrescoRuntimeException;
|
||||
import org.alfresco.model.ContentModel;
|
||||
import org.alfresco.repo.admin.SysAdminParamsImpl;
|
||||
@@ -519,50 +515,48 @@ public class AuthenticationTest extends TestCase
|
||||
assertTrue("The user should exist", dao.userExists(userName));
|
||||
}
|
||||
|
||||
public void testCreateAndyUserAndOtherCRUD() throws NoSuchAlgorithmException, UnsupportedEncodingException
|
||||
public void testCreateAndyUserAndUpdatePassword()
|
||||
{
|
||||
RepositoryAuthenticationDao dao = createRepositoryAuthenticationDao();
|
||||
|
||||
dao.createUser("Andy", "cabbage".toCharArray());
|
||||
assertNotNull(dao.getUserOrNull("Andy"));
|
||||
|
||||
UserDetails AndyDetails = (UserDetails) dao.loadUserByUsername("Andy");
|
||||
assertNotNull(AndyDetails);
|
||||
assertEquals("Andy", AndyDetails.getUsername());
|
||||
// assertNotNull(dao.getSalt(AndyDetails));
|
||||
assertTrue(AndyDetails.isAccountNonExpired());
|
||||
assertTrue(AndyDetails.isAccountNonLocked());
|
||||
assertTrue(AndyDetails.isCredentialsNonExpired());
|
||||
assertTrue(AndyDetails.isEnabled());
|
||||
assertNotSame("cabbage", AndyDetails.getPassword());
|
||||
assertTrue(compositePasswordEncoder.matches(compositePasswordEncoder.getPreferredEncoding(),"cabbage", AndyDetails.getPassword(), null));
|
||||
assertEquals(1, AndyDetails.getAuthorities().length);
|
||||
RepositoryAuthenticatedUser andyDetails = (RepositoryAuthenticatedUser) dao.loadUserByUsername("Andy");
|
||||
assertNotNull("User unexpectedly null", andyDetails);
|
||||
assertEquals("Unexpected username", "Andy", andyDetails.getUsername());
|
||||
Object originalSalt = andyDetails.getSalt();
|
||||
assertNotNull("Salt was not generated", originalSalt);
|
||||
assertTrue("Account unexpectedly expired", andyDetails.isAccountNonExpired());
|
||||
assertTrue("Account unexpectedly locked", andyDetails.isAccountNonLocked());
|
||||
assertTrue("Credentials unexpectedly expired", andyDetails.isCredentialsNonExpired());
|
||||
assertTrue("User unexpectedly disabled", andyDetails.isEnabled());
|
||||
assertNotSame("Password was not hashed", "cabbage", andyDetails.getPassword());
|
||||
assertTrue("Failed to recalculate same password hash", compositePasswordEncoder.matches(compositePasswordEncoder.getPreferredEncoding(),"cabbage", andyDetails.getPassword(), originalSalt));
|
||||
assertEquals("User does not have a single authority", 1, andyDetails.getAuthorities().length);
|
||||
|
||||
// Object oldSalt = dao.getSalt(AndyDetails);
|
||||
dao.updateUser("Andy", "carrot".toCharArray());
|
||||
UserDetails newDetails = (UserDetails) dao.loadUserByUsername("Andy");
|
||||
assertNotNull(newDetails);
|
||||
assertEquals("Andy", newDetails.getUsername());
|
||||
// assertNotNull(dao.getSalt(newDetails));
|
||||
assertTrue(newDetails.isAccountNonExpired());
|
||||
assertTrue(newDetails.isAccountNonLocked());
|
||||
assertTrue(newDetails.isCredentialsNonExpired());
|
||||
assertTrue(newDetails.isEnabled());
|
||||
assertNotSame("carrot", newDetails.getPassword());
|
||||
assertEquals(1, newDetails.getAuthorities().length);
|
||||
RepositoryAuthenticatedUser newDetails = (RepositoryAuthenticatedUser) dao.loadUserByUsername("Andy");
|
||||
assertNotNull("New details were null", newDetails);
|
||||
assertEquals("New details contain wrong username", "Andy", newDetails.getUsername());
|
||||
Object updatedSalt = newDetails.getSalt();
|
||||
assertNotNull("New details contain null salt", updatedSalt);
|
||||
assertTrue("Updated account is expired", newDetails.isAccountNonExpired());
|
||||
assertTrue("Updated account is locked", newDetails.isAccountNonLocked());
|
||||
assertTrue("Updated account has expired credentials", newDetails.isCredentialsNonExpired());
|
||||
assertTrue("Updated account is not enabled", newDetails.isEnabled());
|
||||
assertNotSame("Updated account contains unhashed password", "carrot", newDetails.getPassword());
|
||||
assertEquals("Updated account should have a single authority", 1, newDetails.getAuthorities().length);
|
||||
assertTrue("Failed to validate updated password hash", compositePasswordEncoder.matches(compositePasswordEncoder.getPreferredEncoding(),"carrot", newDetails.getPassword(), updatedSalt));
|
||||
assertNotSame("Expected salt to be replaced when password was updated", originalSalt, updatedSalt);
|
||||
|
||||
assertNotSame(AndyDetails.getPassword(), newDetails.getPassword());
|
||||
RepositoryAuthenticatedUser rau = (RepositoryAuthenticatedUser) newDetails;
|
||||
assertTrue(compositePasswordEncoder.matchesPassword("carrot", newDetails.getPassword(), null, rau.getHashIndicator()));
|
||||
// assertNotSame(oldSalt, dao.getSalt(newDetails));
|
||||
|
||||
//Update again
|
||||
dao.updateUser("Andy", "potato".toCharArray());
|
||||
newDetails = (UserDetails) dao.loadUserByUsername("Andy");
|
||||
assertNotNull(newDetails);
|
||||
assertEquals("Andy", newDetails.getUsername());
|
||||
rau = (RepositoryAuthenticatedUser) newDetails;
|
||||
assertTrue(compositePasswordEncoder.matchesPassword("potato", newDetails.getPassword(), null, rau.getHashIndicator()));
|
||||
// Update back to first password again.
|
||||
dao.updateUser("Andy", "cabbage".toCharArray());
|
||||
RepositoryAuthenticatedUser thirdDetails = (RepositoryAuthenticatedUser) dao.loadUserByUsername("Andy");
|
||||
Object thirdSalt = thirdDetails.getSalt();
|
||||
assertNotSame("New salt should not match original salt", thirdSalt, originalSalt);
|
||||
assertNotSame("New salt should not match previous salt", thirdSalt, updatedSalt);
|
||||
assertTrue("New password hash was not reproducible", compositePasswordEncoder.matches(compositePasswordEncoder.getPreferredEncoding(), "cabbage", thirdDetails.getPassword(), thirdSalt));
|
||||
|
||||
dao.deleteUser("Andy");
|
||||
assertFalse("Should not be a cache entry for 'Andy'.", authenticationCache.contains("Andy"));
|
||||
@@ -1989,131 +1983,142 @@ public class AuthenticationTest extends TestCase
|
||||
* Tests the scenario where a user logs in after the system has been upgraded.
|
||||
* Their password should get re-hashed using the preferred encoding.
|
||||
*/
|
||||
public void testRehashedPasswordOnAuthentication() throws Exception
|
||||
public void testRehashedPasswordOnAuthentication()
|
||||
{
|
||||
// create the Andy authentication
|
||||
assertNull(authenticationComponent.getCurrentAuthentication());
|
||||
authenticationComponent.setSystemUserAsCurrentUser();
|
||||
pubAuthenticationService.createAuthentication("Andy", "auth1".toCharArray());
|
||||
|
||||
// find the node representing the Andy user and it's properties
|
||||
NodeRef andyUserNodeRef = getRepositoryAuthenticationDao(). getUserOrNull("Andy");
|
||||
assertNotNull(andyUserNodeRef);
|
||||
|
||||
// ensure the properties are in the state we're expecting
|
||||
Map<QName, Serializable> userProps = nodeService.getProperties(andyUserNodeRef);
|
||||
String passwordProp = (String)userProps.get(ContentModel.PROP_PASSWORD);
|
||||
assertNull("Expected the password property to be null", passwordProp);
|
||||
String password2Prop = (String)userProps.get(ContentModel.PROP_PASSWORD_SHA256);
|
||||
assertNull("Expected the password2 property to be null", password2Prop);
|
||||
String passwordHashProp = (String)userProps.get(ContentModel.PROP_PASSWORD_HASH);
|
||||
assertNotNull("Expected the passwordHash property to be populated", passwordHashProp);
|
||||
List<String> hashIndicatorProp = (List<String>)userProps.get(ContentModel.PROP_HASH_INDICATOR);
|
||||
assertNotNull("Expected the hashIndicator property to be populated", hashIndicatorProp);
|
||||
|
||||
// re-generate an md4 hashed password
|
||||
MD4PasswordEncoderImpl md4PasswordEncoder = new MD4PasswordEncoderImpl();
|
||||
String md4Password = md4PasswordEncoder.encodePassword("auth1", null);
|
||||
|
||||
// re-generate a sha256 hashed password
|
||||
String salt = (String)userProps.get(ContentModel.PROP_SALT);
|
||||
ShaPasswordEncoderImpl sha256PasswordEncoder = new ShaPasswordEncoderImpl(256);
|
||||
String sha256Password = sha256PasswordEncoder.encodePassword("auth1", salt);
|
||||
|
||||
// change the underlying user object to represent state in previous release
|
||||
userProps.put(ContentModel.PROP_PASSWORD, md4Password);
|
||||
userProps.put(ContentModel.PROP_PASSWORD_SHA256, sha256Password);
|
||||
userProps.remove(ContentModel.PROP_PASSWORD_HASH);
|
||||
userProps.remove(ContentModel.PROP_HASH_INDICATOR);
|
||||
nodeService.setProperties(andyUserNodeRef, userProps);
|
||||
|
||||
// make sure the changes took effect
|
||||
Map<QName, Serializable> updatedProps = nodeService.getProperties(andyUserNodeRef);
|
||||
String usernameProp = (String)updatedProps.get(ContentModel.PROP_USER_USERNAME);
|
||||
assertEquals("Expected the username property to be 'Andy'", "Andy", usernameProp);
|
||||
passwordProp = (String)updatedProps.get(ContentModel.PROP_PASSWORD);
|
||||
assertNotNull("Expected the password property to be populated", passwordProp);
|
||||
password2Prop = (String)updatedProps.get(ContentModel.PROP_PASSWORD_SHA256);
|
||||
assertNotNull("Expected the password2 property to be populated", password2Prop);
|
||||
passwordHashProp = (String)updatedProps.get(ContentModel.PROP_PASSWORD_HASH);
|
||||
assertNull("Expected the passwordHash property to be null", passwordHashProp);
|
||||
hashIndicatorProp = (List<String>)updatedProps.get(ContentModel.PROP_HASH_INDICATOR);
|
||||
assertNull("Expected the hashIndicator property to be null", hashIndicatorProp);
|
||||
|
||||
// authenticate the user
|
||||
authenticationComponent.clearCurrentSecurityContext();
|
||||
pubAuthenticationService.authenticate("Andy", "auth1".toCharArray());
|
||||
assertEquals("Andy", authenticationService.getCurrentUserName());
|
||||
|
||||
// commit the transaction to invoke the password hashing of the user
|
||||
userTransaction.commit();
|
||||
|
||||
// start another transaction and change to system user
|
||||
userTransaction = transactionService.getUserTransaction();
|
||||
userTransaction.begin();
|
||||
authenticationComponent.setSystemUserAsCurrentUser();
|
||||
|
||||
// verify that the new properties are populated and the old ones are cleaned up
|
||||
Map<QName, Serializable> upgradedProps = nodeService.getProperties(andyUserNodeRef);
|
||||
passwordProp = (String)upgradedProps.get(ContentModel.PROP_PASSWORD);
|
||||
assertNull("Expected the password property to be null", passwordProp);
|
||||
password2Prop = (String)upgradedProps.get(ContentModel.PROP_PASSWORD_SHA256);
|
||||
assertNull("Expected the password2 property to be null", password2Prop);
|
||||
passwordHashProp = (String)upgradedProps.get(ContentModel.PROP_PASSWORD_HASH);
|
||||
assertNotNull("Expected the passwordHash property to be populated", passwordHashProp);
|
||||
hashIndicatorProp = (List<String>)upgradedProps.get(ContentModel.PROP_HASH_INDICATOR);
|
||||
assertNotNull("Expected the hashIndicator property to be populated", hashIndicatorProp);
|
||||
assertTrue("Expected there to be a single hash indicator entry", (hashIndicatorProp.size() == 1));
|
||||
String preferredEncoding = compositePasswordEncoder.getPreferredEncoding();
|
||||
String hashEncoding = (String)hashIndicatorProp.get(0);
|
||||
assertEquals("Expected hash indicator to be '" + preferredEncoding + "' but it was: " + hashEncoding,
|
||||
// This test requires upgrading from md4 to sha256 hashing.
|
||||
String defaultPreferredEncoding = compositePasswordEncoder.getPreferredEncoding();
|
||||
compositePasswordEncoder.setPreferredEncoding("md4");
|
||||
|
||||
try
|
||||
{
|
||||
// create the Andy authentication
|
||||
assertNull(authenticationComponent.getCurrentAuthentication());
|
||||
authenticationComponent.setSystemUserAsCurrentUser();
|
||||
pubAuthenticationService.createAuthentication("Andy", "auth1".toCharArray());
|
||||
|
||||
// find the node representing the Andy user and its properties
|
||||
NodeRef andyUserNodeRef = getRepositoryAuthenticationDao().getUserOrNull("Andy");
|
||||
assertNotNull(andyUserNodeRef);
|
||||
|
||||
// ensure the properties are in the state we're expecting
|
||||
Map<QName, Serializable> userProps = nodeService.getProperties(andyUserNodeRef);
|
||||
String passwordProp = (String) userProps.get(ContentModel.PROP_PASSWORD);
|
||||
assertNull("Expected the password property to be null", passwordProp);
|
||||
String password2Prop = (String) userProps.get(ContentModel.PROP_PASSWORD_SHA256);
|
||||
assertNull("Expected the password2 property to be null", password2Prop);
|
||||
String passwordHashProp = (String) userProps.get(ContentModel.PROP_PASSWORD_HASH);
|
||||
assertNotNull("Expected the passwordHash property to be populated", passwordHashProp);
|
||||
List<String> hashIndicatorProp = (List<String>) userProps.get(ContentModel.PROP_HASH_INDICATOR);
|
||||
assertNotNull("Expected the hashIndicator property to be populated", hashIndicatorProp);
|
||||
|
||||
// re-generate an md4 hashed password
|
||||
MD4PasswordEncoderImpl md4PasswordEncoder = new MD4PasswordEncoderImpl();
|
||||
String md4Password = md4PasswordEncoder.encodePassword("auth1", null);
|
||||
|
||||
// re-generate a sha256 hashed password
|
||||
String salt = (String) userProps.get(ContentModel.PROP_SALT);
|
||||
ShaPasswordEncoderImpl sha256PasswordEncoder = new ShaPasswordEncoderImpl(256);
|
||||
String sha256Password = sha256PasswordEncoder.encodePassword("auth1", salt);
|
||||
|
||||
// change the underlying user object to represent state in previous release
|
||||
userProps.put(ContentModel.PROP_PASSWORD, md4Password);
|
||||
userProps.put(ContentModel.PROP_PASSWORD_SHA256, sha256Password);
|
||||
userProps.remove(ContentModel.PROP_PASSWORD_HASH);
|
||||
userProps.remove(ContentModel.PROP_HASH_INDICATOR);
|
||||
nodeService.setProperties(andyUserNodeRef, userProps);
|
||||
|
||||
// make sure the changes took effect
|
||||
Map<QName, Serializable> updatedProps = nodeService.getProperties(andyUserNodeRef);
|
||||
String usernameProp = (String) updatedProps.get(ContentModel.PROP_USER_USERNAME);
|
||||
assertEquals("Expected the username property to be 'Andy'", "Andy", usernameProp);
|
||||
passwordProp = (String) updatedProps.get(ContentModel.PROP_PASSWORD);
|
||||
assertNotNull("Expected the password property to be populated", passwordProp);
|
||||
password2Prop = (String) updatedProps.get(ContentModel.PROP_PASSWORD_SHA256);
|
||||
assertNotNull("Expected the password2 property to be populated", password2Prop);
|
||||
passwordHashProp = (String) updatedProps.get(ContentModel.PROP_PASSWORD_HASH);
|
||||
assertNull("Expected the passwordHash property to be null", passwordHashProp);
|
||||
hashIndicatorProp = (List<String>) updatedProps.get(ContentModel.PROP_HASH_INDICATOR);
|
||||
assertNull("Expected the hashIndicator property to be null", hashIndicatorProp);
|
||||
|
||||
// authenticate the user
|
||||
authenticationComponent.clearCurrentSecurityContext();
|
||||
pubAuthenticationService.authenticate("Andy", "auth1".toCharArray());
|
||||
assertEquals("Andy", authenticationService.getCurrentUserName());
|
||||
|
||||
// commit the transaction to invoke the password hashing of the user
|
||||
userTransaction.commit();
|
||||
|
||||
// start another transaction and change to system user
|
||||
userTransaction = transactionService.getUserTransaction();
|
||||
userTransaction.begin();
|
||||
authenticationComponent.setSystemUserAsCurrentUser();
|
||||
|
||||
// verify that the new properties are populated and the old ones are cleaned up
|
||||
Map<QName, Serializable> upgradedProps = nodeService.getProperties(andyUserNodeRef);
|
||||
passwordProp = (String) upgradedProps.get(ContentModel.PROP_PASSWORD);
|
||||
assertNull("Expected the password property to be null", passwordProp);
|
||||
password2Prop = (String) upgradedProps.get(ContentModel.PROP_PASSWORD_SHA256);
|
||||
assertNull("Expected the password2 property to be null", password2Prop);
|
||||
passwordHashProp = (String) upgradedProps.get(ContentModel.PROP_PASSWORD_HASH);
|
||||
assertNotNull("Expected the passwordHash property to be populated", passwordHashProp);
|
||||
hashIndicatorProp = (List<String>) upgradedProps.get(ContentModel.PROP_HASH_INDICATOR);
|
||||
assertNotNull("Expected the hashIndicator property to be populated", hashIndicatorProp);
|
||||
assertTrue("Expected there to be a single hash indicator entry", (hashIndicatorProp.size() == 1));
|
||||
String preferredEncoding = compositePasswordEncoder.getPreferredEncoding();
|
||||
String hashEncoding = hashIndicatorProp.get(0);
|
||||
assertEquals("Expected hash indicator to be '" + preferredEncoding + "' but it was: " + hashEncoding,
|
||||
preferredEncoding, hashEncoding);
|
||||
|
||||
// delete the user and clear the security context
|
||||
this.deleteAndy();
|
||||
authenticationComponent.clearCurrentSecurityContext();
|
||||
|
||||
// delete the user and clear the security context
|
||||
this.deleteAndy();
|
||||
authenticationComponent.clearCurrentSecurityContext();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
finally
|
||||
{
|
||||
compositePasswordEncoder.setPreferredEncoding(defaultPreferredEncoding);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* For on premise the default is MD4, for cloud BCRYPT10
|
||||
*
|
||||
* @throws Exception
|
||||
* Test password encoding with MD4 without a salt.
|
||||
*/
|
||||
public void testDefaultEncodingIsMD4() throws Exception
|
||||
public void testGetsMD4Password()
|
||||
{
|
||||
assertNotNull(compositePasswordEncoder);
|
||||
assertEquals("md4", compositePasswordEncoder.getPreferredEncoding());
|
||||
}
|
||||
String defaultPreferredEncoding = compositePasswordEncoder.getPreferredEncoding();
|
||||
compositePasswordEncoder.setPreferredEncoding("md4");
|
||||
|
||||
/**
|
||||
* For on premise the default is MD4, get it
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
public void testGetsMD4Password() throws Exception
|
||||
{
|
||||
String user = "mduzer";
|
||||
String rawPass = "roarPazzw0rd";
|
||||
assertEquals("md4", compositePasswordEncoder.getPreferredEncoding());
|
||||
dao.createUser(user, null, rawPass.toCharArray());
|
||||
NodeRef userNodeRef = getRepositoryAuthenticationDao().getUserOrNull(user);
|
||||
assertNotNull(userNodeRef);
|
||||
String pass = dao.getMD4HashedPassword(user);
|
||||
assertNotNull(pass);
|
||||
assertTrue(compositePasswordEncoder.matches("md4", rawPass, pass, null));
|
||||
try
|
||||
{
|
||||
String user = "mduzer";
|
||||
String rawPass = "roarPazzw0rd";
|
||||
dao.createUser(user, null, rawPass.toCharArray());
|
||||
NodeRef userNodeRef = getRepositoryAuthenticationDao().getUserOrNull(user);
|
||||
assertNotNull(userNodeRef);
|
||||
String pass = dao.getMD4HashedPassword(user);
|
||||
assertNotNull(pass);
|
||||
assertTrue(compositePasswordEncoder.matches("md4", rawPass, pass, null));
|
||||
|
||||
Map<QName, Serializable> properties = nodeService.getProperties(userNodeRef);
|
||||
properties.remove(ContentModel.PROP_PASSWORD_HASH);
|
||||
properties.remove(ContentModel.PROP_HASH_INDICATOR);
|
||||
properties.remove(ContentModel.PROP_PASSWORD);
|
||||
properties.remove(ContentModel.PROP_PASSWORD_SHA256);
|
||||
String encoded = compositePasswordEncoder.encode("md4",new String(rawPass), null);
|
||||
properties.put(ContentModel.PROP_PASSWORD, encoded);
|
||||
nodeService.setProperties(userNodeRef, properties);
|
||||
pass = dao.getMD4HashedPassword(user);
|
||||
assertNotNull(pass);
|
||||
assertEquals(encoded, pass);
|
||||
dao.deleteUser(user);
|
||||
Map<QName, Serializable> properties = nodeService.getProperties(userNodeRef);
|
||||
properties.remove(ContentModel.PROP_PASSWORD_HASH);
|
||||
properties.remove(ContentModel.PROP_HASH_INDICATOR);
|
||||
properties.remove(ContentModel.PROP_PASSWORD);
|
||||
properties.remove(ContentModel.PROP_PASSWORD_SHA256);
|
||||
String encoded = compositePasswordEncoder.encodePassword("md4", rawPass, List.of("md4"));
|
||||
properties.put(ContentModel.PROP_PASSWORD, encoded);
|
||||
nodeService.setProperties(userNodeRef, properties);
|
||||
pass = dao.getMD4HashedPassword(user);
|
||||
assertNotNull(pass);
|
||||
assertEquals(encoded, pass);
|
||||
dao.deleteUser(user);
|
||||
}
|
||||
finally
|
||||
{
|
||||
compositePasswordEncoder.setPreferredEncoding(defaultPreferredEncoding);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@@ -2,7 +2,7 @@
|
||||
* #%L
|
||||
* Alfresco Repository
|
||||
* %%
|
||||
* Copyright (C) 2005 - 2018 Alfresco Software Limited
|
||||
* Copyright (C) 2005 - 2023 Alfresco Software Limited
|
||||
* %%
|
||||
* This file is part of the Alfresco software.
|
||||
* If the software was purchased under a paid Alfresco license, the terms of
|
||||
@@ -25,14 +25,16 @@
|
||||
*/
|
||||
package org.alfresco.repo.security.authentication.identityservice;
|
||||
|
||||
import static org.mockito.Mockito.doNothing;
|
||||
import static org.mockito.Mockito.doThrow;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import java.net.ConnectException;
|
||||
|
||||
import org.alfresco.error.ExceptionStackUtil;
|
||||
import org.alfresco.repo.security.authentication.AuthenticationContext;
|
||||
import org.alfresco.repo.security.authentication.AuthenticationException;
|
||||
import org.alfresco.repo.security.authentication.identityservice.IdentityServiceFacade.CredentialsVerificationException;
|
||||
import org.alfresco.repo.security.sync.UserRegistrySynchronizer;
|
||||
import org.alfresco.service.cmr.repository.NodeService;
|
||||
import org.alfresco.service.cmr.security.PersonService;
|
||||
@@ -41,9 +43,6 @@ import org.alfresco.util.BaseSpringTest;
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.keycloak.authorization.client.AuthzClient;
|
||||
import org.keycloak.authorization.client.util.HttpResponseException;
|
||||
import org.keycloak.representations.AccessTokenResponse;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
|
||||
public class IdentityServiceAuthenticationComponentTest extends BaseSpringTest
|
||||
@@ -65,7 +64,7 @@ public class IdentityServiceAuthenticationComponentTest extends BaseSpringTest
|
||||
@Autowired
|
||||
private PersonService personService;
|
||||
|
||||
private AuthzClient mockAuthzClient;
|
||||
private IdentityServiceFacade mockIdentityServiceFacade;
|
||||
|
||||
@Before
|
||||
public void setUp()
|
||||
@@ -76,8 +75,8 @@ public class IdentityServiceAuthenticationComponentTest extends BaseSpringTest
|
||||
authComponent.setNodeService(nodeService);
|
||||
authComponent.setPersonService(personService);
|
||||
|
||||
mockAuthzClient = mock(AuthzClient.class);
|
||||
authComponent.setAuthenticatorAuthzClient(mockAuthzClient);
|
||||
mockIdentityServiceFacade = mock(IdentityServiceFacade.class);
|
||||
authComponent.setIdentityServiceFacade(mockIdentityServiceFacade);
|
||||
}
|
||||
|
||||
@After
|
||||
@@ -89,8 +88,9 @@ public class IdentityServiceAuthenticationComponentTest extends BaseSpringTest
|
||||
@Test (expected=AuthenticationException.class)
|
||||
public void testAuthenticationFail()
|
||||
{
|
||||
when(mockAuthzClient.obtainAccessToken("username", "password"))
|
||||
.thenThrow(new HttpResponseException("Unauthorized", 401, "Unauthorized", null));
|
||||
doThrow(new CredentialsVerificationException("Failed"))
|
||||
.when(mockIdentityServiceFacade)
|
||||
.verifyCredentials("username", "password");
|
||||
|
||||
authComponent.authenticateImpl("username", "password".toCharArray());
|
||||
}
|
||||
@@ -98,8 +98,9 @@ public class IdentityServiceAuthenticationComponentTest extends BaseSpringTest
|
||||
@Test(expected = AuthenticationException.class)
|
||||
public void testAuthenticationFail_connectionException()
|
||||
{
|
||||
when(mockAuthzClient.obtainAccessToken("username", "password")).thenThrow(
|
||||
new RuntimeException("Couldn't connect to server", new ConnectException("ConnectionRefused")));
|
||||
doThrow(new CredentialsVerificationException("Couldn't connect to server", new ConnectException("ConnectionRefused")))
|
||||
.when(mockIdentityServiceFacade)
|
||||
.verifyCredentials("username", "password");
|
||||
|
||||
try
|
||||
{
|
||||
@@ -116,8 +117,9 @@ public class IdentityServiceAuthenticationComponentTest extends BaseSpringTest
|
||||
@Test (expected=AuthenticationException.class)
|
||||
public void testAuthenticationFail_otherException()
|
||||
{
|
||||
when(mockAuthzClient.obtainAccessToken("username", "password"))
|
||||
.thenThrow(new RuntimeException("Some other errors!"));
|
||||
doThrow(new RuntimeException("Some other errors!"))
|
||||
.when(mockIdentityServiceFacade)
|
||||
.verifyCredentials("username", "password");
|
||||
|
||||
authComponent.authenticateImpl("username", "password".toCharArray());
|
||||
}
|
||||
@@ -125,8 +127,7 @@ public class IdentityServiceAuthenticationComponentTest extends BaseSpringTest
|
||||
@Test
|
||||
public void testAuthenticationPass()
|
||||
{
|
||||
when(mockAuthzClient.obtainAccessToken("username", "password"))
|
||||
.thenReturn(new AccessTokenResponse());
|
||||
doNothing().when(mockIdentityServiceFacade).verifyCredentials("username", "password");
|
||||
|
||||
authComponent.authenticateImpl("username", "password".toCharArray());
|
||||
|
||||
@@ -135,9 +136,9 @@ public class IdentityServiceAuthenticationComponentTest extends BaseSpringTest
|
||||
}
|
||||
|
||||
@Test (expected= AuthenticationException.class)
|
||||
public void testFallthroughWhenAuthzClientIsNull()
|
||||
public void testFallthroughWhenIdentityServiceFacadeIsNull()
|
||||
{
|
||||
authComponent.setAuthenticatorAuthzClient(null);
|
||||
authComponent.setIdentityServiceFacade(null);
|
||||
authComponent.authenticateImpl("username", "password".toCharArray());
|
||||
}
|
||||
|
||||
|
@@ -2,7 +2,7 @@
|
||||
* #%L
|
||||
* Alfresco Repository
|
||||
* %%
|
||||
* Copyright (C) 2005 - 2016 Alfresco Software Limited
|
||||
* Copyright (C) 2005 - 2023 Alfresco Software Limited
|
||||
* %%
|
||||
* This file is part of the Alfresco software.
|
||||
* If the software was purchased under a paid Alfresco license, the terms of
|
||||
@@ -25,377 +25,89 @@
|
||||
*/
|
||||
package org.alfresco.repo.security.authentication.identityservice;
|
||||
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static java.util.Optional.ofNullable;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
|
||||
import static org.mockito.ArgumentMatchers.anyString;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.net.InetAddress;
|
||||
import java.net.NetworkInterface;
|
||||
import java.net.SocketException;
|
||||
import java.security.KeyPair;
|
||||
import java.security.KeyPairGenerator;
|
||||
import java.security.PublicKey;
|
||||
import java.util.Enumeration;
|
||||
import java.util.Map;
|
||||
import java.util.Vector;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
|
||||
import org.alfresco.repo.management.subsystems.AbstractChainedSubsystemTest;
|
||||
import org.alfresco.repo.management.subsystems.ChildApplicationContextFactory;
|
||||
import org.alfresco.repo.management.subsystems.DefaultChildApplicationContextManager;
|
||||
import junit.framework.TestCase;
|
||||
import org.alfresco.repo.security.authentication.AuthenticationException;
|
||||
import org.alfresco.repo.security.authentication.external.RemoteUserMapper;
|
||||
import org.alfresco.repo.security.authentication.identityservice.IdentityServiceConfig;
|
||||
import org.alfresco.util.ApplicationContextHelper;
|
||||
import org.apache.http.HttpEntity;
|
||||
import org.apache.http.HttpResponse;
|
||||
import org.apache.http.StatusLine;
|
||||
import org.apache.http.client.HttpClient;
|
||||
import org.keycloak.adapters.KeycloakDeployment;
|
||||
import org.keycloak.adapters.rotation.HardcodedPublicKeyLocator;
|
||||
import org.keycloak.common.util.Base64;
|
||||
import org.keycloak.common.util.Time;
|
||||
import org.keycloak.jose.jws.JWSBuilder;
|
||||
import org.keycloak.representations.AccessToken;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.alfresco.repo.security.authentication.identityservice.IdentityServiceFacade.TokenException;
|
||||
import org.alfresco.service.cmr.security.PersonService;
|
||||
import org.springframework.security.oauth2.server.resource.web.DefaultBearerTokenResolver;
|
||||
|
||||
/**
|
||||
* Tests the Identity Service based authentication subsystem.
|
||||
*
|
||||
* @author Gavin Cornwell
|
||||
*/
|
||||
public class IdentityServiceRemoteUserMapperTest extends AbstractChainedSubsystemTest
|
||||
public class IdentityServiceRemoteUserMapperTest extends TestCase
|
||||
{
|
||||
private static final String REMOTE_USER_MAPPER_BEAN_NAME = "remoteUserMapper";
|
||||
private static final String DEPLOYMENT_BEAN_NAME = "identityServiceDeployment";
|
||||
private static final String CONFIG_BEAN_NAME = "identityServiceConfig";
|
||||
|
||||
private static final String TEST_USER_USERNAME = "testuser";
|
||||
private static final String TEST_USER_EMAIL = "testuser@mail.com";
|
||||
|
||||
private static final String AUTHORIZATION_HEADER = "Authorization";
|
||||
private static final String BEARER_PREFIX = "Bearer ";
|
||||
private static final String BASIC_PREFIX = "Basic ";
|
||||
|
||||
private static final String CONFIG_SILENT_ERRORS = "identity-service.authentication.validation.failure.silent";
|
||||
|
||||
private static final String PASSWORD_GRANT_RESPONSE = "{" +
|
||||
"\"access_token\": \"%s\"," +
|
||||
"\"expires_in\": 300," +
|
||||
"\"refresh_expires_in\": 1800," +
|
||||
"\"refresh_token\": \"%s\"," +
|
||||
"\"token_type\": \"bearer\"," +
|
||||
"\"not-before-policy\": 0," +
|
||||
"\"session_state\": \"71c2c5ba-9c98-49fc-882f-dedcf80ee1b5\"}";
|
||||
|
||||
ApplicationContext ctx = ApplicationContextHelper.getApplicationContext();
|
||||
DefaultChildApplicationContextManager childApplicationContextManager;
|
||||
ChildApplicationContextFactory childApplicationContextFactory;
|
||||
|
||||
private KeyPair keyPair;
|
||||
private IdentityServiceConfig identityServiceConfig;
|
||||
|
||||
@Override
|
||||
protected void setUp() throws Exception
|
||||
public void testValidToken()
|
||||
{
|
||||
// switch authentication to use token auth
|
||||
childApplicationContextManager = (DefaultChildApplicationContextManager) ctx.getBean("Authentication");
|
||||
childApplicationContextManager.stop();
|
||||
childApplicationContextManager.setProperty("chain", "identity-service1:identity-service");
|
||||
childApplicationContextFactory = getChildApplicationContextFactory(childApplicationContextManager, "identity-service1");
|
||||
|
||||
// generate keys for test
|
||||
this.keyPair = KeyPairGenerator.getInstance("RSA").generateKeyPair();
|
||||
|
||||
// hardcode the realm public key in the deployment bean to stop it fetching keys
|
||||
applyHardcodedPublicKey(this.keyPair.getPublic());
|
||||
|
||||
// extract config
|
||||
this.identityServiceConfig = (IdentityServiceConfig)childApplicationContextFactory.
|
||||
getApplicationContext().getBean(CONFIG_BEAN_NAME);
|
||||
final IdentityServiceRemoteUserMapper mapper = givenMapper(Map.of("VaLiD-ToKeN", () -> "johny"));
|
||||
|
||||
HttpServletRequest mockRequest = createMockTokenRequest("VaLiD-ToKeN");
|
||||
|
||||
final String user = mapper.getRemoteUser(mockRequest);
|
||||
assertEquals("johny", user);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void tearDown() throws Exception
|
||||
public void testWrongTokenWithSilentValidation()
|
||||
{
|
||||
childApplicationContextManager.destroy();
|
||||
childApplicationContextManager = null;
|
||||
childApplicationContextFactory = null;
|
||||
final IdentityServiceRemoteUserMapper mapper = givenMapper(Map.of("WrOnG-ToKeN", () -> {throw new TokenException("Expected ");}));
|
||||
mapper.setValidationFailureSilent(true);
|
||||
|
||||
HttpServletRequest mockRequest = createMockTokenRequest("WrOnG-ToKeN");
|
||||
|
||||
final String user = mapper.getRemoteUser(mockRequest);
|
||||
assertNull(user);
|
||||
}
|
||||
|
||||
public void testKeycloakConfig() throws Exception
|
||||
public void testWrongTokenWithoutSilentValidation()
|
||||
{
|
||||
//Get the host of the IDS test server
|
||||
String ip = "localhost";
|
||||
try {
|
||||
Enumeration<NetworkInterface> interfaces = NetworkInterface.getNetworkInterfaces();
|
||||
while (interfaces.hasMoreElements()) {
|
||||
NetworkInterface iface = interfaces.nextElement();
|
||||
// filters out 127.0.0.1 and inactive interfaces
|
||||
if (iface.isLoopback() || !iface.isUp())
|
||||
continue;
|
||||
final IdentityServiceRemoteUserMapper mapper = givenMapper(Map.of("WrOnG-ToKeN", () -> {throw new TokenException("Expected");}));
|
||||
mapper.setValidationFailureSilent(false);
|
||||
|
||||
Enumeration<InetAddress> addresses = iface.getInetAddresses();
|
||||
while(addresses.hasMoreElements()) {
|
||||
InetAddress addr = addresses.nextElement();
|
||||
if(Pattern.matches("([0-9]{1,3}\\.){3}[0-9]{1,3}", addr.getHostAddress())){
|
||||
ip = addr.getHostAddress();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (SocketException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
HttpServletRequest mockRequest = createMockTokenRequest("WrOnG-ToKeN");
|
||||
|
||||
// check string overrides
|
||||
assertEquals("identity-service.auth-server-url", "http://"+ip+":8999/auth",
|
||||
this.identityServiceConfig.getAuthServerUrl());
|
||||
|
||||
assertEquals("identity-service.realm", "alfresco",
|
||||
this.identityServiceConfig.getRealm());
|
||||
assertThatExceptionOfType(AuthenticationException.class)
|
||||
.isThrownBy(() -> mapper.getRemoteUser(mockRequest))
|
||||
.havingCause().withNoCause().withMessage("Expected");
|
||||
}
|
||||
|
||||
assertEquals("identity-service.realm-public-key",
|
||||
"MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvWLQxipXNe6cLnVPGy7l" +
|
||||
"BgyR51bDiK7Jso8Rmh2TB+bmO4fNaMY1ETsxECSM0f6NTV0QHks9+gBe+pB6JNeM" +
|
||||
"uPmaE/M/MsE9KUif9L2ChFq3zor6s2foFv2DTiTkij+1aQF9fuIjDNH4FC6L252W" +
|
||||
"ydZzh+f73Xuy5evdPj+wrPYqWyP7sKd+4Q9EIILWAuTDvKEjwyZmIyfM/nUn6ltD" +
|
||||
"P6W8xMP0PoEJNAAp79anz2jk2HP2PvC2qdjVsphdTk3JG5qQMB0WJUh4Kjgabd4j" +
|
||||
"QJ77U8gTRswKgNHRRPWhruiIcmmkP+zI0ozNW6rxH3PF4L7M9rXmfcmUcBcKf+Yx" +
|
||||
"jwIDAQAB",
|
||||
this.identityServiceConfig.getRealmKey());
|
||||
|
||||
assertEquals("identity-service.ssl-required", "external",
|
||||
this.identityServiceConfig.getSslRequired());
|
||||
|
||||
assertEquals("identity-service.resource", "test",
|
||||
this.identityServiceConfig.getResource());
|
||||
|
||||
assertEquals("identity-service.cors-allowed-headers", "Authorization",
|
||||
this.identityServiceConfig.getCorsAllowedHeaders());
|
||||
|
||||
assertEquals("identity-service.cors-allowed-methods", "POST, PUT, DELETE, GET",
|
||||
this.identityServiceConfig.getCorsAllowedMethods());
|
||||
|
||||
assertEquals("identity-service.cors-exposed-headers", "WWW-Authenticate, My-custom-exposed-Header",
|
||||
this.identityServiceConfig.getCorsExposedHeaders());
|
||||
|
||||
assertEquals("identity-service.truststore",
|
||||
"classpath:/alfresco/subsystems/identityServiceAuthentication/keystore.jks",
|
||||
this.identityServiceConfig.getTruststore());
|
||||
|
||||
assertEquals("identity-service.truststore-password", "password",
|
||||
this.identityServiceConfig.getTruststorePassword());
|
||||
|
||||
assertEquals("identity-service.client-keystore",
|
||||
"classpath:/alfresco/subsystems/identityServiceAuthentication/keystore.jks",
|
||||
this.identityServiceConfig.getClientKeystore());
|
||||
|
||||
assertEquals("identity-service.client-keystore-password", "password",
|
||||
this.identityServiceConfig.getClientKeystorePassword());
|
||||
|
||||
assertEquals("identity-service.client-key-password", "password",
|
||||
this.identityServiceConfig.getClientKeyPassword());
|
||||
|
||||
assertEquals("identity-service.token-store", "SESSION",
|
||||
this.identityServiceConfig.getTokenStore());
|
||||
|
||||
assertEquals("identity-service.principal-attribute", "preferred_username",
|
||||
this.identityServiceConfig.getPrincipalAttribute());
|
||||
|
||||
// check number overrides
|
||||
assertEquals("identity-service.confidential-port", 100,
|
||||
this.identityServiceConfig.getConfidentialPort());
|
||||
|
||||
assertEquals("identity-service.cors-max-age", 1000,
|
||||
this.identityServiceConfig.getCorsMaxAge());
|
||||
|
||||
assertEquals("identity-service.connection-pool-size", 5,
|
||||
this.identityServiceConfig.getConnectionPoolSize());
|
||||
|
||||
assertEquals("identity-service.register-node-period", 50,
|
||||
this.identityServiceConfig.getRegisterNodePeriod());
|
||||
|
||||
assertEquals("identity-service.token-minimum-time-to-live", 10,
|
||||
this.identityServiceConfig.getTokenMinimumTimeToLive());
|
||||
|
||||
assertEquals("identity-service.min-time-between-jwks-requests", 60,
|
||||
this.identityServiceConfig.getMinTimeBetweenJwksRequests());
|
||||
|
||||
assertEquals("identity-service.public-key-cache-ttl", 3600,
|
||||
this.identityServiceConfig.getPublicKeyCacheTtl());
|
||||
private IdentityServiceRemoteUserMapper givenMapper(Map<String, Supplier<String>> tokenToUser)
|
||||
{
|
||||
final IdentityServiceFacade facade = mock(IdentityServiceFacade.class);
|
||||
when(facade.extractUsernameFromToken(anyString()))
|
||||
.thenAnswer(i ->
|
||||
ofNullable(tokenToUser.get(i.getArgument(0, String.class)))
|
||||
.map(Supplier::get));
|
||||
|
||||
assertEquals("identity-service.client-connection-timeout", 3000,
|
||||
this.identityServiceConfig.getClientConnectionTimeout());
|
||||
final PersonService personService = mock(PersonService.class);
|
||||
when(personService.getUserIdentifier(anyString())).thenAnswer(i -> i.getArgument(0, String.class));
|
||||
|
||||
assertEquals("identity-service.client-socket-timeout", 1000,
|
||||
this.identityServiceConfig.getClientSocketTimeout());
|
||||
final IdentityServiceRemoteUserMapper mapper = new IdentityServiceRemoteUserMapper();
|
||||
mapper.setIdentityServiceFacade(facade);
|
||||
mapper.setPersonService(personService);
|
||||
mapper.setActive(true);
|
||||
mapper.setBearerTokenResolver(new DefaultBearerTokenResolver());
|
||||
|
||||
// check boolean overrides
|
||||
assertFalse("identity-service.public-client",
|
||||
this.identityServiceConfig.isPublicClient());
|
||||
|
||||
assertTrue("identity-service.use-resource-role-mappings",
|
||||
this.identityServiceConfig.isUseResourceRoleMappings());
|
||||
|
||||
assertTrue("identity-service.enable-cors",
|
||||
this.identityServiceConfig.isCors());
|
||||
|
||||
assertTrue("identity-service.expose-token",
|
||||
this.identityServiceConfig.isExposeToken());
|
||||
|
||||
assertTrue("identity-service.bearer-only",
|
||||
this.identityServiceConfig.isBearerOnly());
|
||||
|
||||
assertTrue("identity-service.autodetect-bearer-only",
|
||||
this.identityServiceConfig.isAutodetectBearerOnly());
|
||||
|
||||
assertTrue("identity-service.enable-basic-auth",
|
||||
this.identityServiceConfig.isEnableBasicAuth());
|
||||
|
||||
assertTrue("identity-service.allow-any-hostname",
|
||||
this.identityServiceConfig.isAllowAnyHostname());
|
||||
|
||||
assertTrue("identity-service.disable-trust-manager",
|
||||
this.identityServiceConfig.isDisableTrustManager());
|
||||
|
||||
assertTrue("identity-service.always-refresh-token",
|
||||
this.identityServiceConfig.isAlwaysRefreshToken());
|
||||
|
||||
assertTrue("identity-service.register-node-at-startup",
|
||||
this.identityServiceConfig.isRegisterNodeAtStartup());
|
||||
|
||||
assertTrue("identity-service.enable-pkce",
|
||||
this.identityServiceConfig.isPkce());
|
||||
|
||||
assertTrue("identity-service.ignore-oauth-query-parameter",
|
||||
this.identityServiceConfig.isIgnoreOAuthQueryParameter());
|
||||
|
||||
assertTrue("identity-service.turn-off-change-session-id-on-login",
|
||||
this.identityServiceConfig.getTurnOffChangeSessionIdOnLogin());
|
||||
|
||||
// check credentials overrides
|
||||
Map<String, Object> credentials = this.identityServiceConfig.getCredentials();
|
||||
assertNotNull("Expected a credentials map", credentials);
|
||||
assertFalse("Expected to retrieve a populated credentials map", credentials.isEmpty());
|
||||
assertEquals("identity-service.credentials.secret", "11111", credentials.get("secret"));
|
||||
assertEquals("identity-service.credentials.provider", "secret", credentials.get("provider"));
|
||||
return mapper;
|
||||
}
|
||||
|
||||
public void testValidToken() throws Exception
|
||||
{
|
||||
// create token
|
||||
String jwt = generateToken(false);
|
||||
|
||||
// create mock request object
|
||||
HttpServletRequest mockRequest = createMockTokenRequest(jwt);
|
||||
|
||||
// validate correct user was found
|
||||
assertEquals(TEST_USER_USERNAME, ((RemoteUserMapper) childApplicationContextFactory.getApplicationContext().getBean(
|
||||
REMOTE_USER_MAPPER_BEAN_NAME)).getRemoteUser(mockRequest));
|
||||
}
|
||||
|
||||
public void testWrongPublicKey() throws Exception
|
||||
{
|
||||
// generate and apply an incorrect public key
|
||||
childApplicationContextFactory.stop();
|
||||
applyHardcodedPublicKey(KeyPairGenerator.getInstance("RSA").generateKeyPair().getPublic());
|
||||
|
||||
// create token
|
||||
String jwt = generateToken(false);
|
||||
|
||||
// create mock request object
|
||||
HttpServletRequest mockRequest = createMockTokenRequest(jwt);
|
||||
|
||||
// ensure null is returned if the public key is wrong
|
||||
assertNull(((RemoteUserMapper) childApplicationContextFactory.getApplicationContext().getBean(
|
||||
REMOTE_USER_MAPPER_BEAN_NAME)).getRemoteUser(mockRequest));
|
||||
}
|
||||
|
||||
public void testWrongPublicKeyWithError() throws Exception
|
||||
{
|
||||
// generate and apply an incorrect public key
|
||||
childApplicationContextFactory.stop();
|
||||
childApplicationContextFactory.setProperty(CONFIG_SILENT_ERRORS, "false");
|
||||
applyHardcodedPublicKey(KeyPairGenerator.getInstance("RSA").generateKeyPair().getPublic());
|
||||
|
||||
// create token
|
||||
String jwt = generateToken(false);
|
||||
|
||||
// create mock request object
|
||||
HttpServletRequest mockRequest = createMockTokenRequest(jwt);
|
||||
|
||||
// ensure user mapper falls through instead of throwing an exception
|
||||
String user = ((RemoteUserMapper)childApplicationContextFactory.getApplicationContext().getBean(
|
||||
REMOTE_USER_MAPPER_BEAN_NAME)).getRemoteUser(mockRequest);
|
||||
assertEquals("Returned user should be null when wrong public key is used.", null, user);
|
||||
}
|
||||
|
||||
public void testInvalidJwt() throws Exception
|
||||
{
|
||||
// create mock request object
|
||||
HttpServletRequest mockRequest = createMockTokenRequest("thisisnotaJWT");
|
||||
|
||||
// ensure null is returned if the JWT is invalid
|
||||
assertNull(((RemoteUserMapper) childApplicationContextFactory.getApplicationContext().getBean(
|
||||
REMOTE_USER_MAPPER_BEAN_NAME)).getRemoteUser(mockRequest));
|
||||
}
|
||||
|
||||
public void testMissingToken() throws Exception
|
||||
{
|
||||
// create mock request object
|
||||
HttpServletRequest mockRequest = createMockTokenRequest("");
|
||||
|
||||
// ensure null is returned if the token is missing
|
||||
assertNull(((RemoteUserMapper) childApplicationContextFactory.getApplicationContext().getBean(
|
||||
REMOTE_USER_MAPPER_BEAN_NAME)).getRemoteUser(mockRequest));
|
||||
}
|
||||
|
||||
public void testExpiredToken() throws Exception
|
||||
{
|
||||
// create token
|
||||
String jwt = generateToken(true);
|
||||
|
||||
// create mock request object
|
||||
HttpServletRequest mockRequest = createMockTokenRequest(jwt);
|
||||
|
||||
// ensure null is returned if the token has expired
|
||||
assertNull(((RemoteUserMapper) childApplicationContextFactory.getApplicationContext().getBean(
|
||||
REMOTE_USER_MAPPER_BEAN_NAME)).getRemoteUser(mockRequest));
|
||||
}
|
||||
|
||||
public void testExpiredTokenWithError() throws Exception
|
||||
{
|
||||
// turn on validation failure reporting
|
||||
childApplicationContextFactory.stop();
|
||||
childApplicationContextFactory.setProperty(CONFIG_SILENT_ERRORS, "false");
|
||||
applyHardcodedPublicKey(this.keyPair.getPublic());
|
||||
|
||||
// create token
|
||||
String jwt = generateToken(true);
|
||||
|
||||
// create mock request object
|
||||
HttpServletRequest mockRequest = createMockTokenRequest(jwt);
|
||||
|
||||
// ensure an exception is thrown with correct description
|
||||
String user = ((RemoteUserMapper)childApplicationContextFactory.getApplicationContext().getBean(
|
||||
REMOTE_USER_MAPPER_BEAN_NAME)).getRemoteUser(mockRequest);
|
||||
assertEquals("Returned user should be null when the token is expired.", null, user);
|
||||
}
|
||||
|
||||
public void testMissingHeader() throws Exception
|
||||
{
|
||||
// create mock request object with no Authorization header
|
||||
HttpServletRequest mockRequest = createMockTokenRequest(null);
|
||||
|
||||
// ensure null is returned if the header was missing
|
||||
assertNull(((RemoteUserMapper) childApplicationContextFactory.getApplicationContext().getBean(
|
||||
REMOTE_USER_MAPPER_BEAN_NAME)).getRemoteUser(mockRequest));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Utility method for creating a mocked Servlet request with a token.
|
||||
*
|
||||
@@ -412,99 +124,12 @@ public class IdentityServiceRemoteUserMapperTest extends AbstractChainedSubsyste
|
||||
{
|
||||
authHeaderValues.add(BEARER_PREFIX + token);
|
||||
}
|
||||
|
||||
when(mockRequest.getHeaders(AUTHORIZATION_HEADER)).thenReturn(authHeaderValues.elements());
|
||||
|
||||
return mockRequest;
|
||||
}
|
||||
|
||||
/**
|
||||
* Utility method for creating a mocked Servlet request with basic auth.
|
||||
*
|
||||
* @return The mocked request object
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
private HttpServletRequest createMockBasicRequest()
|
||||
{
|
||||
// Mock a request with the token in the Authorization header (if supplied)
|
||||
HttpServletRequest mockRequest = mock(HttpServletRequest.class);
|
||||
|
||||
Vector<String> authHeaderValues = new Vector<>(1);
|
||||
String userPwd = TEST_USER_USERNAME + ":" + TEST_USER_USERNAME;
|
||||
authHeaderValues.add(BASIC_PREFIX + Base64.encodeBytes(userPwd.getBytes()));
|
||||
|
||||
// NOTE: as getHeaders gets called twice provide two separate Enumeration objects so that
|
||||
// an empty result is not returned for the second invocation.
|
||||
when(mockRequest.getHeaders(AUTHORIZATION_HEADER)).thenReturn(authHeaderValues.elements(),
|
||||
authHeaderValues.elements());
|
||||
|
||||
return mockRequest;
|
||||
}
|
||||
|
||||
private HttpClient createMockHttpClient() throws Exception
|
||||
{
|
||||
// mock HttpClient object and set on keycloak deployment to avoid basic auth
|
||||
// attempting to get a token using HTTP POST
|
||||
HttpClient mockHttpClient = mock(HttpClient.class);
|
||||
HttpResponse mockHttpResponse = mock(HttpResponse.class);
|
||||
StatusLine mockStatusLine = mock(StatusLine.class);
|
||||
HttpEntity mockHttpEntity = mock(HttpEntity.class);
|
||||
|
||||
// for the purpose of this test use the same token for access and refresh
|
||||
String token = generateToken(false);
|
||||
String jsonResponse = String.format(PASSWORD_GRANT_RESPONSE, token, token);
|
||||
ByteArrayInputStream jsonResponseStream = new ByteArrayInputStream(jsonResponse.getBytes());
|
||||
|
||||
when(mockHttpClient.execute(any())).thenReturn(mockHttpResponse);
|
||||
when(mockHttpResponse.getStatusLine()).thenReturn(mockStatusLine);
|
||||
when(mockHttpResponse.getEntity()).thenReturn(mockHttpEntity);
|
||||
when(mockStatusLine.getStatusCode()).thenReturn(200);
|
||||
when(mockHttpEntity.getContent()).thenReturn(jsonResponseStream);
|
||||
|
||||
return mockHttpClient;
|
||||
}
|
||||
|
||||
/**
|
||||
* Utility method to create tokens for testing.
|
||||
*
|
||||
* @param expired Determines whether to create an expired JWT
|
||||
* @return The string representation of the JWT
|
||||
*/
|
||||
private String generateToken(boolean expired) throws Exception
|
||||
{
|
||||
String issuerUrl = this.identityServiceConfig.getAuthServerUrl() + "/realms/" + this.identityServiceConfig.getRealm();
|
||||
|
||||
AccessToken token = new AccessToken();
|
||||
token.type("Bearer");
|
||||
token.id("1234");
|
||||
token.subject("abc123");
|
||||
token.issuer(issuerUrl);
|
||||
token.setPreferredUsername(TEST_USER_USERNAME);
|
||||
token.setEmail(TEST_USER_EMAIL);
|
||||
token.setGivenName("Joe");
|
||||
token.setFamilyName("Bloggs");
|
||||
|
||||
if (expired)
|
||||
{
|
||||
token.expiration(Time.currentTime() - 60);
|
||||
}
|
||||
|
||||
String jwt = new JWSBuilder()
|
||||
.jsonContent(token)
|
||||
.rsa256(keyPair.getPrivate());
|
||||
when(mockRequest.getHeaders(AUTHORIZATION_HEADER))
|
||||
.thenReturn(authHeaderValues.elements());
|
||||
when(mockRequest.getHeader(AUTHORIZATION_HEADER))
|
||||
.thenReturn(authHeaderValues.isEmpty() ? null : authHeaderValues.get(0));
|
||||
|
||||
return jwt;
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds the keycloak deployment bean and applies a hardcoded public key locator using the
|
||||
* provided public key.
|
||||
*/
|
||||
private void applyHardcodedPublicKey(PublicKey publicKey)
|
||||
{
|
||||
KeycloakDeployment deployment = (KeycloakDeployment)childApplicationContextFactory.getApplicationContext().
|
||||
getBean(DEPLOYMENT_BEAN_NAME);
|
||||
HardcodedPublicKeyLocator publicKeyLocator = new HardcodedPublicKeyLocator(publicKey);
|
||||
deployment.setPublicKeyLocator(publicKeyLocator);
|
||||
return mockRequest;
|
||||
}
|
||||
}
|
||||
|
@@ -0,0 +1,101 @@
|
||||
/*
|
||||
* #%L
|
||||
* Alfresco Repository
|
||||
* %%
|
||||
* Copyright (C) 2005 - 2023 Alfresco Software Limited
|
||||
* %%
|
||||
* This file is part of the Alfresco software.
|
||||
* If the software was purchased under a paid Alfresco license, the terms of
|
||||
* the paid license agreement will prevail. Otherwise, the software is
|
||||
* provided under the following open source license terms:
|
||||
*
|
||||
* Alfresco is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Alfresco 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 Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
|
||||
* #L%
|
||||
*/
|
||||
package org.alfresco.repo.security.authentication.identityservice;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.verifyNoInteractions;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import org.alfresco.repo.security.authentication.identityservice.IdentityServiceFacade.IdentityServiceFacadeException;
|
||||
import org.alfresco.repo.security.authentication.identityservice.IdentityServiceFacadeFactoryBean.LazyInstantiatingIdentityServiceFacade;
|
||||
import org.junit.Test;
|
||||
|
||||
public class LazyInstantiatingIdentityServiceFacadeUnitTest
|
||||
{
|
||||
private static final String USER_NAME = "marlon";
|
||||
private static final String PASSWORD = "brando";
|
||||
private static final String TOKEN = "token";
|
||||
@Test
|
||||
public void shouldRecoverFromInitialAuthorizationServerUnavailability()
|
||||
{
|
||||
final IdentityServiceFacade targetFacade = mock(IdentityServiceFacade.class);
|
||||
final LazyInstantiatingIdentityServiceFacade facade = new LazyInstantiatingIdentityServiceFacade(faultySupplier(3, targetFacade));
|
||||
|
||||
assertThatExceptionOfType(IdentityServiceFacadeException.class)
|
||||
.isThrownBy(() -> facade.extractUsernameFromToken(TOKEN))
|
||||
.havingCause().withNoCause().withMessage("Expected failure #1");
|
||||
verifyNoInteractions(targetFacade);
|
||||
|
||||
assertThatExceptionOfType(IdentityServiceFacadeException.class)
|
||||
.isThrownBy(() -> facade.verifyCredentials(USER_NAME, PASSWORD))
|
||||
.havingCause().withNoCause().withMessage("Expected failure #2");
|
||||
verifyNoInteractions(targetFacade);
|
||||
|
||||
assertThatExceptionOfType(IdentityServiceFacadeException.class)
|
||||
.isThrownBy(() -> facade.extractUsernameFromToken(TOKEN))
|
||||
.havingCause().withNoCause().withMessage("Expected failure #3");
|
||||
verifyNoInteractions(targetFacade);
|
||||
|
||||
facade.verifyCredentials(USER_NAME, PASSWORD);
|
||||
verify(targetFacade).verifyCredentials(USER_NAME, PASSWORD);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldAvoidCreatingMultipleInstanceOfOAuth2AuthorizedClientManager()
|
||||
{
|
||||
final IdentityServiceFacade targetFacade = mock(IdentityServiceFacade.class);
|
||||
final Supplier<IdentityServiceFacade> supplier = mock(Supplier.class);
|
||||
when(supplier.get()).thenReturn(targetFacade);
|
||||
|
||||
final LazyInstantiatingIdentityServiceFacade facade = new LazyInstantiatingIdentityServiceFacade(supplier);
|
||||
|
||||
facade.verifyCredentials(USER_NAME, PASSWORD);
|
||||
facade.extractUsernameFromToken(TOKEN);
|
||||
facade.verifyCredentials(USER_NAME, PASSWORD);
|
||||
facade.extractUsernameFromToken(TOKEN);
|
||||
facade.verifyCredentials(USER_NAME, PASSWORD);
|
||||
verify(supplier, times(1)).get();
|
||||
verify(targetFacade, times(3)).verifyCredentials(USER_NAME, PASSWORD);
|
||||
verify(targetFacade, times(2)).extractUsernameFromToken(TOKEN);
|
||||
}
|
||||
|
||||
private Supplier<IdentityServiceFacade> faultySupplier(int numberOfInitialFailures, IdentityServiceFacade facade)
|
||||
{
|
||||
final int[] counter = new int[]{0};
|
||||
return () -> {
|
||||
if (counter[0]++ < numberOfInitialFailures)
|
||||
{
|
||||
throw new RuntimeException("Expected failure #" + counter[0]);
|
||||
}
|
||||
return facade;
|
||||
};
|
||||
}
|
||||
}
|
@@ -0,0 +1,73 @@
|
||||
/*
|
||||
* #%L
|
||||
* Alfresco Repository
|
||||
* %%
|
||||
* Copyright (C) 2005 - 2023 Alfresco Software Limited
|
||||
* %%
|
||||
* This file is part of the Alfresco software.
|
||||
* If the software was purchased under a paid Alfresco license, the terms of
|
||||
* the paid license agreement will prevail. Otherwise, the software is
|
||||
* provided under the following open source license terms:
|
||||
*
|
||||
* Alfresco is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Alfresco 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 Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
|
||||
* #L%
|
||||
*/
|
||||
package org.alfresco.repo.security.authentication.identityservice;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import org.alfresco.repo.security.authentication.identityservice.IdentityServiceFacade.CredentialsVerificationException;
|
||||
import org.alfresco.repo.security.authentication.identityservice.IdentityServiceFacade.TokenException;
|
||||
import org.alfresco.repo.security.authentication.identityservice.IdentityServiceFacadeFactoryBean.SpringBasedIdentityServiceFacade;
|
||||
import org.junit.Test;
|
||||
import org.springframework.security.oauth2.client.OAuth2AuthorizedClientManager;
|
||||
import org.springframework.security.oauth2.jwt.JwtDecoder;
|
||||
|
||||
public class SpringBasedIdentityServiceFacadeUnitTest
|
||||
{
|
||||
private static final String USER_NAME = "user";
|
||||
private static final String PASSWORD = "password";
|
||||
private static final String TOKEN = "tEsT-tOkEn";
|
||||
|
||||
@Test
|
||||
public void shouldThrowVerificationExceptionOnFailure()
|
||||
{
|
||||
final OAuth2AuthorizedClientManager authClientManager = mock(OAuth2AuthorizedClientManager.class);
|
||||
final JwtDecoder jwtDecoder = mock(JwtDecoder.class);
|
||||
when(authClientManager.authorize(any())).thenThrow(new RuntimeException("Expected"));
|
||||
|
||||
final SpringBasedIdentityServiceFacade facade = new SpringBasedIdentityServiceFacade(authClientManager, jwtDecoder);
|
||||
|
||||
assertThatExceptionOfType(CredentialsVerificationException.class)
|
||||
.isThrownBy(() -> facade.verifyCredentials(USER_NAME, PASSWORD))
|
||||
.havingCause().withNoCause().withMessage("Expected");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldThrowTokenExceptionOnFailure()
|
||||
{
|
||||
final OAuth2AuthorizedClientManager authClientManager = mock(OAuth2AuthorizedClientManager.class);
|
||||
final JwtDecoder jwtDecoder = mock(JwtDecoder.class);
|
||||
when(jwtDecoder.decode(TOKEN)).thenThrow(new RuntimeException("Expected"));
|
||||
|
||||
final SpringBasedIdentityServiceFacade facade = new SpringBasedIdentityServiceFacade(authClientManager, jwtDecoder);
|
||||
|
||||
assertThatExceptionOfType(TokenException.class)
|
||||
.isThrownBy(() -> facade.extractUsernameFromToken(TOKEN))
|
||||
.havingCause().withNoCause().withMessage("Expected");
|
||||
}
|
||||
}
|
@@ -0,0 +1,178 @@
|
||||
/*
|
||||
* #%L
|
||||
* Alfresco Remote API
|
||||
* %%
|
||||
* Copyright (C) 2005 - 2023 Alfresco Software Limited
|
||||
* %%
|
||||
* This file is part of the Alfresco software.
|
||||
* If the software was purchased under a paid Alfresco license, the terms of
|
||||
* the paid license agreement will prevail. Otherwise, the software is
|
||||
* provided under the following open source license terms:
|
||||
*
|
||||
* Alfresco is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Alfresco 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 Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
|
||||
* #L%
|
||||
*/
|
||||
package org.alfresco.repo.tagging;
|
||||
|
||||
import static org.alfresco.model.ContentModel.ASPECT_TAGGABLE;
|
||||
import static org.alfresco.model.ContentModel.PROP_TAGS;
|
||||
import static org.alfresco.repo.tagging.TaggingServiceImpl.TAG_UPDATES;
|
||||
import static org.alfresco.service.cmr.repository.StoreRef.STORE_REF_WORKSPACE_SPACESSTORE;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.catchThrowable;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.BDDMockito.given;
|
||||
import static org.mockito.BDDMockito.then;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.times;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.alfresco.repo.policy.PolicyComponent;
|
||||
import org.alfresco.repo.transaction.AlfrescoTransactionSupport;
|
||||
import org.alfresco.service.cmr.repository.ChildAssociationRef;
|
||||
import org.alfresco.service.cmr.repository.DuplicateChildNodeNameException;
|
||||
import org.alfresco.service.cmr.repository.NodeRef;
|
||||
import org.alfresco.service.cmr.repository.NodeService;
|
||||
import org.alfresco.service.cmr.repository.StoreRef;
|
||||
import org.alfresco.service.cmr.search.CategoryService;
|
||||
import org.alfresco.service.cmr.search.ResultSet;
|
||||
import org.alfresco.service.cmr.search.SearchService;
|
||||
import org.alfresco.util.Pair;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.InjectMocks;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.MockitoJUnitRunner;
|
||||
|
||||
@RunWith(MockitoJUnitRunner.class)
|
||||
public class TaggingServiceImplUnitTest
|
||||
{
|
||||
private static final String TAG_ID = "tag-node-id";
|
||||
private static final String TAG_NAME = "tag-dummy-name";
|
||||
private static final NodeRef TAG_NODE_REF = new NodeRef(StoreRef.STORE_REF_WORKSPACE_SPACESSTORE, TAG_ID);
|
||||
private static final NodeRef CONTENT_NODE_REF = new NodeRef(StoreRef.STORE_REF_WORKSPACE_SPACESSTORE, "content-id");
|
||||
|
||||
@Mock
|
||||
private NodeService nodeServiceMock;
|
||||
@Mock
|
||||
private CategoryService categoryServiceMock;
|
||||
@Mock
|
||||
private PolicyComponent policyComponentMock;
|
||||
@Mock
|
||||
private SearchService searchServiceMock;
|
||||
@Mock
|
||||
private ResultSet resultSetMock;
|
||||
@Mock(extraInterfaces = List.class)
|
||||
private Serializable currentTagsMock;
|
||||
|
||||
@InjectMocks
|
||||
private TaggingServiceImpl taggingService;
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception
|
||||
{
|
||||
AlfrescoTransactionSupport.bindResource(TAG_UPDATES, new HashMap<>());
|
||||
taggingService.init();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreateTags()
|
||||
{
|
||||
final ChildAssociationRef tagAssociationMock = mock(ChildAssociationRef.class);
|
||||
given(categoryServiceMock.getRootCategories(any(), any(), any(String.class), eq(true))).willReturn(List.of(tagAssociationMock));
|
||||
given(tagAssociationMock.getChildRef()).willReturn(TAG_NODE_REF);
|
||||
|
||||
//when
|
||||
final List<Pair<String, NodeRef>> actualTagPairs = taggingService.createTags(STORE_REF_WORKSPACE_SPACESSTORE, List.of(TAG_NAME));
|
||||
|
||||
then(categoryServiceMock).should().getRootCategories(STORE_REF_WORKSPACE_SPACESSTORE, ASPECT_TAGGABLE, TAG_NAME, false);
|
||||
then(categoryServiceMock).should().getRootCategories(STORE_REF_WORKSPACE_SPACESSTORE, ASPECT_TAGGABLE, TAG_NAME, true);
|
||||
then(categoryServiceMock).shouldHaveNoMoreInteractions();
|
||||
List<Pair<String, NodeRef>> expectedTagPairs = List.of(new Pair<>(TAG_NAME, TAG_NODE_REF));
|
||||
assertThat(actualTagPairs)
|
||||
.isNotNull()
|
||||
.isEqualTo(expectedTagPairs);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreateTags_whileTagAlreadyExists()
|
||||
{
|
||||
given(categoryServiceMock.getRootCategories(any(), any(), any(String.class), eq(false))).willThrow(new DuplicateChildNodeNameException(null, null, null, null));
|
||||
|
||||
//when
|
||||
final Throwable actualException = catchThrowable(() -> taggingService.createTags(STORE_REF_WORKSPACE_SPACESSTORE, List.of(TAG_NAME)));
|
||||
|
||||
then(categoryServiceMock).should().getRootCategories(STORE_REF_WORKSPACE_SPACESSTORE, ASPECT_TAGGABLE, TAG_NAME, false);
|
||||
then(categoryServiceMock).shouldHaveNoMoreInteractions();
|
||||
assertThat(actualException).isInstanceOf(DuplicateChildNodeNameException.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("unchecked")
|
||||
public void testChangeTag()
|
||||
{
|
||||
final String newTagName = "new-tag-name";
|
||||
final NodeRef newTagNodeRef = new NodeRef(StoreRef.STORE_REF_WORKSPACE_SPACESSTORE, newTagName);
|
||||
given(searchServiceMock.query(any(), any(String.class), any(String.class))).willReturn(resultSetMock);
|
||||
given(resultSetMock.getNodeRefs()).willReturn(List.of(CONTENT_NODE_REF), Collections.emptyList());
|
||||
given(nodeServiceMock.hasAspect(CONTENT_NODE_REF, ASPECT_TAGGABLE)).willReturn(true);
|
||||
given(categoryServiceMock.getRootCategories(STORE_REF_WORKSPACE_SPACESSTORE, ASPECT_TAGGABLE, TAG_NAME, false)).willReturn(childAssociationsOf(TAG_NODE_REF));
|
||||
given(nodeServiceMock.getProperty(CONTENT_NODE_REF, PROP_TAGS)).willReturn(currentTagsMock);
|
||||
given(((List<NodeRef>) currentTagsMock).size()).willReturn(1);
|
||||
given(((List<NodeRef>) currentTagsMock).contains(TAG_NODE_REF)).willReturn(true);
|
||||
given(categoryServiceMock.getRootCategories(STORE_REF_WORKSPACE_SPACESSTORE, ASPECT_TAGGABLE, newTagName, true)).willReturn(childAssociationsOf(newTagNodeRef));
|
||||
given(((List<NodeRef>) currentTagsMock).contains(newTagNodeRef)).willReturn(false);
|
||||
|
||||
//when
|
||||
taggingService.changeTag(STORE_REF_WORKSPACE_SPACESSTORE, TAG_NAME, newTagName);
|
||||
|
||||
then((List<NodeRef>) currentTagsMock).should().remove(TAG_NODE_REF);
|
||||
then((List<NodeRef>) currentTagsMock).should().add(newTagNodeRef);
|
||||
then(nodeServiceMock).should(times(2)).setProperty(CONTENT_NODE_REF, PROP_TAGS, currentTagsMock);
|
||||
then(categoryServiceMock).should().deleteCategory(TAG_NODE_REF);
|
||||
then(categoryServiceMock).should(times(2)).getRootCategories(STORE_REF_WORKSPACE_SPACESSTORE, ASPECT_TAGGABLE, newTagName, true);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testChangeOrphanTag()
|
||||
{
|
||||
final String newTagName = "new-tag-name";
|
||||
given(searchServiceMock.query(any(), any(String.class), any(String.class))).willReturn(resultSetMock);
|
||||
given(resultSetMock.getNodeRefs()).willReturn(Collections.emptyList());
|
||||
given(categoryServiceMock.getRootCategories(STORE_REF_WORKSPACE_SPACESSTORE, ASPECT_TAGGABLE, TAG_NAME, false)).willReturn(childAssociationsOf(TAG_NODE_REF));
|
||||
|
||||
//when
|
||||
taggingService.changeTag(STORE_REF_WORKSPACE_SPACESSTORE, TAG_NAME, newTagName);
|
||||
|
||||
then(nodeServiceMock).should(never()).setProperty(any(), any(), any());
|
||||
then(categoryServiceMock).should().deleteCategory(TAG_NODE_REF);
|
||||
then(categoryServiceMock).should().getRootCategories(STORE_REF_WORKSPACE_SPACESSTORE, ASPECT_TAGGABLE, newTagName, true);
|
||||
}
|
||||
|
||||
private static List<ChildAssociationRef> childAssociationsOf(final NodeRef... childNodeRefs)
|
||||
{
|
||||
return Arrays.stream(childNodeRefs)
|
||||
.map(childNodeRef -> new ChildAssociationRef(null, null, null, childNodeRef))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
}
|
@@ -1220,7 +1220,7 @@ public class MultiTDemoTest extends TestCase
|
||||
}
|
||||
|
||||
// Find all root categories
|
||||
String query = "PATH:\"/cm:generalclassifiable/*\"";
|
||||
String query = "PATH:\"/cm:categoryRoot/cm:generalclassifiable/*\"";
|
||||
ResultSet resultSet = searchService.query(SPACES_STORE, SearchService.LANGUAGE_LUCENE, query);
|
||||
int cnt = resultSet.length();
|
||||
|
||||
@@ -1246,7 +1246,7 @@ public class MultiTDemoTest extends TestCase
|
||||
assertEquals(cnt, resultSet.length());
|
||||
resultSet.close();
|
||||
|
||||
String queryMembers = "PATH:\"/cm:generalclassifiable//cm:catA/member\"";
|
||||
String queryMembers = "PATH:\"/cm:categoryRoot/cm:generalclassifiable//cm:catA/member\"";
|
||||
resultSet = searchService.query(SPACES_STORE, SearchService.LANGUAGE_LUCENE, queryMembers);
|
||||
assertEquals(0, resultSet.length());
|
||||
resultSet.close();
|
||||
@@ -1273,7 +1273,7 @@ public class MultiTDemoTest extends TestCase
|
||||
assertEquals(1, categories.size());
|
||||
|
||||
// test ETHREEOH-210
|
||||
queryMembers = "PATH:\"/cm:generalclassifiable//cm:CatA/member\"";
|
||||
queryMembers = "PATH:\"/cm:categoryRoot/cm:generalclassifiable//cm:CatA/member\"";
|
||||
resultSet = searchService.query(SPACES_STORE, SearchService.LANGUAGE_LUCENE, queryMembers);
|
||||
assertEquals(1, resultSet.length());
|
||||
resultSet.close();
|
||||
|
@@ -1,59 +0,0 @@
|
||||
/*
|
||||
* #%L
|
||||
* Alfresco Repository
|
||||
* %%
|
||||
* Copyright (C) 2005 - 2016 Alfresco Software Limited
|
||||
* %%
|
||||
* This file is part of the Alfresco software.
|
||||
* If the software was purchased under a paid Alfresco license, the terms of
|
||||
* the paid license agreement will prevail. Otherwise, the software is
|
||||
* provided under the following open source license terms:
|
||||
*
|
||||
* Alfresco is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Alfresco 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 Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
|
||||
* #L%
|
||||
*/
|
||||
package org.alfresco.repo.urlshortening;
|
||||
|
||||
import junit.framework.TestCase;
|
||||
|
||||
/**
|
||||
* @deprecated as BitlyUrlShortenerImpl is no longer used in the core repository code.
|
||||
*/
|
||||
@Deprecated
|
||||
public class BitlyUrlShortenerTest extends TestCase
|
||||
{
|
||||
private BitlyUrlShortenerImpl shortener;
|
||||
|
||||
public void testShorten()
|
||||
{
|
||||
String url = "http://www.alfresco.com/";
|
||||
String shortUrl = shortener.shortenUrl(url);
|
||||
assertNotNull(shortUrl);
|
||||
assertFalse(shortUrl.isEmpty());
|
||||
assertFalse(url.equals(shortUrl));
|
||||
assertTrue(shortUrl.length() < url.length());
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
protected void setUp() throws Exception
|
||||
{
|
||||
this.shortener = new BitlyUrlShortenerImpl();;
|
||||
shortener.setApiKey("R_ca15c6c89e9b25ccd170bafd209a0d4f");
|
||||
shortener.setUrlLength(20);
|
||||
shortener.setUsername("brianalfresco");
|
||||
}
|
||||
}
|
@@ -66,3 +66,5 @@ encryption.cipherAlgorithm=DESede/CBC/PKCS5Padding
|
||||
encryption.keystore.type=JCEKS
|
||||
encryption.keystore.backup.type=JCEKS
|
||||
|
||||
# For CI override the default hashing algorithm for password storage to save build time.
|
||||
system.preferred.password.encoding=sha256
|
||||
|
Reference in New Issue
Block a user