Skip to content
This repository has been archived by the owner on Dec 15, 2019. It is now read-only.

Commit

Permalink
first stab at docs
Browse files Browse the repository at this point in the history
  • Loading branch information
hawkowl committed Aug 26, 2014
1 parent fc06fd0 commit 8ad76a6
Show file tree
Hide file tree
Showing 15 changed files with 1,114 additions and 1 deletion.
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,5 @@ build/
htmlcov/
.tox/
_build
.DS_Store
.DS_Store
*~
153 changes: 153 additions & 0 deletions docs/Makefile
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."
85 changes: 85 additions & 0 deletions docs/apidescription.rst
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.
135 changes: 135 additions & 0 deletions docs/authentication.rst
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))

0 comments on commit 8ad76a6

Please sign in to comment.