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: {0:>s}".format(headers)
145 # send the request and receive the response
146 response = ecp.phase2(request, acsu, idp_entity_id, headers,
148 except Exception, exc:
149 exception_trace("soap", exc, log)
151 log.info("SoapClient exception: %s" % (exc,))
156 # synchronous operation
157 aresp = authn_response(cls.config, acsu, log=log, asynchop=False,
158 allow_unsolicited=True)
160 except Exception, exc:
162 log.error("%s", (exc,))
166 _resp = aresp.load_instance(response).verify()
167 except Exception, err:
169 log.error("%s" % err)
173 log.error("Didn't like the response")
176 return _resp.assertion
181 def only_allowed_attributes(client, assertion, allowed):
183 _aconvs = client.config.attribute_converters
185 for statement in assertion.attribute_statement:
186 for attribute in statement.attribute:
187 if attribute.friendly_name:
188 fname = attribute.friendly_name
192 if acv.name_form == attribute.name_form:
193 fname = acv._fro[attribute.name]
196 res.append(attribute)
201 def post_auth(authData):
202 """ Attribute aggregation after authentication
203 This is the function that is accessible from the freeradius server core.
212 # Extract the data we need.
218 if t[0] == 'User-Name':
219 userName = t[1][1:-1]
220 elif t[0] == "GSS-Acceptor-Service-Name":
221 serviceName = t[1][1:-1]
222 elif t[0] == "GSS-Acceptor-Host-Name":
223 hostName = t[1][1:-1]
225 _srv = "%s:%s" % (serviceName, hostName)
226 log(radiusd.L_DBG, "Working on behalf of: %s" % _srv)
228 # Find the endpoint to use
229 sso_service = CLIENT.config.single_sign_on_services(config.IDP_ENTITYID,
233 "Couldn't find an single-sign-on endpoint for: %s" % (
234 config.IDP_ENTITYID,))
235 return radiusd.RLM_MODULE_FAIL
237 location = sso_service[0]
239 log(radiusd.L_DBG, "location: %s" % location)
241 #ECP.http.clear_credentials()
243 log(radiusd.L_DBG, "Login using user:%s password:'%s'" % (ECP.user,
246 _assertion = authentication_request(CLIENT, ECP,
252 if _assertion is None:
253 return radiusd.RLM_MODULE_FAIL
255 if _assertion is False:
256 log(radiusd.L_DBG, "IdP returned: %s" % HTTP.server.error_description)
257 return radiusd.RLM_MODULE_FAIL
259 # remove the subject confirmation if there is one
260 _assertion.subject.subject_confirmation = []
262 log(radiusd.L_DBG, "Assertion: %s" % _assertion)
265 log(radiusd.L_DBG, 'user accepted: %s' % (userName, ))
267 # We are adding to the RADIUS packet
268 # We need to set an Auth-Type.
270 # UKERNA, 25622; attribute ID is 132
271 attr = "SAML-AAA-Assertion"
272 #attr = "UKERNA-Attr-%d" % 132
273 #attr = "Vendor-%d-Attr-%d" % (25622, 132)
274 restup = (tuple([(attr, x) for x in eq_len_parts("%s" % _assertion, 247)]))
276 return radiusd.RLM_MODULE_UPDATED, restup, None
280 if __name__ == '__main__':
282 # print authorize((('User-Name', '"map"'), ('User-Password', '"abc"')))
283 print post_auth((('User-Name', '"roland"'), ('User-Password', '"one"')))