diff options
author | Lars Wirzenius <liw@liw.fi> | 2017-07-31 14:42:36 +0300 |
---|---|---|
committer | Lars Wirzenius <liw@liw.fi> | 2017-07-31 14:42:36 +0300 |
commit | b361acdc692cc98642652ce0a9128276ac6658d0 (patch) | |
tree | 8295648f8812d1049ddb384696fc22f9628a983c | |
parent | 41a5b87f00a30caf6c6e366809269929772d46d0 (diff) | |
download | apifw-b361acdc692cc98642652ce0a9128276ac6658d0.tar.gz |
Add: stuff to allow tests to run
-rw-r--r-- | apitest.key | 51 | ||||
-rw-r--r-- | apitest.key.pub | 1 | ||||
-rw-r--r-- | apitest.py | 145 | ||||
-rwxr-xr-x | check | 25 | ||||
-rwxr-xr-x | create-token | 44 | ||||
-rwxr-xr-x | generate-rsa-key | 34 | ||||
-rw-r--r-- | pylint.conf | 20 | ||||
-rw-r--r-- | without-tests | 5 |
8 files changed, 325 insertions, 0 deletions
diff --git a/apitest.key b/apitest.key new file mode 100644 index 0000000..e3518cd --- /dev/null +++ b/apitest.key @@ -0,0 +1,51 @@ +-----BEGIN RSA PRIVATE KEY----- +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 +-----END RSA PRIVATE KEY-----
\ No newline at end of file diff --git a/apitest.key.pub b/apitest.key.pub new file mode 100644 index 0000000..397dd2c --- /dev/null +++ b/apitest.key.pub @@ -0,0 +1 @@ +ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQDaHgjFPq6QUDMjVqp3v/MRGGC9zEXA13T0EwlvoZE5LOeTVri6mMNwaqxQyMEi1535sAVuSdb6VgYuzlMSRnEOEvadTSbmIj2TEvH3r0vfd33210I4WDTSEzdPbMiQRhmMG+EbW8UKIEWArJyyu6TQJGMmAsNMpr6mXi5AS0SmZSe+DK6JY4EgC6y2g9yGsPNoXYtIwdcdNUQ0w/FGdleFGP3/FzHj7KG3qL2Serpw0EDYEhGR1ZVQIptNAOwXdkCwdPdmZeREs3/W3Fl2sAf8M0wHgUe6qTzOC/cabmoGNlTNuQh9YKv3Y4D3gaZvhUvJIcVOuwtP4EwBcFjiX0wq3noYtnnVZEib3UIFV56RCMmJpfCTytTYCuNN6MHoJ1K1fN18SYKj1hR0cg0eqNcnHphx+1lA4c2mINMKNoIaH7hSIrNiviWhaPO4P2goMDg8ReCsUUi+0AAZ5VY71L6R6zn1Q8QeX1/2F4WU+AeYd64HBj4Z808NgBS09zJANVJ8k+up/kux8dwsHC7ItNGNQAkYqVw9ZYGUYNqUkzBUrxHtZRcH8k5F2fk5S5yQWKoP5N6pkLW4XYfXTFcFJ/pZg0VQRLxgowx90P7EW5vwi7WQnfSNdRW1WMVFzgwpFpS/7p4wMucD9P35opRG36TY5N2TBy+GYfUIQBVXZ7DVqQ==
\ No newline at end of file diff --git a/apitest.py b/apitest.py new file mode 100644 index 0000000..b3987d8 --- /dev/null +++ b/apitest.py @@ -0,0 +1,145 @@ +#!/usr/bin/python3 +# Copyright (C) 2017 Lars Wirzenius +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +# This is a test application for apifw, used by the apifw.yarn test +# suite. It also acts as an example of how apifw can be used. + +import logging +import os + +import yaml + +import apifw + + +# apifw requires a class to define routes on demand. This interface is provided +# by the apifw.Api class. This interface consists of a single method +# find_missing_route. In addition callback methods for particular routes +# may also be present. + +class Api(apifw.Api): + + def find_missing_route(self, path): + logging.info('find_missing_route called!\n') + return [ + { + 'path': '/version', + 'callback': self.version, + }, + { + 'method': 'PUT', + 'path': '/upload', + 'callback': self.upload, + }, + ] + + def version(self, content_type, body): + return apifw.Response({ + 'status': apifw.HTTP_OK, + 'body': 'version: 4.2', + 'headers': { + 'Content-Type': 'text/plain', + }, + }) + + def upload(self, content_type, body): + return apifw.Response({ + 'status': apifw.HTTP_OK, + 'body': 'thank you for %s\n' % body.decode('ascii'), + 'headers': { + 'Content-Type': 'text/plain', + }, + }) + + +# We want logging. gunicorn provides logging, but only of its own +# stuff, and if we log something ourselves, using logging.debug and +# its sibling functions, they don't end up in the log file defined by +# gunicorn. +# +# Instead a separate logger is defined. The logging in +# apifw.HttpTransaction are based on logging *dicts*, with arbitrary +# key/value pairs. This is because Lars liked log files that are easy +# to process programmatically. To use this, we define a "dict logger" +# function, which we tell apifw to use; by default, there is no +# logging. We also configure the Python logging library to write a log +# file; the name of the log file will be gotten from the APITEST_LOG +# environment variable. +# +# Ideally, we would parse command line arguments and a configuration +# file here, instead of using environment variables. Unfortunately, +# gunicorn wants to hog all of the command line for itself. +# +# It has been concluded that the sensible way is for a web app to no +# have command line options and to read configuration file from a +# fixed location, or have the name of the configuration file given +# using an environment variable. +# +# See also <https://stackoverflow.com/questions/8495367/>. + +def dict_logger(log, stack_info=None): + logging.info('Start log entry') + for key in sorted(log.keys()): + logging.info(' %r=%r', key, log[key]) + logging.info('Endlog entry') + if stack_info: + logging.info('Traceback', exc_info=True) + + +logfile = os.environ.get('APITEST_LOG') +if logfile: + logging.basicConfig(filename=logfile, level=logging.DEBUG) + + + +# Here is some configuaration for the framework. +# +# To validate the access tokens we'll be using in the API, we need to +# tell apifw which public key to use. We get that from an environment +# variable. It should be in OpenSSH public key format. +# +# The framework also needs to know the "audience" for which a token is +# created. We get that from the environment as well. + +config = { + 'token-public-key': os.environ['APITEST_PUBKEY'], + 'token-audience': os.environ['APITEST_AUD'], + 'token-issuer': os.environ['APITEST_ISS'], +} + + +# Here is the magic part. We create an instance of our Api class, and +# create a new Bottle application, using the +# apifw.create_bottle_application function. We assign the Bottle app +# to the variable "app", and when we start this program using +# gunicorn, we tell it to use the "app" variable. +# +# gunicorn3 --bind 127.0.0.1:12765 apitest:app +# +# gunicorn and Bottle then collaborate, using mysterious, unclear, and +# undocumented magic to run a web service. We don't know, and +# hopefully don't need to care, how the magic works. + +api = Api() +app = apifw.create_bottle_application(api, dict_logger, config) + +# If we are running this program directly with Python, and not via +# gunicorn, we can use the Bottle built-in debug server, which can +# make some things easier to debug. + +if __name__ == '__main__': + print('running in debug mode') + app.run(host='127.0.0.1', port=12765) @@ -0,0 +1,25 @@ +#!/bin/sh +# Copyright (C) 2017 Lars Wirzenius +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + + +set -eu + +python3 -m CoverageTestRunner --ignore-missing-from=without-tests + +pep8 apifw +pylint3 -j0 --rcfile pylint.conf apifw + +yarn apifw.yarn "$@" diff --git a/create-token b/create-token new file mode 100755 index 0000000..e2e9fbb --- /dev/null +++ b/create-token @@ -0,0 +1,44 @@ +#!/usr/bin/python3 +# Copyright (C) 2017 Lars Wirzenius +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + + +import sys +import time + +import Crypto.PublicKey.RSA + +import apifw + + +filename = sys.argv[1] +iss = sys.argv[2] +aud = sys.argv[3] +scopes = ' '.join(sys.argv[4].split()) + +key_text = open(filename, 'r').read() +key = Crypto.PublicKey.RSA.importKey(key_text) + +now = time.time() +claims = { + 'iss': iss, + 'sub': 'subject-uuid', + 'aud': aud, + 'exp': now + 3600, + 'scope': scopes, +} + +token = apifw.create_token(claims, key) +sys.stdout.write(token.decode('ascii')) diff --git a/generate-rsa-key b/generate-rsa-key new file mode 100755 index 0000000..d73ba0d --- /dev/null +++ b/generate-rsa-key @@ -0,0 +1,34 @@ +#!/usr/bin/python3 +# Copyright (C) 2017 Lars Wirzenius +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + + +import sys + +import Crypto.PublicKey.RSA + + +RSA_KEY_BITS = 4096 # A nice, currently safe length + +key = Crypto.PublicKey.RSA.generate(RSA_KEY_BITS) + +filename = sys.argv[1] + +def write(filename, byts): + with open(filename, 'w') as f: + f.write(byts.decode('ascii')) + +write(filename, key.exportKey('PEM')) +write(filename + '.pub', key.exportKey('OpenSSH')) diff --git a/pylint.conf b/pylint.conf new file mode 100644 index 0000000..95f7e92 --- /dev/null +++ b/pylint.conf @@ -0,0 +1,20 @@ +[MASTER] +persistent=no + +[MESSAGES CONTROL] +disable= + invalid-name, + missing-docstring, + no-member, + no-self-use, + redefined-variable-type, + too-few-public-methods, + too-many-public-methods, + unidiomatic-typecheck, + unused-argument, + +[REPORTS] +reports=no + +[SIMILARITIES] +min-similarity-lines=999 diff --git a/without-tests b/without-tests new file mode 100644 index 0000000..ba7dc90 --- /dev/null +++ b/without-tests @@ -0,0 +1,5 @@ +apitest.py +apifw/__init__.py +apifw/http.py +apifw/bottleapp.py +apifw/apixface.py |