Authorization

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.

from wiki

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.

Mannie Kagan, Paul Ilechko 

Don’t Do Role-Based Authorization Checks; Do Activity-Based Checks

Derick Bailey

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!

Flask

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."

Django

  • 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

Pyramid

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)

 

Prev Post Next Post