This repository has been archived by the owner on Dec 15, 2019. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
15 changed files
with
1,114 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -8,4 +8,5 @@ build/ | |
htmlcov/ | ||
.tox/ | ||
_build | ||
.DS_Store | ||
.DS_Store | ||
*~ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,153 @@ | ||
# Makefile for Sphinx documentation | ||
# | ||
|
||
# You can set these variables from the command line. | ||
SPHINXOPTS = | ||
SPHINXBUILD = sphinx-build | ||
PAPER = | ||
BUILDDIR = _build | ||
|
||
# Internal variables. | ||
PAPEROPT_a4 = -D latex_paper_size=a4 | ||
PAPEROPT_letter = -D latex_paper_size=letter | ||
ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . | ||
# the i18n builder cannot share the environment and doctrees with the others | ||
I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . | ||
|
||
.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext | ||
|
||
help: | ||
@echo "Please use \`make <target>' where <target> is one of" | ||
@echo " html to make standalone HTML files" | ||
@echo " dirhtml to make HTML files named index.html in directories" | ||
@echo " singlehtml to make a single large HTML file" | ||
@echo " pickle to make pickle files" | ||
@echo " json to make JSON files" | ||
@echo " htmlhelp to make HTML files and a HTML help project" | ||
@echo " qthelp to make HTML files and a qthelp project" | ||
@echo " devhelp to make HTML files and a Devhelp project" | ||
@echo " epub to make an epub" | ||
@echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" | ||
@echo " latexpdf to make LaTeX files and run them through pdflatex" | ||
@echo " text to make text files" | ||
@echo " man to make manual pages" | ||
@echo " texinfo to make Texinfo files" | ||
@echo " info to make Texinfo files and run them through makeinfo" | ||
@echo " gettext to make PO message catalogs" | ||
@echo " changes to make an overview of all changed/added/deprecated items" | ||
@echo " linkcheck to check all external links for integrity" | ||
@echo " doctest to run all doctests embedded in the documentation (if enabled)" | ||
|
||
clean: | ||
-rm -rf $(BUILDDIR)/* | ||
|
||
html: | ||
$(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html | ||
@echo | ||
@echo "Build finished. The HTML pages are in $(BUILDDIR)/html." | ||
|
||
dirhtml: | ||
$(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml | ||
@echo | ||
@echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." | ||
|
||
singlehtml: | ||
$(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml | ||
@echo | ||
@echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." | ||
|
||
pickle: | ||
$(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle | ||
@echo | ||
@echo "Build finished; now you can process the pickle files." | ||
|
||
json: | ||
$(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json | ||
@echo | ||
@echo "Build finished; now you can process the JSON files." | ||
|
||
htmlhelp: | ||
$(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp | ||
@echo | ||
@echo "Build finished; now you can run HTML Help Workshop with the" \ | ||
".hhp project file in $(BUILDDIR)/htmlhelp." | ||
|
||
qthelp: | ||
$(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp | ||
@echo | ||
@echo "Build finished; now you can run "qcollectiongenerator" with the" \ | ||
".qhcp project file in $(BUILDDIR)/qthelp, like this:" | ||
@echo "# qcollectiongenerator $(BUILDDIR)/qthelp/Haddock.qhcp" | ||
@echo "To view the help file:" | ||
@echo "# assistant -collectionFile $(BUILDDIR)/qthelp/Haddock.qhc" | ||
|
||
devhelp: | ||
$(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp | ||
@echo | ||
@echo "Build finished." | ||
@echo "To view the help file:" | ||
@echo "# mkdir -p $$HOME/.local/share/devhelp/Haddock" | ||
@echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/Haddock" | ||
@echo "# devhelp" | ||
|
||
epub: | ||
$(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub | ||
@echo | ||
@echo "Build finished. The epub file is in $(BUILDDIR)/epub." | ||
|
||
latex: | ||
$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex | ||
@echo | ||
@echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." | ||
@echo "Run \`make' in that directory to run these through (pdf)latex" \ | ||
"(use \`make latexpdf' here to do that automatically)." | ||
|
||
latexpdf: | ||
$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex | ||
@echo "Running LaTeX files through pdflatex..." | ||
$(MAKE) -C $(BUILDDIR)/latex all-pdf | ||
@echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." | ||
|
||
text: | ||
$(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text | ||
@echo | ||
@echo "Build finished. The text files are in $(BUILDDIR)/text." | ||
|
||
man: | ||
$(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man | ||
@echo | ||
@echo "Build finished. The manual pages are in $(BUILDDIR)/man." | ||
|
||
texinfo: | ||
$(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo | ||
@echo | ||
@echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." | ||
@echo "Run \`make' in that directory to run these through makeinfo" \ | ||
"(use \`make info' here to do that automatically)." | ||
|
||
info: | ||
$(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo | ||
@echo "Running Texinfo files through makeinfo..." | ||
make -C $(BUILDDIR)/texinfo info | ||
@echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." | ||
|
||
gettext: | ||
$(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale | ||
@echo | ||
@echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." | ||
|
||
changes: | ||
$(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes | ||
@echo | ||
@echo "The overview file is in $(BUILDDIR)/changes." | ||
|
||
linkcheck: | ||
$(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck | ||
@echo | ||
@echo "Link check complete; look for any errors in the above output " \ | ||
"or in $(BUILDDIR)/linkcheck/output.txt." | ||
|
||
doctest: | ||
$(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest | ||
@echo "Testing of doctests in the sources finished, look at the " \ | ||
"results in $(BUILDDIR)/doctest/output.txt." |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,85 @@ | ||
Haddock API Description | ||
======================= | ||
|
||
The Haddock API Description is a standard structure that Haddock uses to build your API. It contains information about your project (``metadata``), your APIs (``api``), the processors behind those APIs (``getProcessors``/``postProcessors``) and parameters that your API takes and responds with (``parameters`` and ``parameterOptions``). | ||
|
||
The API Description ends up having two top-level parts - the ``metadata`` and the ``api``. They are laid out like this:: | ||
|
||
{ | ||
"metadata": { | ||
... | ||
}, | ||
"api": { | ||
... | ||
} | ||
} | ||
|
||
|
||
Metadata | ||
-------- | ||
|
||
The ``metadata`` contains three things: | ||
|
||
- ``name``: The computer-friendly name. | ||
- ``friendlyName``: The user-friendly name. | ||
- ``versions``: A list of applicable versions. They don't have to be 1, 2, or whatever - they're just used later on in ``api``. Note that there is one special version - "ROOT", which moves all of the endpoints to the root (for example, ``/weather``, instead of ``/v1/weather``). | ||
- ``apiInfo``: Whether or not you want automatic API documentation generated. | ||
|
||
|
||
API | ||
--- | ||
|
||
The ``api`` contains a list of dicts, which are API endpoints. In each API method there is: | ||
|
||
- ``name``: The computer-friendly name. This is used in naming your functions later! | ||
- ``friendlyName``: The user-friendly name. | ||
- ``description``: The user-friendly description. | ||
- ``endpoint``: The URL endpoint. For example, it will make a processor for v1 be under "/v1/weather". | ||
- ``requiresAuthentication`` (optional): A boolean that defines whether this API needs authentication. Default is false. | ||
- ``rateLimitNumber`` (optional): How many times per unit of time that this API may be called by the API key. | ||
- ``rateLimitTimescale`` (optional): The timescale that the limit number works off, in seconds. For example, a ``rateLimitNumber`` of 10 and a ``rateLimitTimescale`` of 60 means that 10 requests can be made in a sliding window of 60 seconds. | ||
- ``getProcessors`` (optional): A list of processors (see below). These processors respond to a HTTP GET. | ||
- ``postProcessors`` (optional): A list of processors (see below). These processors respond to a HTTP POST. | ||
|
||
|
||
Processors | ||
---------- | ||
|
||
Processors are the bits of your API that do things. They are made up of dicts, and contain the following fields: | ||
|
||
- ``versions``: A list of versions (see ``metadata``) which this endpoint applies to. | ||
- ``paramsType`` (optional): Where the params will be - either ``url`` (in ``request.args``) or ``jsonbody`` (for example, the body of a HTTP POST). Defaults to ``url``. | ||
- ``returnFormat`` (optional): Either ``dict`` or ``list`` (which means a ``list`` of ``dict``s, conforming to the params below) | ||
- ``requiredParams`` (optional): Parameters that the API consumer *has* to give. This is a list, the contents of which are explained below. | ||
- ``optionalParams`` (optional): Parameters that the API consumer can give, if they want to. This is a list, the contents of which are explained below. | ||
- ``returnParams`` (optional): Parameters that your API *has* to return. This is a list, the contents of which are explained below. | ||
- ``optionalReturnParams`` (optional): Parameters that your API may return. This is a list, the contents of which are explained below. | ||
|
||
Please note that if you have set ``requiredParams``, you MUST set every other key that may be given in ``optionalParams``! Same goes with ``returnParams``. | ||
|
||
|
||
Parameters | ||
---------- | ||
|
||
When defining the parameters your API can give/take, you can do it two ways. The first way is just giving a string containing the param key, the second is giving it a more detailed ``dict``. The dict fields are below. | ||
|
||
- ``param``: The parameter key. | ||
- ``description`` (optional): The user-friendly description of what this parameter is for. This is shown in the API documentation. | ||
- ``type`` (optional): A type that can be displayed in the API documentation. This isn't enforced. | ||
- ``example`` (optional): An example value, for the API documentation. | ||
- ``paramOptions`` (optional): If this parameter can only accept a certain number of values, use this to restrict it to them automatically. It's a list, containing either a string of an acceptable value, or a ``dict`` (details below). These are also shown in the API documentation. | ||
|
||
|
||
Parameter Options | ||
----------------- | ||
|
||
If you want to document your parameter options a bit better than simply giving it values, you can do so by making ``paramOptions`` a list of ``dict``s with the following values: | ||
- ``data``: The acceptable value. | ||
- ``meaning``: The meaning of this value. Used in the API documentation. | ||
|
||
|
||
Example | ||
------- | ||
|
||
For a proper example, see ``betterAPI.json`` under ``haddock/test/``. It has nearly every option defined in there somewhere. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,135 @@ | ||
============================= | ||
Introduction - Authentication | ||
============================= | ||
|
||
Some APIs may need authentication before accessing - for example, if you are writing a service rather than just a public data API. Haddock allows you to either do the authentication yourself, or hook in a "Shared Secret Source" which will request a user's shared secret from your backend. | ||
|
||
Using a Shared Secret Source | ||
============================ | ||
|
||
For this example, we will be using a new API Description. | ||
|
||
API Description | ||
--------------- | ||
|
||
Put this in ``authapi.json``:: | ||
|
||
{ | ||
"metadata": { | ||
"name": "authapi", | ||
"friendlyName": "An Authenticated API", | ||
"versions": [1], | ||
"apiInfo": true | ||
}, | ||
"api": [ | ||
{ | ||
"name": "supersecretdata", | ||
"friendlyName": "Super secret data endpoint!!!!", | ||
"endpoint": "supersecretdata", | ||
"requiresAuthentication": true, | ||
"getProcessors": [ | ||
{ | ||
"versions": [1] | ||
} | ||
] | ||
} | ||
] | ||
} | ||
|
||
The new part of this is ``requiresAuthentication`` in our single API, which is now set to ``true``. | ||
|
||
Python Implementation | ||
--------------------- | ||
|
||
Put this into ``authapi.py``:: | ||
|
||
import json | ||
from haddock.api import API, DefaultServiceClass | ||
from haddock import auth | ||
|
||
class AuthAPIServiceClass(DefaultServiceClass): | ||
def __init__(self): | ||
users = [{ | ||
"username": "squirrel", | ||
"canonicalUsername": "secretsquirrel@mi6.gov.uk", | ||
"password": "secret" | ||
}] | ||
self.auth = auth.DefaultHaddockAuthenticator( | ||
auth.InMemoryStringSharedSecretSource(users)) | ||
|
||
class AuthAPI(object): | ||
class v1(object): | ||
def supersecretdata_GET(self, request, params): | ||
return "Logged in as %s" % (params.get("haddockAuth"),) | ||
|
||
APIDescription = json.load(open("authapi.json")) | ||
myAPI = API(AuthAPI, APIDescription, serviceClass=AuthAPIServiceClass()) | ||
myAPI.getApp().run("127.0.0.1", 8094) | ||
|
||
In our implementation, we now import ``haddock.auth``, and use two portions of it when creating our service class. We set ``self.auth`` to be a new instance of ``auth.DefaultHaddockAuthenticator``, with a ``auth.InMemoryStringSharedSecretSource`` as its only argument, with that taking a list of users. | ||
|
||
How Authentication in Haddock Works | ||
----------------------------------- | ||
|
||
Before your API method is called, Haddock checks the API description, looking for ``requiresAuthentication`` on the endpoint. If it's found, then it will look in the HTTP ``Authorized`` header for ``Basic``. It will then call ``auth_usernameAndPassword`` on the Haddock authenticator, which will then check it and decide whether or not to allow the request. | ||
|
||
Since this is boilerplate, Haddock abstracts it into the ``DefaultHaddockAuthenticator``, which takes a ``SharedSecretSource``. Currently, the source requires only one function - ``getUserDetails``. This is called, asking for the details of a user, which the authenticator will then check against the request. If it is successful, the authenticator will return either the user's *canonical username* or their username. | ||
|
||
Canonical usernames are returned by the Haddock authenticator when possible, which are then placed in a ``haddockAuth`` param. Your API method will get this, and know that this is the user which has been successfully authenticated. | ||
|
||
The ``InMemoryStringSharedSecretSource`` Source | ||
----------------------------------------------- | ||
|
||
The ``InMemoryStringSharedSecretSource`` takes a list of users, which consists of a ``username``, ``password`` and optionally a ``canonicalUsername``. | ||
|
||
Running It | ||
---------- | ||
|
||
Now, since we have got our authentication-capable API, let's test it. Try running ``curl http://localhost:8094/v1/supersecretdata``, you should get this back:: | ||
|
||
{"status": "fail", "data": "Authentication required."} | ||
|
||
Haddock is now checking for authentication. Let's try giving it a username and password, with ``curl http://localhost:8094/v1/supersecretdata -u squirrel:secret``:: | ||
|
||
{"status": "success", "data": "Logged in as secretsquirrel@mi6.gov.uk"} | ||
|
||
As you can see, we returned the canonical username in ``supersecretdata_GET``, which is ``secretsquirrel@mi6.gov.uk``. | ||
|
||
|
||
Why Canonical Usernames? | ||
======================== | ||
|
||
Since this is an API, it may have sensitive data behind it, which you want to control access to. Controlling it via authentication is only solving part of the problem - you need to make sure that if the shared secret is lost, you can rescind access to it. Since changing passwords is a pain for users, a better solution is to have *API specific credentials*, and Haddock's authentication is made to support that. | ||
|
||
When giving out access to an API, you should create a set of API specific credentials - that is, a randomly generated username and password which is then used against your API, and can be revoked if required. Simply store the random creds, and a link to the user's real (canonical) username, and give that to the authenticator. | ||
|
||
Implementing Your Own Shared Secret Source | ||
========================================== | ||
|
||
This is taken from Tomato Salad, a project using Haddock. | ||
:: | ||
|
||
class tsSharedSecretSource(object): | ||
def __init__(self, db): | ||
self.db = db | ||
|
||
def getUserDetails(self, username): | ||
def _continue(result): | ||
if result: | ||
res = {} | ||
res["username"] = result["APIKeyUsername"] | ||
res["canonicalUsername"] = result["userEmail"] | ||
res["password"] = result["APIKeyPassword"] | ||
return res | ||
raise AuthenticationFailed("Incorrect API key.") | ||
|
||
d = self.db.fetchAPIKey(username) | ||
d.addCallback(_continue) | ||
return d | ||
|
||
class tsServiceClass(DefaultServiceClass): | ||
def __init__(self): | ||
self.db = Database( | ||
{"connectionString": "sqlite:///tomatosalad.db"}) | ||
self.auth = DefaultHaddockAuthenticator( | ||
tsSharedSecretSource(self.db)) |
Oops, something went wrong.