Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.
Comment: Migrated to Confluence 5.3

...

Specific changes that will be made to implement the solutions described above.

5.

...

05 Permissions behavioral rules

  • we only support comparing 2 permissions of type WebloggerPermission, and WebloggerPermission cannot be subclassed
  • a permission with type = null indicates a global (application wide) permission
  • permissions that are global (type = null) cannot specify an object (global perms do not pertain to objects)
  • permissions of a specific type must also specify an object (typed perms must correspond to an object)
  • permissions must be of the same type to be compared (we do not compare global and 'weblog' permissions)
  • permissions of a specific type must also specify the same object to be compared ('weblog' perms for different weblogs are never comparable)
  • a permission action can only imply other permissions of the same type ('weblog' perms can only imply other 'weblog' perms)
  • the 'all' action is a special action that implies all possible actions within the specified permission type/object combo
  • a global permission with the 'all' action implies all permissions in the system (the global admin role)
  • a permission can imply any number of other permissions

5.1 Define new permissions classes

Define new permissions classes using java.security.Permission as the base class. A Permssion object defines a list of "actions" that are permitted.

Class WebloggerPermission extends java.security.Permission. This is the permission class for all Roller Weblogger permissions and really only extends the base Permission class to include the ideas that a Permission has both a "type" and an "object" to which the set of actions apply. This class also implements the very important Permission method implies() which contains the logic sorts out if one permission implies another.

Key differences between this class and the version(s) suggested in the proposal:

  • All instances of this class are immutable, which is supposed to be part of the contract when using any java.security.Permission object. From the javadoc "Permission objects are similar to String objects in that they are immutable once they have been created. Subclasses should not provide methods that can change the state of a permission once it has been created."
  • This class is not meant to be subclassed and thus "could" potentially be written as a final class. The implementation is purposely generic and does not require a subclass per "type" such as WeblogPermission. This is beneficial because the security system can then be expanded dynamically without requiring new code and Permission classes.
Code Block

package org.apache.roller.weblogger.pojos;

import java.security.Permission;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import org.apache.roller.weblogger.util.Utilities;


/**
 * Base permission class for Weblogger. 
 */
public final class WebloggerPermission extends java.security.Permission {
    
    private final String type;
    private final String object;
    private final String actions;
    private final Set<String> actionsAsSet;
    private final Set<String> completeActionsAsSet;
    
    
    public WebloggerPermission(String actions) {
        this(null, null, actions);
    }
    
    public WebloggerPermission(String type, String object, String actions) {
        super("WebloggerPermission

Define new permissions classes using java.security.Permission as the base class. A Permssion object defines a list of "actions" that are permitted.

Class WebloggerPermission extends java.security.Permission. This is the permission class for all Roller Weblogger permissions and really only extends the base Permission class to include the ideas that a Permission has both a "type" and an "object" to which the set of actions apply. This class also implements the very important Permission method implies() which contains the logic sorts out if one permission implies another.

Key differences between this class and the version(s) suggested in the proposal:

  • All instances of this class are immutable, which is supposed to be part of the contract when using any java.security.Permission object. From the javadoc "Permission objects are similar to String objects in that they are immutable once they have been created. Subclasses should not provide methods that can change the state of a permission once it has been created."
  • This class is not meant to be subclassed and thus "could" potentially be written as a final class. The implementation is purposely generic and does not require a subclass per "type" such as WeblogPermission. This is beneficial because the security system can then be expanded dynamically without requiring new code and Permission classes.
Code Block

package org.apache.roller.weblogger.pojos;

import java.security.Permission;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import org.apache.roller.weblogger.util.Utilities;


/**
 * Base permission class for Weblogger. 
 */
public final class WebloggerPermission extends java.security.Permission {
    
    private final String type;
    private final String object;
    private final String actions;
    private final Set<String> actionsAsSet;
    private final Set<String> completeActionsAsSet;
    
    
    public WebloggerPermission(String actions) {
        this(null, null, actions);
    }
    
    public WebloggerPermission(String type, String object, String actions) {
        super("WebloggerPermission");
        
        if((type != null && object == null) || (type == null && object != null))
            throw new IllegalArgumentException("'type' and 'object' must be either both null or both non-null.");
        
        this.if((type != type;
        this.object = object;
 null && object == null) || (type == null && object != null))
         this.actions = actions;
        this.actionsAsSet = Utilities.stringToStringSet(actions, ","   throw new IllegalArgumentException("'type' and 'object' must be either both null or both non-null.");
        
        this.completeActionsAsSettype = buildFullActionsSet(actionsAsSet)type;
    }
    this.object = object;
    
    // -this.actions = actions;
        this.actionsAsSet = Utilities.stringToStringSet(actions, ",");
        this.completeActionsAsSet = buildFullActionsSet(actionsAsSet);
    }
    
    
    // --------------------------------------------------- Public Methods
    
    
    public String getType() {
        return type;
    }
    
    public String getObject() {
        return object;
    }
    
    public String getActions() {
        return actions;
    }

    public Set<String> getActionsAsSet() {
        return actionsAsSet;
    }
    
    
    public boolean implies(Permission permission) {
        
        // only compare WebloggerPermission objects
        if (!(permission instanceof WebloggerPermission))
            return false;
            
        WebloggerPermission that = (WebloggerPermission) permission;
        
        // only compare 2 permissions of the same type
        if(that.getType() != null) {
            // this type == null or the 2 types are not equal, invalid comparison
            if(this.getType() == null || !(this.getType().equals(that.getType())))
                    return false;
        } else if(this.getType() != null) {
            // that has type == null but this has type != null, invalid comparison
            return false;
        }
        
        // only compare 2 permissions of the same object
        if(that.getObject() != null) {
            // this object == null or the 2 objects are not equal, invalid comparison
            if(this.getObject() == null || !that.getObject().equals(this.getObject()))
                return false;
        } else if(this.getObject() != null) {
            // that has object == null but this has object != null, invalid comparison
            return false;
        }
        
        // does "this" permission contain all actions of "that" permission?
        
        // we consider a permission implied if our set of granted actions
        // contains all of the actions of the permission we are checking
        if(this.completeActionsAsSet.containsAll(that.completeActionsAsSet))
            return true;
        
        return false;
    }
    
    
    public boolean equals(Object arg0) {
        throw new UnsupportedOperationException("Not supported yet.");
    }

    public int hashCode() {
        throw new UnsupportedOperationException("Not supported yet.");
    }
    
    @Override
    public String toString() {
        StringBuilder sb = new StringBuilder();
        sb.append(getName()).append(" : ");
        for (String action : getActionsAsSet()) { 
            sb.append(" ").append(action).append(" ");
        }
        return sb.toString();
    }
    
    
    // --------------------------------------------------- Private Methods
    
    
    // build an expanded Set of all actions implied by a Set of actions
    private Set<String> buildFullActionsSet(Set<String> actions) {
        
        Set<String> fullSet = new HashSet<String>();
        
        for( String action : actions ) {
            Set<String> impliedActions = getImpliedActions(action);
            if(impliedActions.size() > 0) {
                // if an action implies other actions, recurse
                fullSet.addAll(buildFullActionsSet(impliedActions));
            } else {
                fullSet.add(action);
            }
        }
        
        return fullSet;
    }
    
    // get implied actions for an action
    private Set<String> getImpliedActions(String action) {
        // TODO: lookup if action implies other actions
        // this would likely be a function of the UserManager somehow
        return Collections.EMPTY_SET;
    }
    
}

...

Code Block
package org.apache.roller.weblogger.pojos;

import java.io.Serializable;
import java.util.Date;
import java.util.Set;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.roller.util.UUIDGenerator;
import org.apache.roller.weblogger.util.Utilities;


/**
 * Permissions for a specific user. 
 */
public class UserPermission implements Serializable {
    
    private static Log log = LogFactory.getLog(UserPermission.class);

    private String id = UUIDGenerator.generateUUID();
    private User user = null;
    private String type = null;
    private String object = null;
    private String actions = null;
    private Date dateCreated = new Date();
    private boolean pending = false;
    
    
    public UserPermission() {}
    
    public UserPermission(User user, String type, String object, String actions) {
        this.user = user;
        this.type = type;
        this.object = object;
        this.actions = actions;
    }
    
    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }
    
    public User getUser() {
        return user;
    }

    public void setUser(User user) {
        this.user = user;
    }

    public String getType() {
        return type;
    }

    public void setType(String type) {
        this.type = type;
    }
    
    public String getObject() {
        return object;
    }

    public void setObject(String object) {
        this.object = object;
    }
    
    public void setActions(String actions) {
        this.actions = actions;
    }

    public String getActions() {
        return actions;
    }

    public Date getDateCreated() {
        return dateCreated;
    }

    public void setDateCreated(Date dateCreated) {
        this.dateCreated = dateCreated;
    }

    public boolean isPending() {
        return pending;
    }

    public void setPending(boolean pending) {
        this.pending = pending;
    }
    
    public void addActions(String newActions) {
        Set<String> existing = Utilities.stringToStringSet(actions, ",");
        Set<String> newActs = Utilities.stringToStringSet(newActions, ",");
        existing.addAll(newActs);
        setActions(Utilities.stringSetToString(existing, ","));
    }
    
    public void removeActions(String removeActions) {
        Set<String> existing = Utilities.stringToStringSet(actions, ",");
        Set<String> removeActs = Utilities.stringToStringSet(removeActions, ",");
        existing.removeAll(removeActs);
        setActions(Utilities.stringSetToString(existing, ","));
    }
    
}

5.2 Define new permissions properties

The need for roles is no longer necessary as a concept separate from permissions, but roles did provide one very crucial benefit that we need to retain. They provided the ability to dynamically group and imply permissions. i.e. they allowed a user with the role "editor" to imply having the permissions "login,weblog". We can accomplish the same thing by allowing any permission to imply additional permissions.

Like roles we'll want to that to be configurable and dynamic so that a permission can be modified to imply more and less other permissions as elements of the system change. Exactly how this would work is not definite, but similar to the original proposal a permission could be defined to imply other permissions, such as ...

Code Block

permission.anonymous=comment
permission.editor=login,comment,createWeblog
permission.admin=login,comment,createWeblog,admin

It's also important that this be expandable to any number of levels, so it would be okay do define ...

Code Block

permission.level1=action0
permission.level2=level1,action1,action2
permission.level3=level2,action3

Thus, a user with permission "level3" is equivalent to having actions "action0,action1,action2,action3"

 = Utilities.stringToStringSet(actions, ",");
        Set<String> removeActs = Utilities.stringToStringSet(removeActions, ",");
        existing.removeAll(removeActs);
        setActions(Utilities.stringSetToString(existing, ","));
    }
    
}

5.2 Define new permissions properties

The need for roles is no longer necessary as a concept separate from permissions, but roles did provide one very crucial benefit that we need to retain. They provided the ability to dynamically group and imply permissions. i.e. they allowed a user with the role "editor" to imply having the permissions "login,weblog". We can accomplish the same thing by allowing any permission to imply additional permissions.

Like roles we'll want to that to be configurable and dynamic so that a permission can be modified to imply more and less other permissions as elements of the system change. Exactly how this would work is not definite, but similar to the original proposal a permission could be defined to imply other permissions, such as ...

Code Block

permission.anonymous=comment
permission.editor=login,comment,createWeblog
permission.admin=login,comment,createWeblog,admin

It's also important that this be expandable to any number of levels, so it would be okay do define ...

Code Block

permission.level1=action0
permission.level2=level1,action1,action2
permission.level3=level2,action3

Thus, a user with permission "level3" is equivalent to having actions "action0,action1,action2,action3"

NOTE: we would probably also want to create a special action for "all" which can be used for admin purposes and implies all actions automatically.

Based on the behavior in Roller 4.0 we would have these permissions to start from ...

Code Block

# Application Permissions
admin=*all*
editor=login,mainMenu,editProfile,createWeblog

# Weblog Permissions
admin=*all*
author=entries,comments,categories,bookmarks,resources
limited=editDraft

The application would not need to actually check for individual actions such as "categories" or "editProfile" when the security model is first updated, it can continue to simply check for "admin" or "author", but eventually the true power of the security system comes in when we make use of more finely grained action controls. That way each action can be granted/revoked to individual users and combined in any way the owner of the application desiresNOTE: we would probably also want to create a special action for "all" which can be used for admin purposes and implies all actions automatically.

5.3 Add new UserManager methods

...