Versions Compared

Key

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

Name

Externalize User and Permissions Management

StatusProposal under development

Implemented in trunk for 4.1

Target Release

Roller Weblogger 4.1

Issue

ROL-1534

Original Authors

Dave Johnson

Proposal to make it possible to externalize user and permissions management so that Roller can be easily customized to pull user profile and role permissions information from a separate user management an external system.

1.0 Abstract

For ease of installation and management, Roller is able to manage it's own users and permissions without relying on any external system other than its RDBMS. We definitely don't want to lose that ability, but as Roller moves into enterprise scenarios where Directory Servers rule and social networking scenarios where user profile information is key we need to make some changes. For Roller to be sucessful in large organizations and social networks, we need to make it easy to integrate Roller with existing user management and permissions systems. The way to do that is to externalize user and permissions management, or rather to make it externalizable.

...

  • Enable Roller to optionally read/write user profile information in an external system
  • Enable Roller to optionally read/write user role information in an external systemEnable Roller to optionally read/write user-weblog and permission information in an external system instead of it's relational database.
  • Increase the number of authentication/authorization options available in Roller by making it possible to configure Container Managed Authentication (CMA) and not only Acegi.

...

4.1.2 Proposed solution

Ensure that User objects are user, role and permission information is always retrieved via UserManager methods and never by ORM relationship management (e.g. never by weblog.getUser()).

Now, let's move from user profiles to user authentication.

4.2 Roller authentication is managed via Acegi

Roller uses a framework called Acegi to handle authentication and authorization. Instead of relying on the authentication and authorization features built into the container on which Roller runs, Roller relies on Acegi.

When Acegi is authenticating a user it pulls username, password and role information from the RollerUserDetailsService, which in turn fetches that information from Roller's User and UserRole POJOs via UserManager.

Acegi is implemented as a Servlet Filter, which intercepts each request and decides if the user is authenticated, needs to login first, etc. Acegi takes care of routing the user to the login page and back to the original page that the user requested. Acegi wraps the ServletRequest so that it can return the
right value when request.getUserPrincipal() is called by the application.

4.2.1 The problem with relying solely on Acegi

Using Acegi makes Roller installation painless in standalone situations, but some Acegi skills are required to reconfigure Roller to authenticate against an LDAP system. Unfortunately, the only SSO system that Acegi supports out of the box is Yale CAS. Plus, some folks would like to disable Acegi to take advantage of the powerful auth & auth services that are built into containers now, e.g. SSO support that's built into Glassfish, Websphere, JBoss, etc.

4.2.2 The Solution

Make it possible to turn off Acegi by modifying web.xml and to extend the Roller application class RollerContext to init without Acegi. Moving forward, we should not introduce further dependencies on Acegi in Roller.

4.3 Roller manages user-weblog permissions

In addition to roles, which are global across a Roller site, Roller also manages each user's permissions to access weblogs. There is a many-to-many relationship between users and weblogs and it's stored in a database table:

Code Block

-- User permissions within a website
-- permission_mask: bitmask 000 limited, 001 author, 011 admin
-- pending: pending user acceptance of invitation to join website
create table roller_user_permissions (
   id              varchar(48) not null primary key,
   website_id      varchar(48) not null,
   user_id         varchar(48) not null,
   permission_mask integer not null, 
   pending         $db.BOOLEAN_SQL_TYPE_TRUE not null
);

There are three permission levels:

  • limited: can edit draft weblog entries only, can submit for review
  • author: can edit draft and publish weblog entries
  • admin: can author and can manage users, weblog settings, theme and etc.

Each User object provides access to the User's weblog permissions. When a user logs in, we use this to display the user's list of weblogs.

User

Code Block

public List getPermissions()
public void setPermissions(List perms)

Each Weblog object provides access to the Weblog's permissions. When a weblog admin uses the manage members page, we use this information to display the list of weblog members and the permissions levels of each.

Weblog

Code Block

public List getPermissions() 
public void setPermissions(List perms) 
public void removePermission(WeblogPermission perms)
public int getUserCount()
public int getAdminUserCount()

WeblogEntry

Code Block

public boolean hasWritePermissions(User user)

4.3.1 Problem

Permissions cannot be managed by external system because the User to Permissions to Weblog relationship is managed by the ORM, the information must be stored in Roller database tables and cannot be externalized and managed by another system.

4.3.2 Solution

That approach will make user/permissions management truly pluggable. Also, refactor the UserManager interface so that it is responsible only for user/perms management. The result of this refactoring will be this arrangement:

  • UserManager - for user and role/permissions management
  • WeblogManager - for Weblog and Page management
  • WeblogEntryManager - for WeblogEntry and Comment management

Now, let's move from user/role management to permissions.

4.2 Roller manages user-weblog permissions

In addition to roles, which are global across a Roller site, Roller also manages each user's permissions to access weblogs. There is a many-to-many relationship between users and weblogs and it's stored in a database table:

Code Block

-- User permissions within a website
-- permission_mask: bitmask 000 limited, 001 author, 011 admin
-- pending: pending user acceptance of invitation to join website
create table roller_user_permissions (
   id              varchar(48) not null primary key,
   website_id      varchar(48) not null,
   user_id         varchar(48) not null,
   permission_mask integer not null, 
   pending         $db.BOOLEAN_SQL_TYPE_TRUE not null
);

There are three permission levels:

  • limited: can edit draft weblog entries only, can submit for review
  • author: can edit draft and publish weblog entries
  • admin: can author and can manage users, weblog settings, theme and etc.

Each User object provides access to the User's weblog permissions. When a user logs in, we use this to display the user's list of weblogs.

User

Code Block

public List getPermissions()
public void setPermissions(List perms)

Each Weblog object provides access to the Weblog's permissions. When a weblog admin uses the manage members page, we use this information to display the list of weblog members and the permissions levels of each.

Weblog

Code Block

public List getPermissions() 
public void setPermissions(List perms) 
public void removePermission(WeblogPermission perms)
public int getUserCount()
public int getAdminUserCount()

WeblogEntry

Code Block

public boolean hasWritePermissions(User user)

4.2.1 Problem with managing permissions via ORM relationships

Permissions cannot be managed by external system because the User to Permissions to Weblog relationship is managed by the ORM, the information must be stored in Roller database tables and cannot be externalized and managed by another system.

4.2.2 Solution

Remove the Roller front-end's dependence on UserRole and WeblogPermissions completely and put in place a single consistent permissions system via the UserManager interface.

Now let's discussion authentication.

4.3 Roller authentication is managed via Acegi

Roller uses a framework called Acegi to handle authentication and authorization. Instead of relying on the authentication and authorization features built into the container on which Roller runs, Roller relies on Acegi.

When Acegi is authenticating a user it pulls username, password and role information from the RollerUserDetailsService, which in turn fetches that information from Roller's User and UserRole POJOs via UserManager.

Acegi is implemented as a Servlet Filter, which intercepts each request and decides if the user is authenticated, needs to login first, etc. Acegi takes care of routing the user to the login page and back to the original page that the user requested. Acegi wraps the ServletRequest so that it can return the
right value when request.getUserPrincipal() is called by the application.

4.3.1 The problem with relying solely on Acegi

Using Acegi makes Roller installation painless in standalone situations, but some Acegi skills are required to reconfigure Roller to authenticate against an LDAP system. Unfortunately, the only SSO system that Acegi supports out of the box is Yale CAS. Plus, some folks would like to disable Acegi to take advantage of the powerful auth & auth services that are built into containers now, e.g. SSO support that's built into Glassfish, Websphere, JBoss, etc.

4.3.2 The Solution

Make it possible to turn off Acegi by modifying web.xml and to extend the Roller application class RollerContext to init without Acegi. Moving forward, we should not introduce further dependencies on Acegi in RollerRemove Roller's dependence on UserRole and WeblogPermissions completely and put in place a single consistent permissions system.

5.0 Specific changes to Managers, POJOS, Actions and JSPs

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

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 RollerPermission extends java.security.Permission

...

. This is simply a common base class for all Roller permissions, it provides no additional functionality over what is provided by Permssion.

Class GlobalPermission extends RollerPermission defines the global actions:actions below. Each GlobalPermission object will have a list of those actions.

  • login: allowed to login and edit user profile
  • comment: allowed to comment on weblog entries
  • createWeblog: allowed to create weblogs
  • admin: allowed to administer the Roller installation

Class WeblogPermission extends RollerPermission defines the weblog specific actions :below. Each WeblogPermission object will have a list of those actions.

  • editDraft: allowed to edit draft posts only
  • post: allowed to edit and post weblog entries
  • admin: allowed to administer the weblog

...

Here are the new properties:

Code Block
role.names=anonymous,editor,admin
role.actions.anonymous=comment
role.actions.editor=login,comment,createWeblog
role.actions.admin=login,comment,createWeblog,admin

For example, if you wanted to prevent users from creating new weblogs you would put this in your roller-custom.properties file: role.actons.editor=login,comment

5.3 Add new UserManager methods

First, we provide a way for the Roller front-end to check any type of permission for a user.

,admin

For example, if you wanted to prevent users from creating new weblogs you would put this in your roller-custom.properties file: role.actons.editor=login,comment

5.3 Add new UserManager methods

Here are the new methods to be added to UserManager:

Code Block

// Provide a way for the Roller front-end to check any type of permission for a user.
public boolean checkPermission(RollerPermission perm, User user);

// A way for the Roller front-end to grant and revoke roles because roles imply global permissions
public void grantRole(String roleName, User user);
public void revokeRole(String roleName, User user);

// The Roller front-end also needs to be able to grant and revoke weblog permissions
public void grantWeblogPermission(WeblogPermission
Code Block

public boolean checkPermission(RollerPermission perm, User user);

For example, if you want to check to see if a user has can post a weblog entry, you would do this:

Code Block

WeblogPermssion desiredPerm = new WeblogPermission(weblog, "post");
boolean allowed = userManager.checkPermssion(desiredPerm, user);

Next, we need to provide a way for the Roller front-end to grant and revoke roles because roles imply global permissions.

Code Block

public void grantRole(String roleName, User user);
public void revokeRole(String roleName, User user);

The Roller front-end also needs to be able to grant and revoke weblog permissions:

Code Block

public void grantWeblogPermission(WeblogPermission perm, User user);
public void revokeWeblogPermission(WeblogPermssion perm, User user);

And finally, the front-end needs to be able to display the roles and permissions associated with each user:

Code Block

public List<String> getRoles(User user);
public List<WeblogPermission> getWeblogPermssions(User user);
public List<WeblogPermission> getWeblogPermssions(Weblog weblog);

...

public void revokeWeblogPermission(WeblogPermssion perm, User user);

// and to display the roles and permissions associated with each user:
public List<String> getRoles(User user);
public List<WeblogPermission> getWeblogPermssions(User user);
public List<WeblogPermission> getWeblogPermssions(Weblog weblog);

For example, if you want to check to see if a user has can post a weblog entry in a weblog, you would do this:

Code Block

WeblogPermssion desiredPerm = new WeblogPermission(weblog, WeblogPermission.POST);
boolean allowed = userManager.checkPermssion(desiredPerm, user);

Roller will look up the WeblogPermission object for the specified user and weblog and will return true if the "post" action is in the permission's action list.

5.4 Implementation of new UserManagement permissions methods

Global permissions are implied by role, so we'll keep the userrole table and UserRole POJO.

...

5.5 Removing dependence on ORM for user/role/perms management

Here are the specific changes to Roller POJOs and front-end code to remove dependence on ORM for user, role and permissions management.

User POJO: remove the set/getRoles() and set/getPermissions() methods

...

5.6 Make Acegi and CMA optional

Here's some notes on what would have to happen to To make CMA optional.:

  • Make RollerContext into an abstract base class so that we can have a Acegi
    version and a non-Acegi version that share the same base logic.

...