Assembla home | Assembla project page
 

Permission Function Notes

Space Permissions

All actions on spaces, and on objects linked to spaces, are permissioned. A user may have the following permissions to access a space and its linked objects:

  • ALL - owner, can view the Admin page, can add and remove any object, and can delete the space
  • EDIT - Editor, can add, edit, and remove linked objects such as flows, files, and wiki pages.
  • VIEW

Before we show someone a space, or an object like a wiki page, flow, or file that is attached to a space, we need to check for the permission of the user in that space.

You should always be able to call space.get_permission(user) with the current logged in user. This will return one of the permission levels – NONE, VIEW, EDIT, ALL. Depending on the operation, you need to get to a minimum permission level or it should reject or disable the operation.

If user is NIL (not logged in), it will return permissions the PUBLIC permission setting (from the space admin page).

Permissions in Controller actions

Before we execute a controller action, we usually need to check permissions. Fortunately, this is done automatically by filter actions.

If you look at the top of the spaces_controller.rb, you will see the before_filter's for has_view_permission, has_edit_permission, or has_all_permission. Each action should be assigned to one of these filters, depending on the permission that is required for the action.

These filters perform the following actions:

  • Get the space_id from params[:id] or params[:space_id], retrieve the related space, and set the variable @space. You will always have a non-nil @space after passing these filters. The filters live in ApplicationController?, which is a parent for the other controllers, so they can set class variables.
  • Check the permission of the current user against @space - @space.get_permission(user). The nil user (not logged in) gets the public permissions.
  • If the user fails the permission check and is not logged in, they probably can get more permissions if they login. We redirect to the login page.
  • If the user fails the permission check and is logged in, we send them to the Not Permitted page.

Permission from the related space needs to be applied for objects that are attached to a space: wiki pages, flows, files. For example, editing a wiki page requires "EDIT" in the related space. Seeing anything at all attached to the space requires "VIEW". The hard part is filtering searches. All search results eventually need to be filtered for VIEW permissions.

We added extended permission filters for actions in flow_controller that receive a flow_id - flow_has_edit_permission, flow_has_view_permission. These filters load the @flows object by params[:id], and then get @flows.space to retrieve the @space object and check its permissions.

The wiki code currently uses a different strategy. It requires params[:id] as the space ID, and params[:wiki_id] as the page name, so it can use the normal permission filters that run off the space ID.

Magic users

We have two magic users in our database:

  • SUPER (login "super" password "super") always has ALL permissions. Obviously we need a secret password for a production system. This user should be useful for testing, both manual and automatic. This user always has an ID of User::SUPER_ID. When you install a new system that is visible on the Internet, please log in as this user and change the password.
  • ANONYMOUS. This is the user we plug in when we log an action, such as wiki-editing, from a non-logged in user. This user always has an ID of User::ANONYMOUS.

Permissions for user objects

We want to protect the privacy of user information by restricing the information that we display in a user profile. The permissioning is applied to each field. There are three levels of permissions for user fields:

  • ALL - only available when the logged in user is the same as the user being viewed, or to the SUPER user.
  • TEAM - available to teammates. Those are people who belong to space teams where the user has marked that space as containing teammates. We will add a "teammates" flag to the user_role, and give users an opportunity to set this flag when they become team members.
  • PUBLIC - available to everyone

The "login" field is always PUBLIC, and it is used as the default name for team members, searches, and article posts.

USER ACTIONS Some actions, which do not have public behavior, require login. You can require login in the controller with the statement (taking arguments describing which actions it applies to)

  before_filter :login_required

It is important to remember that all actions in user_controller require login by default. If you add an action that does not require login, such as a registration, then you need to add the action to the exception list in method UserController?.protect?

MY ORIGINAL NOTES ON Checking Permissions and Scalability

There will be many cases where a user has permission to view Portfolio X, and Portfolio X links to nodes that are from Workspace Y, which is visible to the guy that set up Portfolio X, but is NOT visible to the current user. In this case, we clearly need to ask each Node whether our user has permission to see it. You can apply the same principle for a search. You get back a bunch of search results, and you need to check permissions for each individual node. This permission checking process poses a lot of scalability problems. It is perhaps the key technical challenge.

I considered many different ways to describe permissions:

  • Users get permissions from roles on the team for a particular node. This must be part of the mix. However, every node can’t have a team. There are situations like a repository where 1000 nodes share the same team.
  • Users get permissions from groups, which can have roles in a team. This is the classic model used with things like LDAP. I would like to avoid the group maintenance required in this case because I think it’s an extra when you already need to do team maintenance. Either way (group or team) gives you up to order N squared scaling when checking all nodes returned in a search multiplied by all groups that a user belongs to.
  • Ideally, roles would chain. For instance, groups could hold other groups, or teams could refer to other teams or groups. I did this in PowerSteering? with very useful effects. It’s great for users because they get permissions they need automatically. Unfortunately, if you have to follow all of the chains to find out if a user is in one, it scales NP complete (n to the n). You need a lot of tricks to do it and then it breaks at about 10,000 nodes by 10,000 users.

I propose what I believe to be the simplest solution that extends the idea that users get permissions from their roles in a team, and makes some compromises for scalability. Each node can “Trust” some set of other nodes, which means that it accepts all of the users and roles from each of those nodes. This does not chain, so object A trusts B, but not everybody that B trusts. A node always trusts itself. Under this plan, you can discover roles (and permissions) for a user on a node with one database select for team members of trusted nodes.

If we need to, we can pre-calculate the Trust relations and make a database of all of the user/node pairs. That database would be of size N squared (potentially very big), but that's what big servers are for.

Because users usually refer repetitively to a subset of objects, one performance enhancer is to cache permissions for each user in RAM as you discover them.