зеркало из https://github.com/mozilla/doorman.git
Add a simple Python Flask example.
This commit is contained in:
Родитель
17336e76e6
Коммит
8bbc739e2c
|
@ -5,3 +5,6 @@ policies.yaml
|
|||
vendor/
|
||||
node_modules/
|
||||
api-docs/
|
||||
__pycache__/
|
||||
*.pyc
|
||||
example/python/records/*.json
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
# Doorman client example
|
||||
|
||||
Here are some example of how to integrate Doorman with your service.
|
||||
|
||||
- [Python / Flask example](python/)
|
|
@ -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" = "*"
|
|
@ -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
|
|
@ -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))
|
Загрузка…
Ссылка в новой задаче