{ "info": { "author": "Shahar Evron", "author_email": "shahar@shoppimon.com", "bugtrack_url": null, "classifiers": [ "Intended Audience :: Developers", "License :: OSI Approved :: Apache Software License", "Operating System :: OS Independent", "Programming Language :: Python :: 3" ], "description": "Authoritah - Framework Agnostic Python RBAC Library\n===================================================\nAuthoritah is a Python RBAC library. It is designed to be framework\nagnostic, in the sense that it is not coupled with any Web framework or ORM\nlibrary. In addition, Authoritah provides a highly granular role system using\na unique approach of context-based role resolution.\n\n[![Build Status](https://travis-ci.org/shoppimon/authoritah.svg?branch=master)](https://travis-ci.org/shoppimon/authoritah)\n\n## Compatibility \nWe test Authoritah on Python 3.5, 3.6, 3.7 and Pypy 3. It is possible that\nolder versions of Authoritah will work with Python 2.7 as well, but Python \nversions below 3.5 are not supported. \n\n## Installation\nThe easiest way to install Authoritah is via pip:\n\n pip install authoritah \n\n## Overview & Terminology\nThe following terms are common in many authorization frameworks, but can have\nspecific meaning in *authoritah* so it is important to clarify them first:\n\n### Identities\nIn simple terms, an identity is a user - an entity using the system (be it a\nperson, a machine authenticating with an API key, a default \"anonymous\" user,\netc.) Identities can have **roles** - in *authoritah*, an identity's roles are\nusually in relation to a given **context object**, although identities can have\ndefault roles as well.\n\nIn *authoritah* you are not expected to use a specific structure for identity\nobjects - they are opaque as far as the library is concerned, and are only\npassed around between different callables you provide.\n\n### Roles\nA role is given to an identity, and defines a set of **permissions** - actions\nthat the user is allowed to perform on an object or in the system.\n\nAn identity can have more than one role (for example a user may be both a\n**hiring_manager** and a **content_editor**). In addition, roles can inherit\npermissions from one or more other roles (for example, a **content_editor** can\ninherit from **content_viewer**).\n\nUnlike many other authorization frameworks, in *authoritah* roles are not\nglobal (although they can be), but are derived from context - for example a\nuser may be a **content_editor** for all articles, or may be a\n**content_editor** only for the articles they created, and **content_viewer**\nfor all other articles.\n\n### Permissions\nPermissions, simply put, are \"rights granted to a given identity based on its\nroles\". For example, someone with a **content_editor** role have the\n**article_edit**, **article_publish** and **article_unpublish** permissions.\n\nImplementing authorization checks in a system normally involves checking\nwhether the user has one or more permissions granted to them before proceeding\nwith an action.\n\n### Strict Mode:\nThe Authorizer class may be instantiated with strict=True (defaults to False).\n\nStrict mode can raise exceptions in two cases:\n\n* If is_allowed is called for a permission not defined in any of the roles defined.\n* If a role in the identity provided to is_allowed is not defined\n\nThis is useful to check if one forgot to add a role or permission.\n\n\n### Context Objects\nThe **context object** is the object on which the operation is performed. For\nexample, when editing an article the context object is the article. As\nmentioned, in *authoritah* context objects have a more central role than\nwith many other authorization frameworks - they are taken into account when\ndeciding the user's role.\n\n## Quick Start\nThe following is a quick start guide to applying authorization to your code\nusing *authoritah*.\n\nWe'll follow an example of an imaginary, simplified content management system\nwith 3 objects: articles, comments and users.\n\n### 1. Define Roles and Permissions\nAs with any RBAC system, it is recommended to start with defining some roles\nand what permissions they grant. With *Authoritah*, it is recommended to think\nof roles in relation to objects in the system.\n\nYou can define your roles and permissions in a configuration file - a `YAML` or\n`JSON`, or even a Python `dict`:\n\n```YAML\n ---\n viewer:\n - article_list\n - article_view\n - comment_list\n - comment_view\n - user_create\n\n user:\n parents: [ 'viewer' ]\n grants:\n - comment_create\n - comment_upvote\n\n contributor:\n parents: [ 'user' ]\n grants:\n - article_create\n\n content_admin:\n - comment_edit\n - comment_delete\n - article_edit\n - article_delete\n\n user_admin:\n - user_edit\n - user_delete\n\n super_admin:\n parents:\n - contributor\n - content_admin\n - user_admin\n```\n\nSome things to notice:\n* Each `role` is designated by a unique key (or name), and defines,\n optionally, a list of permissions (`grants`).\n* Roles can inherit permissions from other roles. This is done by providing\n a list of inherited roles as `parents`.\n* As a shortcut, you can define a role by simply providing the list of\n permissions it grants, skipping the `grants` key. This format works nicely\n for roles not inheriting from any other role.\n\nMost importantly, roles are defined in a very granular way following an\napproach of least possible access. The right way to think of permissions in\n*authoritah* is to consider whether someone with the given role should have\na given permission **under all circumstances** or only in specific contexts.\n\nIn our example, a `contributor` can create any article, but cannot delete\nany article - only their own articles. Later, we will see how to use dynamic\nrole resolution to elevate a specific user to `content_admin` for specific\ncontexts, so they can edit and delete their own articles.\n\n### 2. Initialize an Authorizer and hook in identity management\nEverything you will do with *authoritah* begins with creating an an\n`Authorizer` object. Assuming we read our roles & permissions configuration\nfrom a YAML file named `authorization.yml`, here is how this is done:\n\n```python\nimport yaml\nfrom authoritah import Authorizer\n\nwith open('authorization.yml') as f:\n roles = yaml.safe_load(f)\n\nauthz = Authorizer(permissions=roles)\n```\n\nSince *authoritah* is not bound to any authentication or identity\nmanagement implementation, you will need to tell your Authorizer how to get\nan identity object, and how to get a list of roles from this object.\n\nThe simplest way to do this would be to use two decorator methods of the\n`Authorizer` object we just created: `authz.identity_provider` and\n`authz.default_role_provider`:\n\n```python\n@authz.identity_provider\ndef get_current_user(request):\n \"\"\"This function returns the current authenticated user object\n \"\"\"\n return request.user\n\n\n@authz.default_role_provider\ndef get_user_roles(user, context=None):\n \"\"\"Get roles for the current user.\n\n This function should always accept an identity object (as returned\n by the defined identity provider) and return either a list of roles,\n a string representing a single role, or None for a user with no roles.\n\n Note that this function also receives the current context object. It\n may be used, if needed, to infer roles - but this is usually not\n recommended.\n \"\"\"\n return user.roles\n```\n\nIn most cases these two functions should be very simple - they are just \"glue\"\nthat integrates your existing code with *authoritah*.\n\n### 3. Define Context-Specific Role Resolution\nIf your system does not require any dynamic role resolution (e.g. permissions\nare global and are not related to context objects in your case), you can skip\nthis phase and use *authoritah* like you would use any other RBAC library.\n\nHowever, in most cases you would like to give users additional roles based on\nthe object they are accessing - the context object.\n\nThis is done using the `authz.context_role_provider` decorator. This decorator\nshould be used to decorate classes, specifying how we should get the roles when\nthe context object is of a certain type.\n\nAssume that our CRM has an ORM or entity class that represents an article:\n ```python\nclass Article:\n\n title = None\n content = None\n created_by = None\n```\n\nYou can now use a decorator to tell Authoritah that `article_user_roles` is the\nrole provider for objects of type `Article`, or any subtype of it:\n```python\n@authz.role_provider(Article)\ndef article_user_roles(user, article):\n if user.id == article.created_by:\n return ['content_admin']\n return []\n```\n\nThus, we have told our authorizer to call `article_user_roles` whenever the \ncontext object is an `Article` object. The list of roles returned by this\ncallable will be appended to the list of existing global roles the user already\nhas. This way, we know that if the user is the creator of the article, they\nshould get permissions as if they have the `content_admin` role (meaning they\ncan edit or delete *this specific article*).\n\nAnother way of doing this will be to use the `class_role_provider` annotation \non the context object class, providing it a method name on the same class to\nuse as a context role provider:\n ```python\n@authz.class_role_provider('user_roles')\nclass ProtectedArticle(Article):\n\n def user_roles(self, user):\n if user.id == self.created_by:\n return ['content_admin']\n return []\n```\n\nNote that this case will work only for objects of `ProtectedArticle`, not the \noriginal `Article` base class. However, it *will* work for any class inheriting\nfrom `ProtectedArticle`. \n\n### 4. Apply Authorization Checks in Your Code\nLast but very important, start checking for permissions before you perform\nsome operations in your code.\n\nThere are two common ways to do this. One is explicit:\n```python\ndef modify_article(article_id, data):\n \"\"\"Assume this is your Web framework's handler for article editing\n \"\"\"\n article = DB.article.get(article_id)\n if not authz.is_allowed('article_edit', article):\n return 'You are not allowed to edit this article', 403\n\n # ... proceed to update the article\n```\nThe other is using a decorator, which works well for object methods where the\nobject is our context object. Let's update our class definition from before:\n\n```python\n@authz.class_role_provider('user_roles')\nclass ProtectedArticle(Article):\n\n @authz.require('article_edit')\n def modify(self, new_data):\n # ... proceed to modify my own attributes\n pass\n\n def user_roles(self, user):\n if user.id == self.created_by:\n return ['content_admin']\n return []\n```\n\nIn the example above, if the user doesn't have the `article_edit` permission,\ncalling `modify()` will raise an `authoritah.NotAuthorized` exception, which\nyou will then need to catch and handle.\n\n## Background: Why Context-Based Role Resolution?\nIn most RBAC / ACL frameworks, each user is given one or more pre-defined\nroles, which in turn decide their permissions to perform operations on various\nobjects. This works well in many cases, but falls short when static permissions\nare not enough to decide whether a user should be allowed to perform an\noperation.\n\nFor example, in a content management system a user may be have an \"editor\" role\ngranting them permission to edit articles. This works well in a \"flat\" system\nwhere all editors can edit all articles.\n\nBut what if we want users to only be able to edit articles that they created?\nOr what if we want users to be able to designate specific editors for an\narticle? Granting a global \"editor\" role here is just not enough.\n\n### Existing Solutions: Post-Hoc Assertions\nMost current RBAC libraries tackle this problem by adding dynamic assertion\ncapabilities on top of static roles and permissions. They allow developers to\nspecify additional assertion callables per granted permission. Once a user is\ngranted permission to take an action based on their role, additional\nassertions are executed, essentially checking if the permission should still\nbe granted, given the user and a context object (in our example the article\nbeing edited).\n\nUnfortunately, this has a few major drawbacks:\n* Writing custom assertions quickly becomes cumbersome as permissions become\n more granular and the number of permissions in the system grows.\n* This model forces an approach of granting roles with the maximal permissions.\n Narrowing down permissions to only apply in specific contexts is an\n afterthought.\n\n### Enter A New Approach: Context-Based Role Resolution\nWith *authoritah*, a user's role is not static but changes based on the context\nobject. Essentially, instead of asking \"what is this user's role?\", we ask\n\"what is the user's role given this object?\". Once the role is dynamically\ndecided, it is very easy to grant or deny permission to perform an action\nwithout any need of additional assertions.\n\nIn addition, it advocates a process where minimal permissions are granted\nthrough each role initially. In the right context, a user may have additional\npermissions through additional roles assigned to them.\n\nThis, in our opinion, reduces the risk of permissions leakage as it encourages\na more granular and limited approach to granting permissions.\n\n# License\nCopyright (c) 2017 Shoppimon LTD\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n\n\n", "description_content_type": "text/markdown", "docs_url": null, "download_url": "", "downloads": { "last_day": -1, "last_month": -1, "last_week": -1 }, "home_page": "https://github.com/shoppimon/authoritah", "keywords": "", "license": "Apache 2.0", "maintainer": "", "maintainer_email": "", "name": "authoritah", "package_url": "https://pypi.org/project/authoritah/", "platform": "", "project_url": "https://pypi.org/project/authoritah/", "project_urls": { "Homepage": "https://github.com/shoppimon/authoritah" }, "release_url": "https://pypi.org/project/authoritah/0.2.0/", "requires_dist": [ "six" ], "requires_python": ">=3.5.0", "summary": "Lightweight, framework agnostic, context based RBAC authorization library", "version": "0.2.0" }, "last_serial": 5878205, "releases": { "0.2.0": [ { "comment_text": "", "digests": { "md5": "87772b28289485679f92e56fc91d5e19", "sha256": "8dedba4a087160879c9f3444948b8a7ebbdd12b50f98d4bf06bfebe72522dbbb" }, "downloads": -1, "filename": "authoritah-0.2.0-py3-none-any.whl", "has_sig": false, "md5_digest": "87772b28289485679f92e56fc91d5e19", "packagetype": "bdist_wheel", "python_version": "py3", "requires_python": ">=3.5.0", "size": 14082, "upload_time": "2019-09-24T08:08:13", "url": "https://files.pythonhosted.org/packages/f0/15/efdee6339a9485348bb732c8a115af6c8344286f617d0420b5192c4e5d79/authoritah-0.2.0-py3-none-any.whl" }, { "comment_text": "", "digests": { "md5": "d59359898b03526db8be95cdd1c2171c", "sha256": "2542946de9b8ac978703b3bc732c6ef11538c610ce867e454fc8617282dc1742" }, "downloads": -1, "filename": "authoritah-0.2.0.tar.gz", "has_sig": false, "md5_digest": "d59359898b03526db8be95cdd1c2171c", "packagetype": "sdist", "python_version": "source", "requires_python": ">=3.5.0", "size": 11819, "upload_time": "2019-09-24T08:08:16", "url": "https://files.pythonhosted.org/packages/20/d9/605b961bedf0dc446d80083d63aed78a5d4af402465e17f07dcb76f2a875/authoritah-0.2.0.tar.gz" } ] }, "urls": [ { "comment_text": "", "digests": { "md5": "87772b28289485679f92e56fc91d5e19", "sha256": "8dedba4a087160879c9f3444948b8a7ebbdd12b50f98d4bf06bfebe72522dbbb" }, "downloads": -1, "filename": "authoritah-0.2.0-py3-none-any.whl", "has_sig": false, "md5_digest": "87772b28289485679f92e56fc91d5e19", "packagetype": "bdist_wheel", "python_version": "py3", "requires_python": ">=3.5.0", "size": 14082, "upload_time": "2019-09-24T08:08:13", "url": "https://files.pythonhosted.org/packages/f0/15/efdee6339a9485348bb732c8a115af6c8344286f617d0420b5192c4e5d79/authoritah-0.2.0-py3-none-any.whl" }, { "comment_text": "", "digests": { "md5": "d59359898b03526db8be95cdd1c2171c", "sha256": "2542946de9b8ac978703b3bc732c6ef11538c610ce867e454fc8617282dc1742" }, "downloads": -1, "filename": "authoritah-0.2.0.tar.gz", "has_sig": false, "md5_digest": "d59359898b03526db8be95cdd1c2171c", "packagetype": "sdist", "python_version": "source", "requires_python": ">=3.5.0", "size": 11819, "upload_time": "2019-09-24T08:08:16", "url": "https://files.pythonhosted.org/packages/20/d9/605b961bedf0dc446d80083d63aed78a5d4af402465e17f07dcb76f2a875/authoritah-0.2.0.tar.gz" } ] }