Skip to content

Commit

Permalink
Merge pull request #109 from plone/authentication
Browse files Browse the repository at this point in the history
Authentication
  • Loading branch information
tisto committed May 24, 2016
2 parents 4ae4928 + b49cb09 commit 8698c0c
Show file tree
Hide file tree
Showing 22 changed files with 912 additions and 4 deletions.
9 changes: 9 additions & 0 deletions docs/source/_json/login.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
POST /plone/@login
Accept: application/json

HTTP 200 OK
content-type: application/json

{
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJmdWxsbmFtZSI6IiIsInN1YiI6ImFkbWluIn0.SZDnl_baH5M_StJJrzfbj7o-5My30NmSFbMrhpSX5I4"
}
9 changes: 9 additions & 0 deletions docs/source/_json/login_renew.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
POST /plone/@login-renew
Accept: application/json

HTTP 200 OK
content-type: application/json

{
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJmdWxsbmFtZSI6IiIsInN1YiI6ImFkbWluIn0.SZDnl_baH5M_StJJrzfbj7o-5My30NmSFbMrhpSX5I4"
}
5 changes: 5 additions & 0 deletions docs/source/_json/logout.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
POST /plone/@logout
Accept: application/json

HTTP 204 No Content

89 changes: 88 additions & 1 deletion docs/source/authentication.rst
Original file line number Diff line number Diff line change
Expand Up @@ -31,4 +31,91 @@ Or the same example using ``curl``:

.. code:: python
curl -u username:password -H 'Accept:application/json' $URL
curl -u username:password -H 'Accept:application/json' $URL
JSON Web Tokens (JWT)
---------------------

``plone.restapi`` includes a Plone PAS plugin for authentication with JWT. The
plugin is installed automatically when installing the product.

A JWT token can be acquired by posting a users credentials to the ``@login``
endpoint.

.. example-code::

.. code-block:: http-request
POST /@login HTTP/1.1
Accept: application/json
Content-Type: application/json
{
'login': 'admin',
'password': 'admin',
}
.. code-block:: curl
curl -i \
-X POST \
-H "Accept: application/json" \
-H "Content-type: application/json" \
--data-raw '{"login":"admin", "password": "admin"}' \
http://localhost:8080/Plone/@login
.. code-block:: python-requests
requests.post('http://localhost:8080/Plone/@login',
headers={'Accept': 'application/json', 'Content-Type': 'application/json'},
data={'login': 'admin', 'password': 'admin'})
The server responds with a JSON object containing the token.

.. literalinclude:: _json/login.json
:language: js

The token can now be used in subsequent requests by including it in the
``Authorization`` header:

.. code::
GET /Plone HTTP/1.1
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJmdWxsbmFtZSI6IiIsInN1YiI6ImFkbWluIiwiZXhwIjoxNDY0MDQyMTAzfQ.aOyvMwdcIMV6pzC0GYQ3ZMdGaHR1_W7DxT0W0ok4FxI
Accept: application/json
By default the token will expire after 12 hours and thus must be renewed before
expiration. To renew the token simply post to the ``@login-renew`` endpoint.

.. code::
POST /@login-renew HTTP/1.1
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJmdWxsbmFtZSI6IiIsInN1YiI6ImFkbWluIiwiZXhwIjoxNDY0MDQyMTAzfQ.aOyvMwdcIMV6pzC0GYQ3ZMdGaHR1_W7DxT0W0ok4FxI
Accept: application/json
The server returns a JSON object with a new token:

.. literalinclude:: _json/login_renew.json
:language: js


The ``@logout`` endpoint can be used to invalidate tokens. However by default
tokens are not persisted on the server and thus can not be invalidated. To enable
token invaldiation, activate the ``store_tokes`` option in the PAS plugin. If you
need tokens that are valid indefinitely you should also disable the use of Plone's
keyring in the PAS plugin (option ``use_keyring``).

The logout request must contain the existing token in the ``Authorization`` header.

.. code::
POST /@logout HTTP/1.1
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJmdWxsbmFtZSI6IiIsInN1YiI6ImFkbWluIiwiZXhwIjoxNDY0MDQyMTAzfQ.aOyvMwdcIMV6pzC0GYQ3ZMdGaHR1_W7DxT0W0ok4FxI
Accept: application/json
If invalidation succeeds, the server responds with an empty 204 reponse:

.. literalinclude:: _json/logout.json
:language: js
1 change: 1 addition & 0 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
install_requires=[
'setuptools',
'plone.rest >= 1.0a6',
'PyJWT',
],
extras_require={'test': [
'Products.Archetypes',
Expand Down
16 changes: 15 additions & 1 deletion src/plone/restapi/__init__.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,14 @@
# -*- coding: utf-8 -*-
from AccessControl import allow_module
allow_module('json')
from AccessControl.Permissions import add_user_folders
from Products.PluggableAuthService.PluggableAuthService import (
registerMultiPlugin)
from plone.restapi.pas import plugin

import pkg_resources

allow_module('json')

try:
pkg_resources.get_distribution('plone.app.testing')
except pkg_resources.DistributionNotFound:
Expand All @@ -21,6 +26,15 @@


def initialize(context):
registerMultiPlugin(plugin.JWTAuthenticationPlugin.meta_type)
context.registerClass(
plugin.JWTAuthenticationPlugin,
permission=add_user_folders,
constructors=(plugin.manage_addJWTAuthenticationPlugin,
plugin.addJWTAuthenticationPlugin),
visibility=None,
)

if REGISTER_TEST_TYPES:
from Products.Archetypes.ArchetypeTool import process_types, listTypes
from Products.CMFCore import permissions
Expand Down
8 changes: 8 additions & 0 deletions src/plone/restapi/configure.zcml
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,14 @@
provides="Products.GenericSetup.interfaces.EXTENSION"
/>

<!-- Register "various" import step -->
<genericsetup:importStep
name="plone.restapi"
title="plone.restapi special import handlers"
description=""
handler="plone.restapi.setuphandlers.import_various"
/>

<include package="plone.rest" file="configure.zcml" />
<include package=".services" />
<include package=".serializer" />
Expand Down
Empty file.
33 changes: 33 additions & 0 deletions src/plone/restapi/pas/add_plugin.zpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:tal="http://xml.zope.org/namespaces/tal">
<body>
<h1 tal:replace="structure here/manage_page_header">Header</h1>

<h2 tal:define="form_title string:JWT Authentication Plugin"
tal:replace="structure here/manage_form_title">Form Title</h2>

<p class="form-help">
Plone PAS plugin for authentication with JSON web tokens (JWT)
</p>

<form action="addJWTAuthenticationPlugin" method="post">
<table>
<tr>
<td class="form-label">Id</td>
<td><input type="text" name="id_" tal:attributes="value request/id|string:jwt_auth"/></td>
</tr>
<tr>
<td class="form-label">Title</td>
<td><input type="text" name="title"/></td>
</tr>
<tr>
<td colspan="2">
<div class="form-element">
<input type="submit" value="Add"/>
</div>
</td>
</tr>
</table>
</form>
</body>
</html>
47 changes: 47 additions & 0 deletions src/plone/restapi/pas/config.zpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
<h1 tal:replace="structure here/manage_page_header"> PAGE HEADER </h1>
<h2 tal:replace="structure here/manage_tabs"> PAGE HEADER </h2>

<h3>JWT Authentication</h3>

<p class="form-help">
Plone PAS plugin for authentication with JSON web tokens (JWT).
</p>

<form action="manage_updateConfig" method="post">
<table>
<tr valign="top">
<td><div class="form-label">Token Validity Timeout (in seconds)</div>
<div class="form-help">After this, the token is invalid and the user
must login again. Set to 0 for the token to remain valid indefinitely.</div>
</td>
<td><input type="text" name="token_timeout"
tal:attributes="value context/token_timeout|nothing"/></td>
</tr>
<tr>
<td align="left" valign="top">
<input type="checkbox" name="use_keyring" id="use-keyring"
tal:attributes="checked python: context.use_keyring and 'checked'"/>&nbsp;<label class="form-label" for="use-keyring">Use Keyring</label>
<div class="form-help">If enabled, tokens are signed with a secret from
Plone's keyring. If you want tokens that remain valid indefinitely you should disable this.</div>
</td>
</tr>
<tr>
<td align="left" valign="top">
<input type="checkbox" name="store_tokens" id="store-tokens"
tal:attributes="checked python: context.store_tokens and 'checked'"/>&nbsp;<label class="form-label" for="store-tokens">Store tokens</label>
<div class="form-help">By default tokens are not stored on the server and
thus can't be invalidated. If enabled, tokens that don't expire can be invalidated.</div>
</td>
</tr>
<tr>
<td colspan="2">
<div class="form-element">
<input type="submit" value="Update"/>
</div>
</td>
</tr>
</table>
</form>


<h1 tal:replace="structure here/manage_page_footer"> PAGE FOOTER </h1>

0 comments on commit 8698c0c

Please sign in to comment.