Skip to content

Commit

Permalink
tests passed for endpoint token
Browse files Browse the repository at this point in the history
  • Loading branch information
sneridagh committed Feb 21, 2012
1 parent 710ed8f commit e6d0ac5
Show file tree
Hide file tree
Showing 12 changed files with 267 additions and 32 deletions.
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,5 @@
*.log
*.pyc
*.egg-info
*.DS_Store
*.DS_Store
.codeintel
137 changes: 137 additions & 0 deletions README.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
Introduction
------------
Osiris (/oʊˈsaɪərɨs/) is an Egyptian god, usually identified as the god of the afterlife, the underworld and the dead. He is classically depicted as a green-skinned man with a pharaoh's beard, partially mummy-wrapped at the legs, wearing a distinctive crown with two large ostrich feathers at either side, and holding a symbolic crook and flail. Osiris was the afterlife's judge, he weighed the dead souls and compare them with the Feather of Truth. Those which weighed the most were sent to Ammut (the soul devourer) and not heavy enough to Aaru (the egyptian paradise).

Osiris is an oAuth 2.0 (draft 22) compliant server based on Pyramid. The current version (1.0) it supports the `Resource owner password credentials` authentication flow. It uses pyramid_who as user backend providing the way to behave as an oAuth authentication gateway. This means that you can use your authentication backend (LDAP, SQL, etc.) oAuth enabled with Osiris. Osiris uses a pluggable store factory to store the issued token information. The current version includes the MongoDB one.

The `Resource owner password credentials` flow
----------------------------------------------
This flow is not the most popular oAuth flow, but it's useful in case that we want to oAuth enable an app or a set of apps in an scenario with an already existing user backend. Using this flow you can use Osiris as a gateway between your existing user store and oAuth enable it. Osiris will authenticate the user credentials against your user store and if suceeds it will issue a oAuth token. Then, an app can use it to impersonate the user's token to access an oAuth enabled REST API, for example.

For that reason and out of the oAuth specification, Osiris features an additional endpoint to allow remote applications and resource servers to check previously issued tokens and users and validate it. This endpoint will respond if the token is valid for the user specified and if the token is not expired or revoked.

You can use Osiris as a standalone application or use it as a Pyramid plugin and make your app Osiris enabled.

Setup
-----

This is the configuration to use it as a standalone Pyramid app, along with your own one using Paste urlmap in your app .ini:

.. code-block:: ini
[server:main]
use = egg:Paste#http
host = 0.0.0.0
port = 80
[composite:main]
use = egg:Paste#urlmap
/ = YOURAPP
/oauth2 = osiris
[app:osiris]
use = egg:osiris
osiris.store = osiris.store.mongodb_store
osiris.store.host = localhost
osiris.store.port = 27017
osiris.store.db = osiris
osiris.store.collection = tokens
osiris.tokenexpiry = 0
osiris.whoconfig = %(here)s/who.ini
[app:YOURAPP]
use = egg:YOURAPP
full_stack = true
static_files = true
You can also Osiris enable your own app, in your __init__.py::

config.include(osiris)

and in the .ini:

.. code-block:: ini
osiris.store = osiris.store.mongodb_store
osiris.store.host = localhost
osiris.store.port = 27017
osiris.store.db = osiris
osiris.store.collection = tokens
osiris.tokenexpiry = 0
osiris.whoconfig = %(here)s/who.ini
Options
-------
These are the .ini options available for Osiris:

osiris.store
Currently only available ``osiris.store.mongodb_store``. Required.

osiris.store.host
Defaults to 'localhost'. Optional.

osiris.store.port
Defaults to '27017'. Optional.

osiris.store.db
The name of the database. Defaults to 'osiris'. Optional.

osiris.store.collection
The collection to store the tokens. Defaults to 'tokens'. Optional.

osiris.tokenexpiry
The time in seconds that the token is valid. Defaults to 0 (unlimited). Optional.

osiris.whoconfig
The pyramid_who (repoze.who) .ini with the configuration of the authentication backends. Required.

REST API for `Resource owner password credentials` flow
-------------------------------------------------------
Following the oAuth 2.0 authentication standard (draft 22), the `Resource owner password credentials` flow must implement this web services and use these parameters:

/token
Method:
POST

Params:
grant_type
Required. Value must be set to password

username
Required. The resource owner username, encoded as UTF-8.

password
Required. The resource owner password, encoded as UTF-8.

scope
Optional. The scope of the access request.

Content-Type:
application/x-www-form-urlencoded

Response:
HTTP/1.1 200 OK
Content-Type: application/json;charset=UTF-8
Cache-Control: no-store
Pragma: no-cache

{
"access_token":"2YotnFZFEjr1zCsicMWpAA",
"token_type":"bearer",
"expires_in":3600,
"scope": "exampleScope"
}

To do
-----
Osiris features only one oAuth 2.0 authentication flow, the `Resource owner password credentials`. It's ready to accomodate the remaining flows defined by oAuth 2.0. A similar case happens with the available storage backends. The current version sports only the MongoDB storage but Osiris support the use of a plugin storage model and can accomodate more storage types.

Of course, any contribution is welcome. Please, feel free to contribute with your own storage plugins and help implementing the remaining oAuth flows.

Credits
-------
Pluggable store factory inspired by Ben Bangert's Velruse (https://github.com/bbangert/velruse).
Borrowed error handling from pyramid-oauth2 (http://code.google.com/p/pyramid-oauth2/) by Kevin Van Wilder et al.
1 change: 0 additions & 1 deletion README.txt

This file was deleted.

3 changes: 2 additions & 1 deletion osiris/authorization.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,8 @@ def password_authorization(request, username, password, scope, expires_in):
# Issue token
if stored:
return dict(
token=token,
access_token=token,
token_type='bearer',
scope=scope,
expires_in=expires_in
)
Expand Down
25 changes: 0 additions & 25 deletions osiris/tests.py

This file was deleted.

Empty file added osiris/tests/__init__.py
Empty file.
1 change: 1 addition & 0 deletions osiris/tests/passwd
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
testuser:GAQ6kCHXfoiog
24 changes: 24 additions & 0 deletions osiris/tests/test.ini
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
[app:main]
use = egg:osiris

pyramid.reload_templates = true
pyramid.debug_authorization = false
pyramid.debug_notfound = false
pyramid.debug_routematch = false
pyramid.debug_templates = true
pyramid.default_locale_name = en
pyramid.includes = pyramid_debugtoolbar

osiris.store = osiris.store.mongodb_store
osiris.store.host = localhost
osiris.store.port = 27017
osiris.store.db = osiris
osiris.store.collection = tokens
osiris.tokenexpiry = 0

osiris.whoconfig = %(here)s/who.ini

[server:main]
use = egg:Paste#http
host = 0.0.0.0
port = 6543
46 changes: 46 additions & 0 deletions osiris/tests/tests.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import unittest
import os
from pyramid import testing
from paste.deploy import loadapp
import pymongo

class osirisTests(unittest.TestCase):
def setUp(self):
conf_dir = os.path.dirname(__file__)
self.app = loadapp('config:test.ini', relative_to=conf_dir)
from webtest import TestApp
self.testapp = TestApp(self.app)

def tearDown(self):
testing.tearDown()

def test_token_endpoint(self):
testurl = '/token?grant_type=password&username=testuser&password=test'
resp = self.testapp.post(testurl, status=200)
response = resp.json
self.assertTrue('access_token' in response and len(response.get('access_token')) == 20)
self.assertTrue('token_type' in response and response.get('token_type') == 'bearer')
self.assertTrue('scope' in response and response.get('scope') == '')
self.assertTrue('expires_in' in response and response.get('expires_in') == '0')
self.assertEqual(resp.content_type, 'application/json')

def test_token_endpoint_autherror(self):
# Not the password
testurl = '/token?grant_type=password&username=testuser&password=notthepassword'
resp = self.testapp.post(testurl, status=401)
self.assertEqual(resp.content_type, 'application/json')

# No such user
testurl = '/token?grant_type=password&username=nosuchuser&password=notthepassword'
resp = self.testapp.post(testurl, status=401)
self.assertEqual(resp.content_type, 'application/json')

def test_token_storage(self):
testurl = '/token?grant_type=password&username=testuser&password=test'
resp = self.testapp.post(testurl, status=200)
response = resp.json

token_store = self.app.registry.osiris_store.retrieve(response.get('access_token'))
self.assertTrue(token_store)
self.assertEqual(token_store.get('token'), response.get('access_token'))
self.assertEqual(token_store.get('username'), 'testuser')
51 changes: 51 additions & 0 deletions osiris/tests/who.ini
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
[plugin:redirform]
# identification and challenge
use = repoze.who.plugins.redirector:make_plugin
login_url = /login

[plugin:basicauth]
# identification and challenge
use = repoze.who.plugins.basicauth:make_plugin
realm = 'OSIRIS'

[plugin:auth_tkt]
# identification
use = repoze.who.plugins.auth_tkt:make_plugin
secret = sEEkr1t
cookie_name = chocolate
secure = False
include_ip = False

[plugin:htpasswd]
# authentication
use = repoze.who.plugins.htpasswd:make_plugin
filename = %(here)s/passwd
check_fn = repoze.who.plugins.htpasswd:crypt_check

[general]
request_classifier = repoze.who.classifiers:default_request_classifier
challenge_decider = repoze.who.classifiers:default_challenge_decider

[identifiers]
# plugin_name;classifier_name:.. or just plugin_name (good for any)
plugins =
auth_tkt
basicauth

[authenticators]
# plugin_name;classifier_name.. or just plugin_name (good for any)
plugins =
auth_tkt
htpasswd

[challengers]
# plugin_name;classifier_name:.. or just plugin_name (good for any)
plugins =
redirform;browser
basicauth

# Metadata providers
[mdproviders]
plugins =


6 changes: 3 additions & 3 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,15 @@
from setuptools import setup, find_packages

here = os.path.abspath(os.path.dirname(__file__))
README = open(os.path.join(here, 'README.txt')).read()
README = open(os.path.join(here, 'README.rst')).read()
CHANGES = open(os.path.join(here, 'CHANGES.txt')).read()

requires = ['pyramid', 'pyramid_debugtoolbar', 'pyramid_who', 'pymongo']

setup(name='osiris',
version='1.0',
description='Pyramid based oAuth server',
long_description=README + '\n\n' + CHANGES,
long_description=README + '\n\n' + CHANGES,
classifiers=[
"Programming Language :: Python",
"Framework :: Pylons",
Expand All @@ -28,7 +28,7 @@
install_requires=requires,
tests_require=requires + ['WebTest'],
test_suite="osiris",
entry_points = """\
entry_points="""\
[paste.app_factory]
main = osiris:make_osiris_app
""",
Expand Down
2 changes: 1 addition & 1 deletion who.ini
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ login_url = /login
[plugin:basicauth]
# identification and challenge
use = repoze.who.plugins.basicauth:make_plugin
realm = 'EPI'
realm = 'OSIRIS'

[plugin:auth_tkt]
# identification
Expand Down

0 comments on commit e6d0ac5

Please sign in to comment.