Skip to content

Commit aad9e11

Browse files
author
Rebecka Gulliksson
committed
Merge branch 'master' of github.com:its-dirg/SATOSA
2 parents 5d2fe28 + 1105ee1 commit aad9e11

File tree

9 files changed

+503
-219
lines changed

9 files changed

+503
-219
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 & 130 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)