summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLars Wirzenius <liw@liw.fi>2017-07-31 14:42:36 +0300
committerLars Wirzenius <liw@liw.fi>2017-07-31 14:42:36 +0300
commitb361acdc692cc98642652ce0a9128276ac6658d0 (patch)
tree8295648f8812d1049ddb384696fc22f9628a983c
parent41a5b87f00a30caf6c6e366809269929772d46d0 (diff)
downloadapifw-b361acdc692cc98642652ce0a9128276ac6658d0.tar.gz
Add: stuff to allow tests to run
-rw-r--r--apitest.key51
-rw-r--r--apitest.key.pub1
-rw-r--r--apitest.py145
-rwxr-xr-xcheck25
-rwxr-xr-xcreate-token44
-rwxr-xr-xgenerate-rsa-key34
-rw-r--r--pylint.conf20
-rw-r--r--without-tests5
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)
diff --git a/check b/check
new file mode 100755
index 0000000..afccfdf
--- /dev/null
+++ b/check
@@ -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