summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLars Wirzenius <liw@liw.fi>2017-07-21 12:03:37 +0300
committerLars Wirzenius <liw@liw.fi>2017-07-21 12:03:37 +0300
commitf6cf60f7e6ac9ddcd52adc0b85b66142265b2693 (patch)
tree18431c8e72f9ab82fdc484a179c5c0f3368eb53a
parentfb5375e384515ac6a3be6b8fa56f1a454f969d16 (diff)
downloadqvisqve-f6cf60f7e6ac9ddcd52adc0b85b66142265b2693.tar.gz
Add: support for apifw callbacks to get request bodies
-rw-r--r--apifw.yarn13
-rw-r--r--apifw/bottleapp.py34
-rw-r--r--apitest.py11
-rwxr-xr-xcreate-token2
-rw-r--r--pylint.conf1
-rwxr-xr-xrun-apitest9
6 files changed, 62 insertions, 8 deletions
diff --git a/apifw.yarn b/apifw.yarn
index 9820d28..322430d 100644
--- a/apifw.yarn
+++ b/apifw.yarn
@@ -37,6 +37,11 @@ It's a silly name. Please suggest something better.
THEN HTTP status code is 200 OK
AND HTTP body is "version: 4.2"
+ WHEN client gets an authorization token with scope "uapi_upload_put"
+ AND client uploads a fake jpg
+ THEN HTTP status code is 200 OK
+ AND HTTP body is "thank you for fake jpg"
+
FINALLY stop apitest
@@ -80,6 +85,14 @@ It's a silly name. Please suggest something better.
curl -sv -H "Authorization: Bearer $token" \
"http://127.0.0.1:12765/version" > "$DATADIR/out" 2> "$DATADIR/err"
+ IMPLEMENTS WHEN client uploads a fake jpg
+ token="$(cat "$DATADIR/token")"
+ curl -sv -H "Authorization: Bearer $token" \
+ -H "Content-type: application/jpeg" \
+ -d "fake jpg" \
+ -X PUT \
+ "http://127.0.0.1:12765/upload" > "$DATADIR/out" 2> "$DATADIR/err"
+
IMPLEMENTS WHEN client gets an authorization token with scope "(.+)"
iss="$(cat "$DATADIR/iss")"
aud="$(cat "$DATADIR/aud")"
diff --git a/apifw/bottleapp.py b/apifw/bottleapp.py
index 60e3944..9002f02 100644
--- a/apifw/bottleapp.py
+++ b/apifw/bottleapp.py
@@ -14,6 +14,7 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
+import logging
import re
import time
@@ -100,17 +101,22 @@ class BottleAuthorizationPlugin:
value = request.get_header('Authorization', '')
if not value:
self.raise_unauthorized('No Authorization header')
+ logging.debug('Request has Authorization header: good')
return value
def parse_authorization_header(self, value):
words = value.split()
if len(words) != 2 or words[0].lower() != 'bearer':
self.raise_unauthorized('Authorization should be "Bearer TOKEN"')
+ logging.debug(
+ 'Request Authorization header looks like a bearer token: good')
return words[1]
def parse_token(self, token):
try:
- return apifw.decode_token(token, self.pubkey, audience=self.aud)
+ token = apifw.decode_token(token, self.pubkey, audience=self.aud)
+ logging.debug('Request Authorization token can be decoded: good')
+ return token
except jwt.InvalidTokenError as e:
self.raise_unauthorized(str(e))
@@ -118,11 +124,18 @@ class BottleAuthorizationPlugin:
if claims['iss'] != self.iss:
self.raise_unauthorized(
'Expected issuer %s, got %s' % (self.iss, claims['iss']))
+ logging.debug('Token issuer is correct: good')
def scope_allows_route(self, claim_scopes, route):
scopes = claim_scopes.split(' ')
route_scope = self.get_scope_for_route(route['method'], route['rule'])
- return route_scope in scopes
+ if route_scope in scopes:
+ logging.debug(
+ 'Route scope %s is in scopes %r', route_scope, scopes)
+ return True
+ logging.error(
+ 'Route scope %s is NOT in scopes %r', route_scope, scopes)
+ return False
def get_scope_for_route(self, method, rule):
scope = re.sub(self.route_pat, 'id', rule)
@@ -160,15 +173,30 @@ class BottleApplication:
routes = self._api.find_missing_route(bottle.request.path)
if routes:
for route in routes:
+ callback = self._callback_with_body(route['callback'])
route_dict = {
'method': route.get('method', 'GET'),
'path': route['path'],
- 'callback': route['callback'],
+ 'callback': callback,
}
self._bottleapp.route(**route_dict)
else:
raise
+ def _callback_with_body(self, callback):
+ def wrapper(*args, **kwargs):
+ content_type, body = self._get_request_body()
+ return callback(content_type, body, *args, **kwargs)
+ return wrapper
+
+ def _get_request_body(self):
+ content_type = bottle.request.get_header('Content-Type')
+ if content_type == 'application/json':
+ body = bottle.request.json
+ else:
+ body = bottle.request.body.read()
+ return content_type, body
+
def create_bottle_application(api, logger, config):
# Create a new bottle.Bottle application, set it up, and return it
diff --git a/apitest.py b/apitest.py
index ff87b37..8b2dc46 100644
--- a/apitest.py
+++ b/apitest.py
@@ -39,12 +39,19 @@ class Api(apifw.Api):
'path': '/version',
'callback': self.version,
},
+ {
+ 'method': 'PUT',
+ 'path': '/upload',
+ 'callback': self.upload,
+ },
]
- def version(self):
- logging.info('/version called!\n')
+ def version(self, content_type, body):
return 'version: 4.2'
+ def upload(self, content_type, body):
+ return 'thank you for %s\n' % body.decode('ascii')
+
# We want logging. gunicorn provides logging, but only of its own
# stuff, and if we log something ourselves, using logging.debug and
diff --git a/create-token b/create-token
index c0e43a6..e2e9fbb 100755
--- a/create-token
+++ b/create-token
@@ -26,7 +26,7 @@ import apifw
filename = sys.argv[1]
iss = sys.argv[2]
aud = sys.argv[3]
-scopes = sys.argv[4]
+scopes = ' '.join(sys.argv[4].split())
key_text = open(filename, 'r').read()
key = Crypto.PublicKey.RSA.importKey(key_text)
diff --git a/pylint.conf b/pylint.conf
index 369aad6..801baae 100644
--- a/pylint.conf
+++ b/pylint.conf
@@ -5,6 +5,7 @@ persistent=no
disable=
invalid-name,
missing-docstring,
+ no-member,
no-self-use,
redefined-variable-type,
too-few-public-methods,
diff --git a/run-apitest b/run-apitest
index 0af9b63..4455e33 100755
--- a/run-apitest
+++ b/run-apitest
@@ -34,9 +34,14 @@ export APITEST_ISS=testissuer
export APITEST_AUD=aud
export APITEST_LOG="$log"
+scopes="
+uapi_version_get
+uapi_upload_put
+"
+
./generate-rsa-key "$tmp/key"
-./create-token "$tmp/key" "$APITEST_ISS" "$APITEST_AUD" uapi_version_get > "$token"
+./create-token "$tmp/key" "$APITEST_ISS" "$APITEST_AUD" "$scopes" > "$token"
export APITEST_PUBKEY="$(cat "$tmp/key.pub")"
-gunicorn --bind 127.0.0.1:12765 -p "$tmp/pid" -w16 \
+gunicorn --bind 127.0.0.1:12765 -p "$tmp/pid" -w1 \
--log-file "$log" --log-level=debug apitest:app