A Policy library provides support for RBAC policy enforcement.
Project description
# Policy
A Policy library provides support for RBAC policy enforcement.
## Preface
When I used ``Flask`` to write a ``RESTful web service``, I didn't find a suitable extension to handle endpoints' permission control. Because I really like the permission control method of ``OpenStack`` services which based on a policy file. So I want to implement a more generic library similar to ``oslo.policy``.
## Demo
### Generate Policy File
Suppose there are two roles: **user** and **admin**, and two resources: **article** and **user**. We have 3 policies:
- Only user can update article
- Creating new user requires admin permission.
- Only article owners or admin-role user can delete articles.
Based on the previous description, we generate the following policy file ``policy.json``:
{
"is_admin": "role:admin",
"is_user": "role:user or role:admin",
"article:update": "rule:is_user",
"article:delete": "role:admin or id:%(user_id)s",
"user:create": "rule:is_admin"
}
### Enforce Policy With Flask Application
Suppose we have a simple ``Flask`` application which provides two api: creating new user and deleting article and we run it:
```
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import functools
from flask import Flask, request, g
from policy import Enforcer
from policy.exceptions import PolicyNotAuthorized
app = Flask(__name__)
enforcer = Enforcer('policy.json', raise_error=True)
@app.errorhandler(PolicyNotAuthorized)
def handle_policy_exception(error):
return str(error)
users = {
'lily': {
'id': 'd55a4192eb3b489589d5ee95dcf3af7d',
'roles': ['user', 'admin']
},
'kate': {
'id': '1a535309687244e2aa434b25ef4bfb59',
'roles': ['user']
},
'lucy': {
'id': '186977181e7f4a9e85104ca017e845f3',
'roles': ['user']
}
}
articles = {
'python': {
'id': 'e6e31ad693734b269099d9acac2cb800',
'user_id': '1a535309687244e2aa434b25ef4bfb59' # owned by kate
}
}
def login_required(func):
@functools.wraps(func)
def wrapped(*args, **kwargs):
username = request.args.get('me')
credential = users.get(username)
if not credential:
raise Exception('login required')
else:
g.cred = credential
return func(*args, **kwargs)
return wrapped
def enforce_policy(rule):
"""Enforce a policy to a API."""
def wrapper(func):
"""Decorator used for wrap API."""
@functools.wraps(func)
def wrapped(*args, **kwargs):
if enforcer.enforce(rule, {}, g.cred):
return func(*args, **kwargs)
return wrapped
return wrapper
@app.route('/user', methods=['GET'])
@login_required
@enforce_policy('user:create')
def create_user():
# do create action here
return 'user created'
@app.route('/article', methods=['GET'])
@login_required
def delete_article():
article_name = request.args.get('name')
article = articles.get(article_name)
# do fine-grained permission check here
enforcer.enforce('article:delete', article, g.cred)
# do delete action here
return 'arcticle %s deleted' % article['id']
if __name__ == '__main__':
app.run(port=8888, debug=True)
```
#### View-Level
We provide a ``enforce_policy`` decorator to enforce policy on views ``create_user``.
We head to http://127.0.0.1:8888/user?me=kate to simulate ``kate``'s creating user and get a error:
user:create on {} by {'roles': ['user'], 'id': '1a535309687244e2aa434b25ef4bfb59'} disallowed by policy
Then we head to http://127.0.0.1:8888/user?me=lily to simulate ``lily``'s creating user and get a successful response:
user created
#### Fine-Grained
In some scenarios we want a fine-grained permission check. We enforce policy inside view ``delete_article``, because outside of it we can't know which article the user wants to delete.
We head to http://127.0.0.1:8888/article?me=lucy&name=python to simulate ``lucy``'s deleting article and get a error:
article:delete on {'user_id': '1a535309687244e2aa434b25ef4bfb59', 'id': 'e6e31ad693734b269099d9acac2cb800'} by {'roles': ['user'], 'id': '186977181e7f4a9e85104ca017e845f3'} disallowed by policy
Then we head to http://127.0.0.1:8888/article?me=kate&name=python to simulate ``kate``'s deleting article and get a successful response because ``kate`` is the article's owner:
arcticle e6e31ad693734b269099d9acac2cb800 deleted
Finally we head to http://127.0.0.1:8888/article?me=lily&name=python to simulate ``lily``'s deleting article and get a successful response because ``lily`` is a admin-role user:
arcticle e6e31ad693734b269099d9acac2cb800 deleted
A Policy library provides support for RBAC policy enforcement.
## Preface
When I used ``Flask`` to write a ``RESTful web service``, I didn't find a suitable extension to handle endpoints' permission control. Because I really like the permission control method of ``OpenStack`` services which based on a policy file. So I want to implement a more generic library similar to ``oslo.policy``.
## Demo
### Generate Policy File
Suppose there are two roles: **user** and **admin**, and two resources: **article** and **user**. We have 3 policies:
- Only user can update article
- Creating new user requires admin permission.
- Only article owners or admin-role user can delete articles.
Based on the previous description, we generate the following policy file ``policy.json``:
{
"is_admin": "role:admin",
"is_user": "role:user or role:admin",
"article:update": "rule:is_user",
"article:delete": "role:admin or id:%(user_id)s",
"user:create": "rule:is_admin"
}
### Enforce Policy With Flask Application
Suppose we have a simple ``Flask`` application which provides two api: creating new user and deleting article and we run it:
```
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import functools
from flask import Flask, request, g
from policy import Enforcer
from policy.exceptions import PolicyNotAuthorized
app = Flask(__name__)
enforcer = Enforcer('policy.json', raise_error=True)
@app.errorhandler(PolicyNotAuthorized)
def handle_policy_exception(error):
return str(error)
users = {
'lily': {
'id': 'd55a4192eb3b489589d5ee95dcf3af7d',
'roles': ['user', 'admin']
},
'kate': {
'id': '1a535309687244e2aa434b25ef4bfb59',
'roles': ['user']
},
'lucy': {
'id': '186977181e7f4a9e85104ca017e845f3',
'roles': ['user']
}
}
articles = {
'python': {
'id': 'e6e31ad693734b269099d9acac2cb800',
'user_id': '1a535309687244e2aa434b25ef4bfb59' # owned by kate
}
}
def login_required(func):
@functools.wraps(func)
def wrapped(*args, **kwargs):
username = request.args.get('me')
credential = users.get(username)
if not credential:
raise Exception('login required')
else:
g.cred = credential
return func(*args, **kwargs)
return wrapped
def enforce_policy(rule):
"""Enforce a policy to a API."""
def wrapper(func):
"""Decorator used for wrap API."""
@functools.wraps(func)
def wrapped(*args, **kwargs):
if enforcer.enforce(rule, {}, g.cred):
return func(*args, **kwargs)
return wrapped
return wrapper
@app.route('/user', methods=['GET'])
@login_required
@enforce_policy('user:create')
def create_user():
# do create action here
return 'user created'
@app.route('/article', methods=['GET'])
@login_required
def delete_article():
article_name = request.args.get('name')
article = articles.get(article_name)
# do fine-grained permission check here
enforcer.enforce('article:delete', article, g.cred)
# do delete action here
return 'arcticle %s deleted' % article['id']
if __name__ == '__main__':
app.run(port=8888, debug=True)
```
#### View-Level
We provide a ``enforce_policy`` decorator to enforce policy on views ``create_user``.
We head to http://127.0.0.1:8888/user?me=kate to simulate ``kate``'s creating user and get a error:
user:create on {} by {'roles': ['user'], 'id': '1a535309687244e2aa434b25ef4bfb59'} disallowed by policy
Then we head to http://127.0.0.1:8888/user?me=lily to simulate ``lily``'s creating user and get a successful response:
user created
#### Fine-Grained
In some scenarios we want a fine-grained permission check. We enforce policy inside view ``delete_article``, because outside of it we can't know which article the user wants to delete.
We head to http://127.0.0.1:8888/article?me=lucy&name=python to simulate ``lucy``'s deleting article and get a error:
article:delete on {'user_id': '1a535309687244e2aa434b25ef4bfb59', 'id': 'e6e31ad693734b269099d9acac2cb800'} by {'roles': ['user'], 'id': '186977181e7f4a9e85104ca017e845f3'} disallowed by policy
Then we head to http://127.0.0.1:8888/article?me=kate&name=python to simulate ``kate``'s deleting article and get a successful response because ``kate`` is the article's owner:
arcticle e6e31ad693734b269099d9acac2cb800 deleted
Finally we head to http://127.0.0.1:8888/article?me=lily&name=python to simulate ``lily``'s deleting article and get a successful response because ``lily`` is a admin-role user:
arcticle e6e31ad693734b269099d9acac2cb800 deleted
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
policy-1.0.0.zip
(21.0 kB
view details)
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
policy-1.0.0-py3-none-any.whl
(11.5 kB
view details)
File details
Details for the file policy-1.0.0.zip.
File metadata
- Download URL: policy-1.0.0.zip
- Upload date:
- Size: 21.0 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/1.11.0 pkginfo/1.4.2 requests/2.18.4 setuptools/39.2.0 requests-toolbelt/0.8.0 tqdm/4.23.4 CPython/3.4.3
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
66b747f621d74d60184f4155ad01510b81e150e15cd596a4de99d48e9fd2f182
|
|
| MD5 |
41c218ba3db230cf1b5691de0bfdbda1
|
|
| BLAKE2b-256 |
8cfcb9d9ea164165f704fe3ddf0f9d8f3b3f63fb96f7c9bfdcbeb7eb1c05b2a6
|
File details
Details for the file policy-1.0.0-py3-none-any.whl.
File metadata
- Download URL: policy-1.0.0-py3-none-any.whl
- Upload date:
- Size: 11.5 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/1.11.0 pkginfo/1.4.2 requests/2.18.4 setuptools/39.2.0 requests-toolbelt/0.8.0 tqdm/4.23.4 CPython/3.4.3
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
9d3c37761720657bed51fc7c608e88f4e666bea4acceed64159cffd70262c711
|
|
| MD5 |
bd601154f53f2e3c315ca1e283dfd929
|
|
| BLAKE2b-256 |
5e61284a7cd89afc4a84576a8f6a62be2545faa0e900e573cfbb8f747d12a4a3
|