3 # Copyright 2011 Roland Hedberg <roland.hedberg@adm.umu.se>
5 # The freeradius extension using ECP
15 from saml2 import saml
17 from saml2.client import Saml2Client
18 from saml2.s_utils import sid
19 from saml2.response import authn_response
20 from saml2.ecp_client import Client
22 # Where's the configuration file is
23 CONFIG_DIR = "/usr/local/etc/moonshot"
24 #CONFIG_DIR = "../etc"
25 sys.path.insert(0, CONFIG_DIR)
33 def eq_len_parts(str, delta=250):
39 res.append("".join(str[n:m]))
43 def exception_trace(tag, exc, log):
44 message = traceback.format_exception(*sys.exc_info())
45 log.error("[%s] ExcList: %s" % (tag, "".join(message),))
46 log.error("[%s] Exception: %s" % (tag, exc))
51 radiusd.radlog(level, 'moonshot.py: ' + s)
56 log(radiusd.L_INFO, txt)
59 log(radiusd.L_ERR, txt)
62 #noinspection PyUnusedLocal
64 """Module Instantiation. 0 for success, -1 for failure.
69 # Use IdP info retrieved from the SP when metadata is missing
72 CLIENT = Saml2Client(config.DEBUG, config_file=config.CONFIG)
75 # Report the error and return -1 for failure.
76 # xxx A more advanced module would retry the database.
77 log(radiusd.L_ERR, str(e))
82 _passwd = config.PASSWD
83 except AttributeError:
86 ECP = Client("", _passwd, None,
87 metadata_file=config.METADATA_FILE)
88 except Exception, err:
89 log(radiusd.L_ERR, str(err))
92 log(radiusd.L_INFO, 'ECP client initialized')
97 def authentication_request(cls, ecp, idp_entity_id, destination,
98 log=None, sign=False):
99 """ Does a authentication request to an Identity provider.
100 This function uses the SOAP binding other bindings could be used but are
104 :param cls: The SAML2 client instance
105 :param ecp: The ECP client instance
106 :param idp_entity_id: The identifier of the subject
107 :param destination: To whom the query should be sent
108 :param log: Function to use for logging
109 :param sign: Whether the request should be signed or not
110 :return: A Authentication Response
117 acsus = cls.config.endpoint('assertion_consumer_service',
119 if not acsus and log:
120 log.error("Couldn't find own PAOS endpoint")
124 spentityid = cls.config.entityid
127 request = cls.authn_request(session_id,
134 binding=saml2.BINDING_PAOS,
135 nameid_format=saml.NAMEID_FORMAT_PERSISTENT)
139 headers = {config.USERNAME_HEADER: ecp.user}
140 except AttributeError:
143 print >> sys.stderr, "Headers: %s" % headers
145 # send the request and receive the response
146 response = ecp.phase2(request, acsu, idp_entity_id, headers)
147 except Exception, exc:
148 exception_trace("soap", exc, log)
150 log.info("SoapClient exception: %s" % (exc,))
155 # synchronous operation
156 aresp = authn_response(cls.config, acsu, log=log, asynchop=False,
157 allow_unsolicited=True)
159 except Exception, exc:
161 log.error("%s", (exc,))
165 _resp = aresp.load_instance(response).verify()
166 except Exception, err:
168 log.error("%s" % err)
172 log.error("Didn't like the response")
175 return _resp.assertion
180 def only_allowed_attributes(client, assertion, allowed):
182 _aconvs = client.config.attribute_converters
184 for statement in assertion.attribute_statement:
185 for attribute in statement.attribute:
186 if attribute.friendly_name:
187 fname = attribute.friendly_name
191 if acv.name_form == attribute.name_form:
192 fname = acv._fro[attribute.name]
195 res.append(attribute)
200 def post_auth(authData):
201 """ Attribute aggregation after authentication
202 This is the function that is accessible from the freeradius server core.
211 # Extract the data we need.
217 if t[0] == 'User-Name':
218 userName = t[1][1:-1]
219 elif t[0] == "GSS-Acceptor-Service-Name":
220 serviceName = t[1][1:-1]
221 elif t[0] == "GSS-Acceptor-Host-Name":
222 hostName = t[1][1:-1]
224 _srv = "%s:%s" % (serviceName, hostName)
225 log(radiusd.L_DBG, "Working on behalf of: %s" % _srv)
227 # Find the endpoint to use
228 sso_service = CLIENT.config.single_sign_on_services(config.IDP_ENTITYID,
232 "Couldn't find an single-sign-on endpoint for: %s" % (
233 config.IDP_ENTITYID,))
234 return radiusd.RLM_MODULE_FAIL
236 location = sso_service[0]
238 log(radiusd.L_DBG, "location: %s" % location)
240 #ECP.http.clear_credentials()
242 log(radiusd.L_DBG, "Login using user:%s password:'%s'" % (ECP.user,
245 _assertion = authentication_request(CLIENT, ECP,
251 if _assertion is None:
252 return radiusd.RLM_MODULE_FAIL
254 if _assertion is False:
255 log(radiusd.L_DBG, "IdP returned: %s" % HTTP.server.error_description)
256 return radiusd.RLM_MODULE_FAIL
258 # remove the subject confirmation if there is one
259 _assertion.subject.subject_confirmation = []
261 log(radiusd.L_DBG, "Assertion: %s" % _assertion)
264 log(radiusd.L_DBG, 'user accepted: %s' % (userName, ))
266 # We are adding to the RADIUS packet
267 # We need to set an Auth-Type.
269 # UKERNA, 25622; attribute ID is 132
270 attr = "SAML-AAA-Assertion"
271 #attr = "UKERNA-Attr-%d" % 132
272 #attr = "Vendor-%d-Attr-%d" % (25622, 132)
273 restup = (tuple([(attr, x) for x in eq_len_parts("%s" % _assertion, 248)]))
275 return radiusd.RLM_MODULE_UPDATED, restup, None
279 if __name__ == '__main__':
281 # print authorize((('User-Name', '"map"'), ('User-Password', '"abc"')))
282 print post_auth((('User-Name', '"roland"'), ('User-Password', '"one"')))