Add a simple Python Flask example.

This commit is contained in:
Rémy HUBSCHER 2017-11-21 15:42:37 +01:00
Родитель 17336e76e6
Коммит 8bbc739e2c
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: E55ACEC592AC303B
7 изменённых файлов: 272 добавлений и 0 удалений

3
.gitignore поставляемый
Просмотреть файл

@ -5,3 +5,6 @@ policies.yaml
vendor/
node_modules/
api-docs/
__pycache__/
*.pyc
example/python/records/*.json

5
examples/README.md Normal file
Просмотреть файл

@ -0,0 +1,5 @@
# Doorman client example
Here are some example of how to integrate Doorman with your service.
- [Python / Flask example](python/)

13
examples/python/Pipfile Normal file
Просмотреть файл

@ -0,0 +1,13 @@
[[source]]
url = "https://pypi.python.org/simple"
verify_ssl = true
name = "pypi"
[dev-packages]
[packages]
flask = "*"
"python-dotenv" = "*"
"python-jose" = "*"
"flask-cors" = "*"

120
examples/python/Pipfile.lock сгенерированный Normal file
Просмотреть файл

@ -0,0 +1,120 @@
{
"_meta": {
"hash": {
"sha256": "8945c85e8436bb5f825358403b459dd640c494b819b6f09a474653091c02f14e"
},
"host-environment-markers": {
"implementation_name": "cpython",
"implementation_version": "3.5.3",
"os_name": "posix",
"platform_machine": "x86_64",
"platform_python_implementation": "CPython",
"platform_release": "4.10.0-33-generic",
"platform_system": "Linux",
"platform_version": "#37-Ubuntu SMP Fri Aug 11 10:55:28 UTC 2017",
"python_full_version": "3.5.3",
"python_version": "3.5",
"sys_platform": "linux"
},
"pipfile-spec": 6,
"requires": {},
"sources": [
{
"name": "pypi",
"url": "https://pypi.python.org/simple",
"verify_ssl": true
}
]
},
"default": {
"click": {
"hashes": [
"sha256:29f99fc6125fbc931b758dc053b3114e55c77a6e4c6c3a2674a2dc986016381d",
"sha256:f15516df478d5a56180fbf80e68f206010e6d160fc39fa508b65e035fd75130b"
],
"version": "==6.7"
},
"ecdsa": {
"hashes": [
"sha256:40d002cf360d0e035cf2cb985e1308d41aaa087cbfc135b2dc2d844296ea546c",
"sha256:64cf1ee26d1cde3c73c6d7d107f835fed7c6a2904aef9eac223d57ad800c43fa"
],
"version": "==0.13"
},
"flask": {
"hashes": [
"sha256:0749df235e3ff61ac108f69ac178c9770caeaccad2509cb762ce1f65570a8856",
"sha256:49f44461237b69ecd901cc7ce66feea0319b9158743dd27a2899962ab214dac1"
],
"version": "==0.12.2"
},
"flask-cors": {
"hashes": [
"sha256:55eb3864b4290f939ff19a2250fcb47d8c0f63d250c6780b922629299d011ec8",
"sha256:ac4b81b3d90f5f18714c995c807f94501df8c9bbc22ef4261c1cd850748c3850",
"sha256:62ebc5ad80dc21ca0ea9f57466c2c74e24a62274af890b391790c260eb7b754b"
],
"version": "==3.0.3"
},
"future": {
"hashes": [
"sha256:e39ced1ab767b5936646cedba8bcce582398233d6a627067d4c6a454c90cfedb"
],
"version": "==0.16.0"
},
"itsdangerous": {
"hashes": [
"sha256:cbb3fcf8d3e33df861709ecaf89d9e6629cff0a217bc2848f1b41cd30d360519"
],
"version": "==0.24"
},
"jinja2": {
"hashes": [
"sha256:2231bace0dfd8d2bf1e5d7e41239c06c9e0ded46e70cc1094a0aa64b0afeb054",
"sha256:ddaa01a212cd6d641401cb01b605f4a4d9f37bfc93043d7f760ec70fb99ff9ff"
],
"version": "==2.9.6"
},
"markupsafe": {
"hashes": [
"sha256:a6be69091dac236ea9c6bc7d012beab42010fa914c459791d627dad4910eb665"
],
"version": "==1.0"
},
"pycrypto": {
"hashes": [
"sha256:f2ce1e989b272cfcb677616763e0a2e7ec659effa67a88aa92b3a65528f60a3c"
],
"version": "==2.6.1"
},
"python-dotenv": {
"hashes": [
"sha256:dc7940052cfe170e881aea40feb4ea7776e6a97170ed038fd2ee7e26e47585f2",
"sha256:45e927c34204c90f5faa35ea8709b894f6b1a7712d77eb50940601068040993b"
],
"version": "==0.7.1"
},
"python-jose": {
"hashes": [
"sha256:fed56224664af0ebc3947853f1bed23b5609f90c7b02e3dce5ef5757d0301664",
"sha256:18e19f200f37a8ee6247921572807cc63ee034abdbf6854df1ae7c1f505cabcc"
],
"version": "==1.4.0"
},
"six": {
"hashes": [
"sha256:832dc0e10feb1aa2c68dcc57dbb658f1c7e65b9b61af69048abc87a2db00a0eb",
"sha256:70e8a77beed4562e7f14fe23a786b54f6296e34344c23bc42f07b15018ff98e9"
],
"version": "==1.11.0"
},
"werkzeug": {
"hashes": [
"sha256:e8549c143af3ce6559699a01e26fa4174f4c591dbee0a499f3cd4c3781cdec3d",
"sha256:903a7b87b74635244548b30d30db4c8947fe64c5198f58899ddcd3a13c23bb26"
],
"version": "==0.12.2"
}
},
"develop": {}
}

Просмотреть файл

@ -0,0 +1,41 @@
import json
import urllib
def json_dumps_ignore_none(d):
return json.dumps({k: v for k, v in d.items() if v is not None})
# Format error response and append status code.
class AuthZError(Exception):
def __init__(self, error, status_code):
self.error = error
self.status_code = status_code
def allowed(server, audience, *,
resource=None, action=None, jwt=None, principals=None, context=None):
iam_url = server + "/allowed"
payload = {
"resource": resource,
"action": action,
"principals": principals,
"context": context,
}
body = json_dumps_ignore_none(payload)
headers = {
"Authorization": jwt,
"Origin": audience,
}
r = urllib.request.Request(iam_url, data=body.encode("utf-8"), headers=headers)
try:
resp = urllib.request.urlopen(r)
except urllib.error.HTTPError as e:
raise AuthZError(e.read().decode("utf-8"), e.code)
response_body = json.loads(resp.read().decode("utf-8"))
if not response_body["allowed"]:
raise AuthZError(response_body, 403)
return response_body

Просмотреть файл

90
examples/python/server.py Normal file
Просмотреть файл

@ -0,0 +1,90 @@
#
# https://auth0.com/docs/quickstart/backend/python#add-api-authorization
# https://github.com/auth0-samples/auth0-python-api-samples/tree/master/00-Starter-Seed
import functools
import json
import os
from flask import Flask, request, jsonify, _app_ctx_stack
from flask_cors import cross_origin
from doorman import allowed, AuthZError
HERE = os.path.abspath(os.path.dirname(__file__))
IAM_SERVER = os.getenv("IAM_SERVER")
API_AUDIENCE = os.getenv("API_AUDIENCE")
app = Flask(__name__)
@app.errorhandler(AuthZError)
def handle_auth_error(ex):
response = jsonify(ex.error)
response.status_code = ex.status_code
return response
def authorized(resource, action):
def wrapped(f):
@functools.wraps(f)
def wrapper(*args, **kwargs):
jwt = request.headers.get("Authorization", None)
payload = allowed(IAM_SERVER, API_AUDIENCE, resource=resource, action=action, jwt=jwt)
_app_ctx_stack.top.current_user = payload
return f(*args, **kwargs)
return wrapper
return wrapped
@app.route("/")
@cross_origin(headers=["Content-Type", "Authorization"])
@cross_origin(headers=["Access-Control-Allow-Origin", "*"])
@authorized(resource="demo:hello", action="read")
def hello():
"""A valid access token is required to access this route
"""
top = _app_ctx_stack.top
return jsonify(top.current_user)
@app.route("/record/<record_id>", methods=('GET', 'PUT'))
@cross_origin(headers=["Content-Type", "Authorization"])
@cross_origin(headers=["Access-Control-Allow-Origin", "*"])
def record(record_id):
jwt = request.headers.get("Authorization", b'')
filename = os.path.join(HERE, "records", "{record_id}.json".format(
record_id=os.path.basename(record_id)))
new = True
if os.path.exists(filename):
new = False
with open(filename, 'r') as f:
record = json.load(f)
author = record["author"]
if request.method == "GET":
# READ
allowed(server=IAM_SERVER, audience=API_AUDIENCE,
resource="record", action="read", jwt=jwt, context={"author": author})
return jsonify(record['body'])
elif request.method == "PUT":
if new:
payload = allowed(server=IAM_SERVER, audience=API_AUDIENCE,
resource="record", action="create", jwt=jwt)
else:
payload = allowed(server=IAM_SERVER, audience=API_AUDIENCE,
resource="record", action="update", jwt=jwt,
context={"author": author})
with open(filename, 'w') as f:
body = {'body': request.get_json(), 'author': payload["principals"][1]}
print(body)
json.dump(body, f)
return jsonify(body)
if __name__ == "__main__":
print("IAM_SERVER", IAM_SERVER)
print("API_AUDIENCE", API_AUDIENCE)
app.run(host="0.0.0.0", port=os.getenv("PORT", 8000))