/*
* SecurityManager.java
*
* This work 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 work 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307
* USA
*
* Copyright (c) 2004-2006 Per Cederberg. All rights reserved.
*/
package org.liquidsite.core.content;
/**
* A content security manager. This class will be called to check all
* protected operations on domain and content objects. Other database
* objects may use the security manager to check the permissions on
* related domain or content objects.
*
* @author Per Cederberg, <per at percederberg dot net>
* @version 1.0
*/
class SecurityManager {
/**
* The read access level.
*/
private static final int READ = 1;
/**
* The write access level.
*/
private static final int WRITE = 2;
/**
* The publish access level.
*/
private static final int PUBLISH = 3;
/**
* The admin access level.
*/
private static final int ADMIN = 4;
/**
* The one and only security manager instance.
*/
private static final SecurityManager INSTANCE = new SecurityManager();
/**
* The security manager instance.
*
* @return the security manager instance
*/
public static SecurityManager getInstance() {
return INSTANCE;
}
/**
* Creates a new content security manager.
*/
private SecurityManager() {
// No initialization needed
}
/**
* Checks the read access for a user on a persistent object.
*
* @param user the user to check, or null for none
* @param obj the persistent object
*
* @return true if the user has read access, or
* false otherwise
*
* @throws ContentException if the database couldn't be accessed
* properly
*/
public boolean hasReadAccess(User user, PersistentObject obj)
throws ContentException {
return hasAccess(user, obj, READ);
}
/**
* Checks the write access for a user on a persistent object.
*
* @param user the user to check, or null for none
* @param obj the persistent object
*
* @return true if the user has write access, or
* false otherwise
*
* @throws ContentException if the database couldn't be accessed
* properly
*/
public boolean hasWriteAccess(User user, PersistentObject obj)
throws ContentException {
return hasAccess(user, obj, WRITE);
}
/**
* Checks the publish access for a user on a persistent object.
* Note that false if always returned for the object where
* publish access isn't applicable.
*
* @param user the user to check, or null for none
* @param obj the persistent object
*
* @return true if the user has publish access, or
* false otherwise
*
* @throws ContentException if the database couldn't be accessed
* properly
*/
public boolean hasPublishAccess(User user, PersistentObject obj)
throws ContentException {
return hasAccess(user, obj, PUBLISH);
}
/**
* Checks the admin access for a user on a persistent object.
* Note that false if always returned for the object where
* admin access isn't applicable.
*
* @param user the user to check, or null for none
* @param obj the persistent object
*
* @return true if the user has admin access, or
* false otherwise
*
* @throws ContentException if the database couldn't be accessed
* properly
*/
public boolean hasAdminAccess(User user, PersistentObject obj)
throws ContentException {
return hasAccess(user, obj, ADMIN);
}
/**
* Checks the access for a user on a persistent object. In the
* absence of permissions or if a permission isn't applicable,
* false is returned.
*
* @param user the user to check, or null for none
* @param obj the persistent object to check
* @param access the access level to check for
*
* @return true if the user has the specified access level, or
* false otherwise
*
* @throws ContentException if the database couldn't be accessed
* properly
*/
private boolean hasAccess(User user, PersistentObject obj, int access)
throws ContentException {
if (obj instanceof Domain) {
return hasAccess(user, (Domain) obj, access);
} else if (obj instanceof Content) {
return hasAccess(user, (Content) obj, access);
} else if (obj instanceof PermissionList) {
if (((PermissionList) obj).getContentId() > 0) {
obj = ((PermissionList) obj).getContent();
} else {
obj = ((PermissionList) obj).getDomain();
}
if (access == READ) {
return hasAccess(user, obj, READ);
} else if (access == WRITE) {
return hasAccess(user, obj, ADMIN);
} else {
return false;
}
} else if (obj instanceof Lock) {
if (access == READ || access == WRITE) {
return hasAccess(user, ((Lock) obj).getContent(), access);
} else {
return false;
}
} else if (obj instanceof User) {
return hasAccess(user, (User) obj, access);
} else if (obj instanceof Group) {
return hasAccess(user, (Group) obj, access);
} else {
return false;
}
}
/**
* Checks the domain access for a user. In the absence of
* permissions, false is returned.
*
* @param user the user to check, or null for none
* @param domain the domain to check
* @param access the access level to check for
*
* @return true if the user has the specified access level, or
* false otherwise
*
* @throws ContentException if the database couldn't be accessed
* properly
*/
private boolean hasAccess(User user, Domain domain, int access)
throws ContentException {
Group[] groups = null;
Permission[] perms;
// Check for superuser
if (user != null && user.isSuperUser()) {
return true;
}
// Check domain permissions
if (user != null) {
if (!user.getDomainName().equals(domain.getName())) {
user = null;
} else {
groups = user.getGroups();
}
}
perms = domain.getPermissions().getPermissions();
for (int i = 0; i < perms.length; i++) {
if (hasAccess(user, groups, perms[i], access)) {
return true;
}
}
return false;
}
/**
* Checks the content access for a user. If no content
* permissions are set for the content object, the parent
* permissions will be checked instead. In the absence of a
* content parent, the domain permissions will be checked.
*
* @param user the user to check, or null for none
* @param content the content object to check
* @param access the access level to check for
*
* @return true if the user has the specified access level, or
* false otherwise
*
* @throws ContentException if the database couldn't be accessed
* properly
*/
private boolean hasAccess(User user, Content content, int access)
throws ContentException {
Group[] groups = null;
Permission[] perms;
// Check for prohibited local permissions
if (access == ADMIN && !hasPermissionList(content)) {
return false;
}
// Check for superuser
if (user != null && user.isSuperUser()) {
return true;
}
// Check content permissions
if (user != null) {
if (!user.getDomainName().equals(content.getDomainName())) {
user = null;
} else {
groups = user.getGroups();
}
}
perms = content.getPermissions(true).getPermissions();
for (int i = 0; i < perms.length; i++) {
if (hasAccess(user, groups, perms[i], access)) {
return true;
}
}
return false;
}
/**
* Checks the user object access for a user. Any user can read a
* user object, but only domain administrators and the user
* itself can write a user object.
*
* @param user the user, or null for none
* @param obj the user object to check
* @param access the access level to check for
*
* @return true if the user has the specified access level, or
* false otherwise
*
* @throws ContentException if the database couldn't be accessed
* properly
*/
private boolean hasAccess(User user, User obj, int access)
throws ContentException {
if (access == READ) {
return true;
} else if (access == WRITE && user != null) {
return user.isSuperUser()
|| user.equals(obj)
|| (obj.getDomain() != null &&
hasAccess(user, obj.getDomain(), ADMIN));
} else {
return false;
}
}
/**
* Checks the group object access for a user. Any user can read a
* group object, but only domain administrators can write a group
* object.
*
* @param user the user, or null for none
* @param obj the group object to check
* @param access the access level to check for
*
* @return true if the user has the specified access level, or
* false otherwise
*
* @throws ContentException if the database couldn't be accessed
* properly
*/
private boolean hasAccess(User user, Group obj, int access)
throws ContentException {
if (access == READ) {
return true;
} else if (access == WRITE && user != null) {
return user.isSuperUser()
|| hasAccess(user, obj.getDomain(), ADMIN);
} else {
return false;
}
}
/**
* Checks the access level for a user on a permission.
*
* @param user the user to check, or null for none
* @param groups the user groups, or null for none
* @param perm the permission to check
* @param access the access level to check for
*
* @return true if the user has the specified access level, or
* false otherwise
*/
private boolean hasAccess(User user,
Group[] groups,
Permission perm,
int access) {
if (!perm.isMatch(user, groups)) {
return false;
} else if (access == READ && perm.getRead()) {
return true;
} else if (access == WRITE && perm.getWrite()) {
return true;
} else if (access == PUBLISH && perm.getPublish()) {
return true;
} else if (access == ADMIN && perm.getAdmin()) {
return true;
} else {
return false;
}
}
/**
* Checks if the specified content object can have a permission
* list. Some categories of content objects cannot have local
* permission lists (for performance reasons) and thus depend
* purely on inherited permissions.
*
* @param content the content object to check
*
* @return true if the object can have a permission list, or
* false otherwise
*
* @throws ContentException if the database couldn't be accessed
* properly
*/
private boolean hasPermissionList(Content content)
throws ContentException {
switch (content.getCategory()) {
case Content.FILE_CATEGORY:
return hasPermissionList(content.getParent());
case Content.DOCUMENT_CATEGORY:
case Content.TOPIC_CATEGORY:
case Content.POST_CATEGORY:
return false;
default:
return true;
}
}
/**
* Verifies that a user has access to insert a persistent object.
*
* @param user the user to check, or null for none
* @param obj the persistent object
*
* @throws ContentException if the database couldn't be accessed
* properly
* @throws ContentSecurityException if the user didn't have the
* specified access
*/
public void checkInsert(User user, PersistentObject obj)
throws ContentException, ContentSecurityException {
if (obj instanceof Domain) {
checkSuperUserAccess(user, (Domain) obj);
} else if (obj instanceof Content) {
if (((Content) obj).getRevisionNumber() > 0) {
checkPublishAccess(user, (Content) obj);
} else {
checkWriteAccess(user, (Content) obj);
}
} else if (obj instanceof PermissionList) {
if (((PermissionList) obj).getContentId() > 0) {
checkAdminAccess(user, ((PermissionList) obj).getContent());
} else {
checkAdminAccess(user, ((PermissionList) obj).getDomain());
}
} else if (obj instanceof Lock) {
checkWriteAccess(user, ((Lock) obj).getContent());
} else if (obj instanceof User) {
checkWriteAccess(user, (User) obj);
} else if (obj instanceof Group) {
checkWriteAccess(user, (Group) obj);
} else {
throw new ContentSecurityException("persistent object " +
"class unknown: " +
obj.getClass());
}
}
/**
* Verifies that a user has access to update a persistent object.
*
* @param user the user to check, or null for none
* @param obj the persistent object
*
* @throws ContentException if the database couldn't be accessed
* properly
* @throws ContentSecurityException if the user didn't have the
* specified access
*/
public void checkUpdate(User user, PersistentObject obj)
throws ContentException, ContentSecurityException {
if (obj instanceof Domain) {
checkSuperUserAccess(user, (Domain) obj);
} else if (obj instanceof Content) {
if (((Content) obj).getRevisionNumber() > 0) {
checkPublishAccess(user, (Content) obj);
} else {
checkWriteAccess(user, (Content) obj);
}
} else if (obj instanceof PermissionList) {
if (((PermissionList) obj).getContentId() > 0) {
checkAdminAccess(user, ((PermissionList) obj).getContent());
} else {
checkAdminAccess(user, ((PermissionList) obj).getDomain());
}
} else if (obj instanceof Lock) {
throw new ContentSecurityException("content locks cannot " +
"be updated");
} else if (obj instanceof User) {
checkWriteAccess(user, (User) obj);
} else if (obj instanceof Group) {
checkWriteAccess(user, (Group) obj);
} else {
throw new ContentSecurityException("persistent object " +
"class unknown: " +
obj.getClass());
}
}
/**
* Verifies that a user has access to restore a persistent object.
*
* @param user the user to check, or null for none
* @param obj the persistent object
*
* @throws ContentException if the database couldn't be accessed
* properly
* @throws ContentSecurityException if the user didn't have the
* specified access
*/
public void checkRestore(User user, PersistentObject obj)
throws ContentException, ContentSecurityException {
if (obj instanceof Domain) {
checkSuperUserAccess(user, (Domain) obj);
} else if (obj instanceof Content) {
checkSuperUserAccess(user, (Content) obj);
} else if (obj instanceof User) {
checkSuperUserAccess(user, (User) obj);
} else if (obj instanceof Group) {
checkSuperUserAccess(user, (Group) obj);
} else {
checkInsert(user, obj);
}
}
/**
* Verifies that a user has access to delete a persistent object.
*
* @param user the user to check, or null for none
* @param obj the persistent object
*
* @throws ContentException if the database couldn't be accessed
* properly
* @throws ContentSecurityException if the user didn't have the
* specified access
*/
public void checkDelete(User user, PersistentObject obj)
throws ContentException, ContentSecurityException {
if (obj instanceof Domain) {
checkSuperUserAccess(user, (Domain) obj);
} else if (obj instanceof Content) {
checkPublishAccess(user, (Content) obj);
} else if (obj instanceof PermissionList) {
if (((PermissionList) obj).getContentId() > 0) {
checkAdminAccess(user, ((PermissionList) obj).getContent());
} else {
checkAdminAccess(user, ((PermissionList) obj).getDomain());
}
} else if (obj instanceof Lock) {
checkWriteAccess(user, ((Lock) obj).getContent());
} else if (obj instanceof User) {
checkWriteAccess(user, (User) obj);
} else if (obj instanceof Group) {
checkWriteAccess(user, (Group) obj);
} else {
throw new ContentSecurityException("persistent object " +
"class unknown: " +
obj.getClass());
}
}
/**
* Verifies that a user has superuser access. The domain being
* modified must also be specified.
*
* @param user the user to check, or null for none
* @param domain the domain object
*
* @throws ContentSecurityException if the user didn't have the
* specified access
*/
private void checkSuperUserAccess(User user, Domain domain)
throws ContentSecurityException {
if (!user.isSuperUser()) {
throw new ContentSecurityException(user, "write", domain);
}
}
/**
* Verifies that a user has superuser access. The content being
* modified must also be specified.
*
* @param user the user to check, or null for none
* @param content the content object
*
* @throws ContentSecurityException if the user didn't have the
* specified access
*/
private void checkSuperUserAccess(User user, Content content)
throws ContentSecurityException {
if (!user.isSuperUser()) {
throw new ContentSecurityException(user, "write", content);
}
}
/**
* Verifies that a user has superuser access. The user being
* modified must also be specified.
*
* @param user the user to check, or null for none
* @param obj the user object
*
* @throws ContentSecurityException if the user didn't have the
* specified access
*/
private void checkSuperUserAccess(User user, User obj)
throws ContentSecurityException {
if (!user.isSuperUser()) {
throw new ContentSecurityException(user, "write", obj);
}
}
/**
* Verifies that a user has superuser access. The group being
* modified must also be specified.
*
* @param user the user to check, or null for none
* @param obj the group object
*
* @throws ContentSecurityException if the user didn't have the
* specified access
*/
private void checkSuperUserAccess(User user, Group obj)
throws ContentSecurityException {
if (!user.isSuperUser()) {
throw new ContentSecurityException(user, "write", obj);
}
}
/**
* Verifies that a user has write access on a content object.
*
* @param user the user to check, or null for none
* @param content the content object
*
* @throws ContentException if the database couldn't be accessed
* properly
* @throws ContentSecurityException if the user didn't have the
* specified access
*/
private void checkWriteAccess(User user, Content content)
throws ContentException, ContentSecurityException {
if (!hasWriteAccess(user, content)) {
throw new ContentSecurityException(user, "write", content);
}
}
/**
* Verifies that a user has write access on a user object.
*
* @param user the user to check, or null for none
* @param obj the user object
*
* @throws ContentException if the database couldn't be accessed
* properly
* @throws ContentSecurityException if the user didn't have the
* specified access
*/
private void checkWriteAccess(User user, User obj)
throws ContentException, ContentSecurityException {
if (!hasWriteAccess(user, obj)) {
throw new ContentSecurityException(user, "write", obj);
}
}
/**
* Verifies that a user has write access on a group object.
*
* @param user the user to check, or null for none
* @param obj the group object
*
* @throws ContentException if the database couldn't be accessed
* properly
* @throws ContentSecurityException if the user didn't have the
* specified access
*/
private void checkWriteAccess(User user, Group obj)
throws ContentException, ContentSecurityException {
if (!hasWriteAccess(user, obj)) {
throw new ContentSecurityException(user, "write", obj);
}
}
/**
* Verifies that a user has publish access on a content object.
*
* @param user the user to check, or null for none
* @param content the content object
*
* @throws ContentException if the database couldn't be accessed
* properly
* @throws ContentSecurityException if the user didn't have the
* specified access
*/
private void checkPublishAccess(User user, Content content)
throws ContentException, ContentSecurityException {
if (!hasPublishAccess(user, content)) {
throw new ContentSecurityException(user, "publish", content);
}
}
/**
* Verifies that a user has admin access on a domain object.
*
* @param user the user to check, or null for none
* @param domain the domain object
*
* @throws ContentException if the database couldn't be accessed
* properly
* @throws ContentSecurityException if the user didn't have the
* specified access
*/
private void checkAdminAccess(User user, Domain domain)
throws ContentException, ContentSecurityException {
if (!hasAdminAccess(user, domain)) {
throw new ContentSecurityException(user, "admin", domain);
}
}
/**
* Verifies that a user has admin access on a content object.
*
* @param user the user to check, or null for none
* @param content the content object
*
* @throws ContentException if the database couldn't be accessed
* properly
* @throws ContentSecurityException if the user didn't have the
* specified access
*/
private void checkAdminAccess(User user, Content content)
throws ContentException, ContentSecurityException {
if (!hasAdminAccess(user, content)) {
throw new ContentSecurityException(user, "admin", content);
}
}
}