Skip to content

Commit 3b3a9cf

Browse files
committed
initial but somewhat tested
1 parent e7e6f5a commit 3b3a9cf

File tree

4 files changed

+155
-1
lines changed

4 files changed

+155
-1
lines changed
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
module: satosa.micro_services.attribute_generation.AddSyntheticAttributes
2+
name: AddSyntheticAttributes
3+
config:
4+
synthetic_attributes:
5+
target_provider1:
6+
requester1:
7+
eduPersonAffiliation: member;employee
8+
default:
9+
default:
10+
schacHomeOrganization: {{eduPersonPrincipalName.scope}}
11+
schacHomeOrganizationType: tomfoolery provider

setup.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,10 +22,11 @@
2222
"PyYAML",
2323
"gunicorn",
2424
"Werkzeug",
25-
"click"
25+
"click",
2626
],
2727
extras_require={
2828
"ldap": ["ldap3"],
29+
"pystache": ["pystache"]
2930
},
3031
zip_safe=False,
3132
classifiers=[
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
import re
2+
import pystache
3+
4+
from .base import ResponseMicroService
5+
6+
def _config(f, requester, provider):
7+
pf = f.get(provider, f.get("", f.get("default", {})))
8+
rf = pf.get(requester, pf.get("", pf.get("default", {})))
9+
return rf.items()
10+
11+
class MustachAttrValue(object):
12+
def __init__(self,values):
13+
self.values = values
14+
if any(['@' in v for v in values]):
15+
local_parts = []
16+
domain_parts = []
17+
scopes = dict()
18+
for v in values:
19+
(local_part, sep, domain_part) = v.partition('@')
20+
# probably not needed now...
21+
local_parts.append(local_part)
22+
domain_parts.append(domain_part)
23+
scopes[domain_part] = True
24+
self._scopes = list(scopes.keys())
25+
else:
26+
self._scopes = None
27+
28+
def __str__(self):
29+
return ";".join(self.values)
30+
31+
@property
32+
def value(self):
33+
if 1 == len(self.values):
34+
return self.values[0]
35+
else:
36+
return self.values
37+
38+
@property
39+
def first(self):
40+
if len(self.values) > 0:
41+
return self.values[0]
42+
else:
43+
return ""
44+
45+
@property
46+
def scope(self):
47+
if self._scopes is not None:
48+
return self._scopes[0]
49+
return ""
50+
51+
52+
class AddSyntheticAttributes(ResponseMicroService):
53+
"""
54+
Add synthetic attributes to the responses.
55+
"""
56+
57+
def __init__(self, config, *args, **kwargs):
58+
super().__init__(*args, **kwargs)
59+
self.synthetic_attributes = config["synthetic_attributes"]
60+
61+
def _synthesize(self, attributes, requester, provider):
62+
syn_attributes = dict()
63+
context = dict()
64+
65+
for attr_name,values in attributes.items():
66+
context[attr_name] = MustachAttrValue(values)
67+
68+
recipes = _config(self.synthetic_attributes, requester, provider)
69+
print(context)
70+
for attr_name, fmt in recipes:
71+
print(fmt)
72+
syn_attributes[attr_name] = re.split("[;\n]+", pystache.render(fmt, context))
73+
print(syn_attributes)
74+
return syn_attributes
75+
76+
def process(self, context, data):
77+
data.attributes.update(self._synthesize(data.attributes, data.requester, data.auth_info.issuer))
78+
return super().process(context, data)
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
from satosa.internal_data import InternalResponse, AuthenticationInformation
2+
from satosa.micro_services.attribute_generation import AddSyntheticAttributes
3+
from satosa.exception import SATOSAAuthenticationError
4+
from satosa.context import Context
5+
6+
class TestAddSyntheticAttributes:
7+
def create_syn_service(self, synthetic_attributes):
8+
authz_service = AddSyntheticAttributes(config=dict(synthetic_attributes=synthetic_attributes),
9+
name="test_gen",
10+
base_url="https://satosa.example.com")
11+
authz_service.next = lambda ctx, data: data
12+
return authz_service
13+
14+
def test_generate_static(self):
15+
synthetic_attributes = {
16+
"": { "default": {"a0": "value1;value2" }}
17+
}
18+
authz_service = self.create_syn_service(synthetic_attributes)
19+
resp = InternalResponse(AuthenticationInformation(None, None, None))
20+
resp.attributes = {
21+
"a1": ["[email protected]"],
22+
}
23+
ctx = Context()
24+
ctx.state = dict()
25+
authz_service.process(ctx, resp)
26+
assert("value1" in resp.attributes['a0'])
27+
assert("value2" in resp.attributes['a0'])
28+
assert("[email protected]" in resp.attributes['a1'])
29+
30+
def test_generate_mustache1(self):
31+
synthetic_attributes = {
32+
"": { "default": {"a0": "{{kaka}}#{{eppn.scope}}" }}
33+
}
34+
authz_service = self.create_syn_service(synthetic_attributes)
35+
resp = InternalResponse(AuthenticationInformation(None, None, None))
36+
resp.attributes = {
37+
"kaka": ["kaka1"],
38+
39+
}
40+
ctx = Context()
41+
ctx.state = dict()
42+
authz_service.process(ctx, resp)
43+
assert("kaka1#example.com" in resp.attributes['a0'])
44+
assert("kaka1" in resp.attributes['kaka'])
45+
assert("[email protected]" in resp.attributes['eppn'])
46+
assert("[email protected]" in resp.attributes['eppn'])
47+
48+
def test_generate_mustache2(self):
49+
synthetic_attributes = {
50+
"": { "default": {"a0": "{{kaka.first}}#{{eppn.scope}}" }}
51+
}
52+
authz_service = self.create_syn_service(synthetic_attributes)
53+
resp = InternalResponse(AuthenticationInformation(None, None, None))
54+
resp.attributes = {
55+
"kaka": ["kaka1","kaka2"],
56+
57+
}
58+
ctx = Context()
59+
ctx.state = dict()
60+
authz_service.process(ctx, resp)
61+
assert("kaka1#example.com" in resp.attributes['a0'])
62+
assert("kaka1" in resp.attributes['kaka'])
63+
assert("[email protected]" in resp.attributes['eppn'])
64+
assert("[email protected]" in resp.attributes['eppn'])

0 commit comments

Comments
 (0)