3 # Copyright 2011 Roland Hedberg <roland.hedberg@adm.umu.se>
5 # The freeradius extension using ECP
14 from saml2 import saml
16 from saml2.client import Saml2Client
17 from saml2.s_utils import sid
18 from saml2.response import authn_response
19 from saml2.ecp_client import Client
21 # Where's the configuration file is
22 #CONFIG_DIR = "/usr/local/etc/moonshot"
24 sys.path.insert(0, CONFIG_DIR)
32 def eq_len_parts(str, delta=250):
38 res.append("".join(str[n:m]))
42 def exception_trace(tag, exc, log):
43 message = traceback.format_exception(*sys.exc_info())
44 log.error("[%s] ExcList: %s" % (tag, "".join(message),))
45 log.error("[%s] Exception: %s" % (tag, exc))
50 radiusd.radlog(level, 'moonshot.py: ' + s)
55 log(radiusd.L_INFO, txt)
58 log(radiusd.L_ERR, txt)
61 #noinspection PyUnusedLocal
63 """Module Instantiation. 0 for success, -1 for failure.
68 # Use IdP info retrieved from the SP when metadata is missing
71 CLIENT = Saml2Client(ecp_config.DEBUG, config_file=ecp_config.CONFIG)
74 # Report the error and return -1 for failure.
75 # xxx A more advanced module would retry the database.
76 log(radiusd.L_ERR, str(e))
80 ECP = Client("", "", None, metadata_file=ecp_config.METADATA_FILE)
81 except Exception, err:
82 log(radiusd.L_ERR, str(err))
85 log(radiusd.L_INFO, 'ECP client initialized')
90 def authentication_request(cls, ecp, idp_entity_id, destination,
91 log=None, sign=False):
92 """ Does a authentication request to an Identity provider.
93 This function uses the SOAP binding other bindings could be used but are
97 :param cls: The SAML2 client instance
98 :param ecp: The ECP client instance
99 :param idp_entity_id: The identifier of the subject
100 :param destination: To whom the query should be sent
101 :param log: Function to use for logging
102 :param sign: Whether the request should be signed or not
103 :return: A Authentication Response
110 acsu = cls.config.endpoint('assertion_consumer_service',
111 saml2.BINDING_PAOS)[0]
112 spentityid = cls.config.entityid
115 request = cls.authn_request(session_id,
122 binding=saml2.BINDING_PAOS,
123 nameid_format=saml.NAMEID_FORMAT_PERSISTENT)
126 # send the request and receive the response
127 response = ecp.phase2(request, acsu, idp_entity_id)
128 except Exception, exc:
129 exception_trace("soap", exc, log)
131 log.info("SoapClient exception: %s" % (exc,))
136 # synchronous operation
137 aresp = authn_response(cls.config, acsu, log=log, asynchop=False,
138 allow_unsolicited=True)
140 except Exception, exc:
142 log.error("%s", (exc,))
146 _resp = aresp.load_instance(response).verify()
147 except Exception, err:
149 log.error("%s" % err)
153 log.error("Didn't like the response")
156 return _resp.assertion
161 def only_allowed_attributes(client, assertion, allowed):
163 _aconvs = client.config.attribute_converters
165 for statement in assertion.attribute_statement:
166 for attribute in statement.attribute:
167 if attribute.friendly_name:
168 fname = attribute.friendly_name
172 if acv.name_form == attribute.name_form:
173 fname = acv._fro[attribute.name]
176 res.append(attribute)
181 def post_auth(authData):
182 """ Attribute aggregation after authentication
183 This is the function that is accessible from the freeradius server core.
191 # Extract the data we need.
198 if t[0] == 'User-Name':
199 userName = t[1][1:-1]
200 elif t[0] == "GSS-Acceptor-Service-Name":
201 serviceName = t[1][1:-1]
202 elif t[0] == "GSS-Acceptor-Host-Name":
203 hostName = t[1][1:-1]
205 _srv = "%s:%s" % (serviceName, hostName)
206 log(radiusd.L_DBG, "Working on behalf of: %s" % _srv)
209 # Find the endpoint to use
210 attribute_service = CLIENT.config.attribute_services(ecp_config.IDP_ENTITYID)
211 location = attribute_service[0].location
213 log(radiusd.L_DBG, "location: %s" % location)
216 _assertion = authentication_request(CLIENT, ECP,
217 ecp_config.IDP_ENTITYID,
220 sign=ecp_config.SIGN)
222 if _assertion is None:
223 return radiusd.RLM_MODULE_FAIL
225 if _assertion is False:
226 log(radiusd.L_DBG, "IdP returned: %s" % HTTP.server.error_description)
227 return radiusd.RLM_MODULE_FAIL
229 # remove the subject confirmation if there is one
230 _assertion.subject.subject_confirmation = []
231 # Only allow attributes that the service should have
233 _assertion = only_allowed_attributes(CLIENT, _assertion,
234 ecp_config.ATTRIBUTE_FILTER[_srv])
238 log(radiusd.L_DBG, "Assertion: %s" % _assertion)
241 log(radiusd.L_DBG, 'user accepted: %s' % (userName, ))
243 # We are adding to the RADIUS packet
244 # We need to set an Auth-Type.
246 # UKERNA, 25622; attribute ID is 132
247 attr = "SAML-AAA-Assertion"
248 #attr = "UKERNA-Attr-%d" % 132
249 #attr = "Vendor-%d-Attr-%d" % (25622, 132)
250 restup = (tuple([(attr, x) for x in eq_len_parts("%s" % _assertion, 248)]))
252 return radiusd.RLM_MODULE_UPDATED, restup, None
256 if __name__ == '__main__':
258 # print authorize((('User-Name', '"map"'), ('User-Password', '"abc"')))
259 print post_auth((('User-Name', '"roland"'), ('User-Password', '"one"')))