Skip to content

Commit 74fcd6f

Browse files
author
Rebecka Gulliksson
committed
Fix OIDC frontend.
Now supports all flows (Authorization Code, Implicit and Hybrid), through use of the pyop library. Supports storing state by using a MongoDB instance, with pyop's bundled MongoWrapper.
1 parent d534683 commit 74fcd6f

File tree

9 files changed

+532
-229
lines changed

9 files changed

+532
-229
lines changed

doc/README.md

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -301,15 +301,23 @@ The OpenID Connect frontend acts as and OpenID Connect Provider (OP), accepting
301301
Connect Relying Parties (RPs). The default configuration file can be found
302302
[here](../example/plugins/frontends/oidc_frontend.yaml.example).
303303

304+
As opposed to the other plugins, this plugin is NOT stateless (due to the nature of OpenID Connect using any other
305+
flow than "Implicit Flow"). However, the frontend supports using a MongoDB instance as its backend storage, so as long
306+
that's reachable from all machines it should not be a problem.
307+
304308
The configuration parameters available:
305309
* `signing_key_path`: path to a RSA Private Key file (PKCS#1). MUST be configured.
306-
* `client_db_path`: path to where the client (RP) database will be stored.
307-
The other parameters should be left with their default values.
310+
* `db_uri`: connection URI to MongoDB instance where the data will be persisted, if it's not specified all data will only
311+
be stored in-memory (not suitable for production use).
312+
* `provider`: provider configuration information. MUST be configured, the following configuration are supported:
313+
* `response_types_supported` (default: `[id_token]`): list of all supported response types, see [Section 3 of OIDC Core](http://openid.net/specs/openid-connect-core-1_0.html#Authentication).
314+
* `subject_types_supported` (default: `[pairwise]`): list of all supported subject identifier types, see [Section 8 of OIDC Core](http://openid.net/specs/openid-connect-core-1_0.html#SubjectIDTypes)
315+
* `scopes_supported` (default: `[openid]`): list of all supported scopes, see [Section 5.4 of OIDC Core](http://openid.net/specs/openid-connect-core-1_0.html#ScopeClaims)
316+
* `client_registration_supported` (default: `No`): boolean whether [dynamic client registration is supported](https://openid.net/specs/openid-connect-registration-1_0.html).
317+
If dynamic client registration is not supported all clients must exist in the MongoDB instance configured by the `db_uri` in the `"clients"` collection of the `"satosa"` database.
318+
The registration info must be stored using the client id as a key, and use the parameter names of a [OIDC Registration Response](https://openid.net/specs/openid-connect-registration-1_0.html#RegistrationResponse).
308319

309-
As opposed to the other plugins, this plugin is NOT stateless (due to the client database). This
310-
makes it impossible to run multiple instances of the SATOSA proxy on different machines (for the
311-
purpose of load balancing) unless the client database file is also distributed among those machines
312-
by some external process.
320+
The other parameters should be left with their default values.
313321

314322
### <a name="social_plugins" style="color:#000000">Social login plugins</a>
315323
The social login plugins can be used as backends for the proxy, allowing the

example/plugins/frontends/openid_connect_frontend.yaml.example

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,9 @@ module: satosa.frontends.openid_connect.OpenIDConnectFrontend
22
name: OIDC
33
config:
44
signing_key_path: frontend.key
5-
client_db_path: ./client_db # optional: will default to in-memory storage if not specified
5+
db_uri: mongodb://db.example.com # optional: only support MongoDB, will default to in-memory storage if not specified
6+
provider:
7+
client_registration_supported: Yes
8+
response_types_supported: ["code", "id_token token"]
9+
subject_types_supported: ["pairwise"]
10+
scopes_supported: ["openid", "email"]

setup.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
package_dir={'': 'src'},
1717
install_requires=[
1818
"oic==0.8.4.0",
19+
"pyop==1.0.0",
1920
"pyjwkest==1.1.5",
2021
"pysaml2==4.0.3",
2122
"requests==2.9.1",

src/satosa/frontends/openid_connect.py

Lines changed: 134 additions & 131 deletions
Large diffs are not rendered by default.

src/satosa/proxy_server.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -102,8 +102,8 @@ def __call__(self, environ, start_response, debug=False):
102102
context.request = unpack_request(environ, content_length)
103103
environ['wsgi.input'].seek(0)
104104

105-
context.wsgi_environ = environ
106105
context.cookie = environ.get("HTTP_COOKIE", "")
106+
context.request_authorization = environ.get("HTTP_AUTHORIZATION", "")
107107

108108
try:
109109
resp = self.run(context)

src/satosa/response.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,3 +98,18 @@ class NotFound(Response):
9898

9999
class ServiceError(Response):
100100
_status = "500 Internal Service Error"
101+
102+
103+
class BadRequest(Response):
104+
_status = "400 Bad Request"
105+
106+
107+
class Created(Response):
108+
_status = "201 Created"
109+
110+
111+
class Unauthorized(Response):
112+
_status = "401 Unauthorized"
113+
114+
def __init__(self, message, headers=None, content=None):
115+
super().__init__(message, headers=headers, content=content)

tests/conftest.py

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -331,3 +331,89 @@ def oidc_backend_config():
331331
}
332332

333333
return data
334+
335+
336+
import atexit
337+
import random
338+
import shutil
339+
import subprocess
340+
import tempfile
341+
import time
342+
343+
import pymongo
344+
import pytest
345+
346+
347+
class MongoTemporaryInstance(object):
348+
"""Singleton to manage a temporary MongoDB instance
349+
350+
Use this for testing purpose only. The instance is automatically destroyed
351+
at the end of the program.
352+
353+
"""
354+
_instance = None
355+
356+
@classmethod
357+
def get_instance(cls):
358+
if cls._instance is None:
359+
cls._instance = cls()
360+
atexit.register(cls._instance.shutdown)
361+
return cls._instance
362+
363+
def __init__(self):
364+
self._tmpdir = tempfile.mkdtemp()
365+
self._port = random.randint(40000, 50000)
366+
self._process = subprocess.Popen(['mongod', '--bind_ip', 'localhost',
367+
'--port', str(self._port),
368+
'--dbpath', self._tmpdir,
369+
'--nojournal', '--nohttpinterface',
370+
'--noauth', '--smallfiles',
371+
'--syncdelay', '0',
372+
'--nssize', '1', ],
373+
stdout=open('/tmp/mongo-temp.log', 'wb'),
374+
stderr=subprocess.STDOUT)
375+
376+
# XXX: wait for the instance to be ready
377+
# Mongo is ready in a glance, we just wait to be able to open a
378+
# Connection.
379+
for i in range(10):
380+
time.sleep(0.2)
381+
try:
382+
self._conn = pymongo.MongoClient('localhost', self._port)
383+
except pymongo.errors.ConnectionFailure:
384+
continue
385+
else:
386+
break
387+
else:
388+
self.shutdown()
389+
assert False, 'Cannot connect to the mongodb test instance'
390+
391+
@property
392+
def conn(self):
393+
return self._conn
394+
395+
@property
396+
def port(self):
397+
return self._port
398+
399+
def shutdown(self):
400+
if self._process:
401+
self._process.terminate()
402+
self._process.wait()
403+
self._process = None
404+
shutil.rmtree(self._tmpdir, ignore_errors=True)
405+
406+
def get_uri(self):
407+
"""
408+
Convenience function to get a mongodb URI to the temporary database.
409+
410+
:return: URI
411+
"""
412+
return 'mongodb://localhost:{port!s}'.format(port=self.port)
413+
414+
415+
@pytest.yield_fixture
416+
def mongodb_instance():
417+
tmp_db = MongoTemporaryInstance()
418+
yield tmp_db
419+
tmp_db.shutdown()

tests/flows/test_oidc-saml.py

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,11 @@
11
import json
2-
import os
32
from urllib.parse import urlparse, urlencode, parse_qsl
43

54
import pytest
65
from jwkest.jwk import rsa_load, RSAKey
76
from jwkest.jws import JWS
87
from oic.oic.message import ClaimsRequest, Claims
9-
from oic.utils import shelve_wrapper
8+
from pyop.storage import MongoWrapper
109
from saml2 import BINDING_HTTP_REDIRECT
1110
from saml2.config import IdPConfig
1211
from werkzeug.test import Client
@@ -23,23 +22,25 @@
2322

2423

2524
@pytest.fixture
26-
def oidc_frontend_config(signing_key_path, tmpdir):
27-
client_db_path = os.path.join(str(tmpdir), "client_db")
28-
cdb = shelve_wrapper.open(client_db_path)
29-
cdb[CLIENT_ID] = {
30-
"redirect_uris": [(REDIRECT_URI, None)],
31-
"response_types": ["id_token"]
32-
}
33-
25+
def oidc_frontend_config(signing_key_path, mongodb_instance):
3426
data = {
3527
"module": "satosa.frontends.openid_connect.OpenIDConnectFrontend",
3628
"name": "OIDCFrontend",
3729
"config": {
3830
"issuer": "https://proxy-op.example.com",
3931
"signing_key_path": signing_key_path,
40-
"client_db_path": client_db_path
32+
"provider": {"response_types_supported": ["id_token"]},
33+
"db_uri": mongodb_instance.get_uri() # use mongodb for integration testing
4134
}
4235
}
36+
37+
# insert client in mongodb
38+
cdb = MongoWrapper(mongodb_instance.get_uri(), "satosa", "clients")
39+
cdb[CLIENT_ID] = {
40+
"redirect_uris": [REDIRECT_URI],
41+
"response_types": ["id_token"]
42+
}
43+
4344
return data
4445

4546

0 commit comments

Comments
 (0)