Skip to content

Commit 731cd44

Browse files
author
Roland Hedberg
committed
Merge pull request #4 from SUNET/template-attributes
templating attributes with mako
2 parents 456e202 + ea6d290 commit 731cd44

File tree

4 files changed

+65
-1
lines changed

4 files changed

+65
-1
lines changed

setup.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
"requests",
2626
"PyYAML",
2727
"gunicorn",
28+
"mako",
2829
"Werkzeug"
2930
],
3031
zip_safe=False,

src/satosa/internal_data.py

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
from enum import Enum
77
import hashlib
88
import json
9+
from mako.template import Template
910
from satosa.exception import SATOSAError
1011

1112
__author__ = 'haho0032'
@@ -42,6 +43,7 @@ def __init__(self, internal_attributes):
4243
(dict[internal_name, dict[external_type, external_name]])
4344
"""
4445
self.separator = "." # separator for nested attribute values, e.g. address.street_address
46+
self.multivalue_separator = ";" # separates multiple values, e.g. when using templates
4547
self.from_internal_attributes = internal_attributes["attributes"]
4648

4749
self.external2internal_attribute_name_mapping = {}
@@ -119,14 +121,23 @@ def to_internal(self, external_type, external_dict):
119121
def _collate_attribute_values_by_priority_order(self, attribute_names, data):
120122
result = []
121123
for attr_name in attribute_names:
122-
attr_val = self._get_nested_attribute_value(attr_name, data)
124+
attr_val = None
125+
if '$' in attr_name: # this looks like a template...
126+
attr_val = self._render_attribute_template(attr_name, data)
127+
else:
128+
attr_val = self._get_nested_attribute_value(attr_name, data)
129+
123130
if isinstance(attr_val, list):
124131
result.extend(attr_val)
125132
elif attr_val:
126133
result.append(attr_val)
127134

128135
return result
129136

137+
def _render_attribute_template(self, template, data):
138+
t = Template(template,cache_enabled=True,imports=["from satosa.util import scope"])
139+
return t.render(**data).split(self.multivalue_separator)
140+
130141
def _get_nested_attribute_value(self, nested_key, data):
131142
keys = nested_key.split(self.separator)
132143

src/satosa/util.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -168,3 +168,9 @@ def rndstr(size=16, alphabet=""):
168168
if not alphabet:
169169
alphabet = string.ascii_letters[0:52] + string.digits
170170
return type(alphabet)().join(rng.choice(alphabet) for _ in range(size))
171+
172+
def scope(s):
173+
if not '@' in s:
174+
raise ValueError("Unscoped string")
175+
(local_part, _, domain_part) = s.partition('@')
176+
return domain_part

tests/satosa/test_internal_data.py

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -249,6 +249,52 @@ def test_to_internal_filter_profile_missing_attribute_mapping(self):
249249
filter = converter.to_internal_filter("bar", ["email", "uid"])
250250
assert Counter(filter) == Counter(["id"])
251251

252+
def test_simple_template_mapping(self):
253+
mapping = {
254+
"attributes": {
255+
"last_name": {
256+
"p1": ["sn"],
257+
"p2": ["sn"]
258+
},
259+
"first_name": {
260+
"p1": ["givenName"],
261+
"p2": ["givenName"]
262+
},
263+
"name": {
264+
"p2": ["cn","${givenName[0]} ${sn[0]}"]
265+
}
266+
}
267+
}
268+
269+
converter = DataConverter(mapping)
270+
internal_repr = converter.to_internal("p2", {"givenName": ["Valfrid"], "sn": ["Lindeman"]})
271+
assert "name" in internal_repr
272+
assert len(internal_repr["name"]) == 1
273+
assert internal_repr["name"][0] == "Valfrid Lindeman"
274+
275+
def test_scoped_template_mapping(self):
276+
mapping = {
277+
"attributes": {
278+
"unscoped_affiliation": {
279+
"p1": ["eduPersonAffiliation"]
280+
},
281+
"uid": {
282+
"p1": ["eduPersonPrincipalName"],
283+
},
284+
"affiliation": {
285+
"p1": ["eduPersonScopedAffiliation","${eduPersonAffiliation[0]}@${eduPersonPrincipalName[0] | scope}"]
286+
}
287+
}
288+
}
289+
290+
converter = DataConverter(mapping)
291+
internal_repr = converter.to_internal("p1", {
292+
"eduPersonAffiliation": ["student"],
293+
"eduPersonPrincipalName": ["[email protected]"]})
294+
assert "affiliation" in internal_repr
295+
assert len(internal_repr["affiliation"]) == 1
296+
assert internal_repr["affiliation"][0] == "[email protected]"
297+
252298

253299
@pytest.mark.parametrize("attribute_value", [
254300
{"email": "[email protected]"},

0 commit comments

Comments
 (0)