Authorization is the function of specifying access rights to resources related to information security and computer security in general and to access control in particular. More formally, "to authorize" is to define an access policy.
Choosing and implementing authorization policy may be quite antiviral sometimes (but usually it's quite trivial task). Tho there is a number of patterns that will help you out.Role-based authorization:
- Role-based authorization provides access to resources based on the fact that the user is a member of some class of users.
- Instance-based authorization pattern provides a more granular level of authorization getting down to the level of an individual object within a system. We are now dealing with protection of data, not just of functionality.
- Relationship/Ownership-based authorization, type of authorization is a specific case of the instance-based pattern where there is an owner relationship within the data structures
- User interface customization, while not truly a type of authorization, a closely related problem is that of customizing a user interface to only show the specific functions that a specific user should be allowed to see.
Don’t Do Role-Based Authorization Checks; Do Activity-Based Checks
Don’t Do Role-Based Authorization Checks; Do Activity-Resource-Based Checks
EAFP (Easier to ask for forgiveness than permission) does not apply to authorization, it's vice versa.
Authentication != Authorization, keep these things separately!
def requires_roles(*roles): def wrapper(f): @wraps(f) def wrapped(*args, **kwargs): if get_current_user_role() not in roles: return error_response() return f(*args, **kwargs) return wrapped return wrapper @app.route('/user') @required_roles('admin', 'user') def user_page(self): return "You've got permission to access this page."
- role based access to views, usually with mixins
- permission based access system for adminpanel without proper LACL support
class MyView(LoginRequiredMixin, EditorRequiredMixin, ...): pass # or myuser.user_permissions.add(permission, permission, ...) @permission_required('polls.can_vote') def my_view(request): pass
In comparition to Django or Flask Pyramid authorization logic feels quite strong and easy to define, it provides:
- Protecting Views by Permissions
- ACL, Local ACL
@view_config(route='blog', name='add_entry.html', permission='add') def blog_entry_add_view(request): """ Add blog entry code goes here """ pass class Blog(object): def __acl__(self): return [ (Allow, Everyone, 'view'), (Allow, self.owner, ('add', 'edit')), (Allow, self.reviewer, 'review'), (Allow, 'group:editors', 'edit'), ] class MyAuthenticationPolicy(AuthTktAuthenticationPolicy): def effective_principals(self, request): principals = [Everyone] user = request.user if user is not None: principals.append(Authenticated) principals.append(str(user.id)) principals.append('role:' + user.role) return principals
Relationship-based authorization example
Silly example for SQL providing DBs.
Make sure every request to DB is filtered by user permissions, user can be chained to any table record through company table. The table with ACL for all content would be a right and simple choice, but for simple hierarchical structure we can do without it by following relation tree. Solution for Django, custom 'objects' for each view and manual filtering content `by_user`(`by_company`) for each request. For the Pyramid and SQLAchemy, the firs part is quite same add `company` method to each model we need to be filtering by that permission. But for making request to DB in views lets Pyramid and SQLAchemy to filter all queries by user.
# listener for sqlalchemy query before_compile event from sqlalchemy import event from sqlalchemy.orm.query import Query NO_ACL_TABELS = (User,) def filter_all_by_company(query): for desc in query.column_descriptions: if desc['type'] in NO_ACL_TABELS: continue company = query.session.request.user.company # making exception more informative if not hasattr(desc['type'], 'company'): raise DataInconsistencyError('Cannot filter %s model by company', desc['name']) # adding additional filter to where clausure statment = (desc['type'].company == company) if company else sqlalchemy.sql.false() query.whereclause.append(statment) return query # dbsessoin obtainer for request def get_tm_session(session_factory, transaction_manager, request=None): dbsession = session_factory() # injecting request into dbsession object dbsession.request = request zope.sqlalchemy.register(dbsession, transaction_manager=transaction_manager) return dbsession # we actually want to register event only for pyramid app, so in case if someone # will use our code as lib this listener won't be set up def includeme(config): .... event.listens_for(Query, "before_compile", retval=True)(filter_all_by_company)