diff --git a/config/alfresco/authentication-services-context.xml b/config/alfresco/authentication-services-context.xml
index ebc880890a..c7c1df71dc 100644
--- a/config/alfresco/authentication-services-context.xml
+++ b/config/alfresco/authentication-services-context.xml
@@ -126,6 +126,9 @@
+
+
+
diff --git a/config/alfresco/cache-context.xml b/config/alfresco/cache-context.xml
index d08050e537..7a2d0b79da 100644
--- a/config/alfresco/cache-context.xml
+++ b/config/alfresco/cache-context.xml
@@ -243,6 +243,39 @@
10
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ org.alfresco.cache.sysAdminCache
+
+
+
+
+
+
+
+
+
+
+
+
+
+ org.alfresco.sysAdminTransactionalCache
+
+
+ 10
+
diff --git a/config/alfresco/core-services-context.xml b/config/alfresco/core-services-context.xml
index 3752d2afb0..0becf55a20 100644
--- a/config/alfresco/core-services-context.xml
+++ b/config/alfresco/core-services-context.xml
@@ -84,6 +84,7 @@
+
@@ -128,7 +129,14 @@
-
+
+
+
+
+
+
+
+
@@ -175,6 +183,9 @@
+
+
+
${server.transaction.allow-writes}
diff --git a/config/alfresco/ehcache-default.xml b/config/alfresco/ehcache-default.xml
index 4c070b0353..d639cc1cd4 100644
--- a/config/alfresco/ehcache-default.xml
+++ b/config/alfresco/ehcache-default.xml
@@ -310,6 +310,12 @@
maxElementsInMemory="1000"
eternal="true"
overflowToDisk="true"
+ />
+
diff --git a/config/alfresco/extension/ehcache-custom.xml.sample.cluster b/config/alfresco/extension/ehcache-custom.xml.sample.cluster
index 00eff08f75..fc87ee2f07 100644
--- a/config/alfresco/extension/ehcache-custom.xml.sample.cluster
+++ b/config/alfresco/extension/ehcache-custom.xml.sample.cluster
@@ -509,8 +509,24 @@
replicateUpdatesViaCopy = true,
replicateAsynchronously = false"/>
+
+
+
+
+
+
+
-
userSet = authenticationService.getUsersWithTickets(true);
+ return userSet.toArray(new String[0]);
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see org.alfresco.repo.admin.RepoServerMgmtMBean#listUserNamesAll()
+ */
+ public String[] listUserNamesAll()
+ {
+ Set userSet = authenticationService.getUsersWithTickets(false);
+ return userSet.toArray(new String[0]);
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see org.alfresco.mbeans.RepoServerMgmtMBean#invalidateTicketsExpired()
+ */
+ public int invalidateTicketsExpired()
+ {
+ return authenticationService.invalidateTickets(true);
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see org.alfresco.mbeans.RepoServerMgmtMBean#invalidateTicketsAll()
+ */
+ public int invalidateTicketsAll()
+ {
+ return authenticationService.invalidateTickets(false);
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see org.alfresco.mbeans.RepoServerMgmtMBean#allowSingleUserOnly(java.lang.String)
+ */
+ public void allowSingleUserOnly(String allowedUsername)
+ {
+ List allowedUsers = null;
+ if (allowedUsername != null)
+ {
+ allowedUsers = new ArrayList(0);
+ allowedUsers.add(allowedUsername);
+
+ invalidateTicketsAll();
+ }
+
+ authenticationService.setAllowedUsers(allowedUsers);
+ }
+}
diff --git a/source/java/org/alfresco/repo/admin/RepoServerMgmtMBean.java b/source/java/org/alfresco/repo/admin/RepoServerMgmtMBean.java
new file mode 100644
index 0000000000..17e49d223e
--- /dev/null
+++ b/source/java/org/alfresco/repo/admin/RepoServerMgmtMBean.java
@@ -0,0 +1,139 @@
+/*
+ * Copyright (C) 2005-2007 Alfresco Software Limited.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+
+ * As a special exception to the terms and conditions of version 2.0 of
+ * the GPL, you may redistribute this Program in connection with Free/Libre
+ * and Open Source Software ("FLOSS") applications as described in Alfresco's
+ * FLOSS exception. You should have recieved a copy of the text describing
+ * the FLOSS exception, and it is also available here:
+ * http://www.alfresco.com/legal/licensing"
+ */
+package org.alfresco.repo.admin;
+
+
+public interface RepoServerMgmtMBean
+{
+ /**
+ * Set whether Repository allows writes or not
+ *
+ * Note: This operation can be clustered (ie. all servers in the cluster will be affected)
+ *
+ * @param readOnly true is READONLY, false is WRITEABLE
+ */
+ public void setReadOnly(boolean readOnly);
+
+ /**
+ * Does the Repository allows writes or not ?
+ *
+ * Note: This operation can be clustered (ie. all servers in the cluster will be affected)
+ *
+ * @return boolean true is READONLY, false is WRITEABLE
+ */
+ public boolean isReadOnly();
+
+ /**
+ * Get count of non-expired tickets
+ *
+ * This may be higher than the user count, since a user can have more than one ticket/session
+ *
+ * Note: This operation can be clustered (ie. all servers in the cluster will be affected)
+ *
+ * @return int number of non-expired tickets
+ */
+ public int getTicketCountNonExpired();
+
+ /**
+ * Get count of all tickets
+ *
+ * This may be higher than the user count, since a user can have more than one ticket/session
+ *
+ * Note: This operation can be clustered (ie. all servers in the cluster will be affected)
+ *
+ * @return int number of tickets (non-expired and expired)
+ */
+ public int getTicketCountAll();
+
+ /**
+ * Get count of non-expired users
+ *
+ * This may be lower than the ticket count, since a user can have more than one ticket/session
+ *
+ * Note: This operation can be clustered (ie. all servers in the cluster will be affected)
+ *
+ * @return int number of non-expired users
+ */
+ public int getUserCountNonExpired();
+
+ /**
+ * Get count of all users
+ *
+ * This may be lower than the ticket count, since a user can have more than one ticket/session
+ *
+ * Note: This operation can be clustered (ie. all servers in the cluster will be affected)
+ *
+ * @return int number of users (non-expired and expired)
+ */
+ public int getUserCountAll();
+
+ /**
+ * Get set of unique non-expired usernames
+ *
+ * Note: This operation can be clustered (ie. all servers in the cluster will be affected)
+ *
+ * @return String[] array of non-expired usernames
+ */
+ public String[] listUserNamesNonExpired();
+
+ /**
+ * Get set of all unique usernames
+ *
+ * Note: This operation can be clustered (ie. all servers in the cluster will be affected)
+ *
+ * @return String[] array of all usernames (non-expired and expired)
+ */
+ public String[] listUserNamesAll();
+
+ /**
+ * Invalidate expired tickets
+ *
+ * Note: This operation can be clustered (ie. all servers in the cluster will be affected)
+ *
+ * @return int count of expired invalidated tickets
+ */
+ public int invalidateTicketsExpired();
+
+ /**
+ * Invalidate all tickets
+ *
+ * Note: This operation can be clustered (ie. all servers in the cluster will be affected)
+ *
+ * @return int count of all invalidated tickets (non-expired and expired)
+ */
+ public int invalidateTicketsAll();
+
+ /**
+ * Set whether Repository allows single user mode or not
+ *
+ * If single user mode is set then all tickets will be invalidated first before allowing the
+ * named user to login (with one or more sessions)
+ *
+ * Note: This operation can be clustered (ie. all servers in the cluster will be affected)
+ *
+ * @param String allowed username (eg. 'admin') or null to unset (ie. allow all users)
+ */
+ public void allowSingleUserOnly(String allowedUsername);
+}
diff --git a/source/java/org/alfresco/repo/security/authentication/AuthenticationServiceImpl.java b/source/java/org/alfresco/repo/security/authentication/AuthenticationServiceImpl.java
index 308c86f5cf..4c912d3584 100644
--- a/source/java/org/alfresco/repo/security/authentication/AuthenticationServiceImpl.java
+++ b/source/java/org/alfresco/repo/security/authentication/AuthenticationServiceImpl.java
@@ -25,8 +25,10 @@
package org.alfresco.repo.security.authentication;
import java.util.Collections;
+import java.util.List;
import java.util.Set;
+import org.alfresco.repo.cache.SimpleCache;
import org.alfresco.service.cmr.security.AuthenticationService;
public class AuthenticationServiceImpl implements AuthenticationService
@@ -45,10 +47,20 @@ public class AuthenticationServiceImpl implements AuthenticationService
private boolean allowsUserPasswordChange = true;
+ // SysAdmin cache - used to cluster certain JMX operations
+ private SimpleCache sysAdminCache;
+ private final static String KEY_SYSADMIN_ALLOWED_USERS = "sysAdminCache.authAllowedUsers";
+
+
public AuthenticationServiceImpl()
{
super();
}
+
+ public void setSysAdminCache(SimpleCache sysAdminCache)
+ {
+ this.sysAdminCache = sysAdminCache;
+ }
public void setAuthenticationDao(MutableAuthenticationDao authenticationDao)
{
@@ -105,13 +117,20 @@ public class AuthenticationServiceImpl implements AuthenticationService
authenticationDao.setEnabled(userName, enabled);
}
+ @SuppressWarnings("unchecked")
public void authenticate(String userName, char[] password) throws AuthenticationException
{
try
{
- // clear context - to avoid MT concurrency issue (causing domain mismatch) - see also 'validate' below
- clearCurrentSecurityContext();
- authenticationComponent.authenticate(userName, password);
+ // clear context - to avoid MT concurrency issue (causing domain mismatch) - see also 'validate' below
+ clearCurrentSecurityContext();
+ List allowedUsers = (List)sysAdminCache.get(KEY_SYSADMIN_ALLOWED_USERS);
+
+ if ((allowedUsers != null) && (! allowedUsers.contains(userName)))
+ {
+ throw new AuthenticationException("Username not allowed: " + userName);
+ }
+ authenticationComponent.authenticate(userName, password);
}
catch(AuthenticationException ae)
{
@@ -119,6 +138,8 @@ public class AuthenticationServiceImpl implements AuthenticationService
throw ae;
}
ticketComponent.clearCurrentTicket();
+
+ ticketComponent.getCurrentTicket(userName); // to ensure new ticket is created (even if client does not explicitly call getCurrentTicket)
}
public boolean authenticationExists(String userName)
@@ -135,11 +156,32 @@ public class AuthenticationServiceImpl implements AuthenticationService
{
ticketComponent.invalidateTicketByUser(userName);
}
+
+ public Set getUsersWithTickets(boolean nonExpiredOnly)
+ {
+ return ticketComponent.getUsersWithTickets(nonExpiredOnly);
+ }
+
+ public void setAllowedUsers(List allowedUsers)
+ {
+ sysAdminCache.put(KEY_SYSADMIN_ALLOWED_USERS, allowedUsers);
+ }
public void invalidateTicket(String ticket) throws AuthenticationException
{
ticketComponent.invalidateTicketById(ticket);
}
+
+ public int countTickets(boolean nonExpiredOnly)
+ {
+ return ticketComponent.countTickets(nonExpiredOnly);
+ }
+
+ public int invalidateTickets(boolean expiredOnly)
+ {
+ return ticketComponent.invalidateTickets(expiredOnly);
+ }
+
public void validate(String ticket) throws AuthenticationException
{
diff --git a/source/java/org/alfresco/repo/security/authentication/InMemoryTicketComponentImpl.java b/source/java/org/alfresco/repo/security/authentication/InMemoryTicketComponentImpl.java
index f450a028b0..751e93d0d1 100644
--- a/source/java/org/alfresco/repo/security/authentication/InMemoryTicketComponentImpl.java
+++ b/source/java/org/alfresco/repo/security/authentication/InMemoryTicketComponentImpl.java
@@ -144,6 +144,76 @@ public class InMemoryTicketComponentImpl implements TicketComponent
String key = ticketString.substring(GRANTED_AUTHORITY_TICKET_PREFIX.length());
ticketsCache.remove(key);
}
+
+ /*
+ * (non-Javadoc)
+ * @see org.alfresco.repo.security.authentication.TicketComponent#getUsersWithTickets(boolean)
+ */
+ public Set getUsersWithTickets(boolean nonExpiredOnly)
+ {
+ Set users = new HashSet();
+ for (String key : ticketsCache.getKeys())
+ {
+ Ticket ticket = ticketsCache.get(key);
+ if ((nonExpiredOnly == false) || (! ticket.hasExpired()))
+ {
+ users.add(ticket.getUserName());
+ }
+ }
+ return users;
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see org.alfresco.repo.security.authentication.TicketComponent#countTickets(boolean)
+ */
+ public int countTickets(boolean nonExpiredOnly)
+ {
+ if (nonExpiredOnly)
+ {
+ int count = 0;
+ for (String key : ticketsCache.getKeys())
+ {
+ Ticket ticket = ticketsCache.get(key);
+ if (! ticket.hasExpired())
+ {
+ count++;
+ }
+ }
+ return count;
+ }
+ else
+ {
+ return ticketsCache.getKeys().size();
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see org.alfresco.repo.security.authentication.TicketComponent#invalidateTickets(boolean)
+ */
+ public int invalidateTickets(boolean expiredOnly)
+ {
+ int count = 0;
+ if (! expiredOnly)
+ {
+ count = ticketsCache.getKeys().size();
+ ticketsCache.clear();
+ }
+ else
+ {
+ for (String key : ticketsCache.getKeys())
+ {
+ Ticket ticket = ticketsCache.get(key);
+ if (ticket.hasExpired())
+ {
+ count++;
+ ticketsCache.remove(key);
+ }
+ }
+ }
+ return count;
+ }
public void invalidateTicketByUser(String userName)
{
diff --git a/source/java/org/alfresco/repo/security/authentication/TicketComponent.java b/source/java/org/alfresco/repo/security/authentication/TicketComponent.java
index e462b2c785..7e694e2746 100644
--- a/source/java/org/alfresco/repo/security/authentication/TicketComponent.java
+++ b/source/java/org/alfresco/repo/security/authentication/TicketComponent.java
@@ -24,6 +24,8 @@
*/
package org.alfresco.repo.security.authentication;
+import java.util.Set;
+
/**
* Manage authentication tickets
@@ -89,6 +91,34 @@ public interface TicketComponent
* @param userName
*/
public void invalidateTicketByUser(String userName);
+
+ /**
+ * Count tickets
+ *
+ * This may be higher than the user count, since a user can have more than one ticket/session
+ *
+ * @param nonExpiredOnly true for non expired tickets, false for all (including expired) tickets
+ * @return int number of tickets
+ */
+ public int countTickets(boolean nonExpiredOnly);
+
+ /**
+ * Get set of users with tickets
+ *
+ * This may be lower than the ticket count, since a user can have more than one ticket/session
+ *
+ * @param nonExpiredOnly true for non expired tickets, false for all (including expired) tickets
+ * @return Set set of users with (one or more) tickets
+ */
+ public Set getUsersWithTickets(boolean nonExpiredOnly);
+
+ /**
+ * Invalidate tickets
+ *
+ * @param expiredOnly true for EXPIRED tickets, false for ALL (including non-expired) tickets
+ * @return int count of invalidated tickets
+ */
+ public int invalidateTickets(boolean expiredOnly);
/**
* Get the authority for the given ticket
diff --git a/source/java/org/alfresco/repo/transaction/TransactionServiceImpl.java b/source/java/org/alfresco/repo/transaction/TransactionServiceImpl.java
index f318f68ab6..33925af7e0 100644
--- a/source/java/org/alfresco/repo/transaction/TransactionServiceImpl.java
+++ b/source/java/org/alfresco/repo/transaction/TransactionServiceImpl.java
@@ -26,6 +26,7 @@ package org.alfresco.repo.transaction;
import javax.transaction.UserTransaction;
+import org.alfresco.repo.cache.SimpleCache;
import org.alfresco.service.transaction.TransactionService;
import org.alfresco.util.transaction.SpringAwareUserTransaction;
import org.springframework.transaction.PlatformTransactionManager;
@@ -39,9 +40,13 @@ import org.springframework.transaction.TransactionDefinition;
public class TransactionServiceImpl implements TransactionService
{
private PlatformTransactionManager transactionManager;
- private boolean readOnly = false;
private int maxRetries = 20;
+ // SysAdmin cache - used to cluster certain JMX operations
+ private SimpleCache sysAdminCache;
+ private final static String KEY_SYSADMIN_ALLOW_WRITE = "sysAdminCache.txAllowWrite";
+
+
/**
* Set the transaction manager to use
*
@@ -51,6 +56,11 @@ public class TransactionServiceImpl implements TransactionService
{
this.transactionManager = transactionManager;
}
+
+ public void setSysAdminCache(SimpleCache sysAdminCache)
+ {
+ this.sysAdminCache = sysAdminCache;
+ }
/**
* Set the read-only mode for all generated transactions.
@@ -59,12 +69,13 @@ public class TransactionServiceImpl implements TransactionService
*/
public void setAllowWrite(boolean allowWrite)
{
- this.readOnly = !allowWrite;
+ sysAdminCache.put(KEY_SYSADMIN_ALLOW_WRITE, allowWrite);
}
public boolean isReadOnly()
{
- return readOnly;
+ Boolean allowWrite = (Boolean)sysAdminCache.get(KEY_SYSADMIN_ALLOW_WRITE);
+ return (allowWrite == null ? false : ! allowWrite);
}
/**
@@ -85,7 +96,7 @@ public class TransactionServiceImpl implements TransactionService
{
SpringAwareUserTransaction txn = new SpringAwareUserTransaction(
transactionManager,
- this.readOnly,
+ isReadOnly(),
TransactionDefinition.ISOLATION_DEFAULT,
TransactionDefinition.PROPAGATION_REQUIRED,
TransactionDefinition.TIMEOUT_DEFAULT);
@@ -99,7 +110,7 @@ public class TransactionServiceImpl implements TransactionService
{
SpringAwareUserTransaction txn = new SpringAwareUserTransaction(
transactionManager,
- (readOnly | this.readOnly),
+ (readOnly | isReadOnly()),
TransactionDefinition.ISOLATION_DEFAULT,
TransactionDefinition.PROPAGATION_REQUIRED,
TransactionDefinition.TIMEOUT_DEFAULT);
@@ -113,7 +124,7 @@ public class TransactionServiceImpl implements TransactionService
{
SpringAwareUserTransaction txn = new SpringAwareUserTransaction(
transactionManager,
- this.readOnly,
+ isReadOnly(),
TransactionDefinition.ISOLATION_DEFAULT,
TransactionDefinition.PROPAGATION_REQUIRES_NEW,
TransactionDefinition.TIMEOUT_DEFAULT);
@@ -127,7 +138,7 @@ public class TransactionServiceImpl implements TransactionService
{
SpringAwareUserTransaction txn = new SpringAwareUserTransaction(
transactionManager,
- (readOnly | this.readOnly),
+ (readOnly | isReadOnly()),
TransactionDefinition.ISOLATION_DEFAULT,
TransactionDefinition.PROPAGATION_REQUIRES_NEW,
TransactionDefinition.TIMEOUT_DEFAULT);
@@ -142,7 +153,7 @@ public class TransactionServiceImpl implements TransactionService
RetryingTransactionHelper helper = new RetryingTransactionHelper();
helper.setMaxRetries(maxRetries);
helper.setTransactionService(this);
- helper.setReadOnly(readOnly);
+ helper.setReadOnly(isReadOnly());
return helper;
}
}
diff --git a/source/java/org/alfresco/repo/transaction/TransactionServiceImplTest.java b/source/java/org/alfresco/repo/transaction/TransactionServiceImplTest.java
index b859d64e75..d93ef0c809 100644
--- a/source/java/org/alfresco/repo/transaction/TransactionServiceImplTest.java
+++ b/source/java/org/alfresco/repo/transaction/TransactionServiceImplTest.java
@@ -29,7 +29,10 @@ import javax.transaction.Status;
import javax.transaction.UserTransaction;
import junit.framework.TestCase;
+import net.sf.ehcache.Cache;
+import net.sf.ehcache.CacheManager;
+import org.alfresco.repo.cache.EhCacheAdapter;
import org.alfresco.repo.security.permissions.AccessDeniedException;
import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback;
import org.alfresco.service.cmr.repository.NodeService;
@@ -57,8 +60,17 @@ public class TransactionServiceImplTest extends TestCase
transactionManager = (PlatformTransactionManager) ctx.getBean("transactionManager");
transactionService = new TransactionServiceImpl();
transactionService.setTransactionManager(transactionManager);
- transactionService.setAllowWrite(true);
+ CacheManager cacheManager = new CacheManager();
+ Cache sysAdminEhCache = new Cache("sysAdminCache", 10, false, true, 0L, 0L);
+ cacheManager.addCache(sysAdminEhCache);
+ EhCacheAdapter sysAdminCache = new EhCacheAdapter();
+ sysAdminCache.setCache(sysAdminEhCache);
+
+ transactionService.setSysAdminCache(sysAdminCache);
+
+ transactionService.setAllowWrite(true);
+
nodeService = (NodeService) ctx.getBean("dbNodeService");
}