org.exist.security.SimpleACLPermission Maven / Gradle / Ivy
/*
* eXist-db Open Source Native XML Database
* Copyright (C) 2001 The eXist-db Authors
*
* [email protected]
* http://www.exist-db.org
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
package org.exist.security;
import java.io.IOException;
import java.util.Arrays;
import org.exist.storage.io.VariableByteInput;
import org.exist.storage.io.VariableByteOutputStream;
import static org.exist.security.PermissionRequired.IS_DBA;
import static org.exist.security.PermissionRequired.IS_OWNER;
import static org.exist.security.PermissionRequired.ACL_WRITE;
/**
* A simple ACL (Access Control List) implementation
* which extends UnixStylePermission with additional
* ACEs (Access Control Entries).
*
* everyone has READ_ACL
* WRITE access implies WRITE_ACL
*
* @author Adam Retter
*/
public class SimpleACLPermission extends UnixStylePermission implements ACLPermission {
public static final short VERSION = 1;
private static final int MAX_ACL_LENGTH = 255; //restrict to sizeof 1 byte
private int[] acl = new int[0];
public SimpleACLPermission(final SecurityManager sm) {
super(sm);
}
public SimpleACLPermission(final SecurityManager sm, final long vector) {
super(sm, vector);
}
public SimpleACLPermission(final SecurityManager sm, final int ownerId, final int groupId, final int mode) {
super(sm, ownerId, groupId, mode);
}
public void addUserACE(final ACE_ACCESS_TYPE access_type, final int userId, final int mode) throws PermissionDeniedException {
addACE(access_type, ACE_TARGET.USER, userId, mode);
}
public void addGroupACE(final ACE_ACCESS_TYPE access_type, final int groupId, final int mode) throws PermissionDeniedException {
addACE(access_type, ACE_TARGET.GROUP, groupId, mode);
}
@Override
public void addACE(final ACE_ACCESS_TYPE access_type, final ACE_TARGET target, final String name, final String modeStr) throws PermissionDeniedException {
addACE(access_type, target, lookupTargetId(target, name), aceSimpleSymbolicModeToInt(modeStr));
}
@Override
public void addACE(final ACE_ACCESS_TYPE access_type, final ACE_TARGET target, final String name, final int mode) throws PermissionDeniedException {
addACE(access_type, target, lookupTargetId(target, name), mode);
}
@PermissionRequired(user = IS_DBA | IS_OWNER, mode = ACL_WRITE)
private void addACE(final ACE_ACCESS_TYPE access_type, final ACE_TARGET target, final int id, final int mode) throws PermissionDeniedException {
if (acl.length >= MAX_ACL_LENGTH) {
throw new PermissionDeniedException("Maximum of " + MAX_ACL_LENGTH + " ACEs has been reached.");
}
final int[] newAcl = new int[acl.length + 1];
System.arraycopy(acl, 0, newAcl, 0, acl.length);
newAcl[newAcl.length - 1] = encodeAsACE(access_type, target, id, mode);
this.acl = newAcl;
}
public void insertUserACE(final int index, final ACE_ACCESS_TYPE access_type, final int userId, final int mode) throws PermissionDeniedException {
insertACE(index, access_type, ACE_TARGET.USER, userId, mode);
}
public void insertGroupACE(final int index, final ACE_ACCESS_TYPE access_type, final int groupId, final int mode) throws PermissionDeniedException {
insertACE(index, access_type, ACE_TARGET.GROUP, groupId, mode);
}
@Override
public void insertACE(final int index, final ACE_ACCESS_TYPE access_type, final ACE_TARGET target, final String name, final String modeStr) throws PermissionDeniedException {
insertACE(index, access_type, target, lookupTargetId(target, name), aceSimpleSymbolicModeToInt(modeStr));
}
@PermissionRequired(user = IS_DBA | IS_OWNER, mode = ACL_WRITE)
private void insertACE(final int index, final ACE_ACCESS_TYPE access_type, final ACE_TARGET target, final int id, final int mode) throws PermissionDeniedException {
if (acl.length >= MAX_ACL_LENGTH) {
throw new PermissionDeniedException("Maximum of " + MAX_ACL_LENGTH + " ACEs has been reached.");
}
if (index < 0 || (acl.length > 0 && acl.length <= index)) {
throw new PermissionDeniedException("No Such ACE index " + index + " in ACL.");
}
final int[] newAcl = new int[acl.length + 1];
System.arraycopy(acl, 0, newAcl, 0, index);
newAcl[index] = encodeAsACE(access_type, target, id, mode);
if (acl.length > 0) {
System.arraycopy(acl, index, newAcl, index + 1, newAcl.length - index - 1);
}
this.acl = newAcl;
}
/**
* Converts the mode string for an ACE to an int.
*
* @param modeStr the mode string for the ACE is simple symbolic format, must be between 1 and 3 characters.
*
* @return the octal mode encoded as an int.
*
* @throws PermissionDeniedException if the mode string is invalid
*/
public static int aceSimpleSymbolicModeToInt(final String modeStr) throws PermissionDeniedException {
if (modeStr == null || modeStr.isEmpty() || modeStr.length() > 3) {
throw new PermissionDeniedException("Invalid mode string '" + modeStr + "'");
}
int mode = 0;
for (final char c : modeStr.toCharArray()) {
switch (c) {
case READ_CHAR:
mode |= READ;
break;
case WRITE_CHAR:
mode |= WRITE;
break;
case EXECUTE_CHAR:
mode |= EXECUTE;
break;
case UNSET_CHAR:
break;
default:
throw new PermissionDeniedException("Unknown char '" + c + "' in mode string '" + modeStr + "'");
}
}
return mode;
}
private int lookupTargetId(final ACE_TARGET target, final String targetName) throws PermissionDeniedException {
final int id;
if (target == ACE_TARGET.USER) {
final Account account = sm.getAccount(targetName);
if (account == null) {
throw new PermissionDeniedException("User Account for username '" + targetName + "' is unknown.");
}
id = account.getId();
} else if (target == ACE_TARGET.GROUP) {
final Group group = sm.getGroup(targetName);
if (group == null) {
throw new PermissionDeniedException("User Group for groupname '" + targetName + "' is unknown.");
}
id = group.getId();
} else {
throw new PermissionDeniedException("Unknown ACE_TARGET type");
}
return id;
}
/**
* should return max of 29 bits - e.g. The maximum numeric value - 536870911
* exact encoding is [target(3),id(20),mode(3),access_type(3)]
*/
private int encodeAsACE(final ACE_ACCESS_TYPE access_type, final ACE_TARGET target, int id, int mode) {
//ensure mode is just 3 bits max (rwu) - TODO(AR) maybe error if not 20 bits
mode = mode & 7;
//makes sure id is only 20 bits max - TODO(AR) maybe error if not 20 bits
id = id & 1048575;
return (target.getVal() << 26) | (id << 6) | (mode << 3) | access_type.getVal();
}
@PermissionRequired(user = IS_DBA | IS_OWNER, mode = ACL_WRITE)
@Override
public void removeACE(final int index) throws PermissionDeniedException {
if (index < 0 || index >= acl.length) {
throw new PermissionDeniedException("ACL Entry does not exist");
}
final int[] newAcl = new int[acl.length - 1];
System.arraycopy(acl, 0, newAcl, 0, index);
System.arraycopy(acl, index + 1, newAcl, index, newAcl.length - index);
this.acl = newAcl;
}
@Override
public void modifyACE(final int index, final ACE_ACCESS_TYPE access_type, final String modeStr) throws PermissionDeniedException {
modifyACE(index, access_type, aceSimpleSymbolicModeToInt(modeStr));
}
@PermissionRequired(user = IS_DBA | IS_OWNER, mode = ACL_WRITE)
@Override
public void modifyACE(final int index, final ACE_ACCESS_TYPE access_type, final int mode) throws PermissionDeniedException {
if (index < 0 || index >= acl.length) {
throw new PermissionDeniedException("ACL Entry does not exist");
}
final int ace = acl[index];
acl[index] = ((ace >>> 6) << 6) | (mode << 3) | access_type.getVal();
}
/**
* Clears all ACE's
*/
@PermissionRequired(user = IS_DBA | IS_OWNER, mode = ACL_WRITE)
@Override
public void clear() {
acl = new int[0];
}
public int getACEId(final int index) {
return (acl[index] >>> 6) & 1048575;
}
/**
* Convenience method for getting the name of the user or group
* of which this ace is applied to
*/
@Override
public String getACEWho(final int index) {
switch (getACETarget(index)) {
case USER:
return sm.getAccount(getACEId(index)).getName();
case GROUP:
return sm.getGroup(getACEId(index)).getName();
default:
return null;
}
}
@Override
public int getACEMode(final int index) {
return (acl[index] >>> 3) & 7;
}
public String getACEModeString(final int index) {
final int aceMode = getACEMode(index);
final char[] ch = new char[]{
(aceMode & READ) != READ ? UNSET_CHAR : READ_CHAR,
(aceMode & WRITE) != WRITE ? UNSET_CHAR : WRITE_CHAR,
(aceMode & EXECUTE) != EXECUTE ? UNSET_CHAR : EXECUTE_CHAR
};
return String.valueOf(ch);
}
@Override
public ACE_TARGET getACETarget(final int index) {
return ACE_TARGET.fromVal(acl[index] >>> 26);
}
@Override
public ACE_ACCESS_TYPE getACEAccessType(final int index) {
return ACE_ACCESS_TYPE.fromVal(acl[index] & 7);
}
@Override
public int getACECount() {
return acl.length;
}
@Override
public void read(final VariableByteInput istream) throws IOException {
super.read(istream);
final int aclLength = istream.read();
acl = new int[aclLength];
for (int i = 0; i < aclLength; i++) {
acl[i] = istream.readInt();
}
}
@Override
public void write(final VariableByteOutputStream ostream) throws IOException {
super.write(ostream);
ostream.write(acl.length);
for (final int ace : acl) {
ostream.writeInt(ace);
}
}
/**
* Evaluation order is -
*
* 1) ACL ACEs are evaluated first
* 2) Classic Unix Style Permissions are evaluated second
*
* The first match is considered the authority
*/
@Override
public boolean validate(final Subject user, final int mode) {
//group dba has full access
if (user.hasDbaRole()) {
return true;
}
final int userId = user.getId();
final int[] userGroupIds = user.getGroupIds();
/*
* START EXTENDED ACL VALIDATION.
*
* exact encoding is [target(3),id(20),mode(3),access_type(3)]
*/
//check ACL
for (final int ace : acl) {
final int aceTarget = ace >>> 26;
final int id = (ace >>> 6) & 1048575;
final int aceMode = (ace >>> 3) & 7;
final int accessType = ace & 7;
if ((aceTarget & ACE_TARGET.USER.getVal()) == ACE_TARGET.USER.getVal()) {
//check for a user
if (id == userId && (aceMode & mode) == mode) {
return (accessType == ACE_ACCESS_TYPE.ALLOWED.getVal());
}
} else if ((aceTarget & ACE_TARGET.GROUP.getVal()) == ACE_TARGET.GROUP.getVal()) {
//check for a group
for (final int userGroupId : userGroupIds) {
if (userGroupId == id && (aceMode & mode) == mode) {
return (accessType == ACE_ACCESS_TYPE.ALLOWED.getVal());
}
}
}
}
/*
* END EXTENDED ACL VALIDATION
*/
/*
* FALLBACK to UNIX STYLE VALIDATION
*/
//check owner
if (userId == (vector >>> 32)) { //check owner
return (mode & ((vector >>> 28) & 7)) == mode; //check owner mode
}
//check group
final int groupId = (int) ((vector >>> 8) & 1048575);
for (final int userGroupId : userGroupIds) {
if (userGroupId == groupId) {
return (mode & ((vector >>> 4) & 7)) == mode;
}
}
//check other
if ((mode & (vector & 7)) == mode) {
return true;
}
/*
* END FALLBACK to UNIX STYLE VALIDATION
*/
return false;
}
@Override
public short getVersion() {
return VERSION;
}
@Override
public boolean isCurrentSubjectCanWriteACL() {
return validate(getCurrentSubject(), WRITE);
}
@Override
public SimpleACLPermission copy() {
final SimpleACLPermission prm = new SimpleACLPermission(sm, vector);
prm.acl = new int[acl.length];
System.arraycopy(acl, 0, prm.acl, 0, acl.length);
return prm;
}
/**
* Determines if this permisisons ACL is equal to that
* of another permissions ACL.
*
* @param other the other ACL to check equality against.
*
* @return true if the ACLs are equal
*/
public boolean equalsAcl(final SimpleACLPermission other) {
if (other == null || other.getACECount() != getACECount()) {
return false;
}
for (int i = 0; i < getACECount(); i++) {
if(getACEAccessType(i) != other.getACEAccessType(i)
|| getACETarget(i) != other.getACETarget(i)
|| (!getACEWho(i).equals(other.getACEWho(i)))
|| getACEMode(i) != other.getACEMode(i)) {
return false;
}
}
return true;
}
@PermissionRequired(user = IS_DBA | IS_OWNER, mode = ACL_WRITE)
public void copyAclOf(final SimpleACLPermission simpleACLPermission) {
this.acl = Arrays.copyOf(simpleACLPermission.acl, simpleACLPermission.acl.length);
}
@Override
public boolean aclEquals(final ACLPermission other) {
if (other == null) {
return false;
}
if (other instanceof SimpleACLPermission) {
// optimisation for when both are the same type
return Arrays.equals(acl, ((SimpleACLPermission) other).acl);
} else {
if (getACECount() != other.getACECount()) {
return false;
}
for (int i = 0; i < getACECount(); i++) {
if (getACEAccessType(i) != other.getACEAccessType(i)
|| getACETarget(i) != other.getACETarget(i)
|| (!getACEWho(i).equals(other.getACEWho(i)))
|| getACEMode(i) != other.getACEMode(i)) {
return false;
}
}
return true;
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy