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"); }