Skip to content

Commit a0b7cf9

Browse files
author
ivan
committed
Add initial eIDAS support
1 parent dde544a commit a0b7cf9

File tree

3 files changed

+91
-4
lines changed

3 files changed

+91
-4
lines changed

src/satosa/backends/saml2.py

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
import satosa.util as util
1818
from satosa.base import SAMLBaseModule
19+
from satosa.base import SAMLEIDASBaseModule
1920
from .base import BackendModule
2021
from ..exception import SATOSAAuthenticationError
2122
from ..internal_data import (InternalResponse,
@@ -386,6 +387,36 @@ def get_metadata_desc(self):
386387
entity_descriptions.append(description)
387388
return entity_descriptions
388389

390+
391+
class SAMLEIDASBackend(SAMLBackend, SAMLEIDASBaseModule):
392+
"""
393+
A saml2 eidas backend module (acting as a SP).
394+
"""
395+
VALUE_ACR_CLASS_REF_DEFAULT = 'http://eidas.europa.eu/LoA/high'
396+
VALUE_ACR_COMPARISON_DEFAULT = 'minimum'
397+
398+
def init_config(self, config):
399+
config = super().init_config(config)
400+
401+
spec_eidas_sp = {
402+
'acr_mapping': {
403+
"": {
404+
'class_ref': self.VALUE_ACR_CLASS_REF_DEFAULT,
405+
'comparison': self.VALUE_ACR_COMPARISON_DEFAULT,
406+
},
407+
},
408+
'sp_config.service.sp.authn_requests_signed': True,
409+
'sp_config.service.sp.want_response_signed': True,
410+
'sp_config.service.sp.allow_unsolicited': False,
411+
'sp_config.service.sp.force_authn': True,
412+
'sp_config.service.sp.hide_assertion_consumer_service': True,
413+
'sp_config.service.sp.sp_type': ['private', 'public'],
414+
'sp_config.service.sp.sp_type_in_metadata': [True, False],
415+
}
416+
417+
return util.check_set_dict_defaults(config, spec_eidas_sp)
418+
419+
389420
class SAMLInternalResponse(InternalResponse):
390421
"""
391422
Like the parent InternalResponse, holds internal representation of
@@ -413,4 +444,3 @@ def to_dict(self):
413444
_dict['name_id'] = None
414445

415446
return _dict
416-

src/satosa/base.py

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,13 @@
33
"""
44
import json
55
import logging
6-
from uuid import uuid4
6+
import uuid
77

8+
from saml2.s_utils import UnknownSystemEntity
9+
10+
from satosa import util
811
from satosa.micro_services import consent
12+
913
from .context import Context
1014
from .exception import SATOSAConfigurationError
1115
from .exception import SATOSAError, SATOSAAuthenticationError, SATOSAUnknownError
@@ -17,7 +21,6 @@
1721
from .plugin_loader import load_request_microservices, load_response_microservices
1822
from .routing import ModuleRouter, SATOSANoBoundEndpointError
1923
from .state import cookie_to_state, SATOSAStateError, State, state_to_cookie
20-
from saml2.s_utils import UnknownSystemEntity
2124

2225

2326
logger = logging.getLogger(__name__)
@@ -212,7 +215,7 @@ def _run_bound_endpoint(self, context, spec):
212215
try:
213216
return spec(context)
214217
except SATOSAAuthenticationError as error:
215-
error.error_id = uuid4().urn
218+
error.error_id = uuid.uuid4().urn
216219
msg = "ERROR_ID [{err_id}]\nSTATE:\n{state}".format(err_id=error.error_id,
217220
state=json.dumps(
218221
error.state.state_dict,
@@ -298,3 +301,16 @@ def init_config(self, config):
298301
def expose_entityid_endpoint(self):
299302
value = self.config.get(self.KEY_ENTITYID_ENDPOINT, False)
300303
return bool(value)
304+
305+
306+
class SAMLEIDASBaseModule(SAMLBaseModule):
307+
VALUE_ATTRIBUTE_PROFILE_DEFAULT = 'eidas'
308+
309+
def init_config(self, config):
310+
config = super().init_config(config)
311+
312+
spec_eidas = {
313+
'entityid_endpoint': True,
314+
}
315+
316+
return util.check_set_dict_defaults(config, spec_eidas)

src/satosa/util.py

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,49 @@
55
import random
66
import string
77

8+
from satosa.logging_util import satosa_logging
9+
10+
811
logger = logging.getLogger(__name__)
912

13+
14+
def check_set_dict_defaults(dic, spec):
15+
for path, value in spec.items():
16+
keys = path.split('.')
17+
try:
18+
_val = dict_get_nested(dic, keys)
19+
except KeyError:
20+
if type(value) is list:
21+
value_default = value[0]
22+
else:
23+
value_default = value
24+
dict_set_nested(dic, keys, value_default)
25+
else:
26+
if type(value) is list:
27+
is_value_valid = _val in value
28+
elif type(value) is dict:
29+
# do not validate dict
30+
is_value_valid = bool(_val)
31+
else:
32+
is_value_valid = _val == value
33+
if not is_value_valid:
34+
satosa_logging(
35+
logger, logging.WARNING,
36+
"Incompatible configuration value '{}' for '{}'."
37+
" Value shoud be: {}".format(_val, path, value),
38+
{})
39+
return dic
40+
41+
def dict_set_nested(dic, keys, value):
42+
for key in keys[:-1]:
43+
dic = dic.setdefault(key, {})
44+
dic[keys[-1]] = value
45+
46+
def dict_get_nested(dic, keys):
47+
for key in keys[:-1]:
48+
dic = dic.setdefault(key, {})
49+
return dic[keys[-1]]
50+
1051
def get_dict_defaults(d, *keys):
1152
for key in keys:
1253
d = d.get(key, d.get("", d.get("default", {})))

0 commit comments

Comments
 (0)