Time for action implementing role-based access control

Run the example application provided in access2.py and log in as admin. You will see that besides Users and Accounts, you are presented with links to Roles and Permissions as well. If you click on Roles, you will see we have defined several roles:

Time for action implementing role-based access control

As you can see in the screenshot, we have also defined a Superuser role to illustrate that it is possible to extend the concept of role-based access control to the maintenance of roles and permissions themselves.

What just happened?

Applications that use this form of access control have to be adapted only slightly. Take a look at access2.py:

Chapter9/access2.py

import os
import cherrypy
from rbacentity import AbstractEntity, Attribute, Picklist, 
AbstractRelation
from browse import Browse
from display import Display
from logondb import LogonDB
db="/tmp/access2.db"

Compared to our previous example, only the first part is different, in that it includes the rbacentity instead of the entity module. This module provides the same functionality as the entity module, but the AbstractEntity class defined in this module has some added magic to provide access to roles and permissions. We will not inspect that in detail here, but will comment on it when we encounter it in the following code.

The next part is the definition of the Entity class. We could have opted for redefining the AbstractEntity class, but here we have chosen to add the functionality to the Entity subclass by adding and overriding methods where necessary:

Chapter9/access2.py

class Entity(AbstractEntity):
	database = db
	userentity = None
	logon = None
	@classmethod
	def setUserEntity(cls,entity):
		cls.userentity = entity
	@classmethod
	def getUserEntity(cls):
		return cls.userentity
	@classmethod
	def setLogon(cls,logon):
		cls.logon = logon
	@classmethod
	def getAuthenticatedUsername(cls):
		if cls.logon :
			return cls.logon.checkauth()
		return None
	def isallowed(self,operation):
		user = self.getUserEntity().list(
			pattern=[('name',self.getAuthenticatedUsername())])[0]
		entity = self.__class__.__name__
		if user.name == 'admin' :
			return True
		roles = user.getRole()
		if len(roles):
			role = roles[0]
			permissions = role.getPermission()
			for p in permissions :
				if p.entity == entity:
					if p.operation=='*' or p.operation==operation:
						if p.level == 0 :
							return True
						elif p.level == 1:
							for owner in self.getUser():
								if user.id == owner.id :
									return True
						return False
					def update(self,**kw):
						if self.isallowed('update'):
							super().update(**kw)

Instead of just defining a database class variable, we now also define a userentity class variable to hold a reference to the class of the entity that represents a user and a logon class variable to hold a reference to a logon instance that can provide us with the name of an authenticated user.

This distinction is identical to examples in the previous chapters: we have a User entity in our main database where we may store all sorts of information related to the user (like full name, telephone number, gender, and so on) and a separate password database that holds just usernames and encrypted passwords. If the user is correctly authenticated against the password database, we know his/her username, which we can then use to retrieve the corresponding User instance with all the extra associated information. The class methods provide the means to get access to these class variables.

In this example, we only override the update() method (highlighted) but in a full implementation you might want to override other Entity methods as well. The pattern is simple: we call the isallowed() method with an argument that indicates which action we would like to check and if isallowed() returns True, we call the original method.

The first thing the isallowed() method itself does, is retrieve the username of the authenticated user with the getAuthenticatedUsername() class method. It then uses this name to find a User instance. Even though we might want to implement a role-based access scheme in our application to allow for the administration of roles and permissions by various users, we still provide a shortcut for the administrator here as a convenience (highlighted). This way we do not have to prime the database with roles and permissions for the admin user. For a real world application, you may choose differently of course.

Next we check if there are any roles associated with the user, and if this is the case, we retrieve all permissions associated with the first role (in this example, users have just one role). We then loop over all those permissions to check if there is one that applies to the entity we are interested in. If so, we check the operation field. If this field contains an asterisk (*) or is equal to the operation we are checking, we look at the level. If this level is zero, this means that the current user may perform this operation on this entity even if he/she is not the owner. If the level is one, he/she is only allowed to perform the operation if he/she owns the entity, so we check whether the user is in the list of users associated with the current entity.

Tip

Retrieving roles and permissions each time an operation is performed might incur a serious performance hit. It might be a good idea to cache some of this information. You have to be careful though and invalidate that cache as soon as the set of permissions for a user changes.

The next part of access2.py, as shown, illustrates how we may use this augmented version of the Entity class:

Chapter9/access2.py

class Relation(AbstractRelation):
	database = db
class User(Entity):
	name = Attribute(notnull=True, unique=True, displayname="Name", 
primary=True)
class Account(Entity):
	name = Attribute(notnull=True, displayname="Name", primary=True)
class OwnerShip(Relation):
	a = User
	b = Account
class UserRoles(Relation):
	a = User
	b = User._rbac().getRole()
	relation_type = "N:1"
logon = LogonDB()
Entity.setUserEntity(User)
Entity.setLogon(logon)

As before, we define User and Account entities, and an ownership relation between them. The rbacentity module will have provided for Role and Permission classes and we can gain access to those with the _rbac() class method available to all AbstractEntity derived classes. The object returned by this _rbac() method provides a getRole() method that returns the class of the Role entity. We use it here to create a relation between users and their roles (highlighted). The final lines associate the password database and the User class with our new Entity class.

To provide access to the lists of roles and permissions, we can use the same _rbac() method to provide the Role and Permission classes needed to create Browse classes:

Chapter9/access2.py

class RoleBrowser(Browse):
		edit = Display(User._rbac().getRole(), edit=True, logon=logon)
		add = Display(User._rbac().getRole(), add=True, logon=logon)
class PermissionBrowser(Browse):
		edit = Display(User._rbac().getPermission(), edit=True,
	logon=logon, columns=User._rbac().getPermission().columns + 
[User._rbac().getRole()])
	add = Display(User._rbac().getPermission(), add=True,
logon=logon, columns=User._rbac().getPermission().columns + [User._
rbac().getRole()])
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.145.103.154