mirror of
https://github.com/Alfresco/alfresco-community-repo.git
synced 2025-07-24 17:32:48 +00:00
Merged enterprise features
git-svn-id: https://svn.alfresco.com/repos/alfresco-enterprise/alfresco/HEAD/root@2746 c4b6b30b-aa2e-2d43-bbcb-ca4b014f7261
This commit is contained in:
140
source/java/org/alfresco/repo/cache/TreeCacheAdapter.java
vendored
Normal file
140
source/java/org/alfresco/repo/cache/TreeCacheAdapter.java
vendored
Normal file
@@ -0,0 +1,140 @@
|
||||
/*
|
||||
* Copyright (C) 2005 Alfresco, Inc.
|
||||
*
|
||||
* Licensed under the Alfresco Network License. You may obtain a
|
||||
* copy of the License at
|
||||
*
|
||||
* http://www.alfrescosoftware.com/legal/
|
||||
*
|
||||
* Please view the license relevant to your network subscription.
|
||||
*
|
||||
* BY CLICKING THE "I UNDERSTAND AND ACCEPT" BOX, OR INSTALLING,
|
||||
* READING OR USING ALFRESCO'S Network SOFTWARE (THE "SOFTWARE"),
|
||||
* YOU ARE AGREEING ON BEHALF OF THE ENTITY LICENSING THE SOFTWARE
|
||||
* ("COMPANY") THAT COMPANY WILL BE BOUND BY AND IS BECOMING A PARTY TO
|
||||
* THIS ALFRESCO NETWORK AGREEMENT ("AGREEMENT") AND THAT YOU HAVE THE
|
||||
* AUTHORITY TO BIND COMPANY. IF COMPANY DOES NOT AGREE TO ALL OF THE
|
||||
* TERMS OF THIS AGREEMENT, DO NOT SELECT THE "I UNDERSTAND AND AGREE"
|
||||
* BOX AND DO NOT INSTALL THE SOFTWARE OR VIEW THE SOURCE CODE. COMPANY
|
||||
* HAS NOT BECOME A LICENSEE OF, AND IS NOT AUTHORIZED TO USE THE
|
||||
* SOFTWARE UNLESS AND UNTIL IT HAS AGREED TO BE BOUND BY THESE LICENSE
|
||||
* TERMS. THE "EFFECTIVE DATE" FOR THIS AGREEMENT SHALL BE THE DAY YOU
|
||||
* CHECK THE "I UNDERSTAND AND ACCEPT" BOX.
|
||||
*/
|
||||
package org.alfresco.repo.cache;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
import org.alfresco.error.AlfrescoRuntimeException;
|
||||
import org.jboss.cache.Fqn;
|
||||
import org.jboss.cache.TreeCache;
|
||||
|
||||
/**
|
||||
* A thin adapter for <b>TreeCache</b> support.
|
||||
*
|
||||
* @author Derek Hulley
|
||||
*/
|
||||
public class TreeCacheAdapter<K extends Serializable, V extends Serializable>
|
||||
implements SimpleCache<K, V>
|
||||
{
|
||||
private TreeCache cache;
|
||||
private Fqn regionFqn;
|
||||
|
||||
public TreeCacheAdapter()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* @param cache the backing Ehcache instance
|
||||
*/
|
||||
public void setCache(TreeCache cache)
|
||||
{
|
||||
this.cache = cache;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the uniquely named region of the cache within which all object must be cached
|
||||
*
|
||||
* @param regionName the cache region
|
||||
*/
|
||||
public void setRegionName(String regionName)
|
||||
{
|
||||
this.regionFqn = new Fqn(regionName);
|
||||
}
|
||||
|
||||
public boolean contains(K key)
|
||||
{
|
||||
try
|
||||
{
|
||||
return cache.exists(regionFqn, key);
|
||||
}
|
||||
catch (Throwable e)
|
||||
{
|
||||
throw new AlfrescoRuntimeException("contains failed", e);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public V get(K key)
|
||||
{
|
||||
try
|
||||
{
|
||||
Object element = cache.get(regionFqn, key);
|
||||
if (element != null)
|
||||
{
|
||||
return (V) element;
|
||||
}
|
||||
else
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
catch (Throwable e)
|
||||
{
|
||||
throw new AlfrescoRuntimeException("Failed to get from TreeCache: \n" +
|
||||
" key: " + key,
|
||||
e);
|
||||
}
|
||||
}
|
||||
|
||||
public void put(K key, V value)
|
||||
{
|
||||
try
|
||||
{
|
||||
cache.put(regionFqn, key, value);
|
||||
}
|
||||
catch (Throwable e)
|
||||
{
|
||||
throw new AlfrescoRuntimeException("Failed to put into TreeCache: \n" +
|
||||
" key: " + key + "\n" +
|
||||
" value: " + value,
|
||||
e);
|
||||
}
|
||||
}
|
||||
|
||||
public void remove(K key)
|
||||
{
|
||||
try
|
||||
{
|
||||
cache.remove(regionFqn, key);
|
||||
}
|
||||
catch (Throwable e)
|
||||
{
|
||||
throw new AlfrescoRuntimeException("Failed to remove from TreeCache: \n" +
|
||||
" key: " + key,
|
||||
e);
|
||||
}
|
||||
}
|
||||
|
||||
public void clear()
|
||||
{
|
||||
try
|
||||
{
|
||||
cache.remove(regionFqn);
|
||||
}
|
||||
catch (Throwable e)
|
||||
{
|
||||
throw new AlfrescoRuntimeException("Failed to clear cache", e);
|
||||
}
|
||||
}
|
||||
}
|
75
source/java/org/alfresco/repo/cache/TreeCacheAdapterTest.java
vendored
Normal file
75
source/java/org/alfresco/repo/cache/TreeCacheAdapterTest.java
vendored
Normal file
@@ -0,0 +1,75 @@
|
||||
/*
|
||||
* Copyright (C) 2005 Alfresco, Inc.
|
||||
*
|
||||
* Licensed under the Alfresco Network License. You may obtain a
|
||||
* copy of the License at
|
||||
*
|
||||
* http://www.alfrescosoftware.com/legal/
|
||||
*
|
||||
* Please view the license relevant to your network subscription.
|
||||
*
|
||||
* BY CLICKING THE "I UNDERSTAND AND ACCEPT" BOX, OR INSTALLING,
|
||||
* READING OR USING ALFRESCO'S Network SOFTWARE (THE "SOFTWARE"),
|
||||
* YOU ARE AGREEING ON BEHALF OF THE ENTITY LICENSING THE SOFTWARE
|
||||
* ("COMPANY") THAT COMPANY WILL BE BOUND BY AND IS BECOMING A PARTY TO
|
||||
* THIS ALFRESCO NETWORK AGREEMENT ("AGREEMENT") AND THAT YOU HAVE THE
|
||||
* AUTHORITY TO BIND COMPANY. IF COMPANY DOES NOT AGREE TO ALL OF THE
|
||||
* TERMS OF THIS AGREEMENT, DO NOT SELECT THE "I UNDERSTAND AND AGREE"
|
||||
* BOX AND DO NOT INSTALL THE SOFTWARE OR VIEW THE SOURCE CODE. COMPANY
|
||||
* HAS NOT BECOME A LICENSEE OF, AND IS NOT AUTHORIZED TO USE THE
|
||||
* SOFTWARE UNLESS AND UNTIL IT HAS AGREED TO BE BOUND BY THESE LICENSE
|
||||
* TERMS. THE "EFFECTIVE DATE" FOR THIS AGREEMENT SHALL BE THE DAY YOU
|
||||
* CHECK THE "I UNDERSTAND AND ACCEPT" BOX.
|
||||
*/
|
||||
package org.alfresco.repo.cache;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
import org.jboss.cache.DummyTransactionManagerLookup;
|
||||
import org.jboss.cache.Fqn;
|
||||
import org.jboss.cache.TreeCache;
|
||||
|
||||
import junit.framework.TestCase;
|
||||
|
||||
/**
|
||||
* @see org.alfresco.repo.cache.TreeCacheAdapter
|
||||
*
|
||||
* @author Derek Hulley
|
||||
*/
|
||||
public class TreeCacheAdapterTest extends TestCase
|
||||
{
|
||||
private static final String KEY_A = "A";
|
||||
private static final String VALUE_A = "AAA";
|
||||
private static final String KEY_B = "B";
|
||||
private static final String VALUE_B = "BBB";
|
||||
|
||||
private TreeCache treeCache;
|
||||
private TreeCacheAdapter<Serializable, Serializable> cache;
|
||||
|
||||
@Override
|
||||
public void setUp() throws Exception
|
||||
{
|
||||
treeCache = new TreeCache();
|
||||
treeCache.setTransactionManagerLookupClass(DummyTransactionManagerLookup.class.getName());
|
||||
treeCache.start();
|
||||
|
||||
cache = new TreeCacheAdapter<Serializable, Serializable>();
|
||||
cache.setCache(treeCache);
|
||||
cache.setRegionName(getName());
|
||||
}
|
||||
|
||||
public void testSimplePutGet() throws Exception
|
||||
{
|
||||
cache.put(KEY_A, VALUE_A);
|
||||
cache.put(KEY_B, VALUE_B);
|
||||
|
||||
// check that this is present in the underlying cache
|
||||
Serializable checkValueA = (Serializable) treeCache.get(new Fqn(getName()), KEY_A);
|
||||
assertNotNull("Value A is not present in underlying cache", checkValueA);
|
||||
assertEquals("Value A is incorrect in underlying cache", VALUE_A, checkValueA);
|
||||
|
||||
Serializable checkValueB = cache.get(KEY_B);
|
||||
assertNotNull("Value B is not present in cache", checkValueB);
|
||||
assertEquals("Value B is incorrect in cache", VALUE_B, checkValueB);
|
||||
}
|
||||
}
|
@@ -0,0 +1,277 @@
|
||||
/*
|
||||
* Copyright (C) 2005 Alfresco, Inc.
|
||||
*
|
||||
* Licensed under the Alfresco Network License. You may obtain a
|
||||
* copy of the License at
|
||||
*
|
||||
* http://www.alfrescosoftware.com/legal/
|
||||
*
|
||||
* Please view the license relevant to your network subscription.
|
||||
*
|
||||
* BY CLICKING THE "I UNDERSTAND AND ACCEPT" BOX, OR INSTALLING,
|
||||
* READING OR USING ALFRESCO'S Network SOFTWARE (THE "SOFTWARE"),
|
||||
* YOU ARE AGREEING ON BEHALF OF THE ENTITY LICENSING THE SOFTWARE
|
||||
* ("COMPANY") THAT COMPANY WILL BE BOUND BY AND IS BECOMING A PARTY TO
|
||||
* THIS ALFRESCO NETWORK AGREEMENT ("AGREEMENT") AND THAT YOU HAVE THE
|
||||
* AUTHORITY TO BIND COMPANY. IF COMPANY DOES NOT AGREE TO ALL OF THE
|
||||
* TERMS OF THIS AGREEMENT, DO NOT SELECT THE "I UNDERSTAND AND AGREE"
|
||||
* BOX AND DO NOT INSTALL THE SOFTWARE OR VIEW THE SOURCE CODE. COMPANY
|
||||
* HAS NOT BECOME A LICENSEE OF, AND IS NOT AUTHORIZED TO USE THE
|
||||
* SOFTWARE UNLESS AND UNTIL IT HAS AGREED TO BE BOUND BY THESE LICENSE
|
||||
* TERMS. THE "EFFECTIVE DATE" FOR THIS AGREEMENT SHALL BE THE DAY YOU
|
||||
* CHECK THE "I UNDERSTAND AND ACCEPT" BOX.
|
||||
*/
|
||||
package org.alfresco.repo.content.replication;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
import org.alfresco.error.AlfrescoRuntimeException;
|
||||
import org.alfresco.repo.content.ContentStore;
|
||||
import org.alfresco.repo.node.index.IndexRecovery;
|
||||
import org.alfresco.service.cmr.repository.ContentReader;
|
||||
import org.alfresco.service.cmr.repository.ContentWriter;
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.quartz.Job;
|
||||
import org.quartz.JobExecutionContext;
|
||||
import org.quartz.JobExecutionException;
|
||||
|
||||
/**
|
||||
* This component performs one-way replication between to content stores.
|
||||
* <p>
|
||||
* It ensure that the content from the first store is copied to the second
|
||||
* store where required, therefore primarily acting as a backup or
|
||||
* replication mechanism.
|
||||
* <p>
|
||||
* Once started, this process runs continuously on a low-priority thread
|
||||
* and cannot be restarted.
|
||||
*
|
||||
* @author Derek Hulley
|
||||
*/
|
||||
public class ContentStoreReplicator
|
||||
{
|
||||
private static Log logger = LogFactory.getLog(ContentStoreReplicator.class);
|
||||
|
||||
private ContentStore sourceStore;
|
||||
private ContentStore targetStore;
|
||||
|
||||
/** used to ensure that this instance gets started once only */
|
||||
private boolean started;
|
||||
/** set this on to keep replicating and never stop. The default is <code>true</code>. */
|
||||
private boolean runContinuously;
|
||||
/** the time to wait between passes */
|
||||
private long waitTime;
|
||||
|
||||
public ContentStoreReplicator()
|
||||
{
|
||||
this.started = false;
|
||||
this.runContinuously = true;
|
||||
this.waitTime = 60000L;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the source that content must be taken from
|
||||
*
|
||||
* @param sourceStore the content source
|
||||
*/
|
||||
public void setSourceStore(ContentStore sourceStore)
|
||||
{
|
||||
this.sourceStore = sourceStore;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the target that content must be written to
|
||||
*
|
||||
* @param targetStore the content target
|
||||
*/
|
||||
public void setTargetStore(ContentStore targetStore)
|
||||
{
|
||||
this.targetStore = targetStore;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set whether the thread should run continuously or terminate after
|
||||
* a first pass.
|
||||
*
|
||||
* @param runContinuously true to run continously (default)
|
||||
*/
|
||||
public void setRunContinuously(boolean runContinuously)
|
||||
{
|
||||
this.runContinuously = runContinuously;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the time to wait between replication passes (in seconds)
|
||||
*
|
||||
* @param waitTime the time between passes (in seconds). Default is 60s.
|
||||
*/
|
||||
public void setWaitTime(long waitTime)
|
||||
{
|
||||
// convert to millis
|
||||
this.waitTime = waitTime * 1000L;
|
||||
}
|
||||
|
||||
/**
|
||||
* Kick off the replication thread. This method can be used once.
|
||||
*/
|
||||
public synchronized void start()
|
||||
{
|
||||
if (started)
|
||||
{
|
||||
throw new AlfrescoRuntimeException("This ContentStoreReplicator has already been started");
|
||||
}
|
||||
// create a low-priority, daemon thread to do the work
|
||||
Runnable runnable = new ReplicationRunner();
|
||||
Thread thread = new Thread(runnable);
|
||||
thread.setPriority(Thread.MIN_PRIORITY);
|
||||
thread.setDaemon(true);
|
||||
// start it
|
||||
thread.start();
|
||||
}
|
||||
|
||||
/**
|
||||
* Stateful thread runnable that performs the replication.
|
||||
*
|
||||
* @author Derek Hulley
|
||||
*/
|
||||
private class ReplicationRunner implements Runnable
|
||||
{
|
||||
public void run()
|
||||
{
|
||||
// keep this thread going permanently
|
||||
while (true)
|
||||
{
|
||||
try
|
||||
{
|
||||
ContentStoreReplicator.this.replicate();
|
||||
// check if the process should terminate
|
||||
if (!runContinuously)
|
||||
{
|
||||
// the thread has caught up with all the available work and should not
|
||||
// run continuously
|
||||
if (logger.isDebugEnabled())
|
||||
{
|
||||
logger.debug("Thread quitting - first pass of replication complete:");
|
||||
}
|
||||
break;
|
||||
}
|
||||
// pause the the required wait time
|
||||
synchronized(ContentStoreReplicator.this)
|
||||
{
|
||||
ContentStoreReplicator.this.wait(waitTime);
|
||||
}
|
||||
}
|
||||
catch (InterruptedException e)
|
||||
{
|
||||
// ignore
|
||||
}
|
||||
catch (Throwable e)
|
||||
{
|
||||
// report
|
||||
logger.error("Replication failure", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform a full replication of all source to target URLs.
|
||||
*/
|
||||
private void replicate()
|
||||
{
|
||||
// get all the URLs from the source
|
||||
Set<String> sourceUrls = sourceStore.getUrls();
|
||||
// get all the URLs from the target
|
||||
Set<String> targetUrls = targetStore.getUrls();
|
||||
// remove source URLs that are present in the target
|
||||
sourceUrls.removeAll(targetUrls);
|
||||
|
||||
// ensure that each remaining source URL is present in the target
|
||||
for (String contentUrl : sourceUrls)
|
||||
{
|
||||
replicate(contentUrl);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the target store has the URL, and if not, replicates the content.
|
||||
* <p>
|
||||
* Any failures are reported and not thrown, but the target URL is removed for
|
||||
* good measure.
|
||||
*
|
||||
* @param contentUrl the URL to replicate
|
||||
*/
|
||||
private void replicate(String contentUrl)
|
||||
{
|
||||
try
|
||||
{
|
||||
// check that the target doesn't have it
|
||||
if (targetStore.exists(contentUrl))
|
||||
{
|
||||
// ignore this as the target has it already
|
||||
if (logger.isDebugEnabled())
|
||||
{
|
||||
logger.debug("No replication required - URL exists in target store: \n" +
|
||||
" source store: " + sourceStore + "\n" +
|
||||
" target store: " + targetStore + "\n" +
|
||||
" content URL: " + contentUrl);
|
||||
}
|
||||
return;
|
||||
}
|
||||
// get a writer to the target store - this can fail if the content is there now
|
||||
ContentWriter writer = targetStore.getWriter(null, contentUrl);
|
||||
// get the source reader
|
||||
ContentReader reader = sourceStore.getReader(contentUrl);
|
||||
if (!reader.exists())
|
||||
{
|
||||
// the content may have disappeared from the source store
|
||||
if (logger.isDebugEnabled())
|
||||
{
|
||||
logger.debug("Source store no longer has URL - no replication possible: \n" +
|
||||
" source store: " + sourceStore + "\n" +
|
||||
" target store: " + targetStore + "\n" +
|
||||
" content URL: " + contentUrl);
|
||||
}
|
||||
return;
|
||||
}
|
||||
// copy from the reader to the writer
|
||||
writer.putContent(reader);
|
||||
}
|
||||
catch (Throwable e)
|
||||
{
|
||||
logger.error("Failed to replicate URL - removing target content: \n" +
|
||||
" source store: " + sourceStore + "\n" +
|
||||
" target store: " + targetStore + "\n" +
|
||||
" content URL: " + contentUrl,
|
||||
e);
|
||||
targetStore.delete(contentUrl);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Kicks off the {@link ContentStoreReplicator content store replicator}.
|
||||
*
|
||||
* @author Derek Hulley
|
||||
*/
|
||||
public class ContentStoreReplicatorJob implements Job
|
||||
{
|
||||
/** KEY_CONTENT_STORE_REPLICATOR = 'contentStoreReplicator' */
|
||||
public static final String KEY_CONTENT_STORE_REPLICATOR = "contentStoreReplicator";
|
||||
|
||||
/**
|
||||
* Forces a full index recovery using the {@link IndexRecovery recovery component} passed
|
||||
* in via the job detail.
|
||||
*/
|
||||
public void execute(JobExecutionContext context) throws JobExecutionException
|
||||
{
|
||||
ContentStoreReplicator contentStoreReplicator = (ContentStoreReplicator) context.getJobDetail()
|
||||
.getJobDataMap().get(KEY_CONTENT_STORE_REPLICATOR);
|
||||
if (contentStoreReplicator == null)
|
||||
{
|
||||
throw new JobExecutionException("Missing job data: " + KEY_CONTENT_STORE_REPLICATOR);
|
||||
}
|
||||
// reindex
|
||||
contentStoreReplicator.start();
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,160 @@
|
||||
/*
|
||||
* Copyright (C) 2005 Alfresco, Inc.
|
||||
*
|
||||
* Licensed under the Alfresco Network License. You may obtain a
|
||||
* copy of the License at
|
||||
*
|
||||
* http://www.alfrescosoftware.com/legal/
|
||||
*
|
||||
* Please view the license relevant to your network subscription.
|
||||
*
|
||||
* BY CLICKING THE "I UNDERSTAND AND ACCEPT" BOX, OR INSTALLING,
|
||||
* READING OR USING ALFRESCO'S Network SOFTWARE (THE "SOFTWARE"),
|
||||
* YOU ARE AGREEING ON BEHALF OF THE ENTITY LICENSING THE SOFTWARE
|
||||
* ("COMPANY") THAT COMPANY WILL BE BOUND BY AND IS BECOMING A PARTY TO
|
||||
* THIS ALFRESCO NETWORK AGREEMENT ("AGREEMENT") AND THAT YOU HAVE THE
|
||||
* AUTHORITY TO BIND COMPANY. IF COMPANY DOES NOT AGREE TO ALL OF THE
|
||||
* TERMS OF THIS AGREEMENT, DO NOT SELECT THE "I UNDERSTAND AND AGREE"
|
||||
* BOX AND DO NOT INSTALL THE SOFTWARE OR VIEW THE SOURCE CODE. COMPANY
|
||||
* HAS NOT BECOME A LICENSEE OF, AND IS NOT AUTHORIZED TO USE THE
|
||||
* SOFTWARE UNLESS AND UNTIL IT HAS AGREED TO BE BOUND BY THESE LICENSE
|
||||
* TERMS. THE "EFFECTIVE DATE" FOR THIS AGREEMENT SHALL BE THE DAY YOU
|
||||
* CHECK THE "I UNDERSTAND AND ACCEPT" BOX.
|
||||
*/
|
||||
package org.alfresco.repo.content.replication;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.Set;
|
||||
|
||||
import junit.framework.TestCase;
|
||||
|
||||
import org.alfresco.repo.content.AbstractContentStore;
|
||||
import org.alfresco.repo.content.ContentStore;
|
||||
import org.alfresco.repo.content.filestore.FileContentStore;
|
||||
import org.alfresco.service.cmr.repository.ContentWriter;
|
||||
import org.alfresco.util.GUID;
|
||||
import org.alfresco.util.TempFileProvider;
|
||||
|
||||
/**
|
||||
* Tests the content store replicator.
|
||||
*
|
||||
* @see org.alfresco.repo.content.replication.ContentStoreReplicator
|
||||
*
|
||||
* @author Derek Hulley
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
public class ContentStoreReplicatorTest extends TestCase
|
||||
{
|
||||
private static final String SOME_CONTENT = "The No. 1 Ladies' Detective Agency";
|
||||
|
||||
private ContentStoreReplicator replicator;
|
||||
private ContentStore sourceStore;
|
||||
private ContentStore targetStore;
|
||||
|
||||
@Override
|
||||
public void setUp() throws Exception
|
||||
{
|
||||
super.setUp();
|
||||
|
||||
File tempDir = TempFileProvider.getTempDir();
|
||||
// create the source file store
|
||||
String storeDir = tempDir.getAbsolutePath() + File.separatorChar + getName() + File.separatorChar + GUID.generate();
|
||||
sourceStore = new FileContentStore(storeDir);
|
||||
// create the target file store
|
||||
storeDir = tempDir.getAbsolutePath() + File.separatorChar + getName() + File.separatorChar + GUID.generate();
|
||||
targetStore = new FileContentStore(storeDir);
|
||||
|
||||
// create the replicator
|
||||
replicator = new ContentStoreReplicator();
|
||||
replicator.setSourceStore(sourceStore);
|
||||
replicator.setTargetStore(targetStore);
|
||||
replicator.setRunContinuously(false); // replicate once
|
||||
replicator.setWaitTime(0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a source with some files and replicates in a single pass, checking the results.
|
||||
*/
|
||||
public void testSinglePassReplication() throws Exception
|
||||
{
|
||||
ContentWriter writer = sourceStore.getWriter(null, null);
|
||||
writer.putContent("123");
|
||||
|
||||
// replicate
|
||||
replicator.start();
|
||||
|
||||
// wait a second
|
||||
synchronized(this)
|
||||
{
|
||||
this.wait(1000L);
|
||||
}
|
||||
|
||||
assertTrue("Target store doesn't have content added to source",
|
||||
targetStore.exists(writer.getContentUrl()));
|
||||
|
||||
// this was a single pass, so now more replication should be done
|
||||
writer = sourceStore.getWriter(null, null);
|
||||
writer.putContent("456");
|
||||
|
||||
// wait a second
|
||||
synchronized(this)
|
||||
{
|
||||
this.wait(1000L);
|
||||
}
|
||||
|
||||
assertFalse("Replication should have been single-pass",
|
||||
targetStore.exists(writer.getContentUrl()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds content to the source while the replicator is going as fast as possible.
|
||||
* Just to make it more interesting, the content is sometimes put in the target
|
||||
* store as well.
|
||||
* <p>
|
||||
* Afterwards, some content is removed from the the target.
|
||||
* <p>
|
||||
* Then, finally, a check is performed to ensure that the source and target are
|
||||
* in synch.
|
||||
*/
|
||||
public void testContinuousReplication() throws Exception
|
||||
{
|
||||
replicator.setRunContinuously(true);
|
||||
replicator.setWaitTime(0L);
|
||||
replicator.start();
|
||||
|
||||
String duplicateUrl = AbstractContentStore.createNewUrl();
|
||||
// start the replicator - it won't wait between iterations
|
||||
for (int i = 0; i < 10; i++)
|
||||
{
|
||||
// put some content into both the target and source
|
||||
duplicateUrl = AbstractContentStore.createNewUrl();
|
||||
ContentWriter duplicateTargetWriter = targetStore.getWriter(null, duplicateUrl);
|
||||
ContentWriter duplicateSourceWriter = sourceStore.getWriter(null, duplicateUrl);
|
||||
duplicateTargetWriter.putContent("Duplicate Target Content: " + i);
|
||||
duplicateSourceWriter.putContent(duplicateTargetWriter.getReader());
|
||||
|
||||
for (int j = 0; j < 100; j++)
|
||||
{
|
||||
// write content
|
||||
ContentWriter writer = sourceStore.getWriter(null, null);
|
||||
writer.putContent("Repeated put: " + j);
|
||||
}
|
||||
}
|
||||
|
||||
// remove the last duplicated URL from the target
|
||||
targetStore.delete(duplicateUrl);
|
||||
|
||||
// allow time for the replicator to catch up
|
||||
synchronized(this)
|
||||
{
|
||||
this.wait(1000L);
|
||||
}
|
||||
|
||||
// check that we have an exact match of URLs
|
||||
Set<String> sourceUrls = sourceStore.getUrls();
|
||||
Set<String> targetUrls = targetStore.getUrls();
|
||||
|
||||
sourceUrls.containsAll(targetUrls);
|
||||
targetUrls.contains(sourceUrls);
|
||||
}
|
||||
}
|
@@ -0,0 +1,447 @@
|
||||
/*
|
||||
* Copyright (C) 2005 Alfresco, Inc.
|
||||
*
|
||||
* Licensed under the Alfresco Network License. You may obtain a
|
||||
* copy of the License at
|
||||
*
|
||||
* http://www.alfrescosoftware.com/legal/
|
||||
*
|
||||
* Please view the license relevant to your network subscription.
|
||||
*
|
||||
* BY CLICKING THE "I UNDERSTAND AND ACCEPT" BOX, OR INSTALLING,
|
||||
* READING OR USING ALFRESCO'S Network SOFTWARE (THE "SOFTWARE"),
|
||||
* YOU ARE AGREEING ON BEHALF OF THE ENTITY LICENSING THE SOFTWARE
|
||||
* ("COMPANY") THAT COMPANY WILL BE BOUND BY AND IS BECOMING A PARTY TO
|
||||
* THIS ALFRESCO NETWORK AGREEMENT ("AGREEMENT") AND THAT YOU HAVE THE
|
||||
* AUTHORITY TO BIND COMPANY. IF COMPANY DOES NOT AGREE TO ALL OF THE
|
||||
* TERMS OF THIS AGREEMENT, DO NOT SELECT THE "I UNDERSTAND AND AGREE"
|
||||
* BOX AND DO NOT INSTALL THE SOFTWARE OR VIEW THE SOURCE CODE. COMPANY
|
||||
* HAS NOT BECOME A LICENSEE OF, AND IS NOT AUTHORIZED TO USE THE
|
||||
* SOFTWARE UNLESS AND UNTIL IT HAS AGREED TO BE BOUND BY THESE LICENSE
|
||||
* TERMS. THE "EFFECTIVE DATE" FOR THIS AGREEMENT SHALL BE THE DAY YOU
|
||||
* CHECK THE "I UNDERSTAND AND ACCEPT" BOX.
|
||||
*/
|
||||
package org.alfresco.repo.content.replication;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ThreadPoolExecutor;
|
||||
import java.util.concurrent.locks.Lock;
|
||||
import java.util.concurrent.locks.ReadWriteLock;
|
||||
import java.util.concurrent.locks.ReentrantReadWriteLock;
|
||||
|
||||
import org.alfresco.error.AlfrescoRuntimeException;
|
||||
import org.alfresco.repo.content.AbstractContentStore;
|
||||
import org.alfresco.repo.content.ContentStore;
|
||||
import org.alfresco.service.cmr.repository.ContentIOException;
|
||||
import org.alfresco.service.cmr.repository.ContentReader;
|
||||
import org.alfresco.service.cmr.repository.ContentStreamListener;
|
||||
import org.alfresco.service.cmr.repository.ContentWriter;
|
||||
import org.alfresco.service.transaction.TransactionService;
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
/**
|
||||
* </h1><u>Replicating Content Store</u></h1>
|
||||
* <p>
|
||||
* A content store implementation that is able to replicate content between stores.
|
||||
* Content is not persisted by this store, but rather it relies on any number of
|
||||
* child {@link org.alfresco.repo.content.ContentStore stores} to provide access to
|
||||
* content readers and writers.
|
||||
* <p>
|
||||
* The order in which the stores appear in the list of stores participating is
|
||||
* important. The first store in the list is known as the <i>primary store</i>.
|
||||
* When the replicator goes to fetch content, the stores are searched
|
||||
* from first to last. The stores should therefore be arranged in order of
|
||||
* speed.
|
||||
* <p>
|
||||
* It supports the notion of inbound and/or outbound replication, both of which can be
|
||||
* operational at the same time.
|
||||
*
|
||||
* </h2><u>Outbound Replication</u></h2>
|
||||
* <p>
|
||||
* When this is enabled, then the primary store is used for writes. When the
|
||||
* content write completes (i.e. the write channel is closed) then the content
|
||||
* is synchronously copied to all other stores. The write is therefore slowed
|
||||
* down, but the content replication will occur <i>in-transaction</i>.
|
||||
* <p>
|
||||
* The {@link #setOutboundThreadPoolExecutor(boolean) outboundThreadPoolExecutor }
|
||||
* property to enable asynchronous replication.<br>
|
||||
* With asynchronous replication, there is always a risk that a failure
|
||||
* occurs during the replication. Depending on the configuration of the server,
|
||||
* further action may need to be taken to rectify the problem manually.
|
||||
*
|
||||
* </h2><u>Inbound Replication</u></h2>
|
||||
* <p>
|
||||
* This can be used to lazily replicate content onto the primary store. When
|
||||
* content can't be found in the primary store, the other stores are checked
|
||||
* in order. If content is found, then it is copied into the local store
|
||||
* before being returned. Subsequent accesses will use the primary store.<br>
|
||||
* This should be used where the secondary stores are much slower, such as in
|
||||
* the case of a store against some kind of archival mechanism.
|
||||
*
|
||||
* <h2><u>No Replication</u></h2>
|
||||
* <p>
|
||||
* Content is not written to the primary store only. The other stores are
|
||||
* only used to retrieve content and the primary store is not updated with
|
||||
* the content.
|
||||
*
|
||||
* @author Derek Hulley
|
||||
*/
|
||||
public class ReplicatingContentStore extends AbstractContentStore
|
||||
{
|
||||
/*
|
||||
* The replication process uses thread synchronization as it can
|
||||
* decide to write content to specific URLs during requests for
|
||||
* a reader.
|
||||
* While this won't help the underlying stores if there are
|
||||
* multiple replications on top of them, it will prevent repeated
|
||||
* work from multiple threads entering an instance of this component
|
||||
* looking for the same content at the same time.
|
||||
*/
|
||||
|
||||
private static Log logger = LogFactory.getLog(ReplicatingContentStore.class);
|
||||
|
||||
private TransactionService transactionService;
|
||||
private ContentStore primaryStore;
|
||||
private List<ContentStore> secondaryStores;
|
||||
private boolean inbound;
|
||||
private boolean outbound;
|
||||
private ThreadPoolExecutor outboundThreadPoolExecutor;
|
||||
|
||||
private Lock readLock;
|
||||
private Lock writeLock;
|
||||
|
||||
/**
|
||||
* Default constructor set <code>inbound = false</code> and <code>outbound = true</code>;
|
||||
*/
|
||||
public ReplicatingContentStore()
|
||||
{
|
||||
inbound = false;
|
||||
outbound = true;
|
||||
|
||||
ReadWriteLock storeLock = new ReentrantReadWriteLock();
|
||||
readLock = storeLock.readLock();
|
||||
writeLock = storeLock.writeLock();
|
||||
}
|
||||
|
||||
/**
|
||||
* Required to ensure that content listeners are executed in a transaction
|
||||
*
|
||||
* @param transactionService
|
||||
*/
|
||||
public void setTransactionService(TransactionService transactionService)
|
||||
{
|
||||
this.transactionService = transactionService;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the primary store that content will be replicated to or from
|
||||
*
|
||||
* @param primaryStore the primary content store
|
||||
*/
|
||||
public void setPrimaryStore(ContentStore primaryStore)
|
||||
{
|
||||
this.primaryStore = primaryStore;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the secondary stores that this component will replicate to or from
|
||||
*
|
||||
* @param stores a list of stores to replicate to or from
|
||||
*/
|
||||
public void setSecondaryStores(List<ContentStore> secondaryStores)
|
||||
{
|
||||
this.secondaryStores = secondaryStores;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set whether or not this component should replicate content to the
|
||||
* primary store if not found.
|
||||
*
|
||||
* @param inbound true to pull content onto the primary store when found
|
||||
* on one of the other stores
|
||||
*/
|
||||
public void setInbound(boolean inbound)
|
||||
{
|
||||
this.inbound = inbound;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set whether or not this component should replicate content to all stores
|
||||
* as it is written.
|
||||
*
|
||||
* @param outbound true to enable synchronous replication to all stores
|
||||
*/
|
||||
public void setOutbound(boolean outbound)
|
||||
{
|
||||
this.outbound = outbound;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the thread pool executer
|
||||
*
|
||||
* @param outboundThreadPoolExecutor set this to have the synchronization occur in a separate
|
||||
* thread
|
||||
*/
|
||||
public void setOutboundThreadPoolExecutor(ThreadPoolExecutor outboundThreadPoolExecutor)
|
||||
{
|
||||
this.outboundThreadPoolExecutor = outboundThreadPoolExecutor;
|
||||
}
|
||||
|
||||
/**
|
||||
* Forwards the call directly to the first store in the list of stores.
|
||||
*/
|
||||
public ContentReader getReader(String contentUrl) throws ContentIOException
|
||||
{
|
||||
if (primaryStore == null)
|
||||
{
|
||||
throw new AlfrescoRuntimeException("ReplicatingContentStore not initialised");
|
||||
}
|
||||
|
||||
// get a read lock so that we are sure that no replication is underway
|
||||
ContentReader existingContentReader = null;
|
||||
readLock.lock();
|
||||
try
|
||||
{
|
||||
// get a reader from the primary store
|
||||
ContentReader primaryReader = primaryStore.getReader(contentUrl);
|
||||
|
||||
// give it straight back if the content is there
|
||||
if (primaryReader.exists())
|
||||
{
|
||||
return primaryReader;
|
||||
}
|
||||
|
||||
// the content is not in the primary reader so we have to go looking for it
|
||||
ContentReader secondaryContentReader = null;
|
||||
for (ContentStore store : secondaryStores)
|
||||
{
|
||||
ContentReader reader = store.getReader(contentUrl);
|
||||
if (reader.exists())
|
||||
{
|
||||
// found the content in a secondary store
|
||||
secondaryContentReader = reader;
|
||||
break;
|
||||
}
|
||||
}
|
||||
// we already know that the primary has nothing
|
||||
// drop out if no content was found
|
||||
if (secondaryContentReader == null)
|
||||
{
|
||||
return primaryReader;
|
||||
}
|
||||
// secondary content was found
|
||||
// return it if we are not doing inbound
|
||||
if (!inbound)
|
||||
{
|
||||
return secondaryContentReader;
|
||||
}
|
||||
|
||||
// we have to replicate inbound
|
||||
existingContentReader = secondaryContentReader;
|
||||
}
|
||||
finally
|
||||
{
|
||||
readLock.unlock();
|
||||
}
|
||||
|
||||
// -- a small gap for concurrent threads to get through --
|
||||
|
||||
// do inbound replication
|
||||
writeLock.lock();
|
||||
try
|
||||
{
|
||||
// double check the primary
|
||||
ContentReader primaryContentReader = primaryStore.getReader(contentUrl);
|
||||
if (primaryContentReader.exists())
|
||||
{
|
||||
// we were beaten to it
|
||||
return primaryContentReader;
|
||||
}
|
||||
// get a writer
|
||||
ContentWriter primaryContentWriter = primaryStore.getWriter(existingContentReader, contentUrl);
|
||||
// copy it over
|
||||
primaryContentWriter.putContent(existingContentReader);
|
||||
// get a writer to the new content
|
||||
primaryContentReader = primaryContentWriter.getReader();
|
||||
// done
|
||||
return primaryContentReader;
|
||||
}
|
||||
finally
|
||||
{
|
||||
writeLock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public ContentWriter getWriter(ContentReader existingContentReader, String newContentUrl) throws ContentIOException
|
||||
{
|
||||
// get the writer
|
||||
ContentWriter writer = primaryStore.getWriter(existingContentReader, newContentUrl);
|
||||
|
||||
// attach a replicating listener if outbound replication is on
|
||||
if (outbound)
|
||||
{
|
||||
if (logger.isDebugEnabled())
|
||||
{
|
||||
logger.debug(
|
||||
"Attaching " + (outboundThreadPoolExecutor == null ? "" : "a") + "synchronous " +
|
||||
"replicating listener to local writer: \n" +
|
||||
" primary store: " + primaryStore + "\n" +
|
||||
" writer: " + writer);
|
||||
}
|
||||
// attach the listener
|
||||
ContentStreamListener listener = new ReplicatingWriteListener(secondaryStores, writer, outboundThreadPoolExecutor);
|
||||
writer.addListener(listener);
|
||||
writer.setTransactionService(transactionService); // mandatory when listeners are added
|
||||
}
|
||||
|
||||
// done
|
||||
return writer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs a delete on the local store and if outbound replication is on, propogates
|
||||
* the delete to the other stores too.
|
||||
*
|
||||
* @return Returns the value returned by the delete on the primary store.
|
||||
*/
|
||||
public boolean delete(String contentUrl) throws ContentIOException
|
||||
{
|
||||
// delete on the primary store
|
||||
boolean deleted = primaryStore.delete(contentUrl);
|
||||
|
||||
// propogate outbound deletions
|
||||
if (outbound)
|
||||
{
|
||||
for (ContentStore store : secondaryStores)
|
||||
{
|
||||
store.delete(contentUrl);
|
||||
}
|
||||
// log
|
||||
if (logger.isDebugEnabled())
|
||||
{
|
||||
logger.debug("Propagated content delete to " + secondaryStores.size() + " stores:" + contentUrl);
|
||||
}
|
||||
}
|
||||
// done
|
||||
if (logger.isDebugEnabled())
|
||||
{
|
||||
logger.debug("Deleted content for URL: " + contentUrl);
|
||||
}
|
||||
return deleted;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Returns the results as given by the primary store, and if inbound
|
||||
* replication is active, merges the URLs from the secondary stores.
|
||||
*/
|
||||
public Set<String> getUrls(Date createdAfter, Date createdBefore) throws ContentIOException
|
||||
{
|
||||
Set<String> urls = new HashSet<String>(1024);
|
||||
|
||||
// add in URLs from primary store
|
||||
Set<String> primaryUrls = primaryStore.getUrls(createdAfter, createdBefore);
|
||||
urls.addAll(primaryUrls);
|
||||
|
||||
// add in URLs from secondary stores (they are visible for reads)
|
||||
for (ContentStore secondaryStore : secondaryStores)
|
||||
{
|
||||
Set<String> secondaryUrls = secondaryStore.getUrls(createdAfter, createdBefore);
|
||||
// merge them
|
||||
urls.addAll(secondaryUrls);
|
||||
}
|
||||
// done
|
||||
if (logger.isDebugEnabled())
|
||||
{
|
||||
logger.debug("Found " + urls.size() + " URLs, of which " + primaryUrls.size() + " are primary: \n" +
|
||||
" created after: " + createdAfter + "\n" +
|
||||
" created before: " + createdBefore);
|
||||
}
|
||||
return urls;
|
||||
}
|
||||
|
||||
/**
|
||||
* Replicates the content upon stream closure. If the thread pool is available,
|
||||
* then the process will be asynchronous.
|
||||
* <p>
|
||||
* No transaction boundaries have been declared as the
|
||||
* {@link ContentWriter#addListener(ContentStreamListener)} method indicates that
|
||||
* all listeners will be called within a transaction.
|
||||
*
|
||||
* @author Derek Hulley
|
||||
*/
|
||||
public static class ReplicatingWriteListener implements ContentStreamListener
|
||||
{
|
||||
private List<ContentStore> stores;
|
||||
private ContentWriter writer;
|
||||
private ThreadPoolExecutor threadPoolExecutor;
|
||||
|
||||
public ReplicatingWriteListener(
|
||||
List<ContentStore> stores,
|
||||
ContentWriter writer,
|
||||
ThreadPoolExecutor threadPoolExecutor)
|
||||
{
|
||||
this.stores = stores;
|
||||
this.writer = writer;
|
||||
this.threadPoolExecutor = threadPoolExecutor;
|
||||
}
|
||||
|
||||
public void contentStreamClosed() throws ContentIOException
|
||||
{
|
||||
Runnable runnable = new ReplicateOnCloseRunnable();
|
||||
if (threadPoolExecutor == null)
|
||||
{
|
||||
// execute direct
|
||||
runnable.run();
|
||||
}
|
||||
else
|
||||
{
|
||||
threadPoolExecutor.execute(runnable);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs the actual replication work.
|
||||
*
|
||||
* @author Derek Hulley
|
||||
*/
|
||||
private class ReplicateOnCloseRunnable implements Runnable
|
||||
{
|
||||
public void run()
|
||||
{
|
||||
for (ContentStore store : stores)
|
||||
{
|
||||
try
|
||||
{
|
||||
// replicate the content to the store - we know the URL that we want to write to
|
||||
ContentReader reader = writer.getReader();
|
||||
String contentUrl = reader.getContentUrl();
|
||||
// in order to replicate, we have to specify the URL that we are going to write to
|
||||
ContentWriter replicatedWriter = store.getWriter(null, contentUrl);
|
||||
// write it
|
||||
replicatedWriter.putContent(reader);
|
||||
|
||||
if (logger.isDebugEnabled())
|
||||
{
|
||||
logger.debug("Replicated content to store: \n" +
|
||||
" url: " + contentUrl + "\n" +
|
||||
" to store: " + store);
|
||||
}
|
||||
}
|
||||
catch (Throwable e)
|
||||
{
|
||||
throw new ContentIOException("Content replication failed: \n" +
|
||||
" url: " + writer.getContentUrl() + "\n" +
|
||||
" to store: " + store);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,201 @@
|
||||
/*
|
||||
* Copyright (C) 2005 Alfresco, Inc.
|
||||
*
|
||||
* Licensed under the Alfresco Network License. You may obtain a
|
||||
* copy of the License at
|
||||
*
|
||||
* http://www.alfrescosoftware.com/legal/
|
||||
*
|
||||
* Please view the license relevant to your network subscription.
|
||||
*
|
||||
* BY CLICKING THE "I UNDERSTAND AND ACCEPT" BOX, OR INSTALLING,
|
||||
* READING OR USING ALFRESCO'S Network SOFTWARE (THE "SOFTWARE"),
|
||||
* YOU ARE AGREEING ON BEHALF OF THE ENTITY LICENSING THE SOFTWARE
|
||||
* ("COMPANY") THAT COMPANY WILL BE BOUND BY AND IS BECOMING A PARTY TO
|
||||
* THIS ALFRESCO NETWORK AGREEMENT ("AGREEMENT") AND THAT YOU HAVE THE
|
||||
* AUTHORITY TO BIND COMPANY. IF COMPANY DOES NOT AGREE TO ALL OF THE
|
||||
* TERMS OF THIS AGREEMENT, DO NOT SELECT THE "I UNDERSTAND AND AGREE"
|
||||
* BOX AND DO NOT INSTALL THE SOFTWARE OR VIEW THE SOURCE CODE. COMPANY
|
||||
* HAS NOT BECOME A LICENSEE OF, AND IS NOT AUTHORIZED TO USE THE
|
||||
* SOFTWARE UNLESS AND UNTIL IT HAS AGREED TO BE BOUND BY THESE LICENSE
|
||||
* TERMS. THE "EFFECTIVE DATE" FOR THIS AGREEMENT SHALL BE THE DAY YOU
|
||||
* CHECK THE "I UNDERSTAND AND ACCEPT" BOX.
|
||||
*/
|
||||
package org.alfresco.repo.content.replication;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.SynchronousQueue;
|
||||
import java.util.concurrent.ThreadPoolExecutor;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.alfresco.repo.content.AbstractContentReadWriteTest;
|
||||
import org.alfresco.repo.content.ContentStore;
|
||||
import org.alfresco.repo.content.filestore.FileContentStore;
|
||||
import org.alfresco.repo.transaction.DummyTransactionService;
|
||||
import org.alfresco.service.cmr.repository.ContentReader;
|
||||
import org.alfresco.service.cmr.repository.ContentWriter;
|
||||
import org.alfresco.util.GUID;
|
||||
import org.alfresco.util.TempFileProvider;
|
||||
|
||||
/**
|
||||
* Tests read and write functionality for the replicating store.
|
||||
* <p>
|
||||
* By default, replication is off for both the inbound and outbound
|
||||
* replication. Specific tests change this.
|
||||
*
|
||||
* @see org.alfresco.repo.content.replication.ReplicatingContentStore
|
||||
*
|
||||
* @author Derek Hulley
|
||||
*/
|
||||
public class ReplicatingContentStoreTest extends AbstractContentReadWriteTest
|
||||
{
|
||||
private static final String SOME_CONTENT = "The No. 1 Ladies' Detective Agency";
|
||||
|
||||
private ReplicatingContentStore replicatingStore;
|
||||
private ContentStore primaryStore;
|
||||
private List<ContentStore> secondaryStores;
|
||||
|
||||
@Override
|
||||
public void setUp() throws Exception
|
||||
{
|
||||
super.setUp();
|
||||
|
||||
File tempDir = TempFileProvider.getTempDir();
|
||||
// create a primary file store
|
||||
String storeDir = tempDir.getAbsolutePath() + File.separatorChar + GUID.generate();
|
||||
primaryStore = new FileContentStore(storeDir);
|
||||
// create some secondary file stores
|
||||
secondaryStores = new ArrayList<ContentStore>(3);
|
||||
for (int i = 0; i < 3; i++)
|
||||
{
|
||||
storeDir = tempDir.getAbsolutePath() + File.separatorChar + GUID.generate();
|
||||
ContentStore store = new FileContentStore(storeDir);
|
||||
secondaryStores.add(store);
|
||||
}
|
||||
replicatingStore = new ReplicatingContentStore();
|
||||
replicatingStore.setTransactionService(new DummyTransactionService());
|
||||
replicatingStore.setPrimaryStore(primaryStore);
|
||||
replicatingStore.setSecondaryStores(secondaryStores);
|
||||
replicatingStore.setOutbound(false);
|
||||
replicatingStore.setInbound(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ContentStore getStore()
|
||||
{
|
||||
return replicatingStore;
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs checks necessary to ensure the proper replication of content for the given
|
||||
* URL
|
||||
*/
|
||||
private void checkForReplication(boolean inbound, boolean outbound, String contentUrl, String content)
|
||||
{
|
||||
if (inbound)
|
||||
{
|
||||
ContentReader reader = primaryStore.getReader(contentUrl);
|
||||
assertTrue("Content was not replicated into the primary store", reader.exists());
|
||||
assertEquals("The replicated content was incorrect", content, reader.getContentString());
|
||||
}
|
||||
if (outbound)
|
||||
{
|
||||
for (ContentStore store : secondaryStores)
|
||||
{
|
||||
ContentReader reader = store.getReader(contentUrl);
|
||||
assertTrue("Content was not replicated out to the secondary stores within a second", reader.exists());
|
||||
assertEquals("The replicated content was incorrect", content, reader.getContentString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks that the url is present in each of the stores
|
||||
*
|
||||
* @param contentUrl
|
||||
* @param mustExist true if the content must exist, false if it must <b>not</b> exist
|
||||
*/
|
||||
private void checkForUrl(String contentUrl, boolean mustExist)
|
||||
{
|
||||
// check that the URL is present for each of the stores
|
||||
for (ContentStore store : secondaryStores)
|
||||
{
|
||||
Set<String> urls = store.getUrls();
|
||||
assertTrue("URL of new content not present in store", urls.contains(contentUrl) == mustExist);
|
||||
}
|
||||
}
|
||||
|
||||
public void testNoReplication() throws Exception
|
||||
{
|
||||
ContentWriter writer = getWriter();
|
||||
writer.putContent(SOME_CONTENT);
|
||||
|
||||
checkForReplication(false, false, writer.getContentUrl(), SOME_CONTENT);
|
||||
}
|
||||
|
||||
public void testOutboundReplication() throws Exception
|
||||
{
|
||||
replicatingStore.setOutbound(true);
|
||||
|
||||
// write some content
|
||||
ContentWriter writer = getWriter();
|
||||
writer.putContent(SOME_CONTENT);
|
||||
String contentUrl = writer.getContentUrl();
|
||||
|
||||
checkForReplication(false, true, contentUrl, SOME_CONTENT);
|
||||
|
||||
// check for outbound deletes
|
||||
replicatingStore.delete(contentUrl);
|
||||
checkForUrl(contentUrl, false);
|
||||
}
|
||||
|
||||
public void testAsyncOutboundReplication() throws Exception
|
||||
{
|
||||
ThreadPoolExecutor tpe = new ThreadPoolExecutor(1, 1, 60L, TimeUnit.SECONDS, new SynchronousQueue<Runnable>());
|
||||
|
||||
replicatingStore.setOutbound(true);
|
||||
replicatingStore.setOutboundThreadPoolExecutor(tpe);
|
||||
|
||||
// write some content
|
||||
ContentWriter writer = getWriter();
|
||||
writer.putContent(SOME_CONTENT);
|
||||
String contentUrl = writer.getContentUrl();
|
||||
|
||||
// wait for a second
|
||||
synchronized(this)
|
||||
{
|
||||
this.wait(1000L);
|
||||
}
|
||||
|
||||
checkForReplication(false, true, contentUrl, SOME_CONTENT);
|
||||
|
||||
// check for outbound deletes
|
||||
replicatingStore.delete(contentUrl);
|
||||
checkForUrl(contentUrl, false);
|
||||
}
|
||||
|
||||
public void testInboundReplication() throws Exception
|
||||
{
|
||||
replicatingStore.setInbound(false);
|
||||
|
||||
// pick a secondary store and write some content to it
|
||||
ContentStore secondaryStore = secondaryStores.get(2);
|
||||
ContentWriter writer = secondaryStore.getWriter(null, null);
|
||||
writer.putContent(SOME_CONTENT);
|
||||
String contentUrl = writer.getContentUrl();
|
||||
|
||||
// get a reader from the replicating store
|
||||
ContentReader reader = replicatingStore.getReader(contentUrl);
|
||||
assertTrue("Reader must have been found in secondary store", reader.exists());
|
||||
|
||||
// set inbound replication on and repeat
|
||||
replicatingStore.setInbound(true);
|
||||
reader = replicatingStore.getReader(contentUrl);
|
||||
|
||||
// this time, it must have been replicated to the primary store
|
||||
checkForReplication(true, false, contentUrl, SOME_CONTENT);
|
||||
}
|
||||
}
|
@@ -0,0 +1,769 @@
|
||||
/*
|
||||
* Copyright (C) 2005 Alfresco, Inc.
|
||||
*
|
||||
* Licensed under the Alfresco Network License. You may obtain a
|
||||
* copy of the License at
|
||||
*
|
||||
* http://www.alfrescosoftware.com/legal/
|
||||
*
|
||||
* Please view the license relevant to your network subscription.
|
||||
*
|
||||
* BY CLICKING THE "I UNDERSTAND AND ACCEPT" BOX, OR INSTALLING,
|
||||
* READING OR USING ALFRESCO'S Network SOFTWARE (THE "SOFTWARE"),
|
||||
* YOU ARE AGREEING ON BEHALF OF THE ENTITY LICENSING THE SOFTWARE
|
||||
* ("COMPANY") THAT COMPANY WILL BE BOUND BY AND IS BECOMING A PARTY TO
|
||||
* THIS ALFRESCO NETWORK AGREEMENT ("AGREEMENT") AND THAT YOU HAVE THE
|
||||
* AUTHORITY TO BIND COMPANY. IF COMPANY DOES NOT AGREE TO ALL OF THE
|
||||
* TERMS OF THIS AGREEMENT, DO NOT SELECT THE "I UNDERSTAND AND AGREE"
|
||||
* BOX AND DO NOT INSTALL THE SOFTWARE OR VIEW THE SOURCE CODE. COMPANY
|
||||
* HAS NOT BECOME A LICENSEE OF, AND IS NOT AUTHORIZED TO USE THE
|
||||
* SOFTWARE UNLESS AND UNTIL IT HAS AGREED TO BE BOUND BY THESE LICENSE
|
||||
* TERMS. THE "EFFECTIVE DATE" FOR THIS AGREEMENT SHALL BE THE DAY YOU
|
||||
* CHECK THE "I UNDERSTAND AND ACCEPT" BOX.
|
||||
*/
|
||||
package org.alfresco.repo.node.index;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.alfresco.error.AlfrescoRuntimeException;
|
||||
import org.alfresco.model.ContentModel;
|
||||
import org.alfresco.repo.domain.NodeStatus;
|
||||
import org.alfresco.repo.search.Indexer;
|
||||
import org.alfresco.repo.search.impl.lucene.LuceneIndexerImpl;
|
||||
import org.alfresco.repo.search.impl.lucene.fts.FullTextSearchIndexer;
|
||||
import org.alfresco.repo.transaction.TransactionUtil;
|
||||
import org.alfresco.repo.transaction.TransactionUtil.TransactionWork;
|
||||
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.transaction.TransactionService;
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.hibernate.CacheMode;
|
||||
import org.hibernate.Query;
|
||||
import org.hibernate.Session;
|
||||
import org.springframework.orm.hibernate3.HibernateCallback;
|
||||
import org.springframework.orm.hibernate3.support.HibernateDaoSupport;
|
||||
|
||||
/**
|
||||
* Ensures that the FTS indexing picks up on any outstanding documents that
|
||||
* require indexing.
|
||||
* <p>
|
||||
* This component must be used as a singleton (one per VM) and may only be
|
||||
* called to reindex once. It will start a thread that processes all available
|
||||
* transactions and keeps checking to ensure that the index is up to date with
|
||||
* the latest database changes.
|
||||
* <p>
|
||||
* <b>The following points are important:</b>
|
||||
* <ul>
|
||||
* <li>
|
||||
* By default, the Hibernate L2 cache is used during processing.
|
||||
* This can be disabled by either disabling the L2 cache globally
|
||||
* for the server (not recommended) or by setting the
|
||||
* {@link #setL2CacheMode(String) l2CacheMode} property. If the
|
||||
* database is static then the L2 cache usage can be set to use
|
||||
* the <code>NORMAL</code> mode. <code>REFRESH</code> should be
|
||||
* used where the server will still be accessed from some clients
|
||||
* despite the database changing.
|
||||
* </li>
|
||||
* <li>
|
||||
* This process should not run continuously on a live
|
||||
* server as it would be performing unecessary work.
|
||||
* If it was left running, however, it would not
|
||||
* lead to data corruption or such-like. Use the
|
||||
* {@link #setRunContinuously(boolean) runContinuously} property
|
||||
* to change this behaviour.
|
||||
* </li>
|
||||
* </ul>
|
||||
*
|
||||
* @author Derek Hulley
|
||||
*/
|
||||
public class FullIndexRecoveryComponent extends HibernateDaoSupport implements IndexRecovery
|
||||
{
|
||||
public static final String QUERY_GET_NEXT_CHANGE_TXN_IDS = "node.GetNextChangeTxnIds";
|
||||
public static final String QUERY_GET_CHANGED_NODE_STATUSES = "node.GetChangedNodeStatuses";
|
||||
public static final String QUERY_GET_CHANGED_NODE_STATUSES_COUNT = "node.GetChangedNodeStatusesCount";
|
||||
|
||||
private static final String START_TXN_ID = "000";
|
||||
|
||||
private static Log logger = LogFactory.getLog(FullIndexRecoveryComponent.class);
|
||||
|
||||
/** ensures that this process is kicked off once per VM */
|
||||
private static boolean started = false;
|
||||
/** The current transaction ID being processed */
|
||||
private static String currentTxnId = START_TXN_ID;
|
||||
/** kept to notify the thread that it should quite */
|
||||
private boolean killThread = false;
|
||||
|
||||
/** provides transactions to atomically index each missed transaction */
|
||||
private TransactionService transactionService;
|
||||
/** the component to index the node hierarchy */
|
||||
private Indexer indexer;
|
||||
/** the FTS indexer that we will prompt to pick up on any un-indexed text */
|
||||
private FullTextSearchIndexer ftsIndexer;
|
||||
/** the component providing searches of the indexed nodes */
|
||||
private SearchService searcher;
|
||||
/** the component giving direct access to <b>node</b> instances */
|
||||
private NodeService nodeService;
|
||||
/** the stores to reindex */
|
||||
private List<StoreRef> storeRefs;
|
||||
/** set this to run the index recovery component */
|
||||
private boolean executeFullRecovery;
|
||||
/** set this on to keep checking for new transactions and never stop */
|
||||
private boolean runContinuously;
|
||||
/** set the time to wait between checking indexes */
|
||||
private long waitTime;
|
||||
/** controls how the L2 cache is used */
|
||||
private CacheMode l2CacheMode;
|
||||
|
||||
/**
|
||||
* @return Returns the ID of the current (or last) transaction processed
|
||||
*/
|
||||
public static String getCurrentTransactionId()
|
||||
{
|
||||
return currentTxnId;
|
||||
}
|
||||
|
||||
public FullIndexRecoveryComponent()
|
||||
{
|
||||
this.storeRefs = new ArrayList<StoreRef>(2);
|
||||
|
||||
this.killThread = false;
|
||||
this.executeFullRecovery = false;
|
||||
this.runContinuously = false;
|
||||
this.waitTime = 1000L;
|
||||
this.l2CacheMode = CacheMode.REFRESH;
|
||||
|
||||
// ensure that we kill the thread when the VM is shutting down
|
||||
Runnable shutdownRunnable = new Runnable()
|
||||
{
|
||||
public void run()
|
||||
{
|
||||
killThread = true;
|
||||
};
|
||||
};
|
||||
Thread shutdownThread = new Thread(shutdownRunnable);
|
||||
Runtime.getRuntime().addShutdownHook(shutdownThread);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Returns true if the component has already been started
|
||||
*/
|
||||
public static boolean isStarted()
|
||||
{
|
||||
return started;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param transactionService provide transactions to index each missed transaction
|
||||
*/
|
||||
public void setTransactionService(TransactionService transactionService)
|
||||
{
|
||||
this.transactionService = transactionService;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param indexer the indexer that will be index
|
||||
*/
|
||||
public void setIndexer(Indexer indexer)
|
||||
{
|
||||
this.indexer = indexer;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ftsIndexer the FTS background indexer
|
||||
*/
|
||||
public void setFtsIndexer(FullTextSearchIndexer ftsIndexer)
|
||||
{
|
||||
this.ftsIndexer = ftsIndexer;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param searcher component providing index searches
|
||||
*/
|
||||
public void setSearcher(SearchService searcher)
|
||||
{
|
||||
this.searcher = searcher;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param nodeService provides information about nodes for indexing
|
||||
*/
|
||||
public void setNodeService(NodeService nodeService)
|
||||
{
|
||||
this.nodeService = nodeService;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the stores that need reindexing
|
||||
*
|
||||
* @param storeRefStrings a list of strings representing store references
|
||||
*/
|
||||
public void setStores(List<String> storeRefStrings)
|
||||
{
|
||||
storeRefs.clear();
|
||||
for (String storeRefStr : storeRefStrings)
|
||||
{
|
||||
StoreRef storeRef = new StoreRef(storeRefStr);
|
||||
storeRefs.add(storeRef);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set this to <code>true</code> to initiate the full index recovery.
|
||||
* <p>
|
||||
* This used to default to <code>true</code> but is now false. Set this
|
||||
* if the potentially long-running process of checking and fixing the
|
||||
* indexes must be started.
|
||||
*
|
||||
* @param executeFullRecovery
|
||||
*/
|
||||
public void setExecuteFullRecovery(boolean executeFullRecovery)
|
||||
{
|
||||
this.executeFullRecovery = executeFullRecovery;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set this to ensure that the process continuously checks for new transactions.
|
||||
* If not, it will permanently terminate once it catches up with the current
|
||||
* transactions.
|
||||
*
|
||||
* @param runContinuously true to never cease looking for new transactions
|
||||
*/
|
||||
public void setRunContinuously(boolean runContinuously)
|
||||
{
|
||||
this.runContinuously = runContinuously;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the time to wait between checking for new transaction changes in the database.
|
||||
*
|
||||
* @param waitTime the time to wait in milliseconds
|
||||
*/
|
||||
public void setWaitTime(long waitTime)
|
||||
{
|
||||
this.waitTime = waitTime;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the hibernate cache mode by name
|
||||
*
|
||||
* @see org.hibernate.CacheMode
|
||||
*/
|
||||
public void setL2CacheMode(String l2CacheModeStr)
|
||||
{
|
||||
if (l2CacheModeStr.equals("GET"))
|
||||
{
|
||||
l2CacheMode = CacheMode.GET;
|
||||
}
|
||||
else if (l2CacheModeStr.equals("IGNORE"))
|
||||
{
|
||||
l2CacheMode = CacheMode.IGNORE;
|
||||
}
|
||||
else if (l2CacheModeStr.equals("NORMAL"))
|
||||
{
|
||||
l2CacheMode = CacheMode.NORMAL;
|
||||
}
|
||||
else if (l2CacheModeStr.equals("PUT"))
|
||||
{
|
||||
l2CacheMode = CacheMode.PUT;
|
||||
}
|
||||
else if (l2CacheModeStr.equals("REFRESH"))
|
||||
{
|
||||
l2CacheMode = CacheMode.REFRESH;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new IllegalArgumentException("Unrecognised Hibernate L2 cache mode: " + l2CacheModeStr);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure that the index is up to date with the current state of the persistence layer.
|
||||
* The full list of unique transaction change IDs is retrieved and used to detect
|
||||
* which are not present in the index. All the node changes and deletions for the
|
||||
* remaining transactions are then indexed.
|
||||
*/
|
||||
public synchronized void reindex()
|
||||
{
|
||||
if (FullIndexRecoveryComponent.started)
|
||||
{
|
||||
throw new AlfrescoRuntimeException
|
||||
("Only one FullIndexRecoveryComponent may be used per VM and it may only be called once");
|
||||
}
|
||||
|
||||
// ensure that we don't redo this work
|
||||
FullIndexRecoveryComponent.started = true;
|
||||
|
||||
// work to mark the stores for full text reindexing
|
||||
TransactionWork<Object> ftsReindexWork = new TransactionWork<Object>()
|
||||
{
|
||||
public Object doWork()
|
||||
{
|
||||
// reindex each store
|
||||
for (StoreRef storeRef : storeRefs)
|
||||
{
|
||||
// check if the store exists
|
||||
if (!nodeService.exists(storeRef))
|
||||
{
|
||||
// store does not exist
|
||||
if (logger.isDebugEnabled())
|
||||
{
|
||||
logger.debug("Skipping reindex of non-existent store: " + storeRef);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
// prompt FTS to reindex the store
|
||||
ftsIndexer.requiresIndex(storeRef);
|
||||
}
|
||||
// done
|
||||
if (logger.isDebugEnabled())
|
||||
{
|
||||
logger.debug("Prompted FTS index on stores: " + storeRefs);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
};
|
||||
TransactionUtil.executeInNonPropagatingUserTransaction(transactionService, ftsReindexWork);
|
||||
|
||||
// start full index recovery, if necessary
|
||||
if (!this.executeFullRecovery)
|
||||
{
|
||||
if (logger.isDebugEnabled())
|
||||
{
|
||||
logger.debug("Full index recovery is off - quitting");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// set the state of the reindex
|
||||
FullIndexRecoveryComponent.currentTxnId = START_TXN_ID;
|
||||
|
||||
// start a stateful thread that will begin processing the reindexing the transactions
|
||||
Runnable runnable = new ReindexRunner();
|
||||
Thread reindexThread = new Thread(runnable);
|
||||
// make it a daemon thread
|
||||
reindexThread.setDaemon(true);
|
||||
// it should not be a high priority
|
||||
reindexThread.setPriority(Thread.MIN_PRIORITY);
|
||||
// start it
|
||||
reindexThread.start();
|
||||
|
||||
if (logger.isDebugEnabled())
|
||||
{
|
||||
logger.debug("Full index recovery thread started: \n" +
|
||||
" continuous: " + runContinuously + "\n" +
|
||||
" stores: " + storeRefs);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Stateful thread runnable that executes reindex calls.
|
||||
*
|
||||
* @see FullIndexRecoveryComponent#reindexNodes()
|
||||
*
|
||||
* @author Derek Hulley
|
||||
*/
|
||||
private class ReindexRunner implements Runnable
|
||||
{
|
||||
public void run()
|
||||
{
|
||||
// keep this thread going permanently
|
||||
while (!killThread)
|
||||
{
|
||||
try
|
||||
{
|
||||
// reindex nodes
|
||||
List<String> txnsIndexed = FullIndexRecoveryComponent.this.reindexNodes();
|
||||
// reindex missing content
|
||||
@SuppressWarnings("unused")
|
||||
int missingContentCount = FullIndexRecoveryComponent.this.reindexMissingContent();
|
||||
// check if the process should terminate
|
||||
if (txnsIndexed.size() == 0 && !runContinuously)
|
||||
{
|
||||
// the thread has caught up with all the available work and should not
|
||||
// run continuously
|
||||
if (logger.isDebugEnabled())
|
||||
{
|
||||
logger.debug("Thread quitting - no more available indexing to do: \n" +
|
||||
" last txn: " + FullIndexRecoveryComponent.getCurrentTransactionId());
|
||||
}
|
||||
break;
|
||||
}
|
||||
// brief pause
|
||||
synchronized(FullIndexRecoveryComponent.this)
|
||||
{
|
||||
FullIndexRecoveryComponent.this.wait(waitTime);
|
||||
}
|
||||
}
|
||||
catch (InterruptedException e)
|
||||
{
|
||||
// ignore
|
||||
}
|
||||
catch (Throwable e)
|
||||
{
|
||||
if (killThread)
|
||||
{
|
||||
// the shutdown may have caused the exception - ignore it
|
||||
}
|
||||
else
|
||||
{
|
||||
// we are still a go; report it
|
||||
logger.error("Reindex failure", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Returns the number of documents reindexed
|
||||
*/
|
||||
private int reindexMissingContent()
|
||||
{
|
||||
int count = 0;
|
||||
for (StoreRef storeRef : storeRefs)
|
||||
{
|
||||
count += reindexMissingContent(storeRef);
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param storeRef the store to check for missing content
|
||||
* @return Returns the number of documents reindexed
|
||||
*/
|
||||
private int reindexMissingContent(StoreRef storeRef)
|
||||
{
|
||||
SearchParameters sp = new SearchParameters();
|
||||
sp.addStore(storeRef);
|
||||
|
||||
// search for it in the index
|
||||
String query = "TEXT:" + LuceneIndexerImpl.NOT_INDEXED_CONTENT_MISSING;
|
||||
sp.setLanguage(SearchService.LANGUAGE_LUCENE);
|
||||
sp.setQuery(query);
|
||||
ResultSet results = null;
|
||||
try
|
||||
{
|
||||
results = searcher.query(sp);
|
||||
|
||||
int count = 0;
|
||||
// loop over the results and get the details of the nodes that have missing content
|
||||
List<ChildAssociationRef> assocRefs = results.getChildAssocRefs();
|
||||
for (ChildAssociationRef assocRef : assocRefs)
|
||||
{
|
||||
final NodeRef childNodeRef = assocRef.getChildRef();
|
||||
// prompt for a reindex - it might fail again, but we just keep plugging away
|
||||
TransactionWork<Object> reindexWork = new TransactionWork<Object>()
|
||||
{
|
||||
public Object doWork()
|
||||
{
|
||||
indexer.updateNode(childNodeRef);
|
||||
return null;
|
||||
}
|
||||
};
|
||||
TransactionUtil.executeInNonPropagatingUserTransaction(transactionService, reindexWork);
|
||||
count++;
|
||||
}
|
||||
// done
|
||||
if (logger.isDebugEnabled())
|
||||
{
|
||||
logger.debug("Reindexed missing content: \n" +
|
||||
" store: " + storeRef + "\n" +
|
||||
" node count: " + count);
|
||||
}
|
||||
return count;
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (results != null)
|
||||
{
|
||||
results.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Returns the transaction ID just reindexed, i.e. where some work was performed
|
||||
*/
|
||||
private List<String> reindexNodes()
|
||||
{
|
||||
// get a list of all transactions still requiring a check
|
||||
List<String> txnsToCheck = getNextChangeTxnIds(FullIndexRecoveryComponent.currentTxnId);
|
||||
|
||||
// loop over each transaction
|
||||
for (String changeTxnId : txnsToCheck)
|
||||
{
|
||||
reindexNodes(changeTxnId);
|
||||
}
|
||||
|
||||
// done
|
||||
return txnsToCheck;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reindexes changes specific to the change transaction ID.
|
||||
* <p>
|
||||
* <b>All exceptions are absorbed.</b>
|
||||
*/
|
||||
private void reindexNodes(final String changeTxnId)
|
||||
{
|
||||
/*
|
||||
* This must execute each within its own transaction.
|
||||
* The cache size is therefore not an issue.
|
||||
*/
|
||||
TransactionWork<Object> reindexWork = new TransactionWork<Object>()
|
||||
{
|
||||
public Object doWork() throws Exception
|
||||
{
|
||||
// perform the work in a Hibernate callback
|
||||
HibernateCallback callback = new ReindexCallback(changeTxnId);
|
||||
getHibernateTemplate().execute(callback);
|
||||
// done
|
||||
return null;
|
||||
}
|
||||
};
|
||||
try
|
||||
{
|
||||
TransactionUtil.executeInNonPropagatingUserTransaction(transactionService, reindexWork);
|
||||
}
|
||||
catch (Throwable e)
|
||||
{
|
||||
logger.error("Transaction reindex failed: \n" +
|
||||
" txn: " + changeTxnId,
|
||||
e);
|
||||
}
|
||||
finally
|
||||
{
|
||||
// Up the current transaction now, in case the process fails at this point.
|
||||
// This will prevent the transaction from being processed again.
|
||||
// This applies to failures as well, which should be dealt with externally
|
||||
// and having the entire process start again, e.g. such as a system reboot
|
||||
currentTxnId = changeTxnId;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Stateful inner class that implements a single reindex call for a given store
|
||||
* and transaction.
|
||||
* <p>
|
||||
* It must be called within its own transaction.
|
||||
*
|
||||
* @author Derek Hulley
|
||||
*/
|
||||
private class ReindexCallback implements HibernateCallback
|
||||
{
|
||||
private final String changeTxnId;
|
||||
|
||||
public ReindexCallback(String changeTxnId)
|
||||
{
|
||||
this.changeTxnId = changeTxnId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Changes the L2 cache usage before reindexing for each store
|
||||
*
|
||||
* @see #reindexNodes(StoreRef, String)
|
||||
*/
|
||||
public Object doInHibernate(Session session)
|
||||
{
|
||||
// set the way the L2 cache is used
|
||||
getSession().setCacheMode(l2CacheMode);
|
||||
|
||||
// reindex each store
|
||||
for (StoreRef storeRef : storeRefs)
|
||||
{
|
||||
if (!nodeService.exists(storeRef))
|
||||
{
|
||||
// the store is not present
|
||||
continue;
|
||||
}
|
||||
// reindex for store
|
||||
reindexNodes(storeRef, changeTxnId);
|
||||
}
|
||||
// done
|
||||
return null;
|
||||
}
|
||||
|
||||
private void reindexNodes(StoreRef storeRef, String changeTxnId)
|
||||
{
|
||||
// check if we need to perform this operation
|
||||
SearchParameters sp = new SearchParameters();
|
||||
sp.addStore(storeRef);
|
||||
|
||||
// search for it in the index
|
||||
String query = "TX:\"" + changeTxnId + "\"";
|
||||
sp.setLanguage(SearchService.LANGUAGE_LUCENE);
|
||||
sp.setQuery(query);
|
||||
ResultSet results = null;
|
||||
try
|
||||
{
|
||||
results = searcher.query(sp);
|
||||
// did the index have any of these changes?
|
||||
if (results.length() > 0)
|
||||
{
|
||||
// the transaction has an entry in the index - assume that it was
|
||||
// atomically correct
|
||||
if (logger.isDebugEnabled())
|
||||
{
|
||||
logger.debug("Transaction present in index - no indexing required: \n" +
|
||||
" store: " + storeRef + "\n" +
|
||||
" txn: " + changeTxnId);
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (results != null)
|
||||
{
|
||||
results.close();
|
||||
}
|
||||
}
|
||||
// the index has no record of this
|
||||
// were there any changes, or is it all just deletions?
|
||||
int changedCount = getChangedNodeStatusesCount(storeRef, changeTxnId);
|
||||
if (changedCount == 0)
|
||||
{
|
||||
// no nodes were changed in the transaction, i.e. they are only deletions
|
||||
// the index is quite right not to have any entries for the transaction
|
||||
if (logger.isDebugEnabled())
|
||||
{
|
||||
logger.debug("Transaction only has deletions - no indexing required: \n" +
|
||||
" store: " + storeRef + "\n" +
|
||||
" txn: " + changeTxnId);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// process the deletions relevant to the txn and the store
|
||||
List<NodeStatus> deletedNodeStatuses = getDeletedNodeStatuses(storeRef, changeTxnId);
|
||||
for (NodeStatus status : deletedNodeStatuses)
|
||||
{
|
||||
NodeRef nodeRef = new NodeRef(storeRef, status.getKey().getGuid());
|
||||
// only the child node ref is relevant
|
||||
ChildAssociationRef assocRef = new ChildAssociationRef(
|
||||
ContentModel.ASSOC_CHILDREN,
|
||||
null,
|
||||
null,
|
||||
nodeRef);
|
||||
indexer.deleteNode(assocRef);
|
||||
}
|
||||
|
||||
// process additions
|
||||
List<NodeStatus> changedNodeStatuses = getChangedNodeStatuses(storeRef, changeTxnId);
|
||||
for (NodeStatus status : changedNodeStatuses)
|
||||
{
|
||||
NodeRef nodeRef = new NodeRef(storeRef, status.getKey().getGuid());
|
||||
// get the primary assoc for the node
|
||||
ChildAssociationRef primaryAssocRef = nodeService.getPrimaryParent(nodeRef);
|
||||
// reindex
|
||||
indexer.createNode(primaryAssocRef);
|
||||
}
|
||||
|
||||
// done
|
||||
if (logger.isDebugEnabled())
|
||||
{
|
||||
logger.debug("Transaction reindexed: \n" +
|
||||
" store: " + storeRef + "\n" +
|
||||
" txn: " + changeTxnId + "\n" +
|
||||
" deletions: " + deletedNodeStatuses.size() + "\n" +
|
||||
" modifications: " + changedNodeStatuses.size());
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieve all transaction IDs that are greater than the given transaction ID.
|
||||
*
|
||||
* @param currentTxnId the transaction ID that must be less than all returned results
|
||||
* @return Returns an ordered list of transaction IDs
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public List<String> getNextChangeTxnIds(final String currentTxnId)
|
||||
{
|
||||
HibernateCallback callback = new HibernateCallback()
|
||||
{
|
||||
public Object doInHibernate(Session session)
|
||||
{
|
||||
Query query = session.getNamedQuery(QUERY_GET_NEXT_CHANGE_TXN_IDS);
|
||||
query.setString("currentTxnId", currentTxnId)
|
||||
.setReadOnly(true);
|
||||
return query.list();
|
||||
}
|
||||
};
|
||||
List<String> queryResults = (List<String>) getHibernateTemplate().execute(callback);
|
||||
// done
|
||||
return queryResults;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public int getChangedNodeStatusesCount(final StoreRef storeRef, final String changeTxnId)
|
||||
{
|
||||
HibernateCallback callback = new HibernateCallback()
|
||||
{
|
||||
public Object doInHibernate(Session session)
|
||||
{
|
||||
Query query = session.getNamedQuery(QUERY_GET_CHANGED_NODE_STATUSES_COUNT);
|
||||
query.setBoolean("deleted", false)
|
||||
.setString("storeProtocol", storeRef.getProtocol())
|
||||
.setString("storeIdentifier", storeRef.getIdentifier())
|
||||
.setString("changeTxnId", changeTxnId)
|
||||
.setReadOnly(true);
|
||||
return query.uniqueResult();
|
||||
}
|
||||
};
|
||||
Integer changeCount = (Integer) getHibernateTemplate().execute(callback);
|
||||
// done
|
||||
return changeCount.intValue();
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public List<NodeStatus> getChangedNodeStatuses(final StoreRef storeRef, final String changeTxnId)
|
||||
{
|
||||
HibernateCallback callback = new HibernateCallback()
|
||||
{
|
||||
public Object doInHibernate(Session session)
|
||||
{
|
||||
Query query = session.getNamedQuery(QUERY_GET_CHANGED_NODE_STATUSES);
|
||||
query.setBoolean("deleted", false)
|
||||
.setString("storeProtocol", storeRef.getProtocol())
|
||||
.setString("storeIdentifier", storeRef.getIdentifier())
|
||||
.setString("changeTxnId", changeTxnId)
|
||||
.setReadOnly(true);
|
||||
return query.list();
|
||||
}
|
||||
};
|
||||
List<NodeStatus> queryResults = (List) getHibernateTemplate().execute(callback);
|
||||
// done
|
||||
return queryResults;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public List<NodeStatus> getDeletedNodeStatuses(final StoreRef storeRef, final String changeTxnId)
|
||||
{
|
||||
HibernateCallback callback = new HibernateCallback()
|
||||
{
|
||||
public Object doInHibernate(Session session)
|
||||
{
|
||||
Query query = session.getNamedQuery(QUERY_GET_CHANGED_NODE_STATUSES);
|
||||
query.setBoolean("deleted", true)
|
||||
.setString("storeProtocol", storeRef.getProtocol())
|
||||
.setString("storeIdentifier", storeRef.getIdentifier())
|
||||
.setString("changeTxnId", changeTxnId)
|
||||
.setReadOnly(true);
|
||||
return query.list();
|
||||
}
|
||||
};
|
||||
List<NodeStatus> queryResults = (List) getHibernateTemplate().execute(callback);
|
||||
// done
|
||||
return queryResults;
|
||||
}
|
||||
}
|
@@ -0,0 +1,166 @@
|
||||
/*
|
||||
* Copyright (C) 2005 Alfresco, Inc.
|
||||
*
|
||||
* Licensed under the Alfresco Network License. You may obtain a
|
||||
* copy of the License at
|
||||
*
|
||||
* http://www.alfrescosoftware.com/legal/
|
||||
*
|
||||
* Please view the license relevant to your network subscription.
|
||||
*
|
||||
* BY CLICKING THE "I UNDERSTAND AND ACCEPT" BOX, OR INSTALLING,
|
||||
* READING OR USING ALFRESCO'S Network SOFTWARE (THE "SOFTWARE"),
|
||||
* YOU ARE AGREEING ON BEHALF OF THE ENTITY LICENSING THE SOFTWARE
|
||||
* ("COMPANY") THAT COMPANY WILL BE BOUND BY AND IS BECOMING A PARTY TO
|
||||
* THIS ALFRESCO NETWORK AGREEMENT ("AGREEMENT") AND THAT YOU HAVE THE
|
||||
* AUTHORITY TO BIND COMPANY. IF COMPANY DOES NOT AGREE TO ALL OF THE
|
||||
* TERMS OF THIS AGREEMENT, DO NOT SELECT THE "I UNDERSTAND AND AGREE"
|
||||
* BOX AND DO NOT INSTALL THE SOFTWARE OR VIEW THE SOURCE CODE. COMPANY
|
||||
* HAS NOT BECOME A LICENSEE OF, AND IS NOT AUTHORIZED TO USE THE
|
||||
* SOFTWARE UNLESS AND UNTIL IT HAS AGREED TO BE BOUND BY THESE LICENSE
|
||||
* TERMS. THE "EFFECTIVE DATE" FOR THIS AGREEMENT SHALL BE THE DAY YOU
|
||||
* CHECK THE "I UNDERSTAND AND ACCEPT" BOX.
|
||||
*/
|
||||
package org.alfresco.repo.node.index;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import junit.framework.TestCase;
|
||||
|
||||
import org.alfresco.model.ContentModel;
|
||||
import org.alfresco.repo.search.Indexer;
|
||||
import org.alfresco.repo.transaction.AlfrescoTransactionSupport;
|
||||
import org.alfresco.repo.transaction.TransactionUtil;
|
||||
import org.alfresco.repo.transaction.TransactionUtil.TransactionWork;
|
||||
import org.alfresco.service.cmr.repository.ChildAssociationRef;
|
||||
import org.alfresco.service.cmr.repository.InvalidStoreRefException;
|
||||
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.namespace.NamespaceService;
|
||||
import org.alfresco.service.namespace.QName;
|
||||
import org.alfresco.service.transaction.TransactionService;
|
||||
import org.alfresco.util.ApplicationContextHelper;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
|
||||
/**
|
||||
* Checks that full index recovery is possible
|
||||
*
|
||||
* @author Derek Hulley
|
||||
*/
|
||||
public class FullIndexRecoveryComponentTest extends TestCase
|
||||
{
|
||||
private static ApplicationContext ctx = ApplicationContextHelper.getApplicationContext();
|
||||
|
||||
private TransactionService transactionService;
|
||||
private FullIndexRecoveryComponent indexRecoverer;
|
||||
private NodeService nodeService;
|
||||
private TransactionService txnService;
|
||||
private Indexer indexer;
|
||||
|
||||
private List<StoreRef> storeRefs;
|
||||
|
||||
public void setUp() throws Exception
|
||||
{
|
||||
transactionService = (TransactionService) ctx.getBean("transactionComponent");
|
||||
indexRecoverer = (FullIndexRecoveryComponent) ctx.getBean("indexRecoveryComponent");
|
||||
txnService = (TransactionService) ctx.getBean("transactionComponent");
|
||||
nodeService = (NodeService) ctx.getBean("nodeService");
|
||||
indexer = (Indexer) ctx.getBean("indexerComponent");
|
||||
|
||||
// create 2 stores
|
||||
TransactionWork<List<StoreRef>> createStoresWork = new TransactionWork<List<StoreRef>>()
|
||||
{
|
||||
public List<StoreRef> doWork() throws Exception
|
||||
{
|
||||
List<StoreRef> storeRefs = new ArrayList<StoreRef>(2);
|
||||
storeRefs.add(nodeService.createStore(StoreRef.PROTOCOL_WORKSPACE, getName() + System.nanoTime()));
|
||||
storeRefs.add(nodeService.createStore(StoreRef.PROTOCOL_WORKSPACE, getName() + System.nanoTime()));
|
||||
return storeRefs;
|
||||
}
|
||||
};
|
||||
storeRefs = TransactionUtil.executeInUserTransaction(transactionService, createStoresWork);
|
||||
}
|
||||
|
||||
public void testNothing() throws Exception
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public void xtestReindexing() throws Exception
|
||||
{
|
||||
// don't do anything if the component has already started
|
||||
if (FullIndexRecoveryComponent.isStarted())
|
||||
{
|
||||
return;
|
||||
}
|
||||
// deletes a content node from the index
|
||||
final List<String> storeRefStrings = new ArrayList<String>(2);
|
||||
TransactionWork<String> dropNodeIndexWork = new TransactionWork<String>()
|
||||
{
|
||||
public String doWork()
|
||||
{
|
||||
// create a node in each store and drop it from the index
|
||||
for (StoreRef storeRef : storeRefs)
|
||||
{
|
||||
try
|
||||
{
|
||||
NodeRef rootNodeRef = nodeService.getRootNode(storeRef);
|
||||
ChildAssociationRef assocRef = nodeService.createNode(
|
||||
rootNodeRef,
|
||||
ContentModel.ASSOC_CONTAINS,
|
||||
QName.createQName(NamespaceService.ALFRESCO_URI, "unindexedChild" + System.currentTimeMillis()),
|
||||
ContentModel.TYPE_BASE);
|
||||
// this will have indexed it, so remove it from the index
|
||||
indexer.deleteNode(assocRef);
|
||||
// make the string version of the storeRef
|
||||
storeRefStrings.add(storeRef.toString());
|
||||
}
|
||||
catch (InvalidStoreRefException e)
|
||||
{
|
||||
// just ignore stores that are invalid
|
||||
}
|
||||
}
|
||||
return AlfrescoTransactionSupport.getTransactionId();
|
||||
}
|
||||
};
|
||||
|
||||
// create un-indexed nodes
|
||||
String txnId = TransactionUtil.executeInNonPropagatingUserTransaction(txnService, dropNodeIndexWork);
|
||||
|
||||
indexRecoverer.setExecuteFullRecovery(true);
|
||||
indexRecoverer.setStores(storeRefStrings);
|
||||
// reindex
|
||||
indexRecoverer.reindex();
|
||||
|
||||
// check that reindexing fails
|
||||
try
|
||||
{
|
||||
indexRecoverer.reindex();
|
||||
fail("Reindexer failed to prevent reindex from being called twice");
|
||||
}
|
||||
catch (RuntimeException e)
|
||||
{
|
||||
// expected
|
||||
}
|
||||
|
||||
// loop for some time, giving it a chance to do its thing
|
||||
String lastProcessedTxnId = null;
|
||||
for (int i = 0; i < 60; i++)
|
||||
{
|
||||
lastProcessedTxnId = FullIndexRecoveryComponent.getCurrentTransactionId();
|
||||
if (lastProcessedTxnId.equals(txnId))
|
||||
{
|
||||
break;
|
||||
}
|
||||
// wait for a second
|
||||
synchronized(this)
|
||||
{
|
||||
this.wait(1000L);
|
||||
}
|
||||
}
|
||||
// check that the index was recovered
|
||||
assertEquals("Index transaction not up to date", txnId, lastProcessedTxnId);
|
||||
}
|
||||
}
|
@@ -0,0 +1,209 @@
|
||||
/*
|
||||
* Copyright (C) 2005 Alfresco, Inc.
|
||||
*
|
||||
* Licensed under the Alfresco Network License. You may obtain a
|
||||
* copy of the License at
|
||||
*
|
||||
* http://www.alfrescosoftware.com/legal/
|
||||
*
|
||||
* Please view the license relevant to your network subscription.
|
||||
*
|
||||
* BY CLICKING THE "I UNDERSTAND AND ACCEPT" BOX, OR INSTALLING,
|
||||
* READING OR USING ALFRESCO'S Network SOFTWARE (THE "SOFTWARE"),
|
||||
* YOU ARE AGREEING ON BEHALF OF THE ENTITY LICENSING THE SOFTWARE
|
||||
* ("COMPANY") THAT COMPANY WILL BE BOUND BY AND IS BECOMING A PARTY TO
|
||||
* THIS ALFRESCO NETWORK AGREEMENT ("AGREEMENT") AND THAT YOU HAVE THE
|
||||
* AUTHORITY TO BIND COMPANY. IF COMPANY DOES NOT AGREE TO ALL OF THE
|
||||
* TERMS OF THIS AGREEMENT, DO NOT SELECT THE "I UNDERSTAND AND AGREE"
|
||||
* BOX AND DO NOT INSTALL THE SOFTWARE OR VIEW THE SOURCE CODE. COMPANY
|
||||
* HAS NOT BECOME A LICENSEE OF, AND IS NOT AUTHORIZED TO USE THE
|
||||
* SOFTWARE UNLESS AND UNTIL IT HAS AGREED TO BE BOUND BY THESE LICENSE
|
||||
* TERMS. THE "EFFECTIVE DATE" FOR THIS AGREEMENT SHALL BE THE DAY YOU
|
||||
* CHECK THE "I UNDERSTAND AND ACCEPT" BOX.
|
||||
*/
|
||||
package org.alfresco.repo.security.authentication.jaas;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import javax.security.auth.callback.Callback;
|
||||
import javax.security.auth.callback.CallbackHandler;
|
||||
import javax.security.auth.callback.LanguageCallback;
|
||||
import javax.security.auth.callback.NameCallback;
|
||||
import javax.security.auth.callback.PasswordCallback;
|
||||
import javax.security.auth.callback.UnsupportedCallbackException;
|
||||
import javax.security.auth.login.LoginContext;
|
||||
import javax.security.auth.login.LoginException;
|
||||
import javax.security.sasl.AuthorizeCallback;
|
||||
import javax.security.sasl.RealmCallback;
|
||||
|
||||
import org.alfresco.i18n.I18NUtil;
|
||||
import org.alfresco.repo.security.authentication.AbstractAuthenticationComponent;
|
||||
import org.alfresco.repo.security.authentication.AuthenticationException;
|
||||
|
||||
/**
|
||||
* JAAS based authentication
|
||||
*
|
||||
* The user name and password are picked up from login.
|
||||
*
|
||||
* The other configurable parameters are:
|
||||
* realm - the authentication realm if required,
|
||||
* and the entry name to use from the login context.
|
||||
*
|
||||
* You will need to be familiar with the JAAS authentication process to set this up.
|
||||
*
|
||||
* In summary you will need to configure java.security (in the lib/security directory of the jre you are using)
|
||||
* to find a jaas configuration.
|
||||
*
|
||||
* This entry could be used if you want to put the login configuration in the same place (in the lib/security directory of the jre you are using)
|
||||
*
|
||||
* <code>
|
||||
* login.config.url.1=file:${java.home}/lib/security/java.login.config
|
||||
* </code>
|
||||
*
|
||||
* Example configuration entries for Kerberos would be:
|
||||
*
|
||||
* <code>
|
||||
* Alfresco {
|
||||
* com.sun.security.auth.module.Krb5LoginModule sufficient;
|
||||
* };
|
||||
*
|
||||
* com.sun.net.ssl.client {
|
||||
* com.sun.security.auth.module.Krb5LoginModule sufficient;
|
||||
* };
|
||||
*
|
||||
* other {
|
||||
* com.sun.security.auth.module.Krb5LoginModule sufficient;
|
||||
* };
|
||||
* </code>
|
||||
*
|
||||
* This sets up authentication using Kerberos for Alfresco and some defaults that would use the same mechanism if sasl failed for example.
|
||||
*
|
||||
* You could use kerberos and LDAP combined against an Active Directory server.
|
||||
*
|
||||
* @author Andy Hind
|
||||
*/
|
||||
public class JAASAuthenticationComponent extends AbstractAuthenticationComponent
|
||||
{
|
||||
|
||||
/**
|
||||
* A key into the login config that defines the authentication mechamisms required.
|
||||
*/
|
||||
private String jaasConfigEntryName = "Alfresco";
|
||||
|
||||
/**
|
||||
* A default realm
|
||||
*/
|
||||
private String realm = null;
|
||||
|
||||
public JAASAuthenticationComponent()
|
||||
{
|
||||
super();
|
||||
}
|
||||
|
||||
// Springification
|
||||
|
||||
public void setJaasConfigEntryName(String jaasConfigEntryName)
|
||||
{
|
||||
this.jaasConfigEntryName = jaasConfigEntryName;
|
||||
}
|
||||
|
||||
|
||||
public void setRealm(String realm)
|
||||
{
|
||||
this.realm = realm;
|
||||
}
|
||||
|
||||
/**
|
||||
* Jaas does not support guest login
|
||||
*/
|
||||
@Override
|
||||
protected boolean implementationAllowsGuestLogin()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implement Authentication
|
||||
*/
|
||||
public void authenticate(String userName, char[] password) throws AuthenticationException
|
||||
{
|
||||
|
||||
LoginContext lc;
|
||||
try
|
||||
{
|
||||
lc = new LoginContext(jaasConfigEntryName, new SimpleCallback(userName, realm, password));
|
||||
}
|
||||
catch (LoginException e)
|
||||
{
|
||||
throw new AuthenticationException("Login Failed", e);
|
||||
}
|
||||
try
|
||||
{
|
||||
lc.login();
|
||||
// Login has gone through OK, set up the acegi context
|
||||
setCurrentUser(userName);
|
||||
}
|
||||
catch (LoginException e)
|
||||
{
|
||||
throw new AuthenticationException("Login Failed", e);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Simple call back class to support the common requirements.
|
||||
*
|
||||
* @author Andy Hind
|
||||
*/
|
||||
private static class SimpleCallback implements CallbackHandler
|
||||
{
|
||||
String userName;
|
||||
|
||||
String realm;
|
||||
|
||||
char[] password;
|
||||
|
||||
SimpleCallback(String userName, String realm, char[] password)
|
||||
{
|
||||
this.userName = userName;
|
||||
this.realm = realm;
|
||||
this.password = password;
|
||||
}
|
||||
|
||||
public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException
|
||||
{
|
||||
for (int i = 0; i < callbacks.length; i++)
|
||||
{
|
||||
if (callbacks[i] instanceof AuthorizeCallback)
|
||||
{
|
||||
AuthorizeCallback cb = (AuthorizeCallback) callbacks[i];
|
||||
cb.setAuthorized(false);
|
||||
}
|
||||
else if (callbacks[i] instanceof LanguageCallback)
|
||||
{
|
||||
LanguageCallback cb = (LanguageCallback) callbacks[i];
|
||||
cb.setLocale(I18NUtil.getLocale());
|
||||
}
|
||||
else if (callbacks[i] instanceof NameCallback)
|
||||
{
|
||||
NameCallback cb = (NameCallback) callbacks[i];
|
||||
cb.setName(userName);
|
||||
}
|
||||
else if (callbacks[i] instanceof PasswordCallback)
|
||||
{
|
||||
PasswordCallback cb = (PasswordCallback) callbacks[i];
|
||||
cb.setPassword(password);
|
||||
}
|
||||
else if (callbacks[i] instanceof RealmCallback)
|
||||
{
|
||||
RealmCallback cb = (RealmCallback) callbacks[i];
|
||||
cb.setText(realm);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new UnsupportedCallbackException(callbacks[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,125 @@
|
||||
/*
|
||||
* Copyright (C) 2005 Alfresco, Inc.
|
||||
*
|
||||
* Licensed under the Alfresco Network License. You may obtain a
|
||||
* copy of the License at
|
||||
*
|
||||
* http://www.alfrescosoftware.com/legal/
|
||||
*
|
||||
* Please view the license relevant to your network subscription.
|
||||
*
|
||||
* BY CLICKING THE "I UNDERSTAND AND ACCEPT" BOX, OR INSTALLING,
|
||||
* READING OR USING ALFRESCO'S Network SOFTWARE (THE "SOFTWARE"),
|
||||
* YOU ARE AGREEING ON BEHALF OF THE ENTITY LICENSING THE SOFTWARE
|
||||
* ("COMPANY") THAT COMPANY WILL BE BOUND BY AND IS BECOMING A PARTY TO
|
||||
* THIS ALFRESCO NETWORK AGREEMENT ("AGREEMENT") AND THAT YOU HAVE THE
|
||||
* AUTHORITY TO BIND COMPANY. IF COMPANY DOES NOT AGREE TO ALL OF THE
|
||||
* TERMS OF THIS AGREEMENT, DO NOT SELECT THE "I UNDERSTAND AND AGREE"
|
||||
* BOX AND DO NOT INSTALL THE SOFTWARE OR VIEW THE SOURCE CODE. COMPANY
|
||||
* HAS NOT BECOME A LICENSEE OF, AND IS NOT AUTHORIZED TO USE THE
|
||||
* SOFTWARE UNLESS AND UNTIL IT HAS AGREED TO BE BOUND BY THESE LICENSE
|
||||
* TERMS. THE "EFFECTIVE DATE" FOR THIS AGREEMENT SHALL BE THE DAY YOU
|
||||
* CHECK THE "I UNDERSTAND AND ACCEPT" BOX.
|
||||
*/
|
||||
package org.alfresco.repo.security.authentication.ldap;
|
||||
|
||||
import javax.naming.NamingException;
|
||||
import javax.naming.directory.InitialDirContext;
|
||||
|
||||
import org.alfresco.repo.security.authentication.AbstractAuthenticationComponent;
|
||||
import org.alfresco.repo.security.authentication.AuthenticationException;
|
||||
|
||||
/**
|
||||
* Currently expects the cn name of the user which is in a fixed location.
|
||||
*
|
||||
* @author Andy Hind
|
||||
*/
|
||||
public class LDAPAuthenticationComponentImpl extends AbstractAuthenticationComponent
|
||||
{
|
||||
|
||||
private String userNameFormat;
|
||||
|
||||
private LDAPInitialDirContextFactory ldapInitialContextFactory;
|
||||
|
||||
public LDAPAuthenticationComponentImpl()
|
||||
{
|
||||
super();
|
||||
}
|
||||
|
||||
|
||||
public void setLDAPInitialDirContextFactory(LDAPInitialDirContextFactory ldapInitialDirContextFactory)
|
||||
{
|
||||
this.ldapInitialContextFactory = ldapInitialDirContextFactory;
|
||||
}
|
||||
|
||||
|
||||
public void setUserNameFormat(String userNameFormat)
|
||||
{
|
||||
this.userNameFormat = userNameFormat;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implement the authentication method
|
||||
*/
|
||||
public void authenticate(String userName, char[] password) throws AuthenticationException
|
||||
{
|
||||
InitialDirContext ctx = null;
|
||||
try
|
||||
{
|
||||
ctx = ldapInitialContextFactory.getInitialDirContext(String.format(userNameFormat, new Object[]{userName}), new String(password));
|
||||
|
||||
// Authentication has been successful.
|
||||
// Set the current user, they are now authenticated.
|
||||
setCurrentUser(userName);
|
||||
|
||||
}
|
||||
finally
|
||||
{
|
||||
if(ctx != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
ctx.close();
|
||||
}
|
||||
catch (NamingException e)
|
||||
{
|
||||
clearCurrentSecurityContext();
|
||||
throw new AuthenticationException("Failed to close connection", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected boolean implementationAllowsGuestLogin()
|
||||
{
|
||||
InitialDirContext ctx = null;
|
||||
try
|
||||
{
|
||||
ctx = ldapInitialContextFactory.getDefaultIntialDirContext();
|
||||
return true;
|
||||
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
finally
|
||||
{
|
||||
if(ctx != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
ctx.close();
|
||||
}
|
||||
catch (NamingException e)
|
||||
{
|
||||
throw new AuthenticationException("Failed to close connection", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
@@ -0,0 +1,695 @@
|
||||
/*
|
||||
* Copyright (C) 2005 Alfresco, Inc.
|
||||
*
|
||||
* Licensed under the Alfresco Network License. You may obtain a
|
||||
* copy of the License at
|
||||
*
|
||||
* http://www.alfrescosoftware.com/legal/
|
||||
*
|
||||
* Please view the license relevant to your network subscription.
|
||||
*
|
||||
* BY CLICKING THE "I UNDERSTAND AND ACCEPT" BOX, OR INSTALLING,
|
||||
* READING OR USING ALFRESCO'S Network SOFTWARE (THE "SOFTWARE"),
|
||||
* YOU ARE AGREEING ON BEHALF OF THE ENTITY LICENSING THE SOFTWARE
|
||||
* ("COMPANY") THAT COMPANY WILL BE BOUND BY AND IS BECOMING A PARTY TO
|
||||
* THIS ALFRESCO NETWORK AGREEMENT ("AGREEMENT") AND THAT YOU HAVE THE
|
||||
* AUTHORITY TO BIND COMPANY. IF COMPANY DOES NOT AGREE TO ALL OF THE
|
||||
* TERMS OF THIS AGREEMENT, DO NOT SELECT THE "I UNDERSTAND AND AGREE"
|
||||
* BOX AND DO NOT INSTALL THE SOFTWARE OR VIEW THE SOURCE CODE. COMPANY
|
||||
* HAS NOT BECOME A LICENSEE OF, AND IS NOT AUTHORIZED TO USE THE
|
||||
* SOFTWARE UNLESS AND UNTIL IT HAS AGREED TO BE BOUND BY THESE LICENSE
|
||||
* TERMS. THE "EFFECTIVE DATE" FOR THIS AGREEMENT SHALL BE THE DAY YOU
|
||||
* CHECK THE "I UNDERSTAND AND ACCEPT" BOX.
|
||||
*/
|
||||
package org.alfresco.repo.security.authentication.ldap;
|
||||
|
||||
import java.io.BufferedWriter;
|
||||
import java.io.File;
|
||||
import java.io.FileWriter;
|
||||
import java.io.IOException;
|
||||
import java.io.Writer;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
|
||||
import javax.naming.NamingEnumeration;
|
||||
import javax.naming.NamingException;
|
||||
import javax.naming.directory.Attribute;
|
||||
import javax.naming.directory.Attributes;
|
||||
import javax.naming.directory.InitialDirContext;
|
||||
import javax.naming.directory.SearchControls;
|
||||
import javax.naming.directory.SearchResult;
|
||||
|
||||
import org.alfresco.model.ContentModel;
|
||||
import org.alfresco.repo.importer.ExportSource;
|
||||
import org.alfresco.repo.importer.ExportSourceImporterException;
|
||||
import org.alfresco.service.namespace.NamespaceService;
|
||||
import org.alfresco.service.namespace.QName;
|
||||
import org.alfresco.util.ApplicationContextHelper;
|
||||
import org.alfresco.util.EqualsHelper;
|
||||
import org.alfresco.util.GUID;
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.dom4j.io.OutputFormat;
|
||||
import org.dom4j.io.XMLWriter;
|
||||
import org.springframework.beans.factory.InitializingBean;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.xml.sax.SAXException;
|
||||
import org.xml.sax.helpers.AttributesImpl;
|
||||
|
||||
public class LDAPGroupExportSource implements ExportSource, InitializingBean
|
||||
{
|
||||
private static Log s_logger = LogFactory.getLog(LDAPGroupExportSource.class);
|
||||
|
||||
private String groupQuery = "(objectclass=groupOfNames)";
|
||||
|
||||
private String searchBase;
|
||||
|
||||
private String groupIdAttributeName = "cn";
|
||||
|
||||
private String userIdAttributeName = "uid";
|
||||
|
||||
private String groupType = "groupOfNames";
|
||||
|
||||
private String personType = "inetOrgPerson";
|
||||
|
||||
private LDAPInitialDirContextFactory ldapInitialContextFactory;
|
||||
|
||||
private NamespaceService namespaceService;
|
||||
|
||||
private String memberAttribute = "member";
|
||||
|
||||
private boolean errorOnMissingMembers = false;
|
||||
|
||||
private QName viewRef;
|
||||
|
||||
private QName viewId;
|
||||
|
||||
private QName viewAssociations;
|
||||
|
||||
private QName childQName;
|
||||
|
||||
private QName viewValueQName;
|
||||
|
||||
private QName viewIdRef;
|
||||
|
||||
public LDAPGroupExportSource()
|
||||
{
|
||||
super();
|
||||
}
|
||||
|
||||
public void setGroupIdAttributeName(String groupIdAttributeName)
|
||||
{
|
||||
this.groupIdAttributeName = groupIdAttributeName;
|
||||
}
|
||||
|
||||
public void setGroupQuery(String groupQuery)
|
||||
{
|
||||
this.groupQuery = groupQuery;
|
||||
}
|
||||
|
||||
public void setGroupType(String groupType)
|
||||
{
|
||||
this.groupType = groupType;
|
||||
}
|
||||
|
||||
public void setLDAPInitialDirContextFactory(LDAPInitialDirContextFactory ldapInitialDirContextFactory)
|
||||
{
|
||||
this.ldapInitialContextFactory = ldapInitialDirContextFactory;
|
||||
}
|
||||
|
||||
public void setMemberAttribute(String memberAttribute)
|
||||
{
|
||||
this.memberAttribute = memberAttribute;
|
||||
}
|
||||
|
||||
public void setNamespaceService(NamespaceService namespaceService)
|
||||
{
|
||||
this.namespaceService = namespaceService;
|
||||
}
|
||||
|
||||
public void setPersonType(String personType)
|
||||
{
|
||||
this.personType = personType;
|
||||
}
|
||||
|
||||
public void setSearchBase(String searchBase)
|
||||
{
|
||||
this.searchBase = searchBase;
|
||||
}
|
||||
|
||||
public void setUserIdAttributeName(String userIdAttributeName)
|
||||
{
|
||||
this.userIdAttributeName = userIdAttributeName;
|
||||
}
|
||||
|
||||
public void setErrorOnMissingMembers(boolean errorOnMissingMembers)
|
||||
{
|
||||
this.errorOnMissingMembers = errorOnMissingMembers;
|
||||
}
|
||||
|
||||
public void generateExport(XMLWriter writer)
|
||||
{
|
||||
HashSet<Group> rootGroups = new HashSet<Group>();
|
||||
HashMap<String, Group> lookup = new HashMap<String, Group>();
|
||||
HashSet<SecondaryLink> secondaryLinks = new HashSet<SecondaryLink>();
|
||||
|
||||
buildGroupsAndRoots(rootGroups, lookup, secondaryLinks);
|
||||
|
||||
buildXML(rootGroups, lookup, secondaryLinks, writer);
|
||||
|
||||
}
|
||||
|
||||
private void buildXML(HashSet<Group> rootGroups, HashMap<String, Group> lookup,
|
||||
HashSet<SecondaryLink> secondaryLinks, XMLWriter writer)
|
||||
{
|
||||
|
||||
Collection<String> prefixes = namespaceService.getPrefixes();
|
||||
QName childQName = QName.createQName(NamespaceService.REPOSITORY_VIEW_PREFIX, "childName", namespaceService);
|
||||
|
||||
try
|
||||
{
|
||||
AttributesImpl attrs = new AttributesImpl();
|
||||
attrs.addAttribute(NamespaceService.REPOSITORY_VIEW_1_0_URI, childQName.getLocalName(), childQName
|
||||
.toPrefixString(), null, ContentModel.TYPE_PERSON.toPrefixString(namespaceService));
|
||||
|
||||
writer.startDocument();
|
||||
|
||||
for (String prefix : prefixes)
|
||||
{
|
||||
if (!prefix.equals("xml"))
|
||||
{
|
||||
String uri = namespaceService.getNamespaceURI(prefix);
|
||||
writer.startPrefixMapping(prefix, uri);
|
||||
}
|
||||
}
|
||||
|
||||
writer.startElement(NamespaceService.REPOSITORY_VIEW_PREFIX, "view",
|
||||
NamespaceService.REPOSITORY_VIEW_PREFIX + ":" + "view", new AttributesImpl());
|
||||
|
||||
// Create group structure
|
||||
|
||||
for (Group group : rootGroups)
|
||||
{
|
||||
addRootGroup(lookup, group, writer);
|
||||
}
|
||||
|
||||
// Create secondary links.
|
||||
|
||||
for (SecondaryLink sl : secondaryLinks)
|
||||
{
|
||||
addSecondarylink(lookup, sl, writer);
|
||||
}
|
||||
|
||||
for (String prefix : prefixes)
|
||||
{
|
||||
if (!prefix.equals("xml"))
|
||||
{
|
||||
writer.endPrefixMapping(prefix);
|
||||
}
|
||||
}
|
||||
|
||||
writer.endElement(NamespaceService.REPOSITORY_VIEW_PREFIX, "view", NamespaceService.REPOSITORY_VIEW_PREFIX
|
||||
+ ":" + "view");
|
||||
|
||||
writer.endDocument();
|
||||
}
|
||||
catch (SAXException e)
|
||||
{
|
||||
throw new ExportSourceImporterException("Failed to create file for import.", e);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private void addSecondarylink(HashMap<String, Group> lookup, SecondaryLink sl, XMLWriter writer)
|
||||
throws SAXException
|
||||
{
|
||||
|
||||
String fromId = lookup.get(sl.from).guid;
|
||||
String toId = lookup.get(sl.to).guid;
|
||||
|
||||
AttributesImpl attrs = new AttributesImpl();
|
||||
attrs.addAttribute(viewIdRef.getNamespaceURI(), viewIdRef.getLocalName(), viewIdRef.toPrefixString(), null, fromId);
|
||||
|
||||
writer.startElement(viewRef.getNamespaceURI(), viewRef.getLocalName(),
|
||||
viewRef.toPrefixString(namespaceService), attrs);
|
||||
|
||||
writer.startElement(viewAssociations.getNamespaceURI(), viewAssociations.getLocalName(), viewAssociations
|
||||
.toPrefixString(namespaceService), new AttributesImpl());
|
||||
|
||||
writer.startElement(ContentModel.ASSOC_MEMBER.getNamespaceURI(), ContentModel.ASSOC_MEMBER.getLocalName(),
|
||||
ContentModel.ASSOC_MEMBER.toPrefixString(namespaceService), new AttributesImpl());
|
||||
|
||||
AttributesImpl attrsRef = new AttributesImpl();
|
||||
attrsRef.addAttribute(viewIdRef.getNamespaceURI(), viewIdRef.getLocalName(), viewIdRef.toPrefixString(), null, toId);
|
||||
attrsRef.addAttribute(childQName.getNamespaceURI(), childQName.getLocalName(), childQName.toPrefixString(),
|
||||
null, QName.createQName(ContentModel.USER_MODEL_URI, sl.to).toPrefixString(namespaceService));
|
||||
|
||||
writer.startElement(viewRef.getNamespaceURI(), viewRef.getLocalName(),
|
||||
viewRef.toPrefixString(namespaceService), attrsRef);
|
||||
|
||||
writer.endElement(viewRef.getNamespaceURI(), viewRef.getLocalName(), viewRef.toPrefixString(namespaceService));
|
||||
|
||||
writer.endElement(ContentModel.ASSOC_MEMBER.getNamespaceURI(), ContentModel.ASSOC_MEMBER.getLocalName(),
|
||||
ContentModel.ASSOC_MEMBER.toPrefixString(namespaceService));
|
||||
|
||||
writer.endElement(viewAssociations.getNamespaceURI(), viewAssociations.getLocalName(), viewAssociations
|
||||
.toPrefixString(namespaceService));
|
||||
|
||||
writer.endElement(viewRef.getNamespaceURI(), viewRef.getLocalName(), viewRef.toPrefixString(namespaceService));
|
||||
|
||||
}
|
||||
|
||||
private void addRootGroup(HashMap<String, Group> lookup, Group group, XMLWriter writer) throws SAXException
|
||||
{
|
||||
|
||||
AttributesImpl attrs = new AttributesImpl();
|
||||
attrs.addAttribute(NamespaceService.REPOSITORY_VIEW_1_0_URI, childQName.getLocalName(), childQName
|
||||
.toPrefixString(), null, QName.createQName(ContentModel.USER_MODEL_URI, group.gid).toPrefixString(
|
||||
namespaceService));
|
||||
attrs.addAttribute(viewId.getNamespaceURI(), viewId.getLocalName(), viewId
|
||||
.toPrefixString(), null, group.guid);
|
||||
|
||||
writer.startElement(ContentModel.TYPE_AUTHORITY_CONTAINER.getNamespaceURI(),
|
||||
ContentModel.TYPE_AUTHORITY_CONTAINER.getLocalName(), ContentModel.TYPE_AUTHORITY_CONTAINER
|
||||
.toPrefixString(namespaceService), attrs);
|
||||
|
||||
writer.startElement(ContentModel.PROP_AUTHORITY_NAME.getNamespaceURI(), ContentModel.PROP_AUTHORITY_NAME
|
||||
.getLocalName(), ContentModel.PROP_AUTHORITY_NAME.toPrefixString(namespaceService),
|
||||
new AttributesImpl());
|
||||
|
||||
writer.characters(group.gid.toCharArray(), 0, group.gid.length());
|
||||
|
||||
writer.endElement(ContentModel.PROP_AUTHORITY_NAME.getNamespaceURI(), ContentModel.PROP_AUTHORITY_NAME
|
||||
.getLocalName(), ContentModel.PROP_AUTHORITY_NAME.toPrefixString(namespaceService));
|
||||
|
||||
if (group.members.size() > 0)
|
||||
{
|
||||
writer.startElement(ContentModel.PROP_MEMBERS.getNamespaceURI(), ContentModel.PROP_MEMBERS.getLocalName(),
|
||||
ContentModel.PROP_MEMBERS.toPrefixString(namespaceService), new AttributesImpl());
|
||||
|
||||
for (String member : group.members)
|
||||
{
|
||||
writer.startElement(viewValueQName.getNamespaceURI(), viewValueQName.getLocalName(), viewValueQName
|
||||
.toPrefixString(namespaceService), new AttributesImpl());
|
||||
|
||||
writer.characters(member.toCharArray(), 0, member.length());
|
||||
|
||||
writer.endElement(viewValueQName.getNamespaceURI(), viewValueQName.getLocalName(), viewValueQName
|
||||
.toPrefixString(namespaceService));
|
||||
}
|
||||
|
||||
writer.endElement(ContentModel.PROP_MEMBERS.getNamespaceURI(), ContentModel.PROP_MEMBERS.getLocalName(),
|
||||
ContentModel.PROP_MEMBERS.toPrefixString(namespaceService));
|
||||
}
|
||||
|
||||
for (Group child : group.children)
|
||||
{
|
||||
addgroup(lookup, child, writer);
|
||||
}
|
||||
|
||||
writer.endElement(ContentModel.TYPE_AUTHORITY_CONTAINER.getNamespaceURI(),
|
||||
ContentModel.TYPE_AUTHORITY_CONTAINER.getLocalName(), ContentModel.TYPE_AUTHORITY_CONTAINER
|
||||
.toPrefixString(namespaceService));
|
||||
|
||||
}
|
||||
|
||||
private void addgroup(HashMap<String, Group> lookup, Group group, XMLWriter writer) throws SAXException
|
||||
{
|
||||
AttributesImpl attrs = new AttributesImpl();
|
||||
|
||||
writer.startElement(ContentModel.ASSOC_MEMBER.getNamespaceURI(), ContentModel.ASSOC_MEMBER.getLocalName(),
|
||||
ContentModel.ASSOC_MEMBER.toPrefixString(namespaceService), attrs);
|
||||
|
||||
addRootGroup(lookup, group, writer);
|
||||
|
||||
writer.endElement(ContentModel.ASSOC_MEMBER.getNamespaceURI(), ContentModel.ASSOC_MEMBER.getLocalName(),
|
||||
ContentModel.ASSOC_MEMBER.toPrefixString(namespaceService));
|
||||
}
|
||||
|
||||
private void buildGroupsAndRoots(HashSet<Group> rootGroups, HashMap<String, Group> lookup,
|
||||
HashSet<SecondaryLink> secondaryLinks)
|
||||
{
|
||||
InitialDirContext ctx = null;
|
||||
try
|
||||
{
|
||||
ctx = ldapInitialContextFactory.getDefaultIntialDirContext();
|
||||
|
||||
SearchControls userSearchCtls = new SearchControls();
|
||||
userSearchCtls.setSearchScope(SearchControls.SUBTREE_SCOPE);
|
||||
|
||||
NamingEnumeration searchResults = ctx.search(searchBase, groupQuery, userSearchCtls);
|
||||
while (searchResults.hasMoreElements())
|
||||
{
|
||||
SearchResult result = (SearchResult) searchResults.next();
|
||||
Attributes attributes = result.getAttributes();
|
||||
Attribute gidAttribute = attributes.get(groupIdAttributeName);
|
||||
if(gidAttribute == null)
|
||||
{
|
||||
throw new ExportSourceImporterException("Group returned by group search does not have mandatory group id attribute "+attributes);
|
||||
}
|
||||
String gid = (String) gidAttribute.get(0);
|
||||
|
||||
Group group = lookup.get(gid);
|
||||
if (group == null)
|
||||
{
|
||||
group = new Group(gid);
|
||||
lookup.put(group.gid, group);
|
||||
rootGroups.add(group);
|
||||
}
|
||||
Attribute memAttribute = attributes.get(memberAttribute);
|
||||
// check for null
|
||||
if (memAttribute != null)
|
||||
{
|
||||
for (int i = 0; i < memAttribute.size(); i++)
|
||||
{
|
||||
String attribute = (String) memAttribute.get(i);
|
||||
if (attribute != null)
|
||||
{
|
||||
group.distinguishedNames.add(attribute);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (s_logger.isDebugEnabled())
|
||||
{
|
||||
s_logger.debug("Found " + lookup.size());
|
||||
}
|
||||
|
||||
for (Group group : lookup.values())
|
||||
{
|
||||
if (s_logger.isDebugEnabled())
|
||||
{
|
||||
s_logger.debug("Linking " + group.gid);
|
||||
}
|
||||
for (String dn : group.distinguishedNames)
|
||||
{
|
||||
if (s_logger.isDebugEnabled())
|
||||
{
|
||||
s_logger.debug("... " + dn);
|
||||
}
|
||||
String id;
|
||||
Boolean isGroup = null;
|
||||
|
||||
SearchControls memberSearchCtls = new SearchControls();
|
||||
memberSearchCtls.setSearchScope(SearchControls.OBJECT_SCOPE);
|
||||
NamingEnumeration memberSearchResults;
|
||||
try
|
||||
{
|
||||
memberSearchResults = ctx.search(dn, "(objectClass=*)", memberSearchCtls);
|
||||
}
|
||||
catch (NamingException e)
|
||||
{
|
||||
if (errorOnMissingMembers)
|
||||
{
|
||||
throw e;
|
||||
}
|
||||
s_logger.warn("Failed to resolve distinguished name: " + dn);
|
||||
continue;
|
||||
}
|
||||
while (memberSearchResults.hasMoreElements())
|
||||
{
|
||||
id = null;
|
||||
|
||||
SearchResult result;
|
||||
try
|
||||
{
|
||||
result = (SearchResult) memberSearchResults.next();
|
||||
}
|
||||
catch (NamingException e)
|
||||
{
|
||||
if (errorOnMissingMembers)
|
||||
{
|
||||
throw e;
|
||||
}
|
||||
s_logger.warn("Failed to resolve distinguished name: " + dn);
|
||||
continue;
|
||||
}
|
||||
Attributes attributes = result.getAttributes();
|
||||
Attribute objectclass = attributes.get("objectclass");
|
||||
if(objectclass == null)
|
||||
{
|
||||
throw new ExportSourceImporterException("Failed to find attribute objectclass for DN "+dn);
|
||||
}
|
||||
for (int i = 0; i < objectclass.size(); i++)
|
||||
{
|
||||
String testType;
|
||||
try
|
||||
{
|
||||
testType = (String) objectclass.get(i);
|
||||
}
|
||||
catch (NamingException e)
|
||||
{
|
||||
if (errorOnMissingMembers)
|
||||
{
|
||||
throw e;
|
||||
}
|
||||
s_logger.warn("Failed to resolve object class attribute for distinguished name: " + dn);
|
||||
continue;
|
||||
}
|
||||
if (testType.equals(groupType))
|
||||
{
|
||||
isGroup = true;
|
||||
try
|
||||
{
|
||||
Attribute groupIdAttribute = attributes.get(groupIdAttributeName);
|
||||
if(groupIdAttribute == null)
|
||||
{
|
||||
throw new ExportSourceImporterException("Group missing group id attribute DN ="+dn + " att = "+groupIdAttributeName);
|
||||
}
|
||||
id = (String) groupIdAttribute.get(0);
|
||||
}
|
||||
catch (NamingException e)
|
||||
{
|
||||
if (errorOnMissingMembers)
|
||||
{
|
||||
throw e;
|
||||
}
|
||||
s_logger.warn("Failed to resolve group identifier "
|
||||
+ groupIdAttributeName + " for distinguished name: " + dn);
|
||||
id = "Unknown sub group";
|
||||
}
|
||||
break;
|
||||
}
|
||||
else if (testType.equals(personType))
|
||||
{
|
||||
isGroup = false;
|
||||
try
|
||||
{
|
||||
Attribute userIdAttribute = attributes.get(userIdAttributeName);
|
||||
if(userIdAttribute == null)
|
||||
{
|
||||
throw new ExportSourceImporterException("User missing user id attribute DN ="+dn + " att = "+userIdAttributeName);
|
||||
}
|
||||
id = (String) userIdAttribute.get(0);
|
||||
}
|
||||
catch (NamingException e)
|
||||
{
|
||||
if (errorOnMissingMembers)
|
||||
{
|
||||
throw e;
|
||||
}
|
||||
s_logger.warn("Failed to resolve group identifier "
|
||||
+ userIdAttributeName + " for distinguished name: " + dn);
|
||||
id = "Unknown member";
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (id != null)
|
||||
{
|
||||
if (isGroup == null)
|
||||
{
|
||||
throw new ExportSourceImporterException("Type not recognised for DN"+dn);
|
||||
}
|
||||
else if (isGroup)
|
||||
{
|
||||
if (s_logger.isDebugEnabled())
|
||||
{
|
||||
s_logger.debug("... is sub group");
|
||||
}
|
||||
Group child = lookup.get("GROUP_" + id);
|
||||
if (child == null)
|
||||
{
|
||||
throw new ExportSourceImporterException("Failed to find child group " + id);
|
||||
}
|
||||
if (rootGroups.contains(child))
|
||||
{
|
||||
if (s_logger.isDebugEnabled())
|
||||
{
|
||||
s_logger.debug("... Primary created from "
|
||||
+ group.gid + " to " + child.gid);
|
||||
}
|
||||
group.children.add(child);
|
||||
rootGroups.remove(child);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (s_logger.isDebugEnabled())
|
||||
{
|
||||
s_logger.debug("... Secondary created from "
|
||||
+ group.gid + " to " + child.gid);
|
||||
}
|
||||
secondaryLinks.add(new SecondaryLink(group.gid, child.gid));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (s_logger.isDebugEnabled())
|
||||
{
|
||||
s_logger.debug("... is member");
|
||||
}
|
||||
group.members.add(id);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (s_logger.isDebugEnabled())
|
||||
{
|
||||
s_logger.debug("Top " + rootGroups.size());
|
||||
s_logger.debug("Secondary " + secondaryLinks.size());
|
||||
}
|
||||
}
|
||||
catch (NamingException e)
|
||||
{
|
||||
throw new ExportSourceImporterException("Failed to import people.", e);
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (ctx != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
ctx.close();
|
||||
}
|
||||
catch (NamingException e)
|
||||
{
|
||||
throw new ExportSourceImporterException("Failed to import people.", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static class Group
|
||||
{
|
||||
String gid;
|
||||
|
||||
String guid = GUID.generate();
|
||||
|
||||
HashSet<Group> children = new HashSet<Group>();
|
||||
|
||||
HashSet<String> members = new HashSet<String>();
|
||||
|
||||
HashSet<String> distinguishedNames = new HashSet<String>();
|
||||
|
||||
private Group(String gid)
|
||||
{
|
||||
this.gid = "GROUP_" + gid;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o)
|
||||
{
|
||||
if (this == o)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
if (!(o instanceof Group))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
Group g = (Group) o;
|
||||
return this.gid.equals(g.gid);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode()
|
||||
{
|
||||
return gid.hashCode();
|
||||
}
|
||||
}
|
||||
|
||||
private static class SecondaryLink
|
||||
{
|
||||
String from;
|
||||
|
||||
String to;
|
||||
|
||||
private SecondaryLink(String from, String to)
|
||||
{
|
||||
this.from = from;
|
||||
this.to = to;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o)
|
||||
{
|
||||
if (this == o)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
if (!(o instanceof Group))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
SecondaryLink l = (SecondaryLink) o;
|
||||
return EqualsHelper.nullSafeEquals(this.from, l.from) && EqualsHelper.nullSafeEquals(this.to, l.to);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode()
|
||||
{
|
||||
int hashCode = 0;
|
||||
if (from != null)
|
||||
{
|
||||
hashCode = hashCode * 37 + from.hashCode();
|
||||
}
|
||||
if (to != null)
|
||||
{
|
||||
hashCode = hashCode * 37 + to.hashCode();
|
||||
}
|
||||
return hashCode;
|
||||
}
|
||||
}
|
||||
|
||||
public static void main(String[] args) throws IOException
|
||||
{
|
||||
ApplicationContext ctx = ApplicationContextHelper.getApplicationContext();
|
||||
ExportSource source = (ExportSource) ctx.getBean("ldapGroupExportSource");
|
||||
|
||||
File file = new File(args[0]);
|
||||
Writer writer = new BufferedWriter(new FileWriter(file));
|
||||
XMLWriter xmlWriter = createXMLExporter(writer);
|
||||
source.generateExport(xmlWriter);
|
||||
xmlWriter.close();
|
||||
|
||||
}
|
||||
|
||||
private static XMLWriter createXMLExporter(Writer writer)
|
||||
{
|
||||
// Define output format
|
||||
OutputFormat format = OutputFormat.createPrettyPrint();
|
||||
format.setNewLineAfterDeclaration(false);
|
||||
format.setIndentSize(3);
|
||||
format.setEncoding("UTF-8");
|
||||
|
||||
// Construct an XML Exporter
|
||||
|
||||
XMLWriter xmlWriter = new XMLWriter(writer, format);
|
||||
return xmlWriter;
|
||||
}
|
||||
|
||||
public void afterPropertiesSet() throws Exception
|
||||
{
|
||||
viewRef = QName.createQName(NamespaceService.REPOSITORY_VIEW_PREFIX, "reference", namespaceService);
|
||||
viewId = QName.createQName(NamespaceService.REPOSITORY_VIEW_PREFIX, "id", namespaceService);
|
||||
viewIdRef = QName.createQName(NamespaceService.REPOSITORY_VIEW_PREFIX, "idref", namespaceService);
|
||||
viewAssociations = QName.createQName(NamespaceService.REPOSITORY_VIEW_PREFIX, "associations", namespaceService);
|
||||
childQName = QName.createQName(NamespaceService.REPOSITORY_VIEW_PREFIX, "childName", namespaceService);
|
||||
viewValueQName = QName.createQName(NamespaceService.REPOSITORY_VIEW_PREFIX, "value", namespaceService);
|
||||
|
||||
}
|
||||
}
|
@@ -0,0 +1,65 @@
|
||||
/*
|
||||
* Copyright (C) 2005 Alfresco, Inc.
|
||||
*
|
||||
* Licensed under the Alfresco Network License. You may obtain a
|
||||
* copy of the License at
|
||||
*
|
||||
* http://www.alfrescosoftware.com/legal/
|
||||
*
|
||||
* Please view the license relevant to your network subscription.
|
||||
*
|
||||
* BY CLICKING THE "I UNDERSTAND AND ACCEPT" BOX, OR INSTALLING,
|
||||
* READING OR USING ALFRESCO'S Network SOFTWARE (THE "SOFTWARE"),
|
||||
* YOU ARE AGREEING ON BEHALF OF THE ENTITY LICENSING THE SOFTWARE
|
||||
* ("COMPANY") THAT COMPANY WILL BE BOUND BY AND IS BECOMING A PARTY TO
|
||||
* THIS ALFRESCO NETWORK AGREEMENT ("AGREEMENT") AND THAT YOU HAVE THE
|
||||
* AUTHORITY TO BIND COMPANY. IF COMPANY DOES NOT AGREE TO ALL OF THE
|
||||
* TERMS OF THIS AGREEMENT, DO NOT SELECT THE "I UNDERSTAND AND AGREE"
|
||||
* BOX AND DO NOT INSTALL THE SOFTWARE OR VIEW THE SOURCE CODE. COMPANY
|
||||
* HAS NOT BECOME A LICENSEE OF, AND IS NOT AUTHORIZED TO USE THE
|
||||
* SOFTWARE UNLESS AND UNTIL IT HAS AGREED TO BE BOUND BY THESE LICENSE
|
||||
* TERMS. THE "EFFECTIVE DATE" FOR THIS AGREEMENT SHALL BE THE DAY YOU
|
||||
* CHECK THE "I UNDERSTAND AND ACCEPT" BOX.
|
||||
*/
|
||||
package org.alfresco.repo.security.authentication.ldap;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import javax.naming.directory.InitialDirContext;
|
||||
|
||||
import org.alfresco.repo.security.authentication.AuthenticationException;
|
||||
|
||||
/**
|
||||
* Interface that defines a factory for obtaining ldap directory contexts.
|
||||
*
|
||||
* @author Andy Hind
|
||||
*/
|
||||
public interface LDAPInitialDirContextFactory
|
||||
{
|
||||
/**
|
||||
* Set the LDAP environment Hashtable properties used ot initialise the LDAP connection.
|
||||
*
|
||||
* @param environment
|
||||
*/
|
||||
public void setInitialDirContextEnvironment(Map<String, String> environment);
|
||||
|
||||
/**
|
||||
* Use the environment properties and connect to the LDAP server.
|
||||
* Used to obtain read only access to the LDAP server.
|
||||
*
|
||||
* @return
|
||||
* @throws AuthenticationException
|
||||
*/
|
||||
public InitialDirContext getDefaultIntialDirContext() throws AuthenticationException;
|
||||
|
||||
/**
|
||||
* Augment the connection environment with the identity and credentials and bind to the ldap server.
|
||||
* Mainly used to validate a user's credentials during authentication.
|
||||
*
|
||||
* @param principal
|
||||
* @param credentials
|
||||
* @return
|
||||
* @throws AuthenticationException
|
||||
*/
|
||||
public InitialDirContext getInitialDirContext(String principal, String credentials) throws AuthenticationException;
|
||||
}
|
@@ -0,0 +1,197 @@
|
||||
/*
|
||||
* Copyright (C) 2005 Alfresco, Inc.
|
||||
*
|
||||
* Licensed under the Alfresco Network License. You may obtain a
|
||||
* copy of the License at
|
||||
*
|
||||
* http://www.alfrescosoftware.com/legal/
|
||||
*
|
||||
* Please view the license relevant to your network subscription.
|
||||
*
|
||||
* BY CLICKING THE "I UNDERSTAND AND ACCEPT" BOX, OR INSTALLING,
|
||||
* READING OR USING ALFRESCO'S Network SOFTWARE (THE "SOFTWARE"),
|
||||
* YOU ARE AGREEING ON BEHALF OF THE ENTITY LICENSING THE SOFTWARE
|
||||
* ("COMPANY") THAT COMPANY WILL BE BOUND BY AND IS BECOMING A PARTY TO
|
||||
* THIS ALFRESCO NETWORK AGREEMENT ("AGREEMENT") AND THAT YOU HAVE THE
|
||||
* AUTHORITY TO BIND COMPANY. IF COMPANY DOES NOT AGREE TO ALL OF THE
|
||||
* TERMS OF THIS AGREEMENT, DO NOT SELECT THE "I UNDERSTAND AND AGREE"
|
||||
* BOX AND DO NOT INSTALL THE SOFTWARE OR VIEW THE SOURCE CODE. COMPANY
|
||||
* HAS NOT BECOME A LICENSEE OF, AND IS NOT AUTHORIZED TO USE THE
|
||||
* SOFTWARE UNLESS AND UNTIL IT HAS AGREED TO BE BOUND BY THESE LICENSE
|
||||
* TERMS. THE "EFFECTIVE DATE" FOR THIS AGREEMENT SHALL BE THE DAY YOU
|
||||
* CHECK THE "I UNDERSTAND AND ACCEPT" BOX.
|
||||
*/
|
||||
package org.alfresco.repo.security.authentication.ldap;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Hashtable;
|
||||
import java.util.Map;
|
||||
|
||||
import javax.naming.Context;
|
||||
import javax.naming.NamingException;
|
||||
import javax.naming.directory.Attribute;
|
||||
import javax.naming.directory.Attributes;
|
||||
import javax.naming.directory.BasicAttribute;
|
||||
import javax.naming.directory.BasicAttributes;
|
||||
import javax.naming.directory.InitialDirContext;
|
||||
|
||||
import org.alfresco.repo.security.authentication.AuthenticationException;
|
||||
import org.alfresco.util.ApplicationContextHelper;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
|
||||
public class LDAPInitialDirContextFactoryImpl implements LDAPInitialDirContextFactory
|
||||
{
|
||||
private Map<String, String> initialDirContextEnvironment = Collections.<String, String> emptyMap();
|
||||
|
||||
static
|
||||
{
|
||||
System.setProperty("javax.security.auth.useSubjectCredentialsOnly", "false");
|
||||
}
|
||||
|
||||
public LDAPInitialDirContextFactoryImpl()
|
||||
{
|
||||
super();
|
||||
}
|
||||
|
||||
public void setInitialDirContextEnvironment(Map<String, String> initialDirContextEnvironment)
|
||||
|
||||
{
|
||||
this.initialDirContextEnvironment = initialDirContextEnvironment;
|
||||
}
|
||||
|
||||
public Map<String, String> getInitialDirContextEnvironment()
|
||||
{
|
||||
return initialDirContextEnvironment;
|
||||
}
|
||||
|
||||
public InitialDirContext getDefaultIntialDirContext() throws AuthenticationException
|
||||
{
|
||||
Hashtable<String, String> env = new Hashtable<String, String>(initialDirContextEnvironment.size());
|
||||
env.putAll(initialDirContextEnvironment);
|
||||
env.put("javax.security.auth.useSubjectCredsOnly", "false");
|
||||
return buildInitialDirContext(env);
|
||||
}
|
||||
|
||||
private InitialDirContext buildInitialDirContext(Hashtable<String, String> env) throws AuthenticationException
|
||||
{
|
||||
try
|
||||
{
|
||||
return new InitialDirContext(env);
|
||||
}
|
||||
catch (javax.naming.AuthenticationException ax)
|
||||
{
|
||||
throw new AuthenticationException("LDAP authentication failed.", ax);
|
||||
}
|
||||
catch (NamingException nx)
|
||||
{
|
||||
throw new AuthenticationException("Unable to connect to LDAP Server; check LDAP configuration", nx);
|
||||
}
|
||||
}
|
||||
|
||||
public InitialDirContext getInitialDirContext(String principal, String credentials) throws AuthenticationException
|
||||
{
|
||||
if (principal == null)
|
||||
{
|
||||
throw new AuthenticationException("Null user name provided.");
|
||||
}
|
||||
|
||||
if (credentials == null)
|
||||
{
|
||||
throw new AuthenticationException("No credentials provided.");
|
||||
}
|
||||
Hashtable<String, String> env = new Hashtable<String, String>(initialDirContextEnvironment.size());
|
||||
env.putAll(initialDirContextEnvironment);
|
||||
env.put(Context.SECURITY_PRINCIPAL, principal);
|
||||
env.put(Context.SECURITY_CREDENTIALS, credentials);
|
||||
|
||||
return buildInitialDirContext(env);
|
||||
}
|
||||
|
||||
public static void main(String[] args)
|
||||
{
|
||||
// ....build a pyramid selling scheme .....
|
||||
|
||||
// A group has three user members and 2 group members .... and off we go ....
|
||||
// We make the people and groups to represent this and stick them into LDAP ...used to populate a test data base for user and groups
|
||||
|
||||
int userMembers = Integer.parseInt(args[3]);
|
||||
|
||||
ApplicationContext applicationContext = ApplicationContextHelper.getApplicationContext();
|
||||
LDAPInitialDirContextFactory factory = (LDAPInitialDirContextFactory) applicationContext
|
||||
.getBean("ldapInitialDirContextFactory");
|
||||
|
||||
InitialDirContext ctx = null;
|
||||
try
|
||||
{
|
||||
ctx = factory.getInitialDirContext("cn=" + args[0] + "," + args[2], args[1]);
|
||||
|
||||
/* Values we'll use in creating the entry */
|
||||
Attribute objClasses = new BasicAttribute("objectclass");
|
||||
objClasses.add("top");
|
||||
objClasses.add("person");
|
||||
objClasses.add("organizationalPerson");
|
||||
objClasses.add("inetOrgPerson");
|
||||
|
||||
for (int i = 0; i < userMembers; i++)
|
||||
{
|
||||
|
||||
Attribute cn = new BasicAttribute("cn", "User" + i + " TestUser");
|
||||
Attribute sn = new BasicAttribute("sn", "TestUser");
|
||||
Attribute givenNames = new BasicAttribute("givenName", "User" + i);
|
||||
Attribute telephoneNumber = new BasicAttribute("telephoneNumber", "123");
|
||||
Attribute uid = new BasicAttribute("uid", "User" + i);
|
||||
Attribute mail = new BasicAttribute("mail", "woof@woof");
|
||||
Attribute o = new BasicAttribute("o", "Alfresco");
|
||||
Attribute userPassword = new BasicAttribute("userPassword", "bobbins");
|
||||
/* Specify the DN we're adding */
|
||||
String dn = "cn=User" + i + " TestUser," + args[2];
|
||||
|
||||
Attributes orig = new BasicAttributes();
|
||||
orig.put(objClasses);
|
||||
orig.put(cn);
|
||||
orig.put(sn);
|
||||
orig.put(givenNames);
|
||||
orig.put(telephoneNumber);
|
||||
orig.put(uid);
|
||||
orig.put(mail);
|
||||
orig.put(o);
|
||||
orig.put(userPassword);
|
||||
|
||||
try
|
||||
{
|
||||
ctx.destroySubcontext(dn);
|
||||
}
|
||||
catch (NamingException e)
|
||||
{
|
||||
// TODO Auto-generated catch block
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
ctx.createSubcontext(dn, orig);
|
||||
}
|
||||
|
||||
}
|
||||
catch (NamingException e)
|
||||
{
|
||||
// TODO Auto-generated catch block
|
||||
e.printStackTrace();
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (ctx != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
ctx.close();
|
||||
}
|
||||
catch (NamingException e)
|
||||
{
|
||||
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,328 @@
|
||||
/*
|
||||
* Copyright (C) 2005 Alfresco, Inc.
|
||||
*
|
||||
* Licensed under the Alfresco Network License. You may obtain a
|
||||
* copy of the License at
|
||||
*
|
||||
* http://www.alfrescosoftware.com/legal/
|
||||
*
|
||||
* Please view the license relevant to your network subscription.
|
||||
*
|
||||
* BY CLICKING THE "I UNDERSTAND AND ACCEPT" BOX, OR INSTALLING,
|
||||
* READING OR USING ALFRESCO'S Network SOFTWARE (THE "SOFTWARE"),
|
||||
* YOU ARE AGREEING ON BEHALF OF THE ENTITY LICENSING THE SOFTWARE
|
||||
* ("COMPANY") THAT COMPANY WILL BE BOUND BY AND IS BECOMING A PARTY TO
|
||||
* THIS ALFRESCO NETWORK AGREEMENT ("AGREEMENT") AND THAT YOU HAVE THE
|
||||
* AUTHORITY TO BIND COMPANY. IF COMPANY DOES NOT AGREE TO ALL OF THE
|
||||
* TERMS OF THIS AGREEMENT, DO NOT SELECT THE "I UNDERSTAND AND AGREE"
|
||||
* BOX AND DO NOT INSTALL THE SOFTWARE OR VIEW THE SOURCE CODE. COMPANY
|
||||
* HAS NOT BECOME A LICENSEE OF, AND IS NOT AUTHORIZED TO USE THE
|
||||
* SOFTWARE UNLESS AND UNTIL IT HAS AGREED TO BE BOUND BY THESE LICENSE
|
||||
* TERMS. THE "EFFECTIVE DATE" FOR THIS AGREEMENT SHALL BE THE DAY YOU
|
||||
* CHECK THE "I UNDERSTAND AND ACCEPT" BOX.
|
||||
*/
|
||||
package org.alfresco.repo.security.authentication.ldap;
|
||||
|
||||
import java.io.BufferedWriter;
|
||||
import java.io.File;
|
||||
import java.io.FileWriter;
|
||||
import java.io.IOException;
|
||||
import java.io.Writer;
|
||||
import java.util.Collection;
|
||||
import java.util.Map;
|
||||
|
||||
import javax.naming.NamingEnumeration;
|
||||
import javax.naming.NamingException;
|
||||
import javax.naming.directory.Attribute;
|
||||
import javax.naming.directory.Attributes;
|
||||
import javax.naming.directory.InitialDirContext;
|
||||
import javax.naming.directory.SearchControls;
|
||||
import javax.naming.directory.SearchResult;
|
||||
|
||||
import org.alfresco.model.ContentModel;
|
||||
import org.alfresco.repo.importer.ExportSource;
|
||||
import org.alfresco.repo.importer.ExportSourceImporterException;
|
||||
import org.alfresco.service.cmr.security.PersonService;
|
||||
import org.alfresco.service.namespace.NamespaceService;
|
||||
import org.alfresco.service.namespace.QName;
|
||||
import org.alfresco.util.ApplicationContextHelper;
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.dom4j.io.OutputFormat;
|
||||
import org.dom4j.io.XMLWriter;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.xml.sax.SAXException;
|
||||
import org.xml.sax.helpers.AttributesImpl;
|
||||
|
||||
public class LDAPPersonExportSource implements ExportSource
|
||||
{
|
||||
private static Log s_logger = LogFactory.getLog(LDAPPersonExportSource.class);
|
||||
|
||||
private String personQuery = "(objectclass=inetOrgPerson)";
|
||||
|
||||
private String searchBase;
|
||||
|
||||
private String userIdAttributeName;
|
||||
|
||||
private LDAPInitialDirContextFactory ldapInitialContextFactory;
|
||||
|
||||
private PersonService personService;
|
||||
|
||||
private Map<String, String> attributeMapping;
|
||||
|
||||
private NamespaceService namespaceService;
|
||||
|
||||
private String defaultHomeFolder;
|
||||
|
||||
public LDAPPersonExportSource()
|
||||
{
|
||||
super();
|
||||
}
|
||||
|
||||
public void setPersonQuery(String personQuery)
|
||||
{
|
||||
this.personQuery = personQuery;
|
||||
}
|
||||
|
||||
public void setSearchBase(String searchBase)
|
||||
{
|
||||
this.searchBase = searchBase;
|
||||
}
|
||||
|
||||
public void setUserIdAttributeName(String userIdAttributeName)
|
||||
{
|
||||
this.userIdAttributeName = userIdAttributeName;
|
||||
}
|
||||
|
||||
public void setLDAPInitialDirContextFactory(LDAPInitialDirContextFactory ldapInitialDirContextFactory)
|
||||
{
|
||||
this.ldapInitialContextFactory = ldapInitialDirContextFactory;
|
||||
}
|
||||
|
||||
public void setPersonService(PersonService personService)
|
||||
{
|
||||
this.personService = personService;
|
||||
}
|
||||
|
||||
public void setDefaultHomeFolder(String defaultHomeFolder)
|
||||
{
|
||||
this.defaultHomeFolder = defaultHomeFolder;
|
||||
}
|
||||
|
||||
public void setNamespaceService(NamespaceService namespaceService)
|
||||
{
|
||||
this.namespaceService = namespaceService;
|
||||
}
|
||||
|
||||
public void setAttributeMapping(Map<String, String> attributeMapping)
|
||||
{
|
||||
this.attributeMapping = attributeMapping;
|
||||
}
|
||||
|
||||
public void generateExport(XMLWriter writer)
|
||||
{
|
||||
QName nodeUUID = QName.createQName("sys:node-uuid", namespaceService);
|
||||
|
||||
Collection<String> prefixes = namespaceService.getPrefixes();
|
||||
QName childQName = QName.createQName(NamespaceService.REPOSITORY_VIEW_PREFIX, "childName", namespaceService);
|
||||
|
||||
try
|
||||
{
|
||||
AttributesImpl attrs = new AttributesImpl();
|
||||
attrs.addAttribute(NamespaceService.REPOSITORY_VIEW_1_0_URI, childQName.getLocalName(), childQName
|
||||
.toPrefixString(), null, ContentModel.TYPE_PERSON.toPrefixString(namespaceService));
|
||||
|
||||
writer.startDocument();
|
||||
|
||||
for (String prefix : prefixes)
|
||||
{
|
||||
if (!prefix.equals("xml"))
|
||||
{
|
||||
String uri = namespaceService.getNamespaceURI(prefix);
|
||||
writer.startPrefixMapping(prefix, uri);
|
||||
}
|
||||
}
|
||||
|
||||
writer.startElement(NamespaceService.REPOSITORY_VIEW_PREFIX, "view",
|
||||
NamespaceService.REPOSITORY_VIEW_PREFIX + ":" + "view", new AttributesImpl());
|
||||
|
||||
InitialDirContext ctx = null;
|
||||
try
|
||||
{
|
||||
ctx = ldapInitialContextFactory.getDefaultIntialDirContext();
|
||||
|
||||
// Authentication has been successful.
|
||||
// Set the current user, they are now authenticated.
|
||||
|
||||
SearchControls userSearchCtls = new SearchControls();
|
||||
userSearchCtls.setSearchScope(SearchControls.SUBTREE_SCOPE);
|
||||
System.out.println("COUNT "+userSearchCtls.getCountLimit());
|
||||
System.out.println("TIME "+userSearchCtls.getTimeLimit());
|
||||
userSearchCtls.setCountLimit(Integer.MAX_VALUE);
|
||||
|
||||
NamingEnumeration searchResults = ctx.search(searchBase, personQuery, userSearchCtls);
|
||||
while (searchResults.hasMoreElements())
|
||||
{
|
||||
SearchResult result = (SearchResult) searchResults.next();
|
||||
Attributes attributes = result.getAttributes();
|
||||
Attribute uidAttribute = attributes.get(userIdAttributeName);
|
||||
String uid = (String) uidAttribute.get(0);
|
||||
|
||||
if(s_logger.isDebugEnabled())
|
||||
{
|
||||
s_logger.debug("Adding user for "+uid);
|
||||
}
|
||||
System.out.println("User "+uid);
|
||||
|
||||
writer.startElement(ContentModel.TYPE_PERSON.getNamespaceURI(), ContentModel.TYPE_PERSON
|
||||
.getLocalName(), ContentModel.TYPE_PERSON.toPrefixString(namespaceService), attrs);
|
||||
|
||||
// permissions
|
||||
|
||||
// owner
|
||||
|
||||
writer.startElement(ContentModel.ASPECT_OWNABLE.getNamespaceURI(), ContentModel.ASPECT_OWNABLE
|
||||
.getLocalName(), ContentModel.ASPECT_OWNABLE.toPrefixString(namespaceService),
|
||||
new AttributesImpl());
|
||||
|
||||
writer.endElement(ContentModel.ASPECT_OWNABLE.getNamespaceURI(), ContentModel.ASPECT_OWNABLE
|
||||
.getLocalName(), ContentModel.ASPECT_OWNABLE.toPrefixString(namespaceService));
|
||||
|
||||
writer.startElement(ContentModel.PROP_OWNER.getNamespaceURI(), ContentModel.PROP_OWNER
|
||||
.getLocalName(), ContentModel.PROP_OWNER.toPrefixString(namespaceService),
|
||||
new AttributesImpl());
|
||||
|
||||
writer.characters(uid.toCharArray(), 0, uid.length());
|
||||
|
||||
writer.endElement(ContentModel.PROP_OWNER.getNamespaceURI(),
|
||||
ContentModel.PROP_OWNER.getLocalName(), ContentModel.PROP_OWNER
|
||||
.toPrefixString(namespaceService));
|
||||
|
||||
for (String key : attributeMapping.keySet())
|
||||
{
|
||||
QName keyQName = QName.createQName(key, namespaceService);
|
||||
|
||||
writer.startElement(keyQName.getNamespaceURI(), keyQName.getLocalName(), keyQName
|
||||
.toPrefixString(namespaceService), new AttributesImpl());
|
||||
|
||||
// cater for null
|
||||
String attribute = attributeMapping.get(key);
|
||||
if (attribute != null)
|
||||
{
|
||||
String value = (String) attributes.get(attribute).get(0);
|
||||
if (value != null)
|
||||
{
|
||||
writer.characters(value.toCharArray(), 0, value.length());
|
||||
}
|
||||
}
|
||||
|
||||
writer.endElement(keyQName.getNamespaceURI(), keyQName.getLocalName(), keyQName
|
||||
.toPrefixString(namespaceService));
|
||||
}
|
||||
|
||||
// Default home folder
|
||||
|
||||
if (!(attributeMapping.keySet().contains(ContentModel.PROP_HOMEFOLDER.toString()) || attributeMapping
|
||||
.keySet().contains(ContentModel.PROP_HOMEFOLDER.toPrefixString(namespaceService))))
|
||||
{
|
||||
// Only if we are creating the person for the first time
|
||||
if (!personService.personExists(uid))
|
||||
{
|
||||
writer.startElement(ContentModel.PROP_HOMEFOLDER.getNamespaceURI(),
|
||||
ContentModel.PROP_HOMEFOLDER.getLocalName(), ContentModel.PROP_HOMEFOLDER
|
||||
.toPrefixString(namespaceService), new AttributesImpl());
|
||||
|
||||
if (defaultHomeFolder != null)
|
||||
{
|
||||
writer.characters(defaultHomeFolder.toCharArray(), 0, defaultHomeFolder.length());
|
||||
}
|
||||
|
||||
writer.endElement(ContentModel.PROP_HOMEFOLDER.getNamespaceURI(),
|
||||
ContentModel.PROP_HOMEFOLDER.getLocalName(), ContentModel.PROP_HOMEFOLDER
|
||||
.toPrefixString(namespaceService));
|
||||
}
|
||||
}
|
||||
|
||||
if (personService.personExists(uid))
|
||||
{
|
||||
String uguid = personService.getPerson(uid).getId();
|
||||
|
||||
writer.startElement(nodeUUID.getNamespaceURI(), nodeUUID.getLocalName(), nodeUUID
|
||||
.toPrefixString(namespaceService), new AttributesImpl());
|
||||
|
||||
writer.characters(uguid.toCharArray(), 0, uguid.length());
|
||||
|
||||
writer.endElement(nodeUUID.getNamespaceURI(), nodeUUID.getLocalName(), nodeUUID
|
||||
.toPrefixString(namespaceService));
|
||||
}
|
||||
writer.endElement(ContentModel.TYPE_PERSON.getNamespaceURI(), ContentModel.TYPE_PERSON
|
||||
.getLocalName(), ContentModel.TYPE_PERSON.toPrefixString(namespaceService));
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
catch (NamingException e)
|
||||
{
|
||||
throw new ExportSourceImporterException("Failed to import people.", e);
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (ctx != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
ctx.close();
|
||||
}
|
||||
catch (NamingException e)
|
||||
{
|
||||
throw new ExportSourceImporterException("Failed to import people.", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (String prefix : prefixes)
|
||||
{
|
||||
if (!prefix.equals("xml"))
|
||||
{
|
||||
writer.endPrefixMapping(prefix);
|
||||
}
|
||||
}
|
||||
|
||||
writer.endElement(NamespaceService.REPOSITORY_VIEW_PREFIX, "view", NamespaceService.REPOSITORY_VIEW_PREFIX
|
||||
+ ":" + "view");
|
||||
|
||||
writer.endDocument();
|
||||
}
|
||||
catch (SAXException e)
|
||||
{
|
||||
throw new ExportSourceImporterException("Failed to create file for import.", e);
|
||||
}
|
||||
}
|
||||
|
||||
public static void main(String[] args) throws IOException
|
||||
{
|
||||
ApplicationContext ctx = ApplicationContextHelper.getApplicationContext();
|
||||
ExportSource source = (ExportSource) ctx.getBean("ldapPeopleExportSource");
|
||||
|
||||
File file = new File(args[0]);
|
||||
Writer writer = new BufferedWriter(new FileWriter(file));
|
||||
XMLWriter xmlWriter = createXMLExporter(writer);
|
||||
source.generateExport(xmlWriter);
|
||||
xmlWriter.close();
|
||||
|
||||
}
|
||||
|
||||
private static XMLWriter createXMLExporter(Writer writer)
|
||||
{
|
||||
// Define output format
|
||||
OutputFormat format = OutputFormat.createPrettyPrint();
|
||||
format.setNewLineAfterDeclaration(false);
|
||||
format.setIndentSize(3);
|
||||
format.setEncoding("UTF-8");
|
||||
|
||||
// Construct an XML Exporter
|
||||
|
||||
XMLWriter xmlWriter = new XMLWriter(writer, format);
|
||||
return xmlWriter;
|
||||
}
|
||||
}
|
@@ -0,0 +1,932 @@
|
||||
/*
|
||||
* Copyright (C) 2005 Alfresco, Inc.
|
||||
*
|
||||
* Licensed under the Alfresco Network License. You may obtain a
|
||||
* copy of the License at
|
||||
*
|
||||
* http://www.alfrescosoftware.com/legal/
|
||||
*
|
||||
* Please view the license relevant to your network subscription.
|
||||
*
|
||||
* BY CLICKING THE "I UNDERSTAND AND ACCEPT" BOX, OR INSTALLING,
|
||||
* READING OR USING ALFRESCO'S Network SOFTWARE (THE "SOFTWARE"),
|
||||
* YOU ARE AGREEING ON BEHALF OF THE ENTITY LICENSING THE SOFTWARE
|
||||
* ("COMPANY") THAT COMPANY WILL BE BOUND BY AND IS BECOMING A PARTY TO
|
||||
* THIS ALFRESCO NETWORK AGREEMENT ("AGREEMENT") AND THAT YOU HAVE THE
|
||||
* AUTHORITY TO BIND COMPANY. IF COMPANY DOES NOT AGREE TO ALL OF THE
|
||||
* TERMS OF THIS AGREEMENT, DO NOT SELECT THE "I UNDERSTAND AND AGREE"
|
||||
* BOX AND DO NOT INSTALL THE SOFTWARE OR VIEW THE SOURCE CODE. COMPANY
|
||||
* HAS NOT BECOME A LICENSEE OF, AND IS NOT AUTHORIZED TO USE THE
|
||||
* SOFTWARE UNLESS AND UNTIL IT HAS AGREED TO BE BOUND BY THESE LICENSE
|
||||
* TERMS. THE "EFFECTIVE DATE" FOR THIS AGREEMENT SHALL BE THE DAY YOU
|
||||
* CHECK THE "I UNDERSTAND AND ACCEPT" BOX.
|
||||
*/
|
||||
package org.alfresco.repo.security.authentication.ntlm;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.InetAddress;
|
||||
import java.net.UnknownHostException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.Provider;
|
||||
import java.security.Security;
|
||||
import java.util.Enumeration;
|
||||
import java.util.Hashtable;
|
||||
|
||||
import net.sf.acegisecurity.Authentication;
|
||||
import net.sf.acegisecurity.AuthenticationServiceException;
|
||||
import net.sf.acegisecurity.BadCredentialsException;
|
||||
import net.sf.acegisecurity.CredentialsExpiredException;
|
||||
import net.sf.acegisecurity.GrantedAuthority;
|
||||
import net.sf.acegisecurity.GrantedAuthorityImpl;
|
||||
|
||||
import org.alfresco.error.AlfrescoRuntimeException;
|
||||
import org.alfresco.filesys.server.auth.PasswordEncryptor;
|
||||
import org.alfresco.filesys.server.auth.passthru.AuthenticateSession;
|
||||
import org.alfresco.filesys.server.auth.passthru.PassthruServers;
|
||||
import org.alfresco.filesys.smb.SMBException;
|
||||
import org.alfresco.filesys.smb.SMBStatus;
|
||||
import org.alfresco.model.ContentModel;
|
||||
import org.alfresco.repo.security.authentication.AbstractAuthenticationComponent;
|
||||
import org.alfresco.repo.security.authentication.AuthenticationException;
|
||||
import org.alfresco.repo.security.authentication.NTLMMode;
|
||||
import org.alfresco.service.cmr.repository.NodeRef;
|
||||
import org.alfresco.service.cmr.repository.NodeService;
|
||||
import org.alfresco.service.cmr.security.PersonService;
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
/**
|
||||
* NTLM Authentication Component Class
|
||||
*
|
||||
* <p>Provides authentication using passthru to a Windows server(s)/domain controller(s) using the accounts
|
||||
* defined on the passthru server to validate users.
|
||||
*
|
||||
* @author GKSpencer
|
||||
*/
|
||||
public class NTLMAuthenticationComponentImpl extends AbstractAuthenticationComponent
|
||||
{
|
||||
// Logging
|
||||
|
||||
private static final Log logger = LogFactory.getLog("org.alfresco.passthru.auth");
|
||||
|
||||
// Constants
|
||||
//
|
||||
// Standard authorities
|
||||
|
||||
public static final String NTLMAuthorityGuest = "Guest";
|
||||
public static final String NTLMAuthorityAdministrator = "Administrator";
|
||||
|
||||
// Active session timeout
|
||||
|
||||
private static final long DefaultSessionTimeout = 60000L; // 1 minute
|
||||
private static final long MinimumSessionTimeout = 5000L; // 5 seconds
|
||||
|
||||
// Passthru authentication servers
|
||||
|
||||
private PassthruServers m_passthruServers;
|
||||
|
||||
// Password encryptor for generating password hash for local authentication
|
||||
|
||||
private PasswordEncryptor m_encryptor;
|
||||
|
||||
// Allow guest access
|
||||
|
||||
private boolean m_allowGuest;
|
||||
|
||||
// Table of currently active passthru authentications and the associated authentication session
|
||||
//
|
||||
// If the two authentication stages are not completed within a reasonable time the authentication
|
||||
// session will be closed by the reaper thread.
|
||||
|
||||
private Hashtable<NTLMPassthruToken,AuthenticateSession> m_passthruSessions;
|
||||
|
||||
// Active authentication session timeout, in milliseconds
|
||||
|
||||
private long m_passthruSessTmo = DefaultSessionTimeout;
|
||||
|
||||
// Authentication session reaper thread
|
||||
|
||||
private PassthruReaperThread m_reaperThread;
|
||||
|
||||
// Person service, used to map passthru usernames to Alfresco person names
|
||||
|
||||
private PersonService m_personService;
|
||||
private NodeService m_nodeService;
|
||||
|
||||
/**
|
||||
* Passthru Session Reaper Thread
|
||||
*/
|
||||
class PassthruReaperThread extends Thread
|
||||
{
|
||||
// Thread shutdown request flag
|
||||
|
||||
private boolean m_ishutdown;
|
||||
|
||||
// Reaper wakeup interval, in milliseconds
|
||||
|
||||
private long m_wakeupInterval = m_passthruSessTmo / 2;
|
||||
|
||||
/**
|
||||
* Default constructor
|
||||
*/
|
||||
PassthruReaperThread()
|
||||
{
|
||||
setDaemon(true);
|
||||
setName("PassthruReaper");
|
||||
start();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the wakeup interval
|
||||
*
|
||||
* @param wakeup long
|
||||
*/
|
||||
public final void setWakeup(long wakeup)
|
||||
{
|
||||
m_wakeupInterval = wakeup;
|
||||
}
|
||||
|
||||
/**
|
||||
* Main thread code
|
||||
*/
|
||||
public void run()
|
||||
{
|
||||
// Loop until shutdown
|
||||
|
||||
m_ishutdown = false;
|
||||
|
||||
while ( m_ishutdown == false)
|
||||
{
|
||||
// Sleep for a while
|
||||
|
||||
try
|
||||
{
|
||||
sleep( m_wakeupInterval);
|
||||
}
|
||||
catch ( InterruptedException ex)
|
||||
{
|
||||
}
|
||||
|
||||
// Check if there are any active sessions to check
|
||||
|
||||
if ( m_passthruSessions.size() > 0)
|
||||
{
|
||||
// Enumerate the active sessions
|
||||
|
||||
Enumeration<NTLMPassthruToken> tokenEnum = m_passthruSessions.keys();
|
||||
long timeNow = System.currentTimeMillis();
|
||||
|
||||
while (tokenEnum.hasMoreElements())
|
||||
{
|
||||
// Get the current NTLM token and check if it has expired
|
||||
|
||||
NTLMPassthruToken ntlmToken = tokenEnum.nextElement();
|
||||
|
||||
if ( ntlmToken != null && ntlmToken.getAuthenticationExpireTime() < timeNow)
|
||||
{
|
||||
// Authentication token has expired, close the associated authentication session
|
||||
|
||||
AuthenticateSession authSess = m_passthruSessions.get(ntlmToken);
|
||||
if ( authSess != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
// Close the authentication session
|
||||
|
||||
authSess.CloseSession();
|
||||
}
|
||||
catch ( Exception ex)
|
||||
{
|
||||
// Debug
|
||||
|
||||
if(logger.isDebugEnabled())
|
||||
logger.debug("Error closing expired authentication session", ex);
|
||||
}
|
||||
}
|
||||
|
||||
// Remove the expired token from the active list
|
||||
|
||||
m_passthruSessions.remove(ntlmToken);
|
||||
|
||||
// Debug
|
||||
|
||||
if(logger.isDebugEnabled())
|
||||
logger.debug("Removed expired NTLM token " + ntlmToken);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Debug
|
||||
|
||||
if(logger.isDebugEnabled())
|
||||
logger.debug("Passthru reaper thread shutdown");
|
||||
}
|
||||
|
||||
/**
|
||||
* Shutdown the reaper thread
|
||||
*/
|
||||
public final void shutdownRequest()
|
||||
{
|
||||
m_ishutdown = true;
|
||||
this.interrupt();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Class constructor
|
||||
*/
|
||||
public NTLMAuthenticationComponentImpl() {
|
||||
|
||||
// Create the passthru authentication server list
|
||||
|
||||
m_passthruServers = new PassthruServers();
|
||||
|
||||
// Create the password encryptor for local password hashing
|
||||
|
||||
m_encryptor = new PasswordEncryptor();
|
||||
|
||||
// Create the active session list and reaper thread
|
||||
|
||||
m_passthruSessions = new Hashtable<NTLMPassthruToken,AuthenticateSession>();
|
||||
m_reaperThread = new PassthruReaperThread();
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if guest logons are allowed
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public final boolean allowsGuest()
|
||||
{
|
||||
return m_allowGuest;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the domain to authenticate against
|
||||
*
|
||||
* @param domain String
|
||||
*/
|
||||
public void setDomain(String domain) {
|
||||
|
||||
// Check if the passthru server list is already configured
|
||||
|
||||
if ( m_passthruServers.getTotalServerCount() > 0)
|
||||
throw new AlfrescoRuntimeException("Passthru server list already configured");
|
||||
|
||||
// Configure the passthru authentication server list using the domain controllers
|
||||
|
||||
m_passthruServers.setDomain(domain);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the server(s) to authenticate against
|
||||
*
|
||||
* @param servers String
|
||||
*/
|
||||
public void setServers(String servers) {
|
||||
|
||||
// Check if the passthru server list is already configured
|
||||
|
||||
if ( m_passthruServers.getTotalServerCount() > 0)
|
||||
throw new AlfrescoRuntimeException("Passthru server list already configured");
|
||||
|
||||
// Configure the passthru authenticaiton list using a list of server names/addresses
|
||||
|
||||
m_passthruServers.setServerList(servers);
|
||||
}
|
||||
|
||||
/**
|
||||
* Use the local server as the authentication server
|
||||
*
|
||||
* @param useLocal String
|
||||
*/
|
||||
public void setUseLocalServer(String useLocal)
|
||||
{
|
||||
// Check if the local server should be used for authentication
|
||||
|
||||
if ( Boolean.parseBoolean(useLocal) == true)
|
||||
{
|
||||
// Check if the passthru server list is already configured
|
||||
|
||||
if ( m_passthruServers.getTotalServerCount() > 0)
|
||||
throw new AlfrescoRuntimeException("Passthru server list already configured");
|
||||
|
||||
try
|
||||
{
|
||||
// Get the list of local network addresses
|
||||
|
||||
InetAddress[] localAddrs = InetAddress.getAllByName(InetAddress.getLocalHost().getHostName());
|
||||
|
||||
// Build the list of local addresses
|
||||
|
||||
if ( localAddrs != null && localAddrs.length > 0)
|
||||
{
|
||||
StringBuilder addrStr = new StringBuilder();
|
||||
|
||||
for ( InetAddress curAddr : localAddrs)
|
||||
{
|
||||
if ( curAddr.isLoopbackAddress() == false)
|
||||
{
|
||||
addrStr.append(curAddr.getHostAddress());
|
||||
addrStr.append(",");
|
||||
}
|
||||
}
|
||||
|
||||
if ( addrStr.length() > 0)
|
||||
addrStr.setLength(addrStr.length() - 1);
|
||||
|
||||
// Set the server list using the local address list
|
||||
|
||||
m_passthruServers.setServerList(addrStr.toString());
|
||||
}
|
||||
else
|
||||
throw new AlfrescoRuntimeException("No local server address(es)");
|
||||
}
|
||||
catch ( UnknownHostException ex)
|
||||
{
|
||||
throw new AlfrescoRuntimeException("Failed to get local address list");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Allow guest access
|
||||
*
|
||||
* @param guest String
|
||||
*/
|
||||
public void setGuestAccess(String guest)
|
||||
{
|
||||
m_allowGuest = Boolean.parseBoolean(guest);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the JCE provider
|
||||
*
|
||||
* @param providerClass String
|
||||
*/
|
||||
public void setJCEProvider(String providerClass)
|
||||
{
|
||||
// Set the JCE provider, required to provide various encryption/hashing algorithms not available
|
||||
// in the standard Sun JDK/JRE
|
||||
|
||||
try
|
||||
{
|
||||
|
||||
// Load the JCE provider class and validate
|
||||
|
||||
Object jceObj = Class.forName(providerClass).newInstance();
|
||||
if (jceObj instanceof java.security.Provider)
|
||||
{
|
||||
|
||||
// Inform listeners, validate the configuration change
|
||||
|
||||
Provider jceProvider = (Provider) jceObj;
|
||||
|
||||
// Add the JCE provider
|
||||
|
||||
Security.addProvider(jceProvider);
|
||||
|
||||
// Debug
|
||||
|
||||
if ( logger.isDebugEnabled())
|
||||
logger.debug("Using JCE provider " + providerClass);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new AlfrescoRuntimeException("JCE provider class is not a valid Provider class");
|
||||
}
|
||||
}
|
||||
catch (ClassNotFoundException ex)
|
||||
{
|
||||
throw new AlfrescoRuntimeException("JCE provider class " + providerClass + " not found");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
throw new AlfrescoRuntimeException("JCE provider class error", ex);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the authentication session timeout, in seconds
|
||||
*
|
||||
* @param sessTmo String
|
||||
*/
|
||||
public void setSessionTimeout(String sessTmo)
|
||||
{
|
||||
// Convert to an integer value and range check the timeout value
|
||||
|
||||
try
|
||||
{
|
||||
// Convert to an integer value
|
||||
|
||||
long sessTmoMilli = Long.parseLong(sessTmo) * 1000L;
|
||||
|
||||
if ( sessTmoMilli < MinimumSessionTimeout)
|
||||
throw new AlfrescoRuntimeException("Authentication session timeout too low, " + sessTmo);
|
||||
|
||||
// Set the authentication session timeout value
|
||||
|
||||
m_passthruSessTmo = sessTmoMilli;
|
||||
|
||||
// Set the reaper thread wakeup interval
|
||||
|
||||
m_reaperThread.setWakeup( sessTmoMilli / 2);
|
||||
}
|
||||
catch(NumberFormatException ex)
|
||||
{
|
||||
throw new AlfrescoRuntimeException("Invalid authenication session timeout value");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the person service
|
||||
*
|
||||
* @param personService PersonService
|
||||
*/
|
||||
public final void setPersonService(PersonService personService)
|
||||
{
|
||||
m_personService = personService;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the node service
|
||||
*
|
||||
* @param nodeService NodeService
|
||||
*/
|
||||
public final void setNodeService(NodeService nodeService)
|
||||
{
|
||||
m_nodeService = nodeService;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the authentication session timeout, in milliseconds
|
||||
*
|
||||
* @return long
|
||||
*/
|
||||
private final long getSessionTimeout()
|
||||
{
|
||||
return m_passthruSessTmo;
|
||||
}
|
||||
|
||||
/**
|
||||
* Authenticate
|
||||
*
|
||||
* @param userName String
|
||||
* @param password char[]
|
||||
* @throws AuthenticationException
|
||||
*/
|
||||
public void authenticate(String userName, char[] password) throws AuthenticationException
|
||||
{
|
||||
// Debug
|
||||
|
||||
if ( logger.isDebugEnabled())
|
||||
logger.debug("Authenticate user=" + userName + " via local credentials");
|
||||
|
||||
// Create a local authentication token
|
||||
|
||||
NTLMLocalToken authToken = new NTLMLocalToken(userName, new String(password));
|
||||
|
||||
// Authenticate using the token
|
||||
|
||||
authenticate( authToken);
|
||||
setCurrentUser( userName.toLowerCase());
|
||||
}
|
||||
|
||||
/**
|
||||
* Authenticate using a token
|
||||
*
|
||||
* @param token Authentication
|
||||
* @return Authentication
|
||||
* @throws AuthenticationException
|
||||
*/
|
||||
public Authentication authenticate(Authentication auth) throws AuthenticationException
|
||||
{
|
||||
// DEBUG
|
||||
|
||||
if ( logger.isDebugEnabled())
|
||||
logger.debug("Authenticate " + auth + " via token");
|
||||
|
||||
// Check if the token is for passthru authentication
|
||||
|
||||
if( auth instanceof NTLMPassthruToken)
|
||||
{
|
||||
// Access the NTLM passthru token
|
||||
|
||||
NTLMPassthruToken ntlmToken = (NTLMPassthruToken) auth;
|
||||
|
||||
// Authenticate using passthru
|
||||
|
||||
authenticatePassthru(ntlmToken);
|
||||
}
|
||||
|
||||
// Check for a local authentication token
|
||||
|
||||
else if( auth instanceof NTLMLocalToken)
|
||||
{
|
||||
AuthenticateSession authSess = null;
|
||||
|
||||
try
|
||||
{
|
||||
|
||||
// Access the NTLM token
|
||||
|
||||
NTLMLocalToken ntlmToken = (NTLMLocalToken) auth;
|
||||
|
||||
// Open a session to an authentication server
|
||||
|
||||
authSess = m_passthruServers.openSession();
|
||||
|
||||
// Authenticate using the credentials supplied
|
||||
|
||||
authenticateLocal(ntlmToken, authSess);
|
||||
}
|
||||
finally
|
||||
{
|
||||
// Make sure the authentication session is closed
|
||||
|
||||
if ( authSess != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
authSess.CloseSession();
|
||||
}
|
||||
catch ( Exception ex)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Unsupported authentication token
|
||||
|
||||
throw new AuthenticationException("Unsupported authentication token type");
|
||||
}
|
||||
|
||||
// Return the updated authentication token
|
||||
|
||||
return getCurrentAuthentication();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the enum that describes NTLM integration
|
||||
*
|
||||
* @return NTLMMode
|
||||
*/
|
||||
public NTLMMode getNTLMMode()
|
||||
{
|
||||
return NTLMMode.PASS_THROUGH;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the MD4 password hash, as required by NTLM based authentication methods.
|
||||
*
|
||||
* @param userName String
|
||||
* @return String
|
||||
*/
|
||||
public String getMD4HashedPassword(String userName)
|
||||
{
|
||||
// Do not support MD4 hashed password
|
||||
|
||||
throw new AlfrescoRuntimeException("MD4 passwords not supported");
|
||||
}
|
||||
|
||||
/**
|
||||
* Authenticate a user using local credentials
|
||||
*
|
||||
* @param ntlmToken NTLMLocalToken
|
||||
* @param authSess AuthenticateSession
|
||||
*/
|
||||
private void authenticateLocal(NTLMLocalToken ntlmToken, AuthenticateSession authSess)
|
||||
{
|
||||
try
|
||||
{
|
||||
// Get the plaintext password and generate an NTLM1 password hash
|
||||
|
||||
String username = (String) ntlmToken.getPrincipal();
|
||||
String plainPwd = (String) ntlmToken.getCredentials();
|
||||
byte[] ntlm1Pwd = m_encryptor.generateEncryptedPassword( plainPwd, authSess.getEncryptionKey(), PasswordEncryptor.NTLM1);
|
||||
|
||||
// Send the logon request to the authentication server
|
||||
//
|
||||
// Note: Only use the stronger NTLM hash, we do not send the LM hash
|
||||
|
||||
authSess.doSessionSetup(username, null, ntlm1Pwd);
|
||||
|
||||
// Check if the session has logged on as a guest
|
||||
|
||||
if ( authSess.isGuest() || username.equalsIgnoreCase("GUEST"))
|
||||
{
|
||||
// If guest access is enabled add a guest authority to the token
|
||||
|
||||
if ( allowsGuest())
|
||||
{
|
||||
// Set the guest authority
|
||||
|
||||
GrantedAuthority[] authorities = new GrantedAuthority[2];
|
||||
authorities[0] = new GrantedAuthorityImpl(NTLMAuthorityGuest);
|
||||
authorities[1] = new GrantedAuthorityImpl("ROLE_AUTHENTICATED");
|
||||
|
||||
ntlmToken.setAuthorities(authorities);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Guest access not allowed
|
||||
|
||||
throw new AuthenticationException("Guest logons disabled");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Set authorities
|
||||
|
||||
GrantedAuthority[] authorities = new GrantedAuthority[1];
|
||||
authorities[0] = new GrantedAuthorityImpl("ROLE_AUTHENTICATED");
|
||||
|
||||
ntlmToken.setAuthorities(authorities);
|
||||
}
|
||||
|
||||
// Indicate that the token is authenticated
|
||||
|
||||
ntlmToken.setAuthenticated(true);
|
||||
|
||||
// Map the passthru username to an Alfresco person
|
||||
|
||||
NodeRef userNode = m_personService.getPerson(username);
|
||||
if ( userNode != null)
|
||||
{
|
||||
// Get the person name and use that as the current user to line up with permission checks
|
||||
|
||||
String personName = (String) m_nodeService.getProperty(userNode, ContentModel.PROP_USERNAME);
|
||||
setCurrentUser(personName);
|
||||
|
||||
// DEBUG
|
||||
|
||||
if ( logger.isDebugEnabled())
|
||||
logger.debug("Setting current user using person " + personName + " (username " + username + ")");
|
||||
}
|
||||
else
|
||||
{
|
||||
// Set using the user name, lowercase the name if hte person service is case insensitive
|
||||
|
||||
if ( m_personService.getUserNamesAreCaseSensitive() == false)
|
||||
username = username.toLowerCase();
|
||||
setCurrentUser( username);
|
||||
|
||||
// DEBUG
|
||||
|
||||
if ( logger.isDebugEnabled())
|
||||
logger.debug("Setting current user using username " + username);
|
||||
}
|
||||
|
||||
// Debug
|
||||
|
||||
if ( logger.isDebugEnabled())
|
||||
logger.debug("Authenticated token=" + ntlmToken);
|
||||
}
|
||||
catch (NoSuchAlgorithmException ex)
|
||||
{
|
||||
// JCE provider does not have the required encryption/hashing algorithms
|
||||
|
||||
throw new AuthenticationServiceException("JCE provider error", ex);
|
||||
}
|
||||
catch (IOException ex)
|
||||
{
|
||||
// Error connecting to the authentication server
|
||||
|
||||
throw new AuthenticationServiceException("I/O error", ex);
|
||||
}
|
||||
catch (SMBException ex)
|
||||
{
|
||||
// Check the returned status code to determine why the logon failed and throw an appropriate exception
|
||||
|
||||
if ( ex.getErrorClass() == SMBStatus.NTErr)
|
||||
{
|
||||
AuthenticationException authEx = null;
|
||||
|
||||
switch( ex.getErrorCode())
|
||||
{
|
||||
case SMBStatus.NTLogonFailure:
|
||||
authEx = new AuthenticationException("Logon failure");
|
||||
break;
|
||||
case SMBStatus.NTAccountDisabled:
|
||||
authEx = new AuthenticationException("Account disabled");
|
||||
break;
|
||||
default:
|
||||
authEx = new AuthenticationException("Logon failure");
|
||||
break;
|
||||
}
|
||||
|
||||
throw authEx;
|
||||
}
|
||||
else
|
||||
throw new AuthenticationException("Logon failure");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Authenticate using passthru authentication with a client
|
||||
*
|
||||
* @param ntlmToken NTLMPassthruToken
|
||||
*/
|
||||
private void authenticatePassthru(NTLMPassthruToken ntlmToken)
|
||||
{
|
||||
// Check if the token has an authentication session, if not then it is either a new token
|
||||
// or the session has been timed out
|
||||
|
||||
AuthenticateSession authSess = m_passthruSessions.get(ntlmToken);
|
||||
|
||||
if ( authSess == null)
|
||||
{
|
||||
// Check if the token has a challenge, if it does then the associated session has been
|
||||
// timed out
|
||||
|
||||
if ( ntlmToken.getChallenge() != null)
|
||||
throw new CredentialsExpiredException("Authentication session expired");
|
||||
|
||||
// Open an authentication session for the new token and add to the active session list
|
||||
|
||||
authSess = m_passthruServers.openSession();
|
||||
|
||||
ntlmToken.setAuthenticationExpireTime(System.currentTimeMillis() + getSessionTimeout());
|
||||
|
||||
// Get the challenge from the initial session negotiate stage
|
||||
|
||||
ntlmToken.setChallenge(new NTLMChallenge(authSess.getEncryptionKey()));
|
||||
|
||||
StringBuilder details = new StringBuilder();
|
||||
|
||||
// Build a details string with the authentication session details
|
||||
|
||||
details.append(authSess.getDomain());
|
||||
details.append("\\");
|
||||
details.append(authSess.getPCShare().getNodeName());
|
||||
details.append(",");
|
||||
details.append(authSess.getSession().getProtocolName());
|
||||
|
||||
ntlmToken.setDetails(details.toString());
|
||||
|
||||
// Put the token/session into the active session list
|
||||
|
||||
m_passthruSessions.put(ntlmToken, authSess);
|
||||
|
||||
// Debug
|
||||
|
||||
if ( logger.isDebugEnabled())
|
||||
logger.debug("Passthru stage 1 token " + ntlmToken);
|
||||
}
|
||||
else
|
||||
{
|
||||
try
|
||||
{
|
||||
// Stage two of the authentication, send the hashed password to the authentication server
|
||||
|
||||
byte[] lmPwd = null;
|
||||
byte[] ntlmPwd = null;
|
||||
|
||||
if ( ntlmToken.getPasswordType() == PasswordEncryptor.LANMAN)
|
||||
lmPwd = ntlmToken.getHashedPassword();
|
||||
else if ( ntlmToken.getPasswordType() == PasswordEncryptor.NTLM1)
|
||||
ntlmPwd = ntlmToken.getHashedPassword();
|
||||
|
||||
String username = (String) ntlmToken.getPrincipal();
|
||||
|
||||
authSess.doSessionSetup(username, lmPwd, ntlmPwd);
|
||||
|
||||
// Check if the session has logged on as a guest
|
||||
|
||||
if ( authSess.isGuest() || username.equalsIgnoreCase("GUEST"))
|
||||
{
|
||||
// If guest access is enabled add a guest authority to the token
|
||||
|
||||
if ( allowsGuest())
|
||||
{
|
||||
// Set the guest authority
|
||||
|
||||
GrantedAuthority[] authorities = new GrantedAuthority[1];
|
||||
authorities[0] = new GrantedAuthorityImpl(NTLMAuthorityGuest);
|
||||
|
||||
ntlmToken.setAuthorities(authorities);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Guest access not allowed
|
||||
|
||||
throw new BadCredentialsException("Guest logons disabled");
|
||||
}
|
||||
}
|
||||
|
||||
// Indicate that the token is authenticated
|
||||
|
||||
ntlmToken.setAuthenticated(true);
|
||||
|
||||
// Map the passthru username to an Alfresco person
|
||||
|
||||
NodeRef userNode = m_personService.getPerson(username);
|
||||
if ( userNode != null)
|
||||
{
|
||||
// Get the person name and use that as the current user to line up with permission checks
|
||||
|
||||
String personName = (String) m_nodeService.getProperty(userNode, ContentModel.PROP_USERNAME);
|
||||
setCurrentUser(personName);
|
||||
|
||||
// DEBUG
|
||||
|
||||
if ( logger.isDebugEnabled())
|
||||
logger.debug("Setting current user using person " + personName + " (username " + username + ")");
|
||||
}
|
||||
else
|
||||
{
|
||||
// Set using the user name, lowercase the name if the person service is case insensitive
|
||||
|
||||
if ( m_personService.getUserNamesAreCaseSensitive() == false)
|
||||
username = username.toLowerCase();
|
||||
setCurrentUser( username);
|
||||
|
||||
// DEBUG
|
||||
|
||||
if ( logger.isDebugEnabled())
|
||||
logger.debug("Setting current user using username " + username);
|
||||
}
|
||||
}
|
||||
catch (IOException ex)
|
||||
{
|
||||
// Error connecting to the authentication server
|
||||
|
||||
throw new AuthenticationServiceException("I/O error", ex);
|
||||
}
|
||||
catch (SMBException ex)
|
||||
{
|
||||
// Debug
|
||||
|
||||
if ( logger.isDebugEnabled())
|
||||
logger.debug("Passthru exception, " + ex);
|
||||
|
||||
// Check the returned status code to determine why the logon failed and throw an appropriate exception
|
||||
|
||||
if ( ex.getErrorClass() == SMBStatus.NTErr)
|
||||
{
|
||||
AuthenticationException authEx = null;
|
||||
|
||||
switch( ex.getErrorCode())
|
||||
{
|
||||
case SMBStatus.NTLogonFailure:
|
||||
authEx = new AuthenticationException("Logon failure");
|
||||
break;
|
||||
case SMBStatus.NTAccountDisabled:
|
||||
authEx = new AuthenticationException("Account disabled");
|
||||
break;
|
||||
default:
|
||||
authEx = new AuthenticationException("Logon failure");
|
||||
break;
|
||||
}
|
||||
|
||||
throw authEx;
|
||||
}
|
||||
else
|
||||
throw new BadCredentialsException("Logon failure");
|
||||
}
|
||||
finally
|
||||
{
|
||||
// Make sure the authentication session is closed
|
||||
|
||||
if ( authSess != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
// Remove the session from the active list
|
||||
|
||||
m_passthruSessions.remove(ntlmToken);
|
||||
|
||||
// Close the session to the authentication server
|
||||
|
||||
authSess.CloseSession();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the user exists
|
||||
*
|
||||
* @param userName String
|
||||
* @return boolean
|
||||
*/
|
||||
public boolean exists(String userName)
|
||||
{
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean implementationAllowsGuestLogin()
|
||||
{
|
||||
return allowsGuest();
|
||||
}
|
||||
|
||||
|
||||
}
|
@@ -0,0 +1,755 @@
|
||||
/*
|
||||
* Copyright (C) 2005 Alfresco, Inc.
|
||||
*
|
||||
* Licensed under the Alfresco Network License. You may obtain a
|
||||
* copy of the License at
|
||||
*
|
||||
* http://www.alfrescosoftware.com/legal/
|
||||
*
|
||||
* Please view the license relevant to your network subscription.
|
||||
*
|
||||
* BY CLICKING THE "I UNDERSTAND AND ACCEPT" BOX, OR INSTALLING,
|
||||
* READING OR USING ALFRESCO'S Network SOFTWARE (THE "SOFTWARE"),
|
||||
* YOU ARE AGREEING ON BEHALF OF THE ENTITY LICENSING THE SOFTWARE
|
||||
* ("COMPANY") THAT COMPANY WILL BE BOUND BY AND IS BECOMING A PARTY TO
|
||||
* THIS ALFRESCO NETWORK AGREEMENT ("AGREEMENT") AND THAT YOU HAVE THE
|
||||
* AUTHORITY TO BIND COMPANY. IF COMPANY DOES NOT AGREE TO ALL OF THE
|
||||
* TERMS OF THIS AGREEMENT, DO NOT SELECT THE "I UNDERSTAND AND AGREE"
|
||||
* BOX AND DO NOT INSTALL THE SOFTWARE OR VIEW THE SOURCE CODE. COMPANY
|
||||
* HAS NOT BECOME A LICENSEE OF, AND IS NOT AUTHORIZED TO USE THE
|
||||
* SOFTWARE UNLESS AND UNTIL IT HAS AGREED TO BE BOUND BY THESE LICENSE
|
||||
* TERMS. THE "EFFECTIVE DATE" FOR THIS AGREEMENT SHALL BE THE DAY YOU
|
||||
* CHECK THE "I UNDERSTAND AND ACCEPT" BOX.
|
||||
*/
|
||||
package org.alfresco.repo.security.authentication.ntlm;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.InetAddress;
|
||||
import java.net.UnknownHostException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.Provider;
|
||||
import java.security.Security;
|
||||
import java.util.Enumeration;
|
||||
import java.util.Hashtable;
|
||||
|
||||
import org.alfresco.error.AlfrescoRuntimeException;
|
||||
import org.alfresco.filesys.server.auth.PasswordEncryptor;
|
||||
import org.alfresco.filesys.server.auth.passthru.AuthenticateSession;
|
||||
import org.alfresco.filesys.server.auth.passthru.PassthruServers;
|
||||
import org.alfresco.filesys.smb.SMBException;
|
||||
import org.alfresco.filesys.smb.SMBStatus;
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
import net.sf.acegisecurity.*;
|
||||
import net.sf.acegisecurity.providers.*;
|
||||
|
||||
/**
|
||||
* NTLM Authentication Provider
|
||||
*
|
||||
* @author GKSpencer
|
||||
*/
|
||||
public class NTLMAuthenticationProvider implements AuthenticationProvider
|
||||
{
|
||||
private static final Log logger = LogFactory.getLog("org.alfresco.acegi");
|
||||
|
||||
// Constants
|
||||
//
|
||||
// Standard authorities
|
||||
|
||||
public static final String NTLMAuthorityGuest = "Guest";
|
||||
public static final String NTLMAuthorityAdministrator = "Administrator";
|
||||
|
||||
// Active session timeout
|
||||
|
||||
private static final long DefaultSessionTimeout = 60000L; // 1 minute
|
||||
private static final long MinimumSessionTimeout = 5000L; // 5 seconds
|
||||
|
||||
// Passthru authentication servers
|
||||
|
||||
private PassthruServers m_passthruServers;
|
||||
|
||||
// Password encryptor for generating password hash for local authentication
|
||||
|
||||
private PasswordEncryptor m_encryptor;
|
||||
|
||||
// Allow guest access
|
||||
|
||||
private boolean m_allowGuest;
|
||||
|
||||
// Table of currently active passthru authentications and the associated authentication session
|
||||
//
|
||||
// If the two authentication stages are not completed within a reasonable time the authentication
|
||||
// session will be closed by the reaper thread.
|
||||
|
||||
private Hashtable<NTLMPassthruToken,AuthenticateSession> m_passthruSessions;
|
||||
|
||||
// Active authentication session timeout, in milliseconds
|
||||
|
||||
private long m_passthruSessTmo = DefaultSessionTimeout;
|
||||
|
||||
// Authentication session reaper thread
|
||||
|
||||
private PassthruReaperThread m_reaperThread;
|
||||
|
||||
/**
|
||||
* Passthru Session Repear Thread
|
||||
*/
|
||||
class PassthruReaperThread extends Thread
|
||||
{
|
||||
// Thread shutdown request flag
|
||||
|
||||
private boolean m_ishutdown;
|
||||
|
||||
// Reaper wakeup interval, in milliseconds
|
||||
|
||||
private long m_wakeupInterval = m_passthruSessTmo / 2;
|
||||
|
||||
/**
|
||||
* Default constructor
|
||||
*/
|
||||
PassthruReaperThread()
|
||||
{
|
||||
setDaemon(true);
|
||||
setName("PassthruReaper");
|
||||
start();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the wakeup interval
|
||||
*
|
||||
* @param wakeup long
|
||||
*/
|
||||
public final void setWakeup(long wakeup)
|
||||
{
|
||||
m_wakeupInterval = wakeup;
|
||||
}
|
||||
|
||||
/**
|
||||
* Main thread code
|
||||
*/
|
||||
public void run()
|
||||
{
|
||||
// Loop until shutdown
|
||||
|
||||
m_ishutdown = false;
|
||||
|
||||
while ( m_ishutdown == false)
|
||||
{
|
||||
// Sleep for a while
|
||||
|
||||
try
|
||||
{
|
||||
sleep( m_wakeupInterval);
|
||||
}
|
||||
catch ( InterruptedException ex)
|
||||
{
|
||||
}
|
||||
|
||||
// Check if there are any active sessions to check
|
||||
|
||||
if ( m_passthruSessions.size() > 0)
|
||||
{
|
||||
// Enumerate the active sessions
|
||||
|
||||
Enumeration<NTLMPassthruToken> tokenEnum = m_passthruSessions.keys();
|
||||
long timeNow = System.currentTimeMillis();
|
||||
|
||||
while (tokenEnum.hasMoreElements())
|
||||
{
|
||||
// Get the current NTLM token and check if it has expired
|
||||
|
||||
NTLMPassthruToken ntlmToken = tokenEnum.nextElement();
|
||||
|
||||
if ( ntlmToken != null && ntlmToken.getAuthenticationExpireTime() < timeNow)
|
||||
{
|
||||
// Authentication token has expired, close the associated authentication session
|
||||
|
||||
AuthenticateSession authSess = m_passthruSessions.get(ntlmToken);
|
||||
if ( authSess != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
// Close the authentication session
|
||||
|
||||
authSess.CloseSession();
|
||||
}
|
||||
catch ( Exception ex)
|
||||
{
|
||||
// Debug
|
||||
|
||||
if(logger.isDebugEnabled())
|
||||
logger.debug("Error closing expired authentication session", ex);
|
||||
}
|
||||
}
|
||||
|
||||
// Remove the expired token from the active list
|
||||
|
||||
m_passthruSessions.remove(ntlmToken);
|
||||
|
||||
// Debug
|
||||
|
||||
if(logger.isDebugEnabled())
|
||||
logger.debug("Removed expired NTLM token " + ntlmToken);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Debug
|
||||
|
||||
if(logger.isDebugEnabled())
|
||||
logger.debug("Passthru reaper thread shutdown");
|
||||
}
|
||||
|
||||
/**
|
||||
* Shutdown the reaper thread
|
||||
*/
|
||||
public final void shutdownRequest()
|
||||
{
|
||||
m_ishutdown = true;
|
||||
this.interrupt();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Class constructor
|
||||
*/
|
||||
public NTLMAuthenticationProvider() {
|
||||
|
||||
// Create the passthru authentication server list
|
||||
|
||||
m_passthruServers = new PassthruServers();
|
||||
|
||||
// Create the password encryptor for local password hashing
|
||||
|
||||
m_encryptor = new PasswordEncryptor();
|
||||
|
||||
// Create the active session list and reaper thread
|
||||
|
||||
m_passthruSessions = new Hashtable<NTLMPassthruToken,AuthenticateSession>();
|
||||
m_reaperThread = new PassthruReaperThread();
|
||||
}
|
||||
|
||||
/**
|
||||
* Authenticate a user
|
||||
*
|
||||
* @param auth Authentication
|
||||
* @return Authentication
|
||||
* @exception AuthenticationException
|
||||
*/
|
||||
public Authentication authenticate(Authentication auth) throws AuthenticationException
|
||||
{
|
||||
// DEBUG
|
||||
|
||||
if ( logger.isDebugEnabled())
|
||||
logger.debug("Authenticate " + auth);
|
||||
|
||||
// Check if the token is for passthru authentication
|
||||
|
||||
if( auth instanceof NTLMPassthruToken)
|
||||
{
|
||||
// Access the NTLM passthru token
|
||||
|
||||
NTLMPassthruToken ntlmToken = (NTLMPassthruToken) auth;
|
||||
|
||||
// Authenticate using passthru
|
||||
|
||||
authenticatePassthru(ntlmToken);
|
||||
}
|
||||
|
||||
// Check for a local authentication token
|
||||
|
||||
else if( auth instanceof NTLMLocalToken)
|
||||
{
|
||||
AuthenticateSession authSess = null;
|
||||
|
||||
try
|
||||
{
|
||||
|
||||
// Access the NTLM token
|
||||
|
||||
NTLMLocalToken ntlmToken = (NTLMLocalToken) auth;
|
||||
|
||||
// Open a session to an authentication server
|
||||
|
||||
authSess = m_passthruServers.openSession();
|
||||
|
||||
// Authenticate using the credentials supplied
|
||||
|
||||
authenticateLocal(ntlmToken, authSess);
|
||||
}
|
||||
finally
|
||||
{
|
||||
// Make sure the authentication session is closed
|
||||
|
||||
if ( authSess != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
authSess.CloseSession();
|
||||
}
|
||||
catch ( Exception ex)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Return the updated authentication token
|
||||
|
||||
return auth;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if this provider supports the specified authentication token
|
||||
*
|
||||
* @param authentication Class
|
||||
*/
|
||||
public boolean supports(Class authentication)
|
||||
{
|
||||
// Check if the authentication is an NTLM authentication token
|
||||
|
||||
if ( NTLMPassthruToken.class.isAssignableFrom(authentication))
|
||||
return true;
|
||||
return NTLMLocalToken.class.isAssignableFrom(authentication);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if guest logons are allowed
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public final boolean allowsGuest()
|
||||
{
|
||||
return m_allowGuest;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the domain to authenticate against
|
||||
*
|
||||
* @param domain String
|
||||
*/
|
||||
public final void setDomain(String domain) {
|
||||
|
||||
// Check if the passthru server list is already configured
|
||||
|
||||
if ( m_passthruServers.getTotalServerCount() > 0)
|
||||
throw new AlfrescoRuntimeException("Passthru server list already configured");
|
||||
|
||||
// Configure the passthru authentication server list using the domain controllers
|
||||
|
||||
m_passthruServers.setDomain(domain);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the server(s) to authenticate against
|
||||
*
|
||||
* @param servers String
|
||||
*/
|
||||
public final void setServers(String servers) {
|
||||
|
||||
// Check if the passthru server list is already configured
|
||||
|
||||
if ( m_passthruServers.getTotalServerCount() > 0)
|
||||
throw new AlfrescoRuntimeException("Passthru server list already configured");
|
||||
|
||||
// Configure the passthru authenticaiton list using a list of server names/addresses
|
||||
|
||||
m_passthruServers.setServerList(servers);
|
||||
}
|
||||
|
||||
/**
|
||||
* Use the local server as the authentication server
|
||||
*
|
||||
* @param useLocal String
|
||||
*/
|
||||
public final void setUseLocalServer(String useLocal)
|
||||
{
|
||||
// Check if the local server should be used for authentication
|
||||
|
||||
if ( Boolean.parseBoolean(useLocal) == true)
|
||||
{
|
||||
// Check if the passthru server list is already configured
|
||||
|
||||
if ( m_passthruServers.getTotalServerCount() > 0)
|
||||
throw new AlfrescoRuntimeException("Passthru server list already configured");
|
||||
|
||||
try
|
||||
{
|
||||
// Get the list of local network addresses
|
||||
|
||||
InetAddress[] localAddrs = InetAddress.getAllByName(InetAddress.getLocalHost().getHostName());
|
||||
|
||||
// Build the list of local addresses
|
||||
|
||||
if ( localAddrs != null && localAddrs.length > 0)
|
||||
{
|
||||
StringBuilder addrStr = new StringBuilder();
|
||||
|
||||
for ( InetAddress curAddr : localAddrs)
|
||||
{
|
||||
if ( curAddr.isLoopbackAddress() == false)
|
||||
{
|
||||
addrStr.append(curAddr.getHostAddress());
|
||||
addrStr.append(",");
|
||||
}
|
||||
}
|
||||
|
||||
if ( addrStr.length() > 0)
|
||||
addrStr.setLength(addrStr.length() - 1);
|
||||
|
||||
// Set the server list using the local address list
|
||||
|
||||
m_passthruServers.setServerList(addrStr.toString());
|
||||
}
|
||||
else
|
||||
throw new AlfrescoRuntimeException("No local server address(es)");
|
||||
}
|
||||
catch ( UnknownHostException ex)
|
||||
{
|
||||
throw new AlfrescoRuntimeException("Failed to get local address list");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Allow guest access
|
||||
*
|
||||
* @param guest String
|
||||
*/
|
||||
public final void setGuestAccess(String guest)
|
||||
{
|
||||
m_allowGuest = Boolean.parseBoolean(guest);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the JCE provider
|
||||
*
|
||||
* @param providerClass String
|
||||
*/
|
||||
public final void setJCEProvider(String providerClass)
|
||||
{
|
||||
// Set the JCE provider, required to provide various encryption/hashing algorithms not available
|
||||
// in the standard Sun JDK/JRE
|
||||
|
||||
try
|
||||
{
|
||||
|
||||
// Load the JCE provider class and validate
|
||||
|
||||
Object jceObj = Class.forName(providerClass).newInstance();
|
||||
if (jceObj instanceof java.security.Provider)
|
||||
{
|
||||
|
||||
// Inform listeners, validate the configuration change
|
||||
|
||||
Provider jceProvider = (Provider) jceObj;
|
||||
|
||||
// Add the JCE provider
|
||||
|
||||
Security.addProvider(jceProvider);
|
||||
|
||||
// Debug
|
||||
|
||||
if ( logger.isDebugEnabled())
|
||||
logger.debug("Using JCE provider " + providerClass);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new AlfrescoRuntimeException("JCE provider class is not a valid Provider class");
|
||||
}
|
||||
}
|
||||
catch (ClassNotFoundException ex)
|
||||
{
|
||||
throw new AlfrescoRuntimeException("JCE provider class " + providerClass + " not found");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
throw new AlfrescoRuntimeException("JCE provider class error", ex);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the authentication session timeout, in seconds
|
||||
*
|
||||
* @param sessTmo String
|
||||
*/
|
||||
public final void setSessionTimeout(String sessTmo)
|
||||
{
|
||||
// Convert to an integer value and range check the timeout value
|
||||
|
||||
try
|
||||
{
|
||||
// Convert to an integer value
|
||||
|
||||
long sessTmoMilli = Long.parseLong(sessTmo) * 1000L;
|
||||
|
||||
if ( sessTmoMilli < MinimumSessionTimeout)
|
||||
throw new AlfrescoRuntimeException("Authentication session timeout too low, " + sessTmo);
|
||||
|
||||
// Set the authentication session timeout value
|
||||
|
||||
m_passthruSessTmo = sessTmoMilli;
|
||||
|
||||
// Set the reaper thread wakeup interval
|
||||
|
||||
m_reaperThread.setWakeup( sessTmoMilli / 2);
|
||||
}
|
||||
catch(NumberFormatException ex)
|
||||
{
|
||||
throw new AlfrescoRuntimeException("Invalid authenication session timeout value");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the authentication session timeout, in milliseconds
|
||||
*
|
||||
* @return long
|
||||
*/
|
||||
private final long getSessionTimeout()
|
||||
{
|
||||
return m_passthruSessTmo;
|
||||
}
|
||||
|
||||
/**
|
||||
* Authenticate a user using local credentials
|
||||
*
|
||||
* @param ntlmToken NTLMLocalToken
|
||||
* @param authSess AuthenticateSession
|
||||
*/
|
||||
private void authenticateLocal(NTLMLocalToken ntlmToken, AuthenticateSession authSess)
|
||||
{
|
||||
try
|
||||
{
|
||||
// Get the plaintext password and generate an NTLM1 password hash
|
||||
|
||||
String username = (String) ntlmToken.getPrincipal();
|
||||
String plainPwd = (String) ntlmToken.getCredentials();
|
||||
byte[] ntlm1Pwd = m_encryptor.generateEncryptedPassword( plainPwd, authSess.getEncryptionKey(), PasswordEncryptor.NTLM1);
|
||||
|
||||
// Send the logon request to the authentication server
|
||||
//
|
||||
// Note: Only use the stronger NTLM hash, we do not send the LM hash
|
||||
|
||||
authSess.doSessionSetup(username, null, ntlm1Pwd);
|
||||
|
||||
// Check if the session has logged on as a guest
|
||||
|
||||
if ( authSess.isGuest() || username.equalsIgnoreCase("GUEST"))
|
||||
{
|
||||
// If guest access is enabled add a guest authority to the token
|
||||
|
||||
if ( allowsGuest())
|
||||
{
|
||||
// Set the guest authority
|
||||
|
||||
GrantedAuthority[] authorities = new GrantedAuthority[1];
|
||||
authorities[0] = new GrantedAuthorityImpl(NTLMAuthorityGuest);
|
||||
|
||||
ntlmToken.setAuthorities(authorities);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Guest access not allowed
|
||||
|
||||
throw new BadCredentialsException("Guest logons disabled");
|
||||
}
|
||||
}
|
||||
|
||||
// Indicate that the token is authenticated
|
||||
|
||||
ntlmToken.setAuthenticated(true);
|
||||
}
|
||||
catch (NoSuchAlgorithmException ex)
|
||||
{
|
||||
// JCE provider does not have the required encryption/hashing algorithms
|
||||
|
||||
throw new AuthenticationServiceException("JCE provider error", ex);
|
||||
}
|
||||
catch (IOException ex)
|
||||
{
|
||||
// Error connecting to the authentication server
|
||||
|
||||
throw new AuthenticationServiceException("I/O error", ex);
|
||||
}
|
||||
catch (SMBException ex)
|
||||
{
|
||||
// Check the returned status code to determine why the logon failed and throw an appropriate exception
|
||||
|
||||
if ( ex.getErrorClass() == SMBStatus.NTErr)
|
||||
{
|
||||
AuthenticationException authEx = null;
|
||||
|
||||
switch( ex.getErrorCode())
|
||||
{
|
||||
case SMBStatus.NTLogonFailure:
|
||||
authEx = new BadCredentialsException("Logon failure");
|
||||
break;
|
||||
case SMBStatus.NTAccountDisabled:
|
||||
authEx = new DisabledException("Account disabled");
|
||||
break;
|
||||
default:
|
||||
authEx = new BadCredentialsException("Logon failure");
|
||||
break;
|
||||
}
|
||||
|
||||
throw authEx;
|
||||
}
|
||||
else
|
||||
throw new BadCredentialsException("Logon failure");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Authenticate using passthru authentication with a client
|
||||
*
|
||||
* @param ntlmToken NTLMPassthruToken
|
||||
*/
|
||||
private void authenticatePassthru(NTLMPassthruToken ntlmToken)
|
||||
{
|
||||
// Check if the token has an authentication session, if not then it is either a new token
|
||||
// or the session has been timed out
|
||||
|
||||
AuthenticateSession authSess = m_passthruSessions.get(ntlmToken);
|
||||
|
||||
if ( authSess == null)
|
||||
{
|
||||
// Check if the token has a challenge, if it does then the associated session has been
|
||||
// timed out
|
||||
|
||||
if ( ntlmToken.getChallenge() != null)
|
||||
throw new CredentialsExpiredException("Authentication session expired");
|
||||
|
||||
// Open an authentication session for the new token and add to the active session list
|
||||
|
||||
authSess = m_passthruServers.openSession();
|
||||
|
||||
ntlmToken.setAuthenticationExpireTime(System.currentTimeMillis() + getSessionTimeout());
|
||||
|
||||
// Get the challenge from the initial session negotiate stage
|
||||
|
||||
ntlmToken.setChallenge(new NTLMChallenge(authSess.getEncryptionKey()));
|
||||
|
||||
StringBuilder details = new StringBuilder();
|
||||
|
||||
// Build a details string with the authentication session details
|
||||
|
||||
details.append(authSess.getDomain());
|
||||
details.append("\\");
|
||||
details.append(authSess.getPCShare().getNodeName());
|
||||
details.append(",");
|
||||
details.append(authSess.getSession().getProtocolName());
|
||||
|
||||
ntlmToken.setDetails(details.toString());
|
||||
|
||||
// Put the token/session into the active session list
|
||||
|
||||
m_passthruSessions.put(ntlmToken, authSess);
|
||||
|
||||
// Debug
|
||||
|
||||
if ( logger.isDebugEnabled())
|
||||
logger.debug("Passthru stage 1 token " + ntlmToken);
|
||||
}
|
||||
else
|
||||
{
|
||||
try
|
||||
{
|
||||
// Stage two of the authentication, send the hashed password to the authentication server
|
||||
|
||||
byte[] lmPwd = null;
|
||||
byte[] ntlmPwd = null;
|
||||
|
||||
if ( ntlmToken.getPasswordType() == PasswordEncryptor.LANMAN)
|
||||
lmPwd = ntlmToken.getHashedPassword();
|
||||
else if ( ntlmToken.getPasswordType() == PasswordEncryptor.NTLM1)
|
||||
ntlmPwd = ntlmToken.getHashedPassword();
|
||||
|
||||
String username = (String) ntlmToken.getPrincipal();
|
||||
|
||||
authSess.doSessionSetup(username, lmPwd, ntlmPwd);
|
||||
|
||||
// Check if the session has logged on as a guest
|
||||
|
||||
if ( authSess.isGuest() || username.equalsIgnoreCase("GUEST"))
|
||||
{
|
||||
// If guest access is enabled add a guest authority to the token
|
||||
|
||||
if ( allowsGuest())
|
||||
{
|
||||
// Set the guest authority
|
||||
|
||||
GrantedAuthority[] authorities = new GrantedAuthority[1];
|
||||
authorities[0] = new GrantedAuthorityImpl(NTLMAuthorityGuest);
|
||||
|
||||
ntlmToken.setAuthorities(authorities);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Guest access not allowed
|
||||
|
||||
throw new BadCredentialsException("Guest logons disabled");
|
||||
}
|
||||
}
|
||||
|
||||
// Indicate that the token is authenticated
|
||||
|
||||
ntlmToken.setAuthenticated(true);
|
||||
}
|
||||
catch (IOException ex)
|
||||
{
|
||||
// Error connecting to the authentication server
|
||||
|
||||
throw new AuthenticationServiceException("I/O error", ex);
|
||||
}
|
||||
catch (SMBException ex)
|
||||
{
|
||||
// Check the returned status code to determine why the logon failed and throw an appropriate exception
|
||||
|
||||
if ( ex.getErrorClass() == SMBStatus.NTErr)
|
||||
{
|
||||
AuthenticationException authEx = null;
|
||||
|
||||
switch( ex.getErrorCode())
|
||||
{
|
||||
case SMBStatus.NTLogonFailure:
|
||||
authEx = new BadCredentialsException("Logon failure");
|
||||
break;
|
||||
case SMBStatus.NTAccountDisabled:
|
||||
authEx = new DisabledException("Account disabled");
|
||||
break;
|
||||
default:
|
||||
authEx = new BadCredentialsException("Logon failure");
|
||||
break;
|
||||
}
|
||||
|
||||
throw authEx;
|
||||
}
|
||||
else
|
||||
throw new BadCredentialsException("Logon failure");
|
||||
}
|
||||
finally
|
||||
{
|
||||
// Make sure the authentication session is closed
|
||||
|
||||
if ( authSess != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
// Remove the session from the active list
|
||||
|
||||
m_passthruSessions.remove(ntlmToken);
|
||||
|
||||
// Close the session to the authentication server
|
||||
|
||||
authSess.CloseSession();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,113 @@
|
||||
/*
|
||||
* Copyright (C) 2005 Alfresco, Inc.
|
||||
*
|
||||
* Licensed under the Alfresco Network License. You may obtain a
|
||||
* copy of the License at
|
||||
*
|
||||
* http://www.alfrescosoftware.com/legal/
|
||||
*
|
||||
* Please view the license relevant to your network subscription.
|
||||
*
|
||||
* BY CLICKING THE "I UNDERSTAND AND ACCEPT" BOX, OR INSTALLING,
|
||||
* READING OR USING ALFRESCO'S Network SOFTWARE (THE "SOFTWARE"),
|
||||
* YOU ARE AGREEING ON BEHALF OF THE ENTITY LICENSING THE SOFTWARE
|
||||
* ("COMPANY") THAT COMPANY WILL BE BOUND BY AND IS BECOMING A PARTY TO
|
||||
* THIS ALFRESCO NETWORK AGREEMENT ("AGREEMENT") AND THAT YOU HAVE THE
|
||||
* AUTHORITY TO BIND COMPANY. IF COMPANY DOES NOT AGREE TO ALL OF THE
|
||||
* TERMS OF THIS AGREEMENT, DO NOT SELECT THE "I UNDERSTAND AND AGREE"
|
||||
* BOX AND DO NOT INSTALL THE SOFTWARE OR VIEW THE SOURCE CODE. COMPANY
|
||||
* HAS NOT BECOME A LICENSEE OF, AND IS NOT AUTHORIZED TO USE THE
|
||||
* SOFTWARE UNLESS AND UNTIL IT HAS AGREED TO BE BOUND BY THESE LICENSE
|
||||
* TERMS. THE "EFFECTIVE DATE" FOR THIS AGREEMENT SHALL BE THE DAY YOU
|
||||
* CHECK THE "I UNDERSTAND AND ACCEPT" BOX.
|
||||
*/
|
||||
package org.alfresco.repo.security.authentication.ntlm;
|
||||
|
||||
import org.alfresco.filesys.util.HexDump;
|
||||
|
||||
/**
|
||||
* Contains the NTLM challenge bytes.
|
||||
*
|
||||
* @author GKSpencer
|
||||
*/
|
||||
public class NTLMChallenge
|
||||
{
|
||||
// Challenge bytes
|
||||
|
||||
private byte[] m_challenge;
|
||||
|
||||
/**
|
||||
* Class constructor
|
||||
*
|
||||
* @param chbyts byte[]
|
||||
*/
|
||||
protected NTLMChallenge(byte[] chbyts)
|
||||
{
|
||||
m_challenge = chbyts;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the challenge bytes
|
||||
*
|
||||
* @return byte[]
|
||||
*/
|
||||
public final byte[] getBytes()
|
||||
{
|
||||
return m_challenge;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check for object equality
|
||||
*
|
||||
* @param obj Object
|
||||
* @return boolean
|
||||
*/
|
||||
public boolean equals(Object obj)
|
||||
{
|
||||
if ( obj instanceof NTLMChallenge)
|
||||
{
|
||||
NTLMChallenge ntlmCh = (NTLMChallenge) obj;
|
||||
|
||||
// Check if both challenges are null
|
||||
|
||||
if ( getBytes() == null && ntlmCh.getBytes() == null)
|
||||
return true;
|
||||
|
||||
// Check if both challenges are the same length
|
||||
|
||||
if ( getBytes() != null && ntlmCh.getBytes() != null &&
|
||||
getBytes().length == ntlmCh.getBytes().length)
|
||||
{
|
||||
// Check if challenages are the same value
|
||||
|
||||
byte[] ntlmBytes = ntlmCh.getBytes();
|
||||
|
||||
for ( int i = 0; i < m_challenge.length; i++)
|
||||
if ( m_challenge[i] != ntlmBytes[i])
|
||||
return false;
|
||||
}
|
||||
else
|
||||
return false;
|
||||
}
|
||||
|
||||
// Not the same type
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the challenge as a string
|
||||
*
|
||||
* @return String
|
||||
*/
|
||||
public String toString()
|
||||
{
|
||||
StringBuilder str = new StringBuilder();
|
||||
|
||||
str.append("[");
|
||||
str.append(HexDump.hexString(getBytes(), " "));
|
||||
str.append("]");
|
||||
|
||||
return str.toString();
|
||||
}
|
||||
}
|
@@ -0,0 +1,105 @@
|
||||
/*
|
||||
* Copyright (C) 2005 Alfresco, Inc.
|
||||
*
|
||||
* Licensed under the Alfresco Network License. You may obtain a
|
||||
* copy of the License at
|
||||
*
|
||||
* http://www.alfrescosoftware.com/legal/
|
||||
*
|
||||
* Please view the license relevant to your network subscription.
|
||||
*
|
||||
* BY CLICKING THE "I UNDERSTAND AND ACCEPT" BOX, OR INSTALLING,
|
||||
* READING OR USING ALFRESCO'S Network SOFTWARE (THE "SOFTWARE"),
|
||||
* YOU ARE AGREEING ON BEHALF OF THE ENTITY LICENSING THE SOFTWARE
|
||||
* ("COMPANY") THAT COMPANY WILL BE BOUND BY AND IS BECOMING A PARTY TO
|
||||
* THIS ALFRESCO NETWORK AGREEMENT ("AGREEMENT") AND THAT YOU HAVE THE
|
||||
* AUTHORITY TO BIND COMPANY. IF COMPANY DOES NOT AGREE TO ALL OF THE
|
||||
* TERMS OF THIS AGREEMENT, DO NOT SELECT THE "I UNDERSTAND AND AGREE"
|
||||
* BOX AND DO NOT INSTALL THE SOFTWARE OR VIEW THE SOURCE CODE. COMPANY
|
||||
* HAS NOT BECOME A LICENSEE OF, AND IS NOT AUTHORIZED TO USE THE
|
||||
* SOFTWARE UNLESS AND UNTIL IT HAS AGREED TO BE BOUND BY THESE LICENSE
|
||||
* TERMS. THE "EFFECTIVE DATE" FOR THIS AGREEMENT SHALL BE THE DAY YOU
|
||||
* CHECK THE "I UNDERSTAND AND ACCEPT" BOX.
|
||||
*/
|
||||
package org.alfresco.repo.security.authentication.ntlm;
|
||||
|
||||
import net.sf.acegisecurity.GrantedAuthority;
|
||||
import net.sf.acegisecurity.providers.*;
|
||||
|
||||
/**
|
||||
* <p>Used to provide authentication with a remote Windows server when the username and password are
|
||||
* provided locally.
|
||||
*
|
||||
* @author GKSpencer
|
||||
*/
|
||||
public class NTLMLocalToken extends UsernamePasswordAuthenticationToken
|
||||
{
|
||||
private static final long serialVersionUID = -7946514578455279387L;
|
||||
|
||||
/**
|
||||
* Class constructor
|
||||
*/
|
||||
protected NTLMLocalToken()
|
||||
{
|
||||
super(null, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Class constructor
|
||||
*
|
||||
* @param username String
|
||||
* @param plainPwd String
|
||||
*/
|
||||
public NTLMLocalToken(String username, String plainPwd) {
|
||||
super(username.toLowerCase(), plainPwd);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the user logged on as a guest
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public final boolean isGuestLogon()
|
||||
{
|
||||
return hasAuthority(NTLMAuthenticationProvider.NTLMAuthorityGuest);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the user is an administrator
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public final boolean isAdministrator()
|
||||
{
|
||||
return hasAuthority(NTLMAuthenticationProvider.NTLMAuthorityAdministrator);
|
||||
}
|
||||
|
||||
/**
|
||||
* Search for the specified authority
|
||||
*
|
||||
* @param authority String
|
||||
* @return boolean
|
||||
*/
|
||||
public final boolean hasAuthority(String authority)
|
||||
{
|
||||
boolean found = false;
|
||||
GrantedAuthority[] authorities = getAuthorities();
|
||||
|
||||
if ( authorities != null && authorities.length > 0)
|
||||
{
|
||||
// Search for the specified authority
|
||||
|
||||
int i = 0;
|
||||
|
||||
while ( found == false && i < authorities.length)
|
||||
{
|
||||
if ( authorities[i++].getAuthority().equals(authority))
|
||||
found = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Return the status
|
||||
|
||||
return found;
|
||||
}
|
||||
}
|
@@ -0,0 +1,158 @@
|
||||
/*
|
||||
* Copyright (C) 2005 Alfresco, Inc.
|
||||
*
|
||||
* Licensed under the Alfresco Network License. You may obtain a
|
||||
* copy of the License at
|
||||
*
|
||||
* http://www.alfrescosoftware.com/legal/
|
||||
*
|
||||
* Please view the license relevant to your network subscription.
|
||||
*
|
||||
* BY CLICKING THE "I UNDERSTAND AND ACCEPT" BOX, OR INSTALLING,
|
||||
* READING OR USING ALFRESCO'S Network SOFTWARE (THE "SOFTWARE"),
|
||||
* YOU ARE AGREEING ON BEHALF OF THE ENTITY LICENSING THE SOFTWARE
|
||||
* ("COMPANY") THAT COMPANY WILL BE BOUND BY AND IS BECOMING A PARTY TO
|
||||
* THIS ALFRESCO NETWORK AGREEMENT ("AGREEMENT") AND THAT YOU HAVE THE
|
||||
* AUTHORITY TO BIND COMPANY. IF COMPANY DOES NOT AGREE TO ALL OF THE
|
||||
* TERMS OF THIS AGREEMENT, DO NOT SELECT THE "I UNDERSTAND AND AGREE"
|
||||
* BOX AND DO NOT INSTALL THE SOFTWARE OR VIEW THE SOURCE CODE. COMPANY
|
||||
* HAS NOT BECOME A LICENSEE OF, AND IS NOT AUTHORIZED TO USE THE
|
||||
* SOFTWARE UNLESS AND UNTIL IT HAS AGREED TO BE BOUND BY THESE LICENSE
|
||||
* TERMS. THE "EFFECTIVE DATE" FOR THIS AGREEMENT SHALL BE THE DAY YOU
|
||||
* CHECK THE "I UNDERSTAND AND ACCEPT" BOX.
|
||||
*/
|
||||
package org.alfresco.repo.security.authentication.ntlm;
|
||||
|
||||
/**
|
||||
* <p>Used to provide passthru authentication to a remote Windows server using multiple stages that
|
||||
* allows authentication details to be passed between a client and the remote authenticating server without
|
||||
* the password being known by the authentication provider.
|
||||
*
|
||||
* @author GKSpencer
|
||||
*/
|
||||
public class NTLMPassthruToken extends NTLMLocalToken
|
||||
{
|
||||
private static final long serialVersionUID = -4635444888514735368L;
|
||||
|
||||
// Challenge for this session
|
||||
|
||||
private NTLMChallenge m_challenge;
|
||||
|
||||
// User name, hashed password and algorithm type
|
||||
|
||||
private String m_username;
|
||||
private byte[] m_hashedPassword;
|
||||
private int m_hashType;
|
||||
|
||||
// Time that the authentication session will expire
|
||||
|
||||
private long m_authExpiresAt;
|
||||
|
||||
/**
|
||||
* Class constructor
|
||||
*/
|
||||
public NTLMPassthruToken()
|
||||
{
|
||||
// We do not know the username yet, and will not know the password
|
||||
|
||||
super("", "");
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the challenge
|
||||
*
|
||||
* @return NTLMChallenge
|
||||
*/
|
||||
public final NTLMChallenge getChallenge()
|
||||
{
|
||||
return m_challenge;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the user account
|
||||
*
|
||||
* @return Object
|
||||
*/
|
||||
public final Object getPrincipal()
|
||||
{
|
||||
return m_username;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the hashed password
|
||||
*
|
||||
* @return byte[]
|
||||
*/
|
||||
public final byte[] getHashedPassword()
|
||||
{
|
||||
return m_hashedPassword;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the hashed password type
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public final int getPasswordType()
|
||||
{
|
||||
return m_hashType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the authentication expiry time, this will be zero if the authentication session has not yet
|
||||
* been opened to the server
|
||||
*
|
||||
* @return long
|
||||
*/
|
||||
public final long getAuthenticationExpireTime()
|
||||
{
|
||||
return m_authExpiresAt;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the hashed password and type
|
||||
*
|
||||
* @param hashedPassword byte[]
|
||||
* @param hashType int
|
||||
*/
|
||||
public final void setUserAndPassword(String username, byte[] hashedPassword, int hashType)
|
||||
{
|
||||
m_username = username.toLowerCase();
|
||||
m_hashedPassword = hashedPassword;
|
||||
m_hashType = hashType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the challenge for this token
|
||||
*
|
||||
* @param challenge NTLMChallenge
|
||||
*/
|
||||
protected final void setChallenge(NTLMChallenge challenge)
|
||||
{
|
||||
m_challenge = challenge;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the authentication expire time, this indicates that an authentication session is associated with this
|
||||
* token and the session will be closed if the authentication is not completed by this time.
|
||||
*
|
||||
* @param startTime long
|
||||
*/
|
||||
protected final void setAuthenticationExpireTime(long expireTime)
|
||||
{
|
||||
m_authExpiresAt = expireTime;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check for object equality
|
||||
*
|
||||
* @param obj Object
|
||||
* @return boolean
|
||||
*/
|
||||
public boolean equals(Object obj)
|
||||
{
|
||||
// Only match on the same object
|
||||
|
||||
return this == obj;
|
||||
}
|
||||
}
|
@@ -0,0 +1,344 @@
|
||||
/*
|
||||
* Copyright (C) 2005 Alfresco, Inc.
|
||||
*
|
||||
* Licensed under the Alfresco Network License. You may obtain a
|
||||
* copy of the License at
|
||||
*
|
||||
* http://www.alfrescosoftware.com/legal/
|
||||
*
|
||||
* Please view the license relevant to your network subscription.
|
||||
*
|
||||
* BY CLICKING THE "I UNDERSTAND AND ACCEPT" BOX, OR INSTALLING,
|
||||
* READING OR USING ALFRESCO'S Network SOFTWARE (THE "SOFTWARE"),
|
||||
* YOU ARE AGREEING ON BEHALF OF THE ENTITY LICENSING THE SOFTWARE
|
||||
* ("COMPANY") THAT COMPANY WILL BE BOUND BY AND IS BECOMING A PARTY TO
|
||||
* THIS ALFRESCO NETWORK AGREEMENT ("AGREEMENT") AND THAT YOU HAVE THE
|
||||
* AUTHORITY TO BIND COMPANY. IF COMPANY DOES NOT AGREE TO ALL OF THE
|
||||
* TERMS OF THIS AGREEMENT, DO NOT SELECT THE "I UNDERSTAND AND AGREE"
|
||||
* BOX AND DO NOT INSTALL THE SOFTWARE OR VIEW THE SOURCE CODE. COMPANY
|
||||
* HAS NOT BECOME A LICENSEE OF, AND IS NOT AUTHORIZED TO USE THE
|
||||
* SOFTWARE UNLESS AND UNTIL IT HAS AGREED TO BE BOUND BY THESE LICENSE
|
||||
* TERMS. THE "EFFECTIVE DATE" FOR THIS AGREEMENT SHALL BE THE DAY YOU
|
||||
* CHECK THE "I UNDERSTAND AND ACCEPT" BOX.
|
||||
*/
|
||||
package org.alfresco.repo.security.authentication.ntlm;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
import net.sf.acegisecurity.UserDetails;
|
||||
import net.sf.acegisecurity.providers.dao.UsernameNotFoundException;
|
||||
|
||||
import org.alfresco.error.AlfrescoRuntimeException;
|
||||
import org.alfresco.repo.security.authentication.AuthenticationException;
|
||||
import org.alfresco.repo.security.authentication.MutableAuthenticationDao;
|
||||
import org.alfresco.service.cmr.repository.NodeService;
|
||||
import org.springframework.dao.DataAccessException;
|
||||
|
||||
/**
|
||||
* Null Mutable Authentication Dao Class
|
||||
*
|
||||
* <p>Mutable authentication implementation that does nothing.
|
||||
*
|
||||
* @author GKSpencer
|
||||
*/
|
||||
public class NullMutableAuthenticationDao implements MutableAuthenticationDao
|
||||
{
|
||||
/**
|
||||
* Method kept just for backward compatibility with older configurations that
|
||||
* might have been passing in a value.
|
||||
*
|
||||
* @param nodeService ignored
|
||||
*/
|
||||
public void setNodeService(NodeService nodeService)
|
||||
{
|
||||
// do nothing
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a user with the given userName and password
|
||||
*
|
||||
* @param userName
|
||||
* @param rawPassword
|
||||
* @throws AuthenticationException
|
||||
*/
|
||||
public void createUser(String userName, char[] rawPassword) throws AuthenticationException
|
||||
{
|
||||
throw new AlfrescoRuntimeException("Not implemented");
|
||||
|
||||
// Nothing to do
|
||||
}
|
||||
|
||||
/**
|
||||
* Update a user's password.
|
||||
*
|
||||
* @param userName
|
||||
* @param rawPassword
|
||||
* @throws AuthenticationException
|
||||
*/
|
||||
public void updateUser(String userName, char[] rawPassword) throws AuthenticationException
|
||||
{
|
||||
throw new AlfrescoRuntimeException("Not implemented");
|
||||
|
||||
// Nothing to do
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a user.
|
||||
*
|
||||
* @param userName
|
||||
* @throws AuthenticationException
|
||||
*/
|
||||
public void deleteUser(String userName) throws AuthenticationException
|
||||
{
|
||||
throw new AlfrescoRuntimeException("Not implemented");
|
||||
|
||||
// Nothing to do
|
||||
}
|
||||
|
||||
/**
|
||||
* Check is a user exists.
|
||||
*
|
||||
* @param userName
|
||||
* @return
|
||||
*/
|
||||
public boolean userExists(String userName)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Enable/disable a user.
|
||||
*
|
||||
* @param userName
|
||||
* @param enabled
|
||||
*/
|
||||
public void setEnabled(String userName, boolean enabled)
|
||||
{
|
||||
throw new AlfrescoRuntimeException("Not implemented");
|
||||
|
||||
// Nothing to do
|
||||
}
|
||||
|
||||
/**
|
||||
* Getter for user enabled
|
||||
*
|
||||
* @param userName
|
||||
* @return
|
||||
*/
|
||||
public boolean getEnabled(String userName)
|
||||
{
|
||||
throw new AlfrescoRuntimeException("Not implemented");
|
||||
|
||||
// return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set if the account should expire
|
||||
*
|
||||
* @param userName
|
||||
* @param expires
|
||||
*/
|
||||
public void setAccountExpires(String userName, boolean expires)
|
||||
{
|
||||
throw new AlfrescoRuntimeException("Not implemented");
|
||||
|
||||
// Nothing to do
|
||||
}
|
||||
|
||||
/**
|
||||
* Does the account expire?
|
||||
*
|
||||
* @param userName
|
||||
* @return
|
||||
*/
|
||||
|
||||
public boolean getAccountExpires(String userName)
|
||||
{
|
||||
throw new AlfrescoRuntimeException("Not implemented");
|
||||
|
||||
// return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Has the account expired?
|
||||
*
|
||||
* @param userName
|
||||
* @return
|
||||
*/
|
||||
public boolean getAccountHasExpired(String userName)
|
||||
{
|
||||
throw new AlfrescoRuntimeException("Not implemented");
|
||||
|
||||
// return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set if the password expires.
|
||||
*
|
||||
* @param userName
|
||||
* @param expires
|
||||
*/
|
||||
public void setCredentialsExpire(String userName, boolean expires)
|
||||
{
|
||||
throw new AlfrescoRuntimeException("Not implemented");
|
||||
|
||||
// Nothing to do
|
||||
}
|
||||
|
||||
/**
|
||||
* Do the credentials for the user expire?
|
||||
*
|
||||
* @param userName
|
||||
* @return
|
||||
*/
|
||||
public boolean getCredentialsExpire(String userName)
|
||||
{
|
||||
throw new AlfrescoRuntimeException("Not implemented");
|
||||
|
||||
// return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Have the credentials for the user expired?
|
||||
*
|
||||
* @param userName
|
||||
* @return
|
||||
*/
|
||||
public boolean getCredentialsHaveExpired(String userName)
|
||||
{
|
||||
throw new AlfrescoRuntimeException("Not implemented");
|
||||
|
||||
// return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set if the account is locked.
|
||||
*
|
||||
* @param userName
|
||||
* @param locked
|
||||
*/
|
||||
public void setLocked(String userName, boolean locked)
|
||||
{
|
||||
throw new AlfrescoRuntimeException("Not implemented");
|
||||
|
||||
// Nothing to do
|
||||
}
|
||||
|
||||
/**
|
||||
* Is the account locked?
|
||||
*
|
||||
* @param userName
|
||||
* @return
|
||||
*/
|
||||
public boolean getAccountlocked(String userName)
|
||||
{
|
||||
throw new AlfrescoRuntimeException("Not implemented");
|
||||
|
||||
// return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the date on which the account expires
|
||||
*
|
||||
* @param userName
|
||||
* @param exipryDate
|
||||
*/
|
||||
public void setAccountExpiryDate(String userName, Date exipryDate)
|
||||
{
|
||||
throw new AlfrescoRuntimeException("Not implemented");
|
||||
|
||||
// Nothing to do
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the date when this account expires.
|
||||
*
|
||||
* @param userName
|
||||
* @return
|
||||
*/
|
||||
public Date getAccountExpiryDate(String userName)
|
||||
{
|
||||
throw new AlfrescoRuntimeException("Not implemented");
|
||||
|
||||
// return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the date when credentials expire.
|
||||
*
|
||||
* @param userName
|
||||
* @param exipryDate
|
||||
*/
|
||||
public void setCredentialsExpiryDate(String userName, Date exipryDate)
|
||||
{
|
||||
throw new AlfrescoRuntimeException("Not implemented");
|
||||
|
||||
// Nothing to do
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the date when the credentials/password expire.
|
||||
*
|
||||
* @param userName
|
||||
* @return
|
||||
*/
|
||||
public Date getCredentialsExpiryDate(String userName)
|
||||
{
|
||||
throw new AlfrescoRuntimeException("Not implemented");
|
||||
|
||||
// return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the MD4 password hash
|
||||
*
|
||||
* @param userName
|
||||
* @return
|
||||
*/
|
||||
public String getMD4HashedPassword(String userName)
|
||||
{
|
||||
throw new AlfrescoRuntimeException("Not implemented");
|
||||
|
||||
// return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Are user names case sensitive?
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public boolean getUserNamesAreCaseSensitive()
|
||||
{
|
||||
throw new AlfrescoRuntimeException("Not implemented");
|
||||
|
||||
// return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the user details for the specified user
|
||||
*
|
||||
* @param user String
|
||||
* @return UserDetails
|
||||
* @exception UsernameNotFoundException
|
||||
* @exception DataAccessException
|
||||
*/
|
||||
public UserDetails loadUserByUsername(String arg0) throws UsernameNotFoundException, DataAccessException
|
||||
{
|
||||
throw new AlfrescoRuntimeException("Not implemented");
|
||||
|
||||
// return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return salt for user
|
||||
*
|
||||
* @param user UserDetails
|
||||
* @return Object
|
||||
*/
|
||||
public Object getSalt(UserDetails user)
|
||||
{
|
||||
throw new AlfrescoRuntimeException("Not implemented");
|
||||
|
||||
// return null;
|
||||
}
|
||||
}
|
@@ -0,0 +1,98 @@
|
||||
/*
|
||||
* Copyright (C) 2005 Alfresco, Inc.
|
||||
*
|
||||
* Licensed under the Alfresco Network License. You may obtain a
|
||||
* copy of the License at
|
||||
*
|
||||
* http://www.alfrescosoftware.com/legal/
|
||||
*
|
||||
* Please view the license relevant to your network subscription.
|
||||
*
|
||||
* BY CLICKING THE "I UNDERSTAND AND ACCEPT" BOX, OR INSTALLING,
|
||||
* READING OR USING ALFRESCO'S Network SOFTWARE (THE "SOFTWARE"),
|
||||
* YOU ARE AGREEING ON BEHALF OF THE ENTITY LICENSING THE SOFTWARE
|
||||
* ("COMPANY") THAT COMPANY WILL BE BOUND BY AND IS BECOMING A PARTY TO
|
||||
* THIS ALFRESCO NETWORK AGREEMENT ("AGREEMENT") AND THAT YOU HAVE THE
|
||||
* AUTHORITY TO BIND COMPANY. IF COMPANY DOES NOT AGREE TO ALL OF THE
|
||||
* TERMS OF THIS AGREEMENT, DO NOT SELECT THE "I UNDERSTAND AND AGREE"
|
||||
* BOX AND DO NOT INSTALL THE SOFTWARE OR VIEW THE SOURCE CODE. COMPANY
|
||||
* HAS NOT BECOME A LICENSEE OF, AND IS NOT AUTHORIZED TO USE THE
|
||||
* SOFTWARE UNLESS AND UNTIL IT HAS AGREED TO BE BOUND BY THESE LICENSE
|
||||
* TERMS. THE "EFFECTIVE DATE" FOR THIS AGREEMENT SHALL BE THE DAY YOU
|
||||
* CHECK THE "I UNDERSTAND AND ACCEPT" BOX.
|
||||
*/
|
||||
package org.alfresco.repo.security.authority;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
import org.alfresco.service.cmr.security.AuthorityType;
|
||||
|
||||
public interface AuthorityDAO
|
||||
{
|
||||
/**
|
||||
* Add an authority to another.
|
||||
*
|
||||
* @param parentName
|
||||
* @param childName
|
||||
*/
|
||||
void addAuthority(String parentName, String childName);
|
||||
|
||||
/**
|
||||
* Create an authority.
|
||||
*
|
||||
* @param parentName
|
||||
* @param name
|
||||
*/
|
||||
void createAuthority(String parentName, String name);
|
||||
|
||||
/**
|
||||
* Delete an authority.
|
||||
*
|
||||
* @param name
|
||||
*/
|
||||
void deleteAuthority(String name);
|
||||
|
||||
/**
|
||||
* Get all root authorities.
|
||||
*
|
||||
* @param type
|
||||
* @return
|
||||
*/
|
||||
Set<String> getAllRootAuthorities(AuthorityType type);
|
||||
|
||||
/**
|
||||
* Get contained authorities.
|
||||
*
|
||||
* @param type
|
||||
* @param name
|
||||
* @param immediate
|
||||
* @return
|
||||
*/
|
||||
Set<String> getContainedAuthorities(AuthorityType type, String name, boolean immediate);
|
||||
|
||||
/**
|
||||
* Remove an authority.
|
||||
*
|
||||
* @param parentName
|
||||
* @param childName
|
||||
*/
|
||||
void removeAuthority(String parentName, String childName);
|
||||
|
||||
/**
|
||||
* Get the authorities that contain the one given.
|
||||
*
|
||||
* @param type
|
||||
* @param name
|
||||
* @param immediate
|
||||
* @return
|
||||
*/
|
||||
Set<String> getContainingAuthorities(AuthorityType type, String name, boolean immediate);
|
||||
|
||||
/**
|
||||
* Get all authorities by type
|
||||
*
|
||||
* @param type
|
||||
* @return
|
||||
*/
|
||||
Set<String> getAllAuthorities(AuthorityType type);
|
||||
}
|
@@ -0,0 +1,488 @@
|
||||
/*
|
||||
* Copyright (C) 2005 Alfresco, Inc.
|
||||
*
|
||||
* Licensed under the Alfresco Network License. You may obtain a
|
||||
* copy of the License at
|
||||
*
|
||||
* http://www.alfrescosoftware.com/legal/
|
||||
*
|
||||
* Please view the license relevant to your network subscription.
|
||||
*
|
||||
* BY CLICKING THE "I UNDERSTAND AND ACCEPT" BOX, OR INSTALLING,
|
||||
* READING OR USING ALFRESCO'S Network SOFTWARE (THE "SOFTWARE"),
|
||||
* YOU ARE AGREEING ON BEHALF OF THE ENTITY LICENSING THE SOFTWARE
|
||||
* ("COMPANY") THAT COMPANY WILL BE BOUND BY AND IS BECOMING A PARTY TO
|
||||
* THIS ALFRESCO NETWORK AGREEMENT ("AGREEMENT") AND THAT YOU HAVE THE
|
||||
* AUTHORITY TO BIND COMPANY. IF COMPANY DOES NOT AGREE TO ALL OF THE
|
||||
* TERMS OF THIS AGREEMENT, DO NOT SELECT THE "I UNDERSTAND AND AGREE"
|
||||
* BOX AND DO NOT INSTALL THE SOFTWARE OR VIEW THE SOURCE CODE. COMPANY
|
||||
* HAS NOT BECOME A LICENSEE OF, AND IS NOT AUTHORIZED TO USE THE
|
||||
* SOFTWARE UNLESS AND UNTIL IT HAS AGREED TO BE BOUND BY THESE LICENSE
|
||||
* TERMS. THE "EFFECTIVE DATE" FOR THIS AGREEMENT SHALL BE THE DAY YOU
|
||||
* CHECK THE "I UNDERSTAND AND ACCEPT" BOX.
|
||||
*/
|
||||
package org.alfresco.repo.security.authority;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import org.alfresco.error.AlfrescoRuntimeException;
|
||||
import org.alfresco.model.ContentModel;
|
||||
import org.alfresco.repo.cache.SimpleCache;
|
||||
import org.alfresco.repo.search.impl.lucene.QueryParser;
|
||||
import org.alfresco.service.cmr.dictionary.DictionaryService;
|
||||
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.repository.datatype.DefaultTypeConverter;
|
||||
import org.alfresco.service.cmr.search.ResultSet;
|
||||
import org.alfresco.service.cmr.search.ResultSetRow;
|
||||
import org.alfresco.service.cmr.search.SearchParameters;
|
||||
import org.alfresco.service.cmr.search.SearchService;
|
||||
import org.alfresco.service.cmr.security.AuthorityType;
|
||||
import org.alfresco.service.namespace.NamespacePrefixResolver;
|
||||
import org.alfresco.service.namespace.QName;
|
||||
import org.alfresco.service.namespace.RegexQNamePattern;
|
||||
import org.alfresco.util.ISO9075;
|
||||
|
||||
public class AuthorityDAOImpl implements AuthorityDAO
|
||||
{
|
||||
private static final StoreRef STOREREF_USERS = new StoreRef("user", "alfrescoUserStore");
|
||||
|
||||
private NodeService nodeService;
|
||||
private NamespacePrefixResolver namespacePrefixResolver;
|
||||
private QName qnameAssocSystem;
|
||||
private QName qnameAssocAuthorities;
|
||||
private SearchService searchService;
|
||||
private DictionaryService dictionaryService;
|
||||
private SimpleCache<String, ArrayList<NodeRef>> userToAuthorityCache;
|
||||
|
||||
public AuthorityDAOImpl()
|
||||
{
|
||||
super();
|
||||
}
|
||||
|
||||
public void setDictionaryService(DictionaryService dictionaryService)
|
||||
{
|
||||
this.dictionaryService = dictionaryService;
|
||||
}
|
||||
|
||||
public void setNamespacePrefixResolver(NamespacePrefixResolver namespacePrefixResolver)
|
||||
{
|
||||
this.namespacePrefixResolver = namespacePrefixResolver;
|
||||
qnameAssocSystem = QName.createQName("sys", "system", namespacePrefixResolver);
|
||||
qnameAssocAuthorities = QName.createQName("sys", "authorities", namespacePrefixResolver);
|
||||
}
|
||||
|
||||
public void setNodeService(NodeService nodeService)
|
||||
{
|
||||
this.nodeService = nodeService;
|
||||
}
|
||||
|
||||
public void setSearchService(SearchService searchService)
|
||||
{
|
||||
this.searchService = searchService;
|
||||
}
|
||||
|
||||
public void setUserToAuthorityCache(SimpleCache<String, ArrayList<NodeRef>> userToAuthorityCache)
|
||||
{
|
||||
this.userToAuthorityCache = userToAuthorityCache;
|
||||
}
|
||||
|
||||
public void addAuthority(String parentName, String childName)
|
||||
{
|
||||
NodeRef parentRef = getAuthorityOrNull(parentName);
|
||||
if (parentRef == null)
|
||||
{
|
||||
throw new UnknownAuthorityException("An authority was not found for " + parentName);
|
||||
}
|
||||
if (AuthorityType.getAuthorityType(childName).equals(AuthorityType.USER))
|
||||
{
|
||||
Collection<String> memberCollection = DefaultTypeConverter.INSTANCE.getCollection(String.class, nodeService
|
||||
.getProperty(parentRef, ContentModel.PROP_MEMBERS));
|
||||
HashSet<String> members = new HashSet<String>();
|
||||
members.addAll(memberCollection);
|
||||
members.add(childName);
|
||||
nodeService.setProperty(parentRef, ContentModel.PROP_MEMBERS, members);
|
||||
userToAuthorityCache.remove(childName);
|
||||
}
|
||||
else
|
||||
{
|
||||
NodeRef childRef = getAuthorityOrNull(childName);
|
||||
if (childRef == null)
|
||||
{
|
||||
throw new UnknownAuthorityException("An authority was not found for " + childName);
|
||||
}
|
||||
nodeService.addChild(
|
||||
parentRef,
|
||||
childRef,
|
||||
ContentModel.ASSOC_MEMBER,
|
||||
QName.createQName("usr", childName, namespacePrefixResolver));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public void createAuthority(String parentName, String name)
|
||||
{
|
||||
HashMap<QName, Serializable> props = new HashMap<QName, Serializable>();
|
||||
props.put(ContentModel.PROP_AUTHORITY_NAME, name);
|
||||
if (parentName != null)
|
||||
{
|
||||
NodeRef parentRef = getAuthorityOrNull(parentName);
|
||||
if (parentRef == null)
|
||||
{
|
||||
throw new UnknownAuthorityException("An authority was not found for " + parentName);
|
||||
}
|
||||
nodeService.createNode(
|
||||
parentRef,
|
||||
ContentModel.ASSOC_MEMBER,
|
||||
QName.createQName("usr", name, namespacePrefixResolver),
|
||||
ContentModel.TYPE_AUTHORITY_CONTAINER,
|
||||
props);
|
||||
}
|
||||
else
|
||||
{
|
||||
NodeRef authorityContainerRef = getAuthorityContainer();
|
||||
nodeService.createNode(
|
||||
authorityContainerRef,
|
||||
ContentModel.ASSOC_MEMBER,
|
||||
QName.createQName("usr", name, namespacePrefixResolver),
|
||||
ContentModel.TYPE_AUTHORITY_CONTAINER,
|
||||
props);
|
||||
}
|
||||
}
|
||||
|
||||
public void deleteAuthority(String name)
|
||||
{
|
||||
NodeRef nodeRef = getAuthorityOrNull(name);
|
||||
if (nodeRef == null)
|
||||
{
|
||||
throw new UnknownAuthorityException("An authority was not found for " + name);
|
||||
}
|
||||
nodeService.deleteNode(nodeRef);
|
||||
|
||||
}
|
||||
|
||||
public Set<String> getAllRootAuthorities(AuthorityType type)
|
||||
{
|
||||
HashSet<String> authorities = new HashSet<String>();
|
||||
NodeRef container = getAuthorityContainer();
|
||||
if (container != null)
|
||||
{
|
||||
findAuthorities(type, container, authorities, false, false, false);
|
||||
}
|
||||
return authorities;
|
||||
}
|
||||
|
||||
public Set<String> getAllAuthorities(AuthorityType type)
|
||||
{
|
||||
HashSet<String> authorities = new HashSet<String>();
|
||||
NodeRef container = getAuthorityContainer();
|
||||
if (container != null)
|
||||
{
|
||||
findAuthorities(type, container, authorities, false, true, false);
|
||||
}
|
||||
return authorities;
|
||||
}
|
||||
|
||||
public Set<String> getContainedAuthorities(AuthorityType type, String name, boolean immediate)
|
||||
{
|
||||
if (AuthorityType.getAuthorityType(name).equals(AuthorityType.USER))
|
||||
{
|
||||
return Collections.<String> emptySet();
|
||||
}
|
||||
else
|
||||
{
|
||||
NodeRef nodeRef = getAuthorityOrNull(name);
|
||||
if (nodeRef == null)
|
||||
{
|
||||
throw new UnknownAuthorityException("An authority was not found for " + name);
|
||||
}
|
||||
HashSet<String> authorities = new HashSet<String>();
|
||||
findAuthorities(type, nodeRef, authorities, false, !immediate, false);
|
||||
return authorities;
|
||||
}
|
||||
}
|
||||
|
||||
public void removeAuthority(String parentName, String childName)
|
||||
{
|
||||
NodeRef parentRef = getAuthorityOrNull(parentName);
|
||||
if (parentRef == null)
|
||||
{
|
||||
throw new UnknownAuthorityException("An authority was not found for " + parentName);
|
||||
}
|
||||
if (AuthorityType.getAuthorityType(childName).equals(AuthorityType.USER))
|
||||
{
|
||||
Collection<String> memberCollection = DefaultTypeConverter.INSTANCE.getCollection(String.class, nodeService
|
||||
.getProperty(parentRef, ContentModel.PROP_MEMBERS));
|
||||
HashSet<String> members = new HashSet<String>();
|
||||
members.addAll(memberCollection);
|
||||
members.remove(childName);
|
||||
nodeService.setProperty(parentRef, ContentModel.PROP_MEMBERS, members);
|
||||
userToAuthorityCache.remove(childName);
|
||||
}
|
||||
else
|
||||
{
|
||||
NodeRef childRef = getAuthorityOrNull(childName);
|
||||
if (childRef == null)
|
||||
{
|
||||
throw new UnknownAuthorityException("An authority was not found for " + childName);
|
||||
}
|
||||
nodeService.removeChild(parentRef, childRef);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public Set<String> getContainingAuthorities(AuthorityType type, String name, boolean immediate)
|
||||
{
|
||||
HashSet<String> authorities = new HashSet<String>();
|
||||
findAuthorities(type, name, authorities, true, !immediate);
|
||||
return authorities;
|
||||
}
|
||||
|
||||
private void findAuthorities(AuthorityType type, String name, Set<String> authorities, boolean parents,
|
||||
boolean recursive)
|
||||
{
|
||||
if (AuthorityType.getAuthorityType(name).equals(AuthorityType.GUEST))
|
||||
{
|
||||
// Nothing to do
|
||||
}
|
||||
else if (AuthorityType.getAuthorityType(name).equals(AuthorityType.USER))
|
||||
{
|
||||
for (NodeRef ref : getUserContainers(name))
|
||||
{
|
||||
findAuthorities(type, ref, authorities, parents, recursive, true);
|
||||
}
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
NodeRef ref = getAuthorityOrNull(name);
|
||||
|
||||
if (ref == null)
|
||||
{
|
||||
throw new UnknownAuthorityException("An authority was not found for " + name);
|
||||
}
|
||||
|
||||
findAuthorities(type, ref, authorities, parents, recursive, false);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
private ArrayList<NodeRef> getUserContainers(String name)
|
||||
{
|
||||
ArrayList<NodeRef> containers = userToAuthorityCache.get(name);
|
||||
if (containers == null)
|
||||
{
|
||||
containers = findUserContainers(name);
|
||||
userToAuthorityCache.put(name, containers);
|
||||
}
|
||||
return containers;
|
||||
}
|
||||
|
||||
private ArrayList<NodeRef> findUserContainers(String name)
|
||||
{
|
||||
SearchParameters sp = new SearchParameters();
|
||||
sp.addStore(STOREREF_USERS);
|
||||
sp.setLanguage("lucene");
|
||||
sp.setQuery("+TYPE:\""
|
||||
+ ContentModel.TYPE_AUTHORITY_CONTAINER
|
||||
+ "\""
|
||||
+ " +@"
|
||||
+ QueryParser.escape("{"
|
||||
+ ContentModel.PROP_MEMBERS.getNamespaceURI() + "}"
|
||||
+ ISO9075.encode(ContentModel.PROP_MEMBERS.getLocalName())) + ":\"" + name + "\"");
|
||||
ResultSet rs = null;
|
||||
try
|
||||
{
|
||||
rs = searchService.query(sp);
|
||||
ArrayList<NodeRef> answer = new ArrayList<NodeRef>(rs.length());
|
||||
for (ResultSetRow row : rs)
|
||||
{
|
||||
answer.add(row.getNodeRef());
|
||||
}
|
||||
return answer;
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (rs != null)
|
||||
{
|
||||
rs.close();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private void findAuthorities(AuthorityType type, NodeRef nodeRef, Set<String> authorities, boolean parents,
|
||||
boolean recursive, boolean includeNode)
|
||||
{
|
||||
List<ChildAssociationRef> cars = parents ? nodeService.getParentAssocs(nodeRef) : nodeService
|
||||
.getChildAssocs(nodeRef);
|
||||
|
||||
if (includeNode)
|
||||
{
|
||||
String authorityName = DefaultTypeConverter.INSTANCE.convert(String.class, nodeService.getProperty(nodeRef,
|
||||
ContentModel.PROP_AUTHORITY_NAME));
|
||||
if (type == null)
|
||||
{
|
||||
authorities.add(authorityName);
|
||||
}
|
||||
else
|
||||
{
|
||||
AuthorityType authorityType = AuthorityType.getAuthorityType(authorityName);
|
||||
if (authorityType.equals(type))
|
||||
{
|
||||
authorities.add(authorityName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Loop over children
|
||||
for (ChildAssociationRef car : cars)
|
||||
{
|
||||
NodeRef current = parents ? car.getParentRef() : car.getChildRef();
|
||||
QName currentType = nodeService.getType(current);
|
||||
if (dictionaryService.isSubClass(currentType, ContentModel.TYPE_AUTHORITY))
|
||||
{
|
||||
|
||||
String authorityName = DefaultTypeConverter.INSTANCE.convert(String.class, nodeService.getProperty(
|
||||
current, ContentModel.PROP_AUTHORITY_NAME));
|
||||
|
||||
if (type == null)
|
||||
{
|
||||
authorities.add(authorityName);
|
||||
if (recursive)
|
||||
{
|
||||
findAuthorities(type, current, authorities, parents, recursive, false);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
AuthorityType authorityType = AuthorityType.getAuthorityType(authorityName);
|
||||
if (authorityType.equals(type))
|
||||
{
|
||||
authorities.add(authorityName);
|
||||
}
|
||||
if (recursive)
|
||||
{
|
||||
findAuthorities(type, current, authorities, parents, recursive, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// loop over properties
|
||||
if (!parents)
|
||||
{
|
||||
Collection<String> members = DefaultTypeConverter.INSTANCE.getCollection(String.class, nodeService
|
||||
.getProperty(nodeRef, ContentModel.PROP_MEMBERS));
|
||||
if (members != null)
|
||||
{
|
||||
for (String user : members)
|
||||
{
|
||||
if (user != null)
|
||||
{
|
||||
if (type == null)
|
||||
{
|
||||
authorities.add(user);
|
||||
}
|
||||
else
|
||||
{
|
||||
AuthorityType authorityType = AuthorityType.getAuthorityType(user);
|
||||
if (authorityType.equals(type))
|
||||
{
|
||||
authorities.add(user);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private NodeRef getAuthorityOrNull(String name)
|
||||
{
|
||||
SearchParameters sp = new SearchParameters();
|
||||
sp.addStore(STOREREF_USERS);
|
||||
sp.setLanguage("lucene");
|
||||
sp.setQuery("+TYPE:\""
|
||||
+ ContentModel.TYPE_AUTHORITY_CONTAINER
|
||||
+ "\""
|
||||
+ " +@"
|
||||
+ QueryParser.escape("{"
|
||||
+ ContentModel.PROP_AUTHORITY_NAME.getNamespaceURI() + "}"
|
||||
+ ISO9075.encode(ContentModel.PROP_AUTHORITY_NAME.getLocalName())) + ":\"" + name + "\"");
|
||||
ResultSet rs = null;
|
||||
try
|
||||
{
|
||||
rs = searchService.query(sp);
|
||||
if (rs.length() == 0)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
else
|
||||
{
|
||||
for (ResultSetRow row : rs)
|
||||
{
|
||||
String test = DefaultTypeConverter.INSTANCE.convert(
|
||||
String.class,
|
||||
nodeService.getProperty(row.getNodeRef(), ContentModel.PROP_AUTHORITY_NAME));
|
||||
if (test.equals(name))
|
||||
{
|
||||
return row.getNodeRef();
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (rs != null)
|
||||
{
|
||||
rs.close();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Returns the authority container, <b>which must exist</b>
|
||||
*/
|
||||
private NodeRef getAuthorityContainer()
|
||||
{
|
||||
NodeRef rootNodeRef = nodeService.getRootNode(STOREREF_USERS);
|
||||
List<ChildAssociationRef> results = nodeService.getChildAssocs(
|
||||
rootNodeRef,
|
||||
RegexQNamePattern.MATCH_ALL,
|
||||
qnameAssocSystem);
|
||||
NodeRef sysNodeRef = null;
|
||||
if (results.size() == 0)
|
||||
{
|
||||
throw new AlfrescoRuntimeException("Required authority system path not found: " + qnameAssocSystem);
|
||||
}
|
||||
else
|
||||
{
|
||||
sysNodeRef = results.get(0).getChildRef();
|
||||
}
|
||||
results = nodeService.getChildAssocs(
|
||||
sysNodeRef,
|
||||
RegexQNamePattern.MATCH_ALL,
|
||||
qnameAssocAuthorities);
|
||||
NodeRef authNodeRef = null;
|
||||
if (results.size() == 0)
|
||||
{
|
||||
throw new AlfrescoRuntimeException("Required authority path not found: " + qnameAssocAuthorities);
|
||||
}
|
||||
else
|
||||
{
|
||||
authNodeRef = results.get(0).getChildRef();
|
||||
}
|
||||
return authNodeRef;
|
||||
}
|
||||
}
|
@@ -0,0 +1,56 @@
|
||||
/*
|
||||
* Copyright (C) 2005 Alfresco, Inc.
|
||||
*
|
||||
* Licensed under the Alfresco Network License. You may obtain a
|
||||
* copy of the License at
|
||||
*
|
||||
* http://www.alfrescosoftware.com/legal/
|
||||
*
|
||||
* Please view the license relevant to your network subscription.
|
||||
*
|
||||
* BY CLICKING THE "I UNDERSTAND AND ACCEPT" BOX, OR INSTALLING,
|
||||
* READING OR USING ALFRESCO'S Network SOFTWARE (THE "SOFTWARE"),
|
||||
* YOU ARE AGREEING ON BEHALF OF THE ENTITY LICENSING THE SOFTWARE
|
||||
* ("COMPANY") THAT COMPANY WILL BE BOUND BY AND IS BECOMING A PARTY TO
|
||||
* THIS ALFRESCO NETWORK AGREEMENT ("AGREEMENT") AND THAT YOU HAVE THE
|
||||
* AUTHORITY TO BIND COMPANY. IF COMPANY DOES NOT AGREE TO ALL OF THE
|
||||
* TERMS OF THIS AGREEMENT, DO NOT SELECT THE "I UNDERSTAND AND AGREE"
|
||||
* BOX AND DO NOT INSTALL THE SOFTWARE OR VIEW THE SOURCE CODE. COMPANY
|
||||
* HAS NOT BECOME A LICENSEE OF, AND IS NOT AUTHORIZED TO USE THE
|
||||
* SOFTWARE UNLESS AND UNTIL IT HAS AGREED TO BE BOUND BY THESE LICENSE
|
||||
* TERMS. THE "EFFECTIVE DATE" FOR THIS AGREEMENT SHALL BE THE DAY YOU
|
||||
* CHECK THE "I UNDERSTAND AND ACCEPT" BOX.
|
||||
*/
|
||||
package org.alfresco.repo.security.authority;
|
||||
|
||||
import org.alfresco.error.AlfrescoRuntimeException;
|
||||
|
||||
public class AuthorityException extends AlfrescoRuntimeException
|
||||
{
|
||||
|
||||
/**
|
||||
* Comment for <code>serialVersionUID</code>
|
||||
*/
|
||||
private static final long serialVersionUID = -5367993045129604445L;
|
||||
|
||||
public AuthorityException(String msgId)
|
||||
{
|
||||
super(msgId);
|
||||
}
|
||||
|
||||
public AuthorityException(String msgId, Object[] msgParams)
|
||||
{
|
||||
super(msgId, msgParams);
|
||||
}
|
||||
|
||||
public AuthorityException(String msgId, Throwable cause)
|
||||
{
|
||||
super(msgId, cause);
|
||||
}
|
||||
|
||||
public AuthorityException(String msgId, Object[] msgParams, Throwable cause)
|
||||
{
|
||||
super(msgId, msgParams, cause);
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,251 @@
|
||||
/*
|
||||
* Copyright (C) 2005 Alfresco, Inc.
|
||||
*
|
||||
* Licensed under the Alfresco Network License. You may obtain a
|
||||
* copy of the License at
|
||||
*
|
||||
* http://www.alfrescosoftware.com/legal/
|
||||
*
|
||||
* Please view the license relevant to your network subscription.
|
||||
*
|
||||
* BY CLICKING THE "I UNDERSTAND AND ACCEPT" BOX, OR INSTALLING,
|
||||
* READING OR USING ALFRESCO'S Network SOFTWARE (THE "SOFTWARE"),
|
||||
* YOU ARE AGREEING ON BEHALF OF THE ENTITY LICENSING THE SOFTWARE
|
||||
* ("COMPANY") THAT COMPANY WILL BE BOUND BY AND IS BECOMING A PARTY TO
|
||||
* THIS ALFRESCO NETWORK AGREEMENT ("AGREEMENT") AND THAT YOU HAVE THE
|
||||
* AUTHORITY TO BIND COMPANY. IF COMPANY DOES NOT AGREE TO ALL OF THE
|
||||
* TERMS OF THIS AGREEMENT, DO NOT SELECT THE "I UNDERSTAND AND AGREE"
|
||||
* BOX AND DO NOT INSTALL THE SOFTWARE OR VIEW THE SOURCE CODE. COMPANY
|
||||
* HAS NOT BECOME A LICENSEE OF, AND IS NOT AUTHORIZED TO USE THE
|
||||
* SOFTWARE UNLESS AND UNTIL IT HAS AGREED TO BE BOUND BY THESE LICENSE
|
||||
* TERMS. THE "EFFECTIVE DATE" FOR THIS AGREEMENT SHALL BE THE DAY YOU
|
||||
* CHECK THE "I UNDERSTAND AND ACCEPT" BOX.
|
||||
*/
|
||||
package org.alfresco.repo.security.authority;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import org.alfresco.model.ContentModel;
|
||||
import org.alfresco.repo.security.authentication.AuthenticationComponent;
|
||||
import org.alfresco.repo.security.permissions.PermissionServiceSPI;
|
||||
import org.alfresco.service.cmr.repository.NodeRef;
|
||||
import org.alfresco.service.cmr.repository.NodeService;
|
||||
import org.alfresco.service.cmr.repository.datatype.DefaultTypeConverter;
|
||||
import org.alfresco.service.cmr.security.AuthorityService;
|
||||
import org.alfresco.service.cmr.security.AuthorityType;
|
||||
import org.alfresco.service.cmr.security.PermissionService;
|
||||
import org.alfresco.service.cmr.security.PersonService;
|
||||
|
||||
/**
|
||||
* The default implementation of the authority service.
|
||||
*
|
||||
* @author Andy Hind
|
||||
*/
|
||||
public class AuthorityServiceImpl implements AuthorityService
|
||||
{
|
||||
private PersonService personService;
|
||||
|
||||
private NodeService nodeService;
|
||||
|
||||
private AuthorityDAO authorityDAO;
|
||||
|
||||
private PermissionServiceSPI permissionServiceSPI;
|
||||
|
||||
private Set<String> adminSet = Collections.singleton(PermissionService.ADMINISTRATOR_AUTHORITY);
|
||||
|
||||
private Set<String> guestSet = Collections.singleton(PermissionService.GUEST_AUTHORITY);
|
||||
|
||||
private Set<String> allSet = Collections.singleton(PermissionService.ALL_AUTHORITIES);
|
||||
|
||||
private Set<String> adminUsers;
|
||||
|
||||
private AuthenticationComponent authenticationComponent;
|
||||
|
||||
public AuthorityServiceImpl()
|
||||
{
|
||||
super();
|
||||
}
|
||||
|
||||
public void setNodeService(NodeService nodeService)
|
||||
{
|
||||
this.nodeService = nodeService;
|
||||
}
|
||||
|
||||
public void setPersonService(PersonService personService)
|
||||
{
|
||||
this.personService = personService;
|
||||
}
|
||||
|
||||
public void setAuthorityDAO(AuthorityDAO authorityDAO)
|
||||
{
|
||||
this.authorityDAO = authorityDAO;
|
||||
}
|
||||
|
||||
public void setPermissionServiceSPI(PermissionServiceSPI permissionServiceSPI)
|
||||
{
|
||||
this.permissionServiceSPI = permissionServiceSPI;
|
||||
}
|
||||
|
||||
/**
|
||||
* Currently the admin authority is granted only to the ALFRESCO_ADMIN_USER
|
||||
* user.
|
||||
*/
|
||||
public boolean hasAdminAuthority()
|
||||
{
|
||||
String currentUserName = authenticationComponent.getCurrentUserName();
|
||||
return ((currentUserName != null) && adminUsers.contains(currentUserName));
|
||||
}
|
||||
|
||||
// IOC
|
||||
|
||||
public void setAuthenticationComponent(AuthenticationComponent authenticationComponent)
|
||||
{
|
||||
this.authenticationComponent = authenticationComponent;
|
||||
}
|
||||
|
||||
public void setAdminUsers(Set<String> adminUsers)
|
||||
{
|
||||
this.adminUsers = adminUsers;
|
||||
}
|
||||
|
||||
public Set<String> getAuthorities()
|
||||
{
|
||||
Set<String> authorities = new HashSet<String>();
|
||||
String currentUserName = authenticationComponent.getCurrentUserName();
|
||||
if (adminUsers.contains(currentUserName))
|
||||
{
|
||||
authorities.addAll(adminSet);
|
||||
}
|
||||
if(AuthorityType.getAuthorityType(currentUserName) != AuthorityType.GUEST)
|
||||
{
|
||||
authorities.addAll(allSet);
|
||||
}
|
||||
authorities.addAll(getContainingAuthorities(null, currentUserName, false));
|
||||
return authorities;
|
||||
}
|
||||
|
||||
public Set<String> getAllAuthorities(AuthorityType type)
|
||||
{
|
||||
Set<String> authorities = new HashSet<String>();
|
||||
switch (type)
|
||||
{
|
||||
case ADMIN:
|
||||
authorities.addAll(adminSet);
|
||||
break;
|
||||
case EVERYONE:
|
||||
authorities.addAll(allSet);
|
||||
break;
|
||||
case GUEST:
|
||||
authorities.addAll(guestSet);
|
||||
break;
|
||||
case GROUP:
|
||||
authorities.addAll(authorityDAO.getAllAuthorities(type));
|
||||
break;
|
||||
case OWNER:
|
||||
break;
|
||||
case ROLE:
|
||||
authorities.addAll(authorityDAO.getAllAuthorities(type));
|
||||
break;
|
||||
case USER:
|
||||
for (NodeRef personRef : personService.getAllPeople())
|
||||
{
|
||||
authorities.add(DefaultTypeConverter.INSTANCE.convert(String.class, nodeService.getProperty(personRef,
|
||||
ContentModel.PROP_USERNAME)));
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return authorities;
|
||||
}
|
||||
|
||||
public void addAuthority(String parentName, String childName)
|
||||
{
|
||||
authorityDAO.addAuthority(parentName, childName);
|
||||
}
|
||||
|
||||
private void checkTypeIsMutable(AuthorityType type)
|
||||
{
|
||||
if((type == AuthorityType.GROUP) || (type == AuthorityType.ROLE))
|
||||
{
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new AuthorityException("Trying to modify a fixed authority");
|
||||
}
|
||||
}
|
||||
|
||||
public String createAuthority(AuthorityType type, String parentName, String shortName)
|
||||
{
|
||||
checkTypeIsMutable(type);
|
||||
String name = getName(type, shortName);
|
||||
authorityDAO.createAuthority(parentName, name);
|
||||
return name;
|
||||
}
|
||||
|
||||
public void deleteAuthority(String name)
|
||||
{
|
||||
AuthorityType type = AuthorityType.getAuthorityType(name);
|
||||
checkTypeIsMutable(type);
|
||||
authorityDAO.deleteAuthority(name);
|
||||
permissionServiceSPI.deletePermissions(name);
|
||||
}
|
||||
|
||||
public Set<String> getAllRootAuthorities(AuthorityType type)
|
||||
{
|
||||
return authorityDAO.getAllRootAuthorities(type);
|
||||
}
|
||||
|
||||
public Set<String> getContainedAuthorities(AuthorityType type, String name, boolean immediate)
|
||||
{
|
||||
return authorityDAO.getContainedAuthorities(type, name, immediate);
|
||||
}
|
||||
|
||||
public Set<String> getContainingAuthorities(AuthorityType type, String name, boolean immediate)
|
||||
{
|
||||
return authorityDAO.getContainingAuthorities(type, name, immediate);
|
||||
}
|
||||
|
||||
public String getName(AuthorityType type, String shortName)
|
||||
{
|
||||
if (type.isFixedString())
|
||||
{
|
||||
return type.getFixedString();
|
||||
}
|
||||
else if (type.isPrefixed())
|
||||
{
|
||||
return type.getPrefixString() + shortName;
|
||||
}
|
||||
else
|
||||
{
|
||||
return shortName;
|
||||
}
|
||||
}
|
||||
|
||||
public String getShortName(String name)
|
||||
{
|
||||
AuthorityType type = AuthorityType.getAuthorityType(name);
|
||||
if (type.isFixedString())
|
||||
{
|
||||
return "";
|
||||
}
|
||||
else if (type.isPrefixed())
|
||||
{
|
||||
return name.substring(type.getPrefixString().length());
|
||||
}
|
||||
else
|
||||
{
|
||||
return name;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public void removeAuthority(String parentName, String childName)
|
||||
{
|
||||
authorityDAO.removeAuthority(parentName, childName);
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,517 @@
|
||||
/*
|
||||
* Copyright (C) 2005 Alfresco, Inc.
|
||||
*
|
||||
* Licensed under the Alfresco Network License. You may obtain a
|
||||
* copy of the License at
|
||||
*
|
||||
* http://www.alfrescosoftware.com/legal/
|
||||
*
|
||||
* Please view the license relevant to your network subscription.
|
||||
*
|
||||
* BY CLICKING THE "I UNDERSTAND AND ACCEPT" BOX, OR INSTALLING,
|
||||
* READING OR USING ALFRESCO'S Network SOFTWARE (THE "SOFTWARE"),
|
||||
* YOU ARE AGREEING ON BEHALF OF THE ENTITY LICENSING THE SOFTWARE
|
||||
* ("COMPANY") THAT COMPANY WILL BE BOUND BY AND IS BECOMING A PARTY TO
|
||||
* THIS ALFRESCO NETWORK AGREEMENT ("AGREEMENT") AND THAT YOU HAVE THE
|
||||
* AUTHORITY TO BIND COMPANY. IF COMPANY DOES NOT AGREE TO ALL OF THE
|
||||
* TERMS OF THIS AGREEMENT, DO NOT SELECT THE "I UNDERSTAND AND AGREE"
|
||||
* BOX AND DO NOT INSTALL THE SOFTWARE OR VIEW THE SOURCE CODE. COMPANY
|
||||
* HAS NOT BECOME A LICENSEE OF, AND IS NOT AUTHORIZED TO USE THE
|
||||
* SOFTWARE UNLESS AND UNTIL IT HAS AGREED TO BE BOUND BY THESE LICENSE
|
||||
* TERMS. THE "EFFECTIVE DATE" FOR THIS AGREEMENT SHALL BE THE DAY YOU
|
||||
* CHECK THE "I UNDERSTAND AND ACCEPT" BOX.
|
||||
*/
|
||||
package org.alfresco.repo.security.authority;
|
||||
|
||||
import javax.transaction.UserTransaction;
|
||||
|
||||
import junit.framework.TestCase;
|
||||
|
||||
import org.alfresco.repo.security.authentication.AuthenticationComponent;
|
||||
import org.alfresco.repo.security.authentication.MutableAuthenticationDao;
|
||||
import org.alfresco.service.ServiceRegistry;
|
||||
import org.alfresco.service.cmr.security.AuthenticationService;
|
||||
import org.alfresco.service.cmr.security.AuthorityService;
|
||||
import org.alfresco.service.cmr.security.AuthorityType;
|
||||
import org.alfresco.service.cmr.security.PermissionService;
|
||||
import org.alfresco.service.cmr.security.PersonService;
|
||||
import org.alfresco.service.transaction.TransactionService;
|
||||
import org.alfresco.util.ApplicationContextHelper;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
|
||||
public class AuthorityServiceTest extends TestCase
|
||||
{
|
||||
private static ApplicationContext ctx = ApplicationContextHelper.getApplicationContext();
|
||||
|
||||
private AuthenticationComponent authenticationComponent;
|
||||
|
||||
private AuthenticationComponent authenticationComponentImpl;
|
||||
|
||||
private AuthenticationService authenticationService;
|
||||
|
||||
private MutableAuthenticationDao authenticationDAO;
|
||||
|
||||
private AuthorityService authorityService;
|
||||
|
||||
private AuthorityService pubAuthorityService;
|
||||
|
||||
private PersonService personService;
|
||||
|
||||
private UserTransaction tx;
|
||||
|
||||
public AuthorityServiceTest()
|
||||
{
|
||||
super();
|
||||
|
||||
}
|
||||
|
||||
public void setUp() throws Exception
|
||||
{
|
||||
authenticationComponent = (AuthenticationComponent) ctx.getBean("authenticationComponent");
|
||||
authenticationComponentImpl = (AuthenticationComponent) ctx.getBean("authenticationComponentImpl");
|
||||
authenticationService = (AuthenticationService) ctx.getBean("authenticationService");
|
||||
authorityService = (AuthorityService) ctx.getBean("authorityService");
|
||||
pubAuthorityService = (AuthorityService) ctx.getBean("AuthorityService");
|
||||
personService = (PersonService) ctx.getBean("personService");
|
||||
authenticationDAO = (MutableAuthenticationDao) ctx.getBean("authenticationDao");
|
||||
|
||||
authenticationComponentImpl.setSystemUserAsCurrentUser();
|
||||
|
||||
TransactionService transactionService = (TransactionService) ctx.getBean(ServiceRegistry.TRANSACTION_SERVICE
|
||||
.getLocalName());
|
||||
tx = transactionService.getUserTransaction();
|
||||
tx.begin();
|
||||
|
||||
if (!authenticationDAO.userExists("andy"))
|
||||
{
|
||||
authenticationService.createAuthentication("andy", "andy".toCharArray());
|
||||
}
|
||||
|
||||
if (!authenticationDAO.userExists("admin"))
|
||||
{
|
||||
authenticationService.createAuthentication("admin", "admin".toCharArray());
|
||||
}
|
||||
|
||||
if (!authenticationDAO.userExists("administrator"))
|
||||
{
|
||||
authenticationService.createAuthentication("administrator", "administrator".toCharArray());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void tearDown() throws Exception
|
||||
{
|
||||
authenticationComponentImpl.clearCurrentSecurityContext();
|
||||
tx.rollback();
|
||||
super.tearDown();
|
||||
}
|
||||
|
||||
public void testNonAdminUser()
|
||||
{
|
||||
authenticationComponent.setCurrentUser("andy");
|
||||
assertFalse(authorityService.hasAdminAuthority());
|
||||
assertFalse(pubAuthorityService.hasAdminAuthority());
|
||||
assertEquals(1, authorityService.getAuthorities().size());
|
||||
}
|
||||
|
||||
public void testAdminUser()
|
||||
{
|
||||
authenticationComponent.setCurrentUser("admin");
|
||||
assertTrue(authorityService.hasAdminAuthority());
|
||||
assertTrue(pubAuthorityService.hasAdminAuthority());
|
||||
assertEquals(2, authorityService.getAuthorities().size());
|
||||
|
||||
authenticationComponent.setCurrentUser("administrator");
|
||||
assertTrue(authorityService.hasAdminAuthority());
|
||||
assertTrue(pubAuthorityService.hasAdminAuthority());
|
||||
assertEquals(2, authorityService.getAuthorities().size());
|
||||
}
|
||||
|
||||
public void testAuthorities()
|
||||
{
|
||||
assertEquals(1, pubAuthorityService.getAllAuthorities(AuthorityType.ADMIN).size());
|
||||
assertTrue(pubAuthorityService.getAllAuthorities(AuthorityType.ADMIN).contains(
|
||||
PermissionService.ADMINISTRATOR_AUTHORITY));
|
||||
assertEquals(1, pubAuthorityService.getAllAuthorities(AuthorityType.EVERYONE).size());
|
||||
assertTrue(pubAuthorityService.getAllAuthorities(AuthorityType.EVERYONE).contains(
|
||||
PermissionService.ALL_AUTHORITIES));
|
||||
assertEquals(0, pubAuthorityService.getAllAuthorities(AuthorityType.GROUP).size());
|
||||
assertFalse(pubAuthorityService.getAllAuthorities(AuthorityType.GROUP).contains(
|
||||
PermissionService.ALL_AUTHORITIES));
|
||||
assertEquals(1, pubAuthorityService.getAllAuthorities(AuthorityType.GUEST).size());
|
||||
assertTrue(pubAuthorityService.getAllAuthorities(AuthorityType.GUEST).contains(PermissionService.GUEST_AUTHORITY));
|
||||
assertEquals(0, pubAuthorityService.getAllAuthorities(AuthorityType.OWNER).size());
|
||||
assertEquals(0, pubAuthorityService.getAllAuthorities(AuthorityType.ROLE).size());
|
||||
assertEquals(personService.getAllPeople().size(), pubAuthorityService.getAllAuthorities(AuthorityType.USER)
|
||||
.size());
|
||||
|
||||
}
|
||||
|
||||
public void testCreateAdminAuth()
|
||||
{
|
||||
try
|
||||
{
|
||||
pubAuthorityService.createAuthority(AuthorityType.ADMIN, null, "woof");
|
||||
fail("Should not be able to create an admin authority");
|
||||
}
|
||||
catch (AuthorityException ae)
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
public void testCreateEveryoneAuth()
|
||||
{
|
||||
try
|
||||
{
|
||||
pubAuthorityService.createAuthority(AuthorityType.EVERYONE, null, "woof");
|
||||
fail("Should not be able to create an everyone authority");
|
||||
}
|
||||
catch (AuthorityException ae)
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
public void testCreateGuestAuth()
|
||||
{
|
||||
try
|
||||
{
|
||||
pubAuthorityService.createAuthority(AuthorityType.GUEST, null, "woof");
|
||||
fail("Should not be able to create an guest authority");
|
||||
}
|
||||
catch (AuthorityException ae)
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
public void testCreateOwnerAuth()
|
||||
{
|
||||
try
|
||||
{
|
||||
pubAuthorityService.createAuthority(AuthorityType.OWNER, null, "woof");
|
||||
fail("Should not be able to create an owner authority");
|
||||
}
|
||||
catch (AuthorityException ae)
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
public void testCreateUserAuth()
|
||||
{
|
||||
try
|
||||
{
|
||||
pubAuthorityService.createAuthority(AuthorityType.USER, null, "woof");
|
||||
fail("Should not be able to create an user authority");
|
||||
}
|
||||
catch (AuthorityException ae)
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
public void testCreateRootAuth()
|
||||
{
|
||||
String auth;
|
||||
|
||||
assertEquals(0, pubAuthorityService.getAllAuthorities(AuthorityType.GROUP).size());
|
||||
assertEquals(0, pubAuthorityService.getAllRootAuthorities(AuthorityType.GROUP).size());
|
||||
auth = pubAuthorityService.createAuthority(AuthorityType.GROUP, null, "woof");
|
||||
assertEquals(1, pubAuthorityService.getAllAuthorities(AuthorityType.GROUP).size());
|
||||
assertEquals(1, pubAuthorityService.getAllRootAuthorities(AuthorityType.GROUP).size());
|
||||
pubAuthorityService.deleteAuthority(auth);
|
||||
assertEquals(0, pubAuthorityService.getAllAuthorities(AuthorityType.GROUP).size());
|
||||
assertEquals(0, pubAuthorityService.getAllRootAuthorities(AuthorityType.GROUP).size());
|
||||
|
||||
assertEquals(0, pubAuthorityService.getAllAuthorities(AuthorityType.ROLE).size());
|
||||
assertEquals(0, pubAuthorityService.getAllRootAuthorities(AuthorityType.ROLE).size());
|
||||
auth = pubAuthorityService.createAuthority(AuthorityType.ROLE, null, "woof");
|
||||
assertEquals(1, pubAuthorityService.getAllAuthorities(AuthorityType.ROLE).size());
|
||||
assertEquals(1, pubAuthorityService.getAllRootAuthorities(AuthorityType.ROLE).size());
|
||||
pubAuthorityService.deleteAuthority(auth);
|
||||
assertEquals(0, pubAuthorityService.getAllAuthorities(AuthorityType.ROLE).size());
|
||||
assertEquals(0, pubAuthorityService.getAllRootAuthorities(AuthorityType.ROLE).size());
|
||||
}
|
||||
|
||||
public void testCreateAuth()
|
||||
{
|
||||
String auth1;
|
||||
String auth2;
|
||||
String auth3;
|
||||
String auth4;
|
||||
String auth5;
|
||||
|
||||
assertEquals(0, pubAuthorityService.getAllAuthorities(AuthorityType.GROUP).size());
|
||||
assertEquals(0, pubAuthorityService.getAllRootAuthorities(AuthorityType.GROUP).size());
|
||||
auth1 = pubAuthorityService.createAuthority(AuthorityType.GROUP, null, "one");
|
||||
assertEquals(1, pubAuthorityService.getAllAuthorities(AuthorityType.GROUP).size());
|
||||
assertEquals(1, pubAuthorityService.getAllRootAuthorities(AuthorityType.GROUP).size());
|
||||
auth2 = pubAuthorityService.createAuthority(AuthorityType.GROUP, null, "two");
|
||||
assertEquals(2, pubAuthorityService.getAllAuthorities(AuthorityType.GROUP).size());
|
||||
assertEquals(2, pubAuthorityService.getAllRootAuthorities(AuthorityType.GROUP).size());
|
||||
auth3 = pubAuthorityService.createAuthority(AuthorityType.GROUP, auth1, "three");
|
||||
assertEquals(3, pubAuthorityService.getAllAuthorities(AuthorityType.GROUP).size());
|
||||
assertEquals(2, pubAuthorityService.getAllRootAuthorities(AuthorityType.GROUP).size());
|
||||
auth4 = pubAuthorityService.createAuthority(AuthorityType.GROUP, auth1, "four");
|
||||
assertEquals(4, pubAuthorityService.getAllAuthorities(AuthorityType.GROUP).size());
|
||||
assertEquals(2, pubAuthorityService.getAllRootAuthorities(AuthorityType.GROUP).size());
|
||||
auth5 = pubAuthorityService.createAuthority(AuthorityType.GROUP, auth2, "five");
|
||||
assertEquals(5, pubAuthorityService.getAllAuthorities(AuthorityType.GROUP).size());
|
||||
assertEquals(2, pubAuthorityService.getAllRootAuthorities(AuthorityType.GROUP).size());
|
||||
|
||||
pubAuthorityService.deleteAuthority(auth5);
|
||||
assertEquals(4, pubAuthorityService.getAllAuthorities(AuthorityType.GROUP).size());
|
||||
assertEquals(2, pubAuthorityService.getAllRootAuthorities(AuthorityType.GROUP).size());
|
||||
pubAuthorityService.deleteAuthority(auth4);
|
||||
assertEquals(3, pubAuthorityService.getAllAuthorities(AuthorityType.GROUP).size());
|
||||
assertEquals(2, pubAuthorityService.getAllRootAuthorities(AuthorityType.GROUP).size());
|
||||
pubAuthorityService.deleteAuthority(auth3);
|
||||
assertEquals(2, pubAuthorityService.getAllAuthorities(AuthorityType.GROUP).size());
|
||||
assertEquals(2, pubAuthorityService.getAllRootAuthorities(AuthorityType.GROUP).size());
|
||||
pubAuthorityService.deleteAuthority(auth2);
|
||||
assertEquals(1, pubAuthorityService.getAllAuthorities(AuthorityType.GROUP).size());
|
||||
assertEquals(1, pubAuthorityService.getAllRootAuthorities(AuthorityType.GROUP).size());
|
||||
pubAuthorityService.deleteAuthority(auth1);
|
||||
assertEquals(0, pubAuthorityService.getAllAuthorities(AuthorityType.GROUP).size());
|
||||
assertEquals(0, pubAuthorityService.getAllRootAuthorities(AuthorityType.GROUP).size());
|
||||
|
||||
assertEquals(0, pubAuthorityService.getAllAuthorities(AuthorityType.ROLE).size());
|
||||
assertEquals(0, pubAuthorityService.getAllRootAuthorities(AuthorityType.ROLE).size());
|
||||
auth1 = pubAuthorityService.createAuthority(AuthorityType.ROLE, null, "one");
|
||||
assertEquals(1, pubAuthorityService.getAllAuthorities(AuthorityType.ROLE).size());
|
||||
assertEquals(1, pubAuthorityService.getAllRootAuthorities(AuthorityType.ROLE).size());
|
||||
auth2 = pubAuthorityService.createAuthority(AuthorityType.ROLE, null, "two");
|
||||
assertEquals(2, pubAuthorityService.getAllAuthorities(AuthorityType.ROLE).size());
|
||||
assertEquals(2, pubAuthorityService.getAllRootAuthorities(AuthorityType.ROLE).size());
|
||||
auth3 = pubAuthorityService.createAuthority(AuthorityType.ROLE, auth1, "three");
|
||||
assertEquals(3, pubAuthorityService.getAllAuthorities(AuthorityType.ROLE).size());
|
||||
assertEquals(2, pubAuthorityService.getAllRootAuthorities(AuthorityType.ROLE).size());
|
||||
auth4 = pubAuthorityService.createAuthority(AuthorityType.ROLE, auth1, "four");
|
||||
assertEquals(4, pubAuthorityService.getAllAuthorities(AuthorityType.ROLE).size());
|
||||
assertEquals(2, pubAuthorityService.getAllRootAuthorities(AuthorityType.ROLE).size());
|
||||
auth5 = pubAuthorityService.createAuthority(AuthorityType.ROLE, auth2, "five");
|
||||
assertEquals(5, pubAuthorityService.getAllAuthorities(AuthorityType.ROLE).size());
|
||||
assertEquals(2, pubAuthorityService.getAllRootAuthorities(AuthorityType.ROLE).size());
|
||||
|
||||
pubAuthorityService.deleteAuthority(auth5);
|
||||
assertEquals(4, pubAuthorityService.getAllAuthorities(AuthorityType.ROLE).size());
|
||||
assertEquals(2, pubAuthorityService.getAllRootAuthorities(AuthorityType.ROLE).size());
|
||||
pubAuthorityService.deleteAuthority(auth4);
|
||||
assertEquals(3, pubAuthorityService.getAllAuthorities(AuthorityType.ROLE).size());
|
||||
assertEquals(2, pubAuthorityService.getAllRootAuthorities(AuthorityType.ROLE).size());
|
||||
pubAuthorityService.deleteAuthority(auth3);
|
||||
assertEquals(2, pubAuthorityService.getAllAuthorities(AuthorityType.ROLE).size());
|
||||
assertEquals(2, pubAuthorityService.getAllRootAuthorities(AuthorityType.ROLE).size());
|
||||
pubAuthorityService.deleteAuthority(auth2);
|
||||
assertEquals(1, pubAuthorityService.getAllAuthorities(AuthorityType.ROLE).size());
|
||||
assertEquals(1, pubAuthorityService.getAllRootAuthorities(AuthorityType.ROLE).size());
|
||||
pubAuthorityService.deleteAuthority(auth1);
|
||||
assertEquals(0, pubAuthorityService.getAllAuthorities(AuthorityType.ROLE).size());
|
||||
assertEquals(0, pubAuthorityService.getAllRootAuthorities(AuthorityType.ROLE).size());
|
||||
}
|
||||
|
||||
public void testCreateAuthTree()
|
||||
{
|
||||
String auth1;
|
||||
String auth2;
|
||||
String auth3;
|
||||
String auth4;
|
||||
String auth5;
|
||||
|
||||
assertEquals(0, pubAuthorityService.getAllAuthorities(AuthorityType.GROUP).size());
|
||||
assertEquals(0, pubAuthorityService.getAllRootAuthorities(AuthorityType.GROUP).size());
|
||||
auth1 = pubAuthorityService.createAuthority(AuthorityType.GROUP, null, "one");
|
||||
assertEquals("GROUP_one", auth1);
|
||||
assertEquals(1, pubAuthorityService.getAllAuthorities(AuthorityType.GROUP).size());
|
||||
assertEquals(1, pubAuthorityService.getAllRootAuthorities(AuthorityType.GROUP).size());
|
||||
auth2 = pubAuthorityService.createAuthority(AuthorityType.GROUP, null, "two");
|
||||
assertEquals("GROUP_two", auth2);
|
||||
assertEquals(2, pubAuthorityService.getAllAuthorities(AuthorityType.GROUP).size());
|
||||
assertEquals(2, pubAuthorityService.getAllRootAuthorities(AuthorityType.GROUP).size());
|
||||
auth3 = pubAuthorityService.createAuthority(AuthorityType.GROUP, auth1, "three");
|
||||
assertEquals("GROUP_three", auth3);
|
||||
assertEquals(3, pubAuthorityService.getAllAuthorities(AuthorityType.GROUP).size());
|
||||
assertEquals(2, pubAuthorityService.getAllRootAuthorities(AuthorityType.GROUP).size());
|
||||
auth4 = pubAuthorityService.createAuthority(AuthorityType.GROUP, auth1, "four");
|
||||
assertEquals("GROUP_four", auth4);
|
||||
assertEquals(4, pubAuthorityService.getAllAuthorities(AuthorityType.GROUP).size());
|
||||
assertEquals(2, pubAuthorityService.getAllRootAuthorities(AuthorityType.GROUP).size());
|
||||
auth5 = pubAuthorityService.createAuthority(AuthorityType.GROUP, auth2, "five");
|
||||
assertEquals("GROUP_five", auth5);
|
||||
assertEquals(5, pubAuthorityService.getAllAuthorities(AuthorityType.GROUP).size());
|
||||
assertEquals(2, pubAuthorityService.getAllRootAuthorities(AuthorityType.GROUP).size());
|
||||
|
||||
assertEquals(2, pubAuthorityService.getAllAuthorities(AuthorityType.USER).size());
|
||||
pubAuthorityService.addAuthority(auth5, "andy");
|
||||
assertEquals(5, pubAuthorityService.getAllAuthorities(AuthorityType.GROUP).size());
|
||||
assertEquals(2, pubAuthorityService.getAllRootAuthorities(AuthorityType.GROUP).size());
|
||||
// The next call looks for people not users :-)
|
||||
assertEquals(2, pubAuthorityService.getAllAuthorities(AuthorityType.USER).size());
|
||||
assertEquals(2, pubAuthorityService.getContainingAuthorities(null, "andy", false).size());
|
||||
assertTrue(pubAuthorityService.getContainingAuthorities(null, "andy", false).contains(auth5));
|
||||
assertTrue(pubAuthorityService.getContainingAuthorities(null, "andy", false).contains(auth2));
|
||||
assertEquals(1, pubAuthorityService.getContainingAuthorities(null, auth5, false).size());
|
||||
assertTrue(pubAuthorityService.getContainingAuthorities(null, auth5, false).contains(auth2));
|
||||
|
||||
assertEquals(2, pubAuthorityService.getContainedAuthorities(null, auth2, false).size());
|
||||
assertTrue(pubAuthorityService.getContainedAuthorities(null, auth2, false).contains(auth5));
|
||||
assertTrue(pubAuthorityService.getContainedAuthorities(null, auth2, false).contains("andy"));
|
||||
|
||||
assertEquals(1, pubAuthorityService.getContainedAuthorities(null, auth5, false).size());
|
||||
assertTrue(pubAuthorityService.getContainedAuthorities(null, auth5, false).contains("andy"));
|
||||
|
||||
pubAuthorityService.removeAuthority(auth5, "andy");
|
||||
assertEquals(5, pubAuthorityService.getAllAuthorities(AuthorityType.GROUP).size());
|
||||
assertEquals(2, pubAuthorityService.getAllRootAuthorities(AuthorityType.GROUP).size());
|
||||
// The next call looks for people not users :-)
|
||||
assertEquals(2, pubAuthorityService.getAllAuthorities(AuthorityType.USER).size());
|
||||
assertEquals(0, pubAuthorityService.getContainingAuthorities(null, "andy", false).size());
|
||||
assertEquals(1, pubAuthorityService.getContainingAuthorities(null, auth5, false).size());
|
||||
assertTrue(pubAuthorityService.getContainingAuthorities(null, auth5, false).contains(auth2));
|
||||
|
||||
assertEquals(1, pubAuthorityService.getContainedAuthorities(null, auth2, false).size());
|
||||
assertTrue(pubAuthorityService.getContainedAuthorities(null, auth2, false).contains(auth5));
|
||||
|
||||
assertEquals(0, pubAuthorityService.getContainedAuthorities(null, auth5, false).size());
|
||||
}
|
||||
|
||||
public void testCreateAuthNet()
|
||||
{
|
||||
String auth1;
|
||||
String auth2;
|
||||
String auth3;
|
||||
String auth4;
|
||||
String auth5;
|
||||
|
||||
assertEquals(0, pubAuthorityService.getAllAuthorities(AuthorityType.GROUP).size());
|
||||
assertEquals(0, pubAuthorityService.getAllRootAuthorities(AuthorityType.GROUP).size());
|
||||
auth1 = pubAuthorityService.createAuthority(AuthorityType.GROUP, null, "one");
|
||||
assertEquals(1, pubAuthorityService.getAllAuthorities(AuthorityType.GROUP).size());
|
||||
assertEquals(1, pubAuthorityService.getAllRootAuthorities(AuthorityType.GROUP).size());
|
||||
auth2 = pubAuthorityService.createAuthority(AuthorityType.GROUP, null, "two");
|
||||
assertEquals(2, pubAuthorityService.getAllAuthorities(AuthorityType.GROUP).size());
|
||||
assertEquals(2, pubAuthorityService.getAllRootAuthorities(AuthorityType.GROUP).size());
|
||||
auth3 = pubAuthorityService.createAuthority(AuthorityType.GROUP, auth1, "three");
|
||||
assertEquals(3, pubAuthorityService.getAllAuthorities(AuthorityType.GROUP).size());
|
||||
assertEquals(2, pubAuthorityService.getAllRootAuthorities(AuthorityType.GROUP).size());
|
||||
auth4 = pubAuthorityService.createAuthority(AuthorityType.GROUP, auth1, "four");
|
||||
assertEquals(4, pubAuthorityService.getAllAuthorities(AuthorityType.GROUP).size());
|
||||
assertEquals(2, pubAuthorityService.getAllRootAuthorities(AuthorityType.GROUP).size());
|
||||
auth5 = pubAuthorityService.createAuthority(AuthorityType.GROUP, auth2, "five");
|
||||
assertEquals(5, pubAuthorityService.getAllAuthorities(AuthorityType.GROUP).size());
|
||||
assertEquals(2, pubAuthorityService.getAllRootAuthorities(AuthorityType.GROUP).size());
|
||||
|
||||
assertEquals(2, pubAuthorityService.getAllAuthorities(AuthorityType.USER).size());
|
||||
pubAuthorityService.addAuthority(auth5, "andy");
|
||||
pubAuthorityService.addAuthority(auth1, "andy");
|
||||
|
||||
assertEquals(5, pubAuthorityService.getAllAuthorities(AuthorityType.GROUP).size());
|
||||
assertEquals(2, pubAuthorityService.getAllRootAuthorities(AuthorityType.GROUP).size());
|
||||
// The next call looks for people not users :-)
|
||||
assertEquals(2, pubAuthorityService.getAllAuthorities(AuthorityType.USER).size());
|
||||
assertEquals(3, pubAuthorityService.getContainingAuthorities(null, "andy", false).size());
|
||||
assertTrue(pubAuthorityService.getContainingAuthorities(null, "andy", false).contains(auth5));
|
||||
assertTrue(pubAuthorityService.getContainingAuthorities(null, "andy", false).contains(auth2));
|
||||
assertTrue(pubAuthorityService.getContainingAuthorities(null, "andy", false).contains(auth1));
|
||||
|
||||
assertEquals(2, pubAuthorityService.getContainedAuthorities(null, auth2, false).size());
|
||||
assertTrue(pubAuthorityService.getContainedAuthorities(null, auth2, false).contains(auth5));
|
||||
assertTrue(pubAuthorityService.getContainedAuthorities(null, auth2, false).contains("andy"));
|
||||
assertEquals(3, pubAuthorityService.getContainedAuthorities(null, auth1, false).size());
|
||||
assertTrue(pubAuthorityService.getContainedAuthorities(null, auth1, false).contains(auth3));
|
||||
assertTrue(pubAuthorityService.getContainedAuthorities(null, auth1, false).contains(auth4));
|
||||
assertTrue(pubAuthorityService.getContainedAuthorities(null, auth1, false).contains("andy"));
|
||||
|
||||
pubAuthorityService.removeAuthority(auth1, "andy");
|
||||
|
||||
assertEquals(5, pubAuthorityService.getAllAuthorities(AuthorityType.GROUP).size());
|
||||
assertEquals(2, pubAuthorityService.getAllRootAuthorities(AuthorityType.GROUP).size());
|
||||
// The next call looks for people not users :-)
|
||||
assertEquals(2, pubAuthorityService.getAllAuthorities(AuthorityType.USER).size());
|
||||
assertEquals(2, pubAuthorityService.getContainingAuthorities(null, "andy", false).size());
|
||||
assertTrue(pubAuthorityService.getContainingAuthorities(null, "andy", false).contains(auth5));
|
||||
assertTrue(pubAuthorityService.getContainingAuthorities(null, "andy", false).contains(auth2));
|
||||
|
||||
assertEquals(2, pubAuthorityService.getContainedAuthorities(null, auth2, false).size());
|
||||
assertTrue(pubAuthorityService.getContainedAuthorities(null, auth2, false).contains(auth5));
|
||||
assertTrue(pubAuthorityService.getContainedAuthorities(null, auth2, false).contains("andy"));
|
||||
assertEquals(2, pubAuthorityService.getContainedAuthorities(null, auth1, false).size());
|
||||
assertTrue(pubAuthorityService.getContainedAuthorities(null, auth1, false).contains(auth3));
|
||||
assertTrue(pubAuthorityService.getContainedAuthorities(null, auth1, false).contains(auth4));
|
||||
}
|
||||
|
||||
public void testCreateAuthNet2()
|
||||
{
|
||||
String auth1;
|
||||
String auth2;
|
||||
String auth3;
|
||||
String auth4;
|
||||
String auth5;
|
||||
|
||||
assertEquals(0, pubAuthorityService.getAllAuthorities(AuthorityType.GROUP).size());
|
||||
assertEquals(0, pubAuthorityService.getAllRootAuthorities(AuthorityType.GROUP).size());
|
||||
auth1 = pubAuthorityService.createAuthority(AuthorityType.GROUP, null, "one");
|
||||
assertEquals(1, pubAuthorityService.getAllAuthorities(AuthorityType.GROUP).size());
|
||||
assertEquals(1, pubAuthorityService.getAllRootAuthorities(AuthorityType.GROUP).size());
|
||||
auth2 = pubAuthorityService.createAuthority(AuthorityType.GROUP, null, "two");
|
||||
assertEquals(2, pubAuthorityService.getAllAuthorities(AuthorityType.GROUP).size());
|
||||
assertEquals(2, pubAuthorityService.getAllRootAuthorities(AuthorityType.GROUP).size());
|
||||
auth3 = pubAuthorityService.createAuthority(AuthorityType.GROUP, auth1, "three");
|
||||
assertEquals(3, pubAuthorityService.getAllAuthorities(AuthorityType.GROUP).size());
|
||||
assertEquals(2, pubAuthorityService.getAllRootAuthorities(AuthorityType.GROUP).size());
|
||||
auth4 = pubAuthorityService.createAuthority(AuthorityType.GROUP, auth1, "four");
|
||||
assertEquals(4, pubAuthorityService.getAllAuthorities(AuthorityType.GROUP).size());
|
||||
assertEquals(2, pubAuthorityService.getAllRootAuthorities(AuthorityType.GROUP).size());
|
||||
auth5 = pubAuthorityService.createAuthority(AuthorityType.GROUP, auth2, "five");
|
||||
assertEquals(5, pubAuthorityService.getAllAuthorities(AuthorityType.GROUP).size());
|
||||
assertEquals(2, pubAuthorityService.getAllRootAuthorities(AuthorityType.GROUP).size());
|
||||
|
||||
assertEquals(2, pubAuthorityService.getAllAuthorities(AuthorityType.USER).size());
|
||||
pubAuthorityService.addAuthority(auth5, "andy");
|
||||
pubAuthorityService.addAuthority(auth1, "andy");
|
||||
|
||||
assertEquals(5, pubAuthorityService.getAllAuthorities(AuthorityType.GROUP).size());
|
||||
assertEquals(2, pubAuthorityService.getAllRootAuthorities(AuthorityType.GROUP).size());
|
||||
// The next call looks for people not users :-)
|
||||
assertEquals(2, pubAuthorityService.getAllAuthorities(AuthorityType.USER).size());
|
||||
assertEquals(3, pubAuthorityService.getContainingAuthorities(null, "andy", false).size());
|
||||
assertTrue(pubAuthorityService.getContainingAuthorities(null, "andy", false).contains(auth5));
|
||||
assertTrue(pubAuthorityService.getContainingAuthorities(null, "andy", false).contains(auth2));
|
||||
assertTrue(pubAuthorityService.getContainingAuthorities(null, "andy", false).contains(auth1));
|
||||
|
||||
assertEquals(2, pubAuthorityService.getContainedAuthorities(null, auth2, false).size());
|
||||
assertTrue(pubAuthorityService.getContainedAuthorities(null, auth2, false).contains(auth5));
|
||||
assertTrue(pubAuthorityService.getContainedAuthorities(null, auth2, false).contains("andy"));
|
||||
assertEquals(3, pubAuthorityService.getContainedAuthorities(null, auth1, false).size());
|
||||
assertTrue(pubAuthorityService.getContainedAuthorities(null, auth1, false).contains(auth3));
|
||||
assertTrue(pubAuthorityService.getContainedAuthorities(null, auth1, false).contains(auth4));
|
||||
assertTrue(pubAuthorityService.getContainedAuthorities(null, auth1, false).contains("andy"));
|
||||
|
||||
|
||||
pubAuthorityService.addAuthority(auth3, auth2);
|
||||
|
||||
assertEquals(5, pubAuthorityService.getAllAuthorities(AuthorityType.GROUP).size());
|
||||
assertEquals(2, pubAuthorityService.getAllRootAuthorities(AuthorityType.GROUP).size());
|
||||
// The next call looks for people not users :-)
|
||||
assertEquals(2, pubAuthorityService.getAllAuthorities(AuthorityType.USER).size());
|
||||
assertEquals(4, pubAuthorityService.getContainingAuthorities(null, "andy", false).size());
|
||||
assertTrue(pubAuthorityService.getContainingAuthorities(null, "andy", false).contains(auth5));
|
||||
assertTrue(pubAuthorityService.getContainingAuthorities(null, "andy", false).contains(auth2));
|
||||
assertTrue(pubAuthorityService.getContainingAuthorities(null, "andy", false).contains(auth1));
|
||||
assertTrue(pubAuthorityService.getContainingAuthorities(null, "andy", false).contains(auth3));
|
||||
|
||||
assertEquals(2, pubAuthorityService.getContainedAuthorities(null, auth2, false).size());
|
||||
assertTrue(pubAuthorityService.getContainedAuthorities(null, auth2, false).contains(auth5));
|
||||
assertTrue(pubAuthorityService.getContainedAuthorities(null, auth2, false).contains("andy"));
|
||||
assertEquals(5, pubAuthorityService.getContainedAuthorities(null, auth1, false).size());
|
||||
assertTrue(pubAuthorityService.getContainedAuthorities(null, auth1, false).contains(auth3));
|
||||
assertTrue(pubAuthorityService.getContainedAuthorities(null, auth1, false).contains(auth4));
|
||||
assertTrue(pubAuthorityService.getContainedAuthorities(null, auth1, false).contains(auth2));
|
||||
assertTrue(pubAuthorityService.getContainedAuthorities(null, auth1, false).contains(auth5));
|
||||
assertTrue(pubAuthorityService.getContainedAuthorities(null, auth1, false).contains("andy"));
|
||||
|
||||
}
|
||||
}
|
@@ -0,0 +1,62 @@
|
||||
/*
|
||||
* Copyright (C) 2005 Alfresco, Inc.
|
||||
*
|
||||
* Licensed under the Alfresco Network License. You may obtain a
|
||||
* copy of the License at
|
||||
*
|
||||
* http://www.alfrescosoftware.com/legal/
|
||||
*
|
||||
* Please view the license relevant to your network subscription.
|
||||
*
|
||||
* BY CLICKING THE "I UNDERSTAND AND ACCEPT" BOX, OR INSTALLING,
|
||||
* READING OR USING ALFRESCO'S Network SOFTWARE (THE "SOFTWARE"),
|
||||
* YOU ARE AGREEING ON BEHALF OF THE ENTITY LICENSING THE SOFTWARE
|
||||
* ("COMPANY") THAT COMPANY WILL BE BOUND BY AND IS BECOMING A PARTY TO
|
||||
* THIS ALFRESCO NETWORK AGREEMENT ("AGREEMENT") AND THAT YOU HAVE THE
|
||||
* AUTHORITY TO BIND COMPANY. IF COMPANY DOES NOT AGREE TO ALL OF THE
|
||||
* TERMS OF THIS AGREEMENT, DO NOT SELECT THE "I UNDERSTAND AND AGREE"
|
||||
* BOX AND DO NOT INSTALL THE SOFTWARE OR VIEW THE SOURCE CODE. COMPANY
|
||||
* HAS NOT BECOME A LICENSEE OF, AND IS NOT AUTHORIZED TO USE THE
|
||||
* SOFTWARE UNLESS AND UNTIL IT HAS AGREED TO BE BOUND BY THESE LICENSE
|
||||
* TERMS. THE "EFFECTIVE DATE" FOR THIS AGREEMENT SHALL BE THE DAY YOU
|
||||
* CHECK THE "I UNDERSTAND AND ACCEPT" BOX.
|
||||
*/
|
||||
package org.alfresco.repo.security.authority;
|
||||
|
||||
import org.alfresco.repo.security.permissions.impl.AbstractPermissionTest;
|
||||
import org.alfresco.repo.security.permissions.impl.SimplePermissionEntry;
|
||||
import org.alfresco.service.cmr.security.AccessStatus;
|
||||
import org.alfresco.service.cmr.security.AuthorityType;
|
||||
import org.alfresco.service.cmr.security.PermissionService;
|
||||
|
||||
public class ExtendedPermissionServiceTest extends AbstractPermissionTest
|
||||
{
|
||||
public void testGroupPermission()
|
||||
{
|
||||
runAs("andy");
|
||||
assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED);
|
||||
permissionService.setPermission(new SimplePermissionEntry(rootNodeRef, getPermission(PermissionService.READ),
|
||||
"GROUP_test", AccessStatus.ALLOWED));
|
||||
assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED);
|
||||
authorityService.createAuthority(AuthorityType.GROUP, null, "test");
|
||||
authorityService.addAuthority("GROUP_test", "andy");
|
||||
assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED);
|
||||
authorityService.removeAuthority("GROUP_test", "andy");
|
||||
assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED);
|
||||
permissionService.clearPermission(rootNodeRef, "andy");
|
||||
}
|
||||
|
||||
public void testDeletePermissionByRecipient()
|
||||
{
|
||||
runAs("andy");
|
||||
assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED);
|
||||
permissionService.setPermission(new SimplePermissionEntry(rootNodeRef, getPermission(PermissionService.READ),
|
||||
"GROUP_test", AccessStatus.ALLOWED));
|
||||
assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED);
|
||||
authorityService.createAuthority(AuthorityType.GROUP, null, "test");
|
||||
authorityService.addAuthority("GROUP_test", "andy");
|
||||
assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED);
|
||||
permissionService.deletePermissions("GROUP_test");
|
||||
assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED);
|
||||
}
|
||||
}
|
@@ -0,0 +1,54 @@
|
||||
/*
|
||||
* Copyright (C) 2005 Alfresco, Inc.
|
||||
*
|
||||
* Licensed under the Alfresco Network License. You may obtain a
|
||||
* copy of the License at
|
||||
*
|
||||
* http://www.alfrescosoftware.com/legal/
|
||||
*
|
||||
* Please view the license relevant to your network subscription.
|
||||
*
|
||||
* BY CLICKING THE "I UNDERSTAND AND ACCEPT" BOX, OR INSTALLING,
|
||||
* READING OR USING ALFRESCO'S Network SOFTWARE (THE "SOFTWARE"),
|
||||
* YOU ARE AGREEING ON BEHALF OF THE ENTITY LICENSING THE SOFTWARE
|
||||
* ("COMPANY") THAT COMPANY WILL BE BOUND BY AND IS BECOMING A PARTY TO
|
||||
* THIS ALFRESCO NETWORK AGREEMENT ("AGREEMENT") AND THAT YOU HAVE THE
|
||||
* AUTHORITY TO BIND COMPANY. IF COMPANY DOES NOT AGREE TO ALL OF THE
|
||||
* TERMS OF THIS AGREEMENT, DO NOT SELECT THE "I UNDERSTAND AND AGREE"
|
||||
* BOX AND DO NOT INSTALL THE SOFTWARE OR VIEW THE SOURCE CODE. COMPANY
|
||||
* HAS NOT BECOME A LICENSEE OF, AND IS NOT AUTHORIZED TO USE THE
|
||||
* SOFTWARE UNLESS AND UNTIL IT HAS AGREED TO BE BOUND BY THESE LICENSE
|
||||
* TERMS. THE "EFFECTIVE DATE" FOR THIS AGREEMENT SHALL BE THE DAY YOU
|
||||
* CHECK THE "I UNDERSTAND AND ACCEPT" BOX.
|
||||
*/
|
||||
package org.alfresco.repo.security.authority;
|
||||
|
||||
public class UnknownAuthorityException extends AuthorityException
|
||||
{
|
||||
|
||||
/**
|
||||
* Comment for <code>serialVersionUID</code>
|
||||
*/
|
||||
private static final long serialVersionUID = 4639634037108317201L;
|
||||
|
||||
public UnknownAuthorityException(String msgId)
|
||||
{
|
||||
super(msgId);
|
||||
}
|
||||
|
||||
public UnknownAuthorityException(String msgId, Object[] msgParams)
|
||||
{
|
||||
super(msgId, msgParams);
|
||||
}
|
||||
|
||||
public UnknownAuthorityException(String msgId, Throwable cause)
|
||||
{
|
||||
super(msgId, cause);
|
||||
}
|
||||
|
||||
public UnknownAuthorityException(String msgId, Object[] msgParams, Throwable cause)
|
||||
{
|
||||
super(msgId, msgParams, cause);
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,98 @@
|
||||
/*
|
||||
* Copyright (C) 2005 Alfresco, Inc.
|
||||
*
|
||||
* Licensed under the Alfresco Network License. You may obtain a
|
||||
* copy of the License at
|
||||
*
|
||||
* http://www.alfrescosoftware.com/legal/
|
||||
*
|
||||
* Please view the license relevant to your network subscription.
|
||||
*
|
||||
* BY CLICKING THE "I UNDERSTAND AND ACCEPT" BOX, OR INSTALLING,
|
||||
* READING OR USING ALFRESCO'S Network SOFTWARE (THE "SOFTWARE"),
|
||||
* YOU ARE AGREEING ON BEHALF OF THE ENTITY LICENSING THE SOFTWARE
|
||||
* ("COMPANY") THAT COMPANY WILL BE BOUND BY AND IS BECOMING A PARTY TO
|
||||
* THIS ALFRESCO NETWORK AGREEMENT ("AGREEMENT") AND THAT YOU HAVE THE
|
||||
* AUTHORITY TO BIND COMPANY. IF COMPANY DOES NOT AGREE TO ALL OF THE
|
||||
* TERMS OF THIS AGREEMENT, DO NOT SELECT THE "I UNDERSTAND AND AGREE"
|
||||
* BOX AND DO NOT INSTALL THE SOFTWARE OR VIEW THE SOURCE CODE. COMPANY
|
||||
* HAS NOT BECOME A LICENSEE OF, AND IS NOT AUTHORIZED TO USE THE
|
||||
* SOFTWARE UNLESS AND UNTIL IT HAS AGREED TO BE BOUND BY THESE LICENSE
|
||||
* TERMS. THE "EFFECTIVE DATE" FOR THIS AGREEMENT SHALL BE THE DAY YOU
|
||||
* CHECK THE "I UNDERSTAND AND ACCEPT" BOX.
|
||||
*/
|
||||
package org.alfresco.repo.transaction;
|
||||
|
||||
import java.util.Properties;
|
||||
|
||||
import javax.transaction.TransactionManager;
|
||||
|
||||
import org.jboss.cache.TransactionManagerLookup;
|
||||
import org.springframework.jndi.JndiObjectFactoryBean;
|
||||
import org.springframework.jndi.JndiTemplate;
|
||||
|
||||
/**
|
||||
* Helper lookup class to supply JBoss components with a <code>TransactionManager</code>.
|
||||
* <p>
|
||||
* The <code>JBossTransactionManagerLookup</code> will work when Alfresco is running in JBoss,
|
||||
* but the <code>TreeCache</code> can be used within other containers; there might not be any
|
||||
* container and the <code>TransactionManager</code> may held in a local JNDI tree.
|
||||
* <p>
|
||||
* For compatibility with other app servers, the JBoss <code>GenericTransactionManagerLookup</code>
|
||||
* could also be used.
|
||||
* <p>
|
||||
* The default constructor configures the object to look in <b>java:/TransactionManager</b>
|
||||
* for a <code>TransactionManager</code>. The only customisation that should be required is
|
||||
* to change the {@link #setJndiName(String) jndiName} property. If more JNDI details need
|
||||
* changing, then the actual {@link #setJndiLookup(JndiObjectFactoryBean) jndiLookup object} can
|
||||
* be substituted with a customized version.
|
||||
*
|
||||
* @author Derek Hulley
|
||||
*/
|
||||
public class TransactionManagerJndiLookup implements TransactionManagerLookup
|
||||
{
|
||||
public static final String DEFAULT_JNDI_NAME = "java:/TransactionManager";
|
||||
|
||||
private JndiObjectFactoryBean jndiLookup;
|
||||
|
||||
public TransactionManagerJndiLookup()
|
||||
{
|
||||
jndiLookup = new JndiObjectFactoryBean();
|
||||
jndiLookup.setJndiName(DEFAULT_JNDI_NAME);
|
||||
jndiLookup.setProxyInterface(TransactionManager.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see org.springframework.jndi.JndiAccessor#setJndiTemplate(org.springframework.jndi.JndiTemplate)
|
||||
*/
|
||||
public void setJndiTemplate(JndiTemplate jndiTemplate)
|
||||
{
|
||||
this.jndiLookup.setJndiTemplate(jndiTemplate);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see org.springframework.jndi.JndiAccessor#setJndiEnvironment(java.util.Properties)
|
||||
*/
|
||||
public void setJndiEnvironment(Properties jndiEnvironment)
|
||||
{
|
||||
this.jndiLookup.setJndiEnvironment(jndiEnvironment);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the JNDI location where the <code>TransactionManager</code> can be found.
|
||||
*
|
||||
* @param jndiName
|
||||
*/
|
||||
public void setJndiName(String jndiName)
|
||||
{
|
||||
jndiLookup.setJndiName(jndiName);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Returns a <code>TransactionManager</code> looked up at the JNDI location
|
||||
*/
|
||||
public TransactionManager getTransactionManager() throws Exception
|
||||
{
|
||||
return (TransactionManager) jndiLookup.getObject();
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user