diff --git a/config/alfresco/core-services-context.xml b/config/alfresco/core-services-context.xml
index 0becf55a20..ea666d9cb8 100644
--- a/config/alfresco/core-services-context.xml
+++ b/config/alfresco/core-services-context.xml
@@ -135,6 +135,8 @@
+ ${server.maxusers}
+ ${server.singleuseronly.name}
diff --git a/config/alfresco/repository.properties b/config/alfresco/repository.properties
index b22c1bcbff..918f295f72 100644
--- a/config/alfresco/repository.properties
+++ b/config/alfresco/repository.properties
@@ -44,6 +44,18 @@ index.tracking.maxRecordSetSize=1000
# Change the failure behaviour of the configuration checker
system.bootstrap.config_check.strict=true
+# Server Single User Mode
+# note:
+# only allow named user (note: if blank or not set then will allow all users)
+# assuming maxusers is not set to 0
+#server.singleuseronly.name=admin
+
+# Server Max Users - limit number of users with non-expired tickets
+# note:
+# -1 allows any number of users, assuming not in single-user mode
+# 0 prevents further logins, including the ability to enter single-user mode
+server.maxusers=-1
+
#
# Properties to limit resources spent on individual searches
#
diff --git a/source/java/org/alfresco/repo/admin/RepoServerMgmt.java b/source/java/org/alfresco/repo/admin/RepoServerMgmt.java
index cd4689483b..030eac63c8 100644
--- a/source/java/org/alfresco/repo/admin/RepoServerMgmt.java
+++ b/source/java/org/alfresco/repo/admin/RepoServerMgmt.java
@@ -27,7 +27,10 @@ package org.alfresco.repo.admin;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
+import java.util.SortedSet;
+import java.util.TreeSet;
+import org.alfresco.error.AlfrescoRuntimeException;
import org.alfresco.repo.security.authentication.AuthenticationServiceImpl;
import org.alfresco.repo.transaction.TransactionServiceImpl;
import org.alfresco.service.license.LicenseService;
@@ -47,6 +50,8 @@ public class RepoServerMgmt implements RepoServerMgmtMBean, ApplicationContextAw
private TransactionServiceImpl transactionService;
private AuthenticationServiceImpl authenticationService;
+ // property key should be the same as the one in core-services-context.xml (to allow repo to start in multi-user mode even if the property is not set)
+ private final static String PROPERTY_KEY_SINGLE_USER_ONLY = "${server.singleuseronly.name}";
public void setTransactionService(TransactionServiceImpl transactionService)
{
@@ -71,13 +76,13 @@ public class RepoServerMgmt implements RepoServerMgmtMBean, ApplicationContextAw
{
if (readOnly && isReadOnly())
{
- log.info("Alfresco Repository is already READONLY");
+ log.info("Alfresco is already read-only");
return;
}
if (!readOnly && !isReadOnly())
{
- log.info("Alfresco Repository is already WRITABLE");
+ log.info("Alfresco is already read-write");
return;
}
@@ -100,11 +105,11 @@ public class RepoServerMgmt implements RepoServerMgmtMBean, ApplicationContextAw
if (readOnly)
{
- log.info("Alfresco Repository set to READONLY");
+ log.info("Alfresco set to be read-only");
}
else
{
- log.info("Alfresco Repository set to WRITABLE");
+ log.info("Alfresco set to be read-write");
}
}
@@ -118,7 +123,7 @@ public class RepoServerMgmt implements RepoServerMgmtMBean, ApplicationContextAw
}
// Note: implementing counts as managed attributes (without params) means that
- // certain JMX consoles can create graphs
+ // certain JMX consoles can monitor
/*
* (non-Javadoc)
@@ -165,7 +170,8 @@ public class RepoServerMgmt implements RepoServerMgmtMBean, ApplicationContextAw
public String[] listUserNamesNonExpired()
{
Set userSet = authenticationService.getUsersWithTickets(true);
- return userSet.toArray(new String[0]);
+ SortedSet sorted = new TreeSet(userSet);
+ return sorted.toArray(new String[0]);
}
/*
@@ -175,7 +181,8 @@ public class RepoServerMgmt implements RepoServerMgmtMBean, ApplicationContextAw
public String[] listUserNamesAll()
{
Set userSet = authenticationService.getUsersWithTickets(false);
- return userSet.toArray(new String[0]);
+ SortedSet sorted = new TreeSet(userSet);
+ return sorted.toArray(new String[0]);
}
/*
@@ -184,7 +191,9 @@ public class RepoServerMgmt implements RepoServerMgmtMBean, ApplicationContextAw
*/
public int invalidateTicketsExpired()
{
- return authenticationService.invalidateTickets(true);
+ int count = authenticationService.invalidateTickets(true);
+ log.info("Expired tickets invalidated: " + count);
+ return count;
}
/*
@@ -193,24 +202,132 @@ public class RepoServerMgmt implements RepoServerMgmtMBean, ApplicationContextAw
*/
public int invalidateTicketsAll()
{
- return authenticationService.invalidateTickets(false);
+ int count = authenticationService.invalidateTickets(false);
+ log.info("All tickets invalidated: " + count);
+ return count;
}
/*
* (non-Javadoc)
- * @see org.alfresco.mbeans.RepoServerMgmtMBean#allowSingleUserOnly(java.lang.String)
+ * @see org.alfresco.repo.admin.RepoServerMgmtMBean#invalidateUser(java.lang.String)
*/
- public void allowSingleUserOnly(String allowedUsername)
+ public void invalidateUser(String username)
{
+ authenticationService.invalidateUserSession(username);
+ log.info("User invalidated: " + username);
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see org.alfresco.repo.admin.RepoServerMgmtMBean#setSingleUserOnly(java.lang.String)
+ */
+ public void setSingleUserOnly(String allowedUsername)
+ {
+ int maxUsers = getMaxUsers();
+
List allowedUsers = null;
- if (allowedUsername != null)
+ if ((allowedUsername != null) && (! allowedUsername.equals("")))
{
- allowedUsers = new ArrayList(0);
- allowedUsers.add(allowedUsername);
-
- invalidateTicketsAll();
+ if (! allowedUsername.equals(PROPERTY_KEY_SINGLE_USER_ONLY))
+ {
+ allowedUsers = new ArrayList(0);
+ allowedUsers.add(allowedUsername);
+
+ invalidateTicketsAll();
+
+ if (maxUsers != 0)
+ {
+ log.info("Alfresco set to allow single-user (" + allowedUsername + ") logins");
+ }
+ else
+ {
+ log.info("Alfresco set to allow single-user (" + allowedUsername + ") logins - although further logins are currently prevented (limit = 0)");
+ }
+ }
+ }
+ else
+ {
+ if (maxUsers == -1)
+ {
+ log.info("Alfresco set to allow logins (no limit set)");
+ }
+ else if (maxUsers == 0)
+ {
+ log.info("Alfresco set to allow logins - although further logins are currently prevented (limit = 0)");
+ }
+ else if (maxUsers != 0)
+ {
+ log.info("Alfresco set to allow logins (limit = " + maxUsers + ")");
+ }
}
authenticationService.setAllowedUsers(allowedUsers);
}
+
+ /*
+ * (non-Javadoc)
+ * @see org.alfresco.repo.admin.RepoServerMgmtMBean#getSingleUserOnly()
+ */
+ public String getSingleUserOnly()
+ {
+ List allowedUsers = authenticationService.getAllowedUsers();
+ if (allowedUsers != null)
+ {
+ if (allowedUsers.size() > 1)
+ {
+ throw new AlfrescoRuntimeException("Unexpected: more than one user allowed");
+ }
+ if (allowedUsers.size() == 1)
+ {
+ return allowedUsers.get(0);
+ }
+ }
+ return null;
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see org.alfresco.repo.admin.RepoServerMgmtMBean#setMaxUsers(int)
+ */
+ public void setMaxUsers(int maxUsers)
+ {
+ authenticationService.setMaxUsers(maxUsers);
+
+ String singleUserOnlyName = getSingleUserOnly();
+ if (maxUsers == -1)
+ {
+ if ((singleUserOnlyName != null) && (! singleUserOnlyName.equals("")))
+ {
+ log.info("Alfresco set to allow logins (no limit set) - although currently restricted to single-user (" + singleUserOnlyName + ")");
+ }
+ else
+ {
+ log.info("Alfresco set to allow logins (no limit set)");
+ }
+ }
+ else if (maxUsers == 0)
+ {
+ log.info("Alfresco set to prevent further logins (limit = 0)");
+ }
+ else
+ {
+ if ((singleUserOnlyName != null) && (! singleUserOnlyName.equals("")))
+ {
+ log.info("Alfresco set to allow logins (limit = " + maxUsers + ") - although currently restricted to single-user (" + singleUserOnlyName + ")");
+ }
+ else
+ {
+ log.info("Alfresco set to allow logins (limit = " + maxUsers + ")");
+ }
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see org.alfresco.repo.admin.RepoServerMgmtMBean#getMaxUsers()
+ */
+ public int getMaxUsers()
+ {
+ return authenticationService.getMaxUsers();
+ }
}
diff --git a/source/java/org/alfresco/repo/admin/RepoServerMgmtMBean.java b/source/java/org/alfresco/repo/admin/RepoServerMgmtMBean.java
index 17e49d223e..ae2961167b 100644
--- a/source/java/org/alfresco/repo/admin/RepoServerMgmtMBean.java
+++ b/source/java/org/alfresco/repo/admin/RepoServerMgmtMBean.java
@@ -24,14 +24,17 @@
*/
package org.alfresco.repo.admin;
-
+/**
+ * Repository Server Management
+ *
+ * Note: The attributes/operations below can be clustered (ie. when configured all servers in the cluster will be affected)
+ *
+ */
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);
@@ -39,8 +42,6 @@ public interface RepoServerMgmtMBean
/**
* 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();
@@ -50,8 +51,6 @@ public interface RepoServerMgmtMBean
*
* 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();
@@ -61,8 +60,6 @@ public interface RepoServerMgmtMBean
*
* 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();
@@ -72,8 +69,6 @@ public interface RepoServerMgmtMBean
*
* 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();
@@ -83,16 +78,12 @@ public interface RepoServerMgmtMBean
*
* 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
*/
@@ -101,16 +92,12 @@ public interface RepoServerMgmtMBean
/**
* 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
*/
@@ -125,15 +112,57 @@ public interface RepoServerMgmtMBean
*/
public int invalidateTicketsAll();
+ /**
+ * Invalidate given users tickets
+ */
+ public void invalidateUser(String username);
+
/**
* 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)
+ * named user to login (with one or more sessions) assuming maxUsers is not set to 0
*
- * Note: This operation can be clustered (ie. all servers in the cluster will be affected)
+ * Note: This can also be configured at startup. Refer to repository property (server.singleuseronly.name).
*
* @param String allowed username (eg. 'admin') or null to unset (ie. allow all users)
*/
- public void allowSingleUserOnly(String allowedUsername);
+ public void setSingleUserOnly(String allowedUsername);
+
+ /**
+ * If Repository is in single user mode then return the name of the allowed user else return null
+ *
+ * @param String allowed username (eg. 'admin') or null (ie. allow all users)
+ */
+ public String getSingleUserOnly();
+
+ /**
+ * Set limit for max users and/or prevent further logins
+ *
+ * If number of non-expired logins is greater or equal to the limit then further logins will be prevented
+ * otherwise valid login attempt will be permitted, unless the system is in single-user mode.
+ *
+ * Note:
+ *
+ * Max users = 0 prevents further logins (will also prevent single-user mode login)
+ * Max users = -1 allow logins (without a max limit)
+ *
+ * Note: This can also be configured at startup. Refer to repository property (server.maxusers).
+ *
+ * @param maxUsers
+ */
+ public void setMaxUsers(int maxUsers);
+
+ /**
+ * Get limit for max users
+ *
+ * If number of non-expired logins is greater or equal to the limit then further logins will be prevented
+ * otherwise valid login attempt will be permitted. However, single-user mode will take precedence.
+ *
+ * Max users = 0 prevents further logins
+ * Max users = -1 allow logins (without a max limit)
+ *
+ * @param int maxUsers
+ */
+ public int getMaxUsers();
}
diff --git a/source/java/org/alfresco/repo/security/authentication/AuthenticationServiceImpl.java b/source/java/org/alfresco/repo/security/authentication/AuthenticationServiceImpl.java
index 4c912d3584..d3702393e2 100644
--- a/source/java/org/alfresco/repo/security/authentication/AuthenticationServiceImpl.java
+++ b/source/java/org/alfresco/repo/security/authentication/AuthenticationServiceImpl.java
@@ -30,6 +30,7 @@ import java.util.Set;
import org.alfresco.repo.cache.SimpleCache;
import org.alfresco.service.cmr.security.AuthenticationService;
+import org.alfresco.service.cmr.security.PermissionService;
public class AuthenticationServiceImpl implements AuthenticationService
{
@@ -49,7 +50,8 @@ public class AuthenticationServiceImpl implements AuthenticationService
// SysAdmin cache - used to cluster certain JMX operations
private SimpleCache sysAdminCache;
- private final static String KEY_SYSADMIN_ALLOWED_USERS = "sysAdminCache.authAllowedUsers";
+ private final static String KEY_SYSADMIN_ALLOWED_USERS = "sysAdminCache.authAllowedUsers"; // List
+ private final static String KEY_SYSADMIN_MAX_USERS = "sysAdminCache.authMaxUsers"; // Integer
public AuthenticationServiceImpl()
@@ -130,7 +132,15 @@ public class AuthenticationServiceImpl implements AuthenticationService
{
throw new AuthenticationException("Username not allowed: " + userName);
}
- authenticationComponent.authenticate(userName, password);
+
+ Integer maxUsers = (Integer)sysAdminCache.get(KEY_SYSADMIN_MAX_USERS);
+
+ if ((maxUsers != null) && (maxUsers != -1) && (ticketComponent.getUsersWithTickets(true).size() >= maxUsers))
+ {
+ throw new AuthenticationException("Max users exceeded: " + maxUsers);
+ }
+
+ authenticationComponent.authenticate(userName, password);
}
catch(AuthenticationException ae)
{
@@ -166,6 +176,24 @@ public class AuthenticationServiceImpl implements AuthenticationService
{
sysAdminCache.put(KEY_SYSADMIN_ALLOWED_USERS, allowedUsers);
}
+
+ @SuppressWarnings("unchecked")
+ public List getAllowedUsers()
+ {
+ return (List)sysAdminCache.get(KEY_SYSADMIN_ALLOWED_USERS);
+ }
+
+ public void setMaxUsers(int maxUsers)
+ {
+ sysAdminCache.put(KEY_SYSADMIN_MAX_USERS, new Integer(maxUsers));
+ }
+
+ @SuppressWarnings("unchecked")
+ public int getMaxUsers()
+ {
+ Integer maxUsers = (Integer)sysAdminCache.get(KEY_SYSADMIN_MAX_USERS);
+ return (maxUsers == null ? -1 : maxUsers.intValue());
+ }
public void invalidateTicket(String ticket) throws AuthenticationException
{
@@ -219,8 +247,16 @@ public class AuthenticationServiceImpl implements AuthenticationService
return authenticationComponent.isSystemUserName(getCurrentUserName());
}
+ @SuppressWarnings("unchecked")
public void authenticateAsGuest() throws AuthenticationException
{
+ List allowedUsers = (List)sysAdminCache.get(KEY_SYSADMIN_ALLOWED_USERS);
+
+ if ((allowedUsers != null) && (! allowedUsers.contains(PermissionService.GUEST_AUTHORITY)))
+ {
+ throw new AuthenticationException("Guest authentication is not allowed");
+ }
+
authenticationComponent.setGuestUserAsCurrentUser();
ticketComponent.clearCurrentTicket();
}