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