Time for action implementing access control

This scenario is implemented in access1.py:

Note

Note: In the code distributed with this chapter and the following one, the logon class is not only initialized with an admin user (with admin as a password) but with the following three name/password combinations: eve/eve, john/john, and mike/mike.

If your run this application and point your browser to http://localhost:8080, you are presented with a list of accounts. If you have logged in as either john or mike both sales persons you can only alter the accounts owned by each of them. If however, you log in as eve, the sales manager, you can alter the information in all accounts.

What just happened?

The application is simple enough and follows a familiar pattern. The relevant definitions are shown here:

Chapter9/access1.py


from permissions1 import isallowed
class Entity(AbstractEntity):
	database = db
	def update(self,**kw):
		if isallowed('update', self, logon.checkauth(),
							self.getUser()):
				super().update(**kw)
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 AccountBrowser(Browse):
	edit = Display(Account, edit=True, logon=logon,
			columns=Account.columns+[User])
	add = Display(Account, add=True, logon=logon,
			columns=Account.columns+[User])
class UserBrowser(Browse):
	edit = Display(User, edit=True, logon=logon)
	add = Display(User, add=True, logon=logon)

The database distributed with the code (access1.db) contains a number of accounts already so the code does not contain any lines to create those. The important part is highlighted in the preceding code: it imports a permissions1 module that contains a dictionary of permissions. This dictionary lists for each combination of entity, action, ownership, and username whether this is permissible or not.

We can now override the update() method in the AbstractEntity class (highlighted): We retrieve the username from the current sessions by calling the checkauth() method and pass it along to the isallowed() function, together with the name of the action we want to check (update in this case), the entity, and a list of users (the owners). If this checks out okay, we call the original update() method.

If we take a look at permissions1.py, we see that because, in this example, we only consider the Account entity and the update action in this list is quite small:

Chapter9/permissions1.py

import entity1
allowed = {
	'Account' : {
		'create' : {
			'admin' : 'all',
			'eve' : 'all',
			'john' : 'owner',
			'mike' : 'owner'
		},
		'update' : {
			'admin' : 'all',
			'eve' : 'all',
			'john' : 'owner',
			'mike' : 'owner'
		},
		'delete' : {
			'admin' : 'all',
			'eve' : 'all',
		}
	}
}
def isallowed(action,entity,user,owner):
if len(owner) < 1 : return True
try:
	privileges = allowed[entity.__class__.__name__][action]
	if not user in privileges :
			return False
	elif privileges[user] == 'all':
			return True
	elif privileges[user] == 'owner' and user == owner[0].name:
			return True
	else:
			return False
except KeyError:
	return True

The dictionary with privileges itself is called allowed (highlighted) and permissions1.py also defines a function called isallowed(), that will return True if there aren't any owners for this entity. Then it checks if there are any privileges known for this entity and action. If this is not the case, any exception will be raised because either the key for the entity or the key for the action does not exist.

If there are privileges known, we check if the user has specific privileges. If there is no key for the user, we return False. If there is, and the privilege is all, we return True: he/she may perform the action on this entity even for an entity instance he/she doesn't own. If the privilege is the owner, we only return True if the user is in fact the owner.

The aforementioned approach outlined is cumbersome for various reasons:

  • If we would like to add a new salesperson, for example, we would have to add permission entries for each entity/action combination. In the example, we only considered the Account entity and the update action, but in a somewhat more realistic application, there would be tens of entities (like Contact, Address, Quote, Lead, and so on) and quite a few actions more to consider (for example, delete and create, but also actions that involve other entities like changing ownership or adding an address to an account). Also, if that sales person was promoted to sales manager, we would have to repeat the whole exercise again.
  • If we added a new type of entity, we would have to add lines for each and every person in the company.
  • Administering permissions in a Python module is not something you normally would expect a non-technical person to do as it is cumbersome, error prone, and requires the application to be restarted if something changes.

The last reason is why we will implement the list of permissions in the database. After all, we already have a framework that allows for easy manipulation of database entries with a web interface. The other reasons are why we will reconsider our first approach and will implement a scheme called role-based access control.

..................Content has been hidden....................

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