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))
81 ECP = Client("", config.PASSWD, None,
82 metadata_file=config.METADATA_FILE)
83 except Exception, err:
84 log(radiusd.L_ERR, str(err))
87 log(radiusd.L_INFO, 'ECP client initialized')
92 def authentication_request(cls, ecp, idp_entity_id, destination,
93 log=None, sign=False):
94 """ Does a authentication request to an Identity provider.
95 This function uses the SOAP binding other bindings could be used but are
99 :param cls: The SAML2 client instance
100 :param ecp: The ECP client instance
101 :param idp_entity_id: The identifier of the subject
102 :param destination: To whom the query should be sent
103 :param log: Function to use for logging
104 :param sign: Whether the request should be signed or not
105 :return: A Authentication Response
112 acsus = cls.config.endpoint('assertion_consumer_service',
116 log.error("Couldn't find own PAOS endpoint for")
119 spentityid = cls.config.entityid
122 request = cls.authn_request(session_id,
129 binding=saml2.BINDING_PAOS,
130 nameid_format=saml.NAMEID_FORMAT_PERSISTENT)
133 # send the request and receive the response
134 response = ecp.phase2(request, acsu, idp_entity_id)
135 except Exception, exc:
136 exception_trace("soap", exc, log)
138 log.info("SoapClient exception: %s" % (exc,))
143 # synchronous operation
144 aresp = authn_response(cls.config, acsu, log=log, asynchop=False,
145 allow_unsolicited=True)
147 except Exception, exc:
149 log.error("%s", (exc,))
153 _resp = aresp.load_instance(response).verify()
154 except Exception, err:
156 log.error("%s" % err)
160 log.error("Didn't like the response")
163 return _resp.assertion
168 def only_allowed_attributes(client, assertion, allowed):
170 _aconvs = client.config.attribute_converters
172 for statement in assertion.attribute_statement:
173 for attribute in statement.attribute:
174 if attribute.friendly_name:
175 fname = attribute.friendly_name
179 if acv.name_form == attribute.name_form:
180 fname = acv._fro[attribute.name]
183 res.append(attribute)
188 def post_auth(authData):
189 """ Attribute aggregation after authentication
190 This is the function that is accessible from the freeradius server core.
199 # Extract the data we need.
205 if t[0] == 'User-Name':
206 userName = t[1][1:-1]
207 elif t[0] == "GSS-Acceptor-Service-Name":
208 serviceName = t[1][1:-1]
209 elif t[0] == "GSS-Acceptor-Host-Name":
210 hostName = t[1][1:-1]
212 _srv = "%s:%s" % (serviceName, hostName)
213 log(radiusd.L_DBG, "Working on behalf of: %s" % _srv)
216 # Find the endpoint to use
217 sso_service = CLIENT.config.single_sign_on_services(config.IDP_ENTITYID,
221 "Couldn't find an single sign on endpoint for: %s" % (
222 config.IDP_ENTITYID,))
223 return radiusd.RLM_MODULE_FAIL
225 location = sso_service[0]
227 log(radiusd.L_DBG, "location: %s" % location)
229 ECP.http.clear_credentials()
231 log(radiusd.L_DBG, "Login using user:%s password:'%s'" % (ECP.user,
234 _assertion = authentication_request(CLIENT, ECP,
240 if _assertion is None:
241 return radiusd.RLM_MODULE_FAIL
243 if _assertion is False:
244 log(radiusd.L_DBG, "IdP returned: %s" % HTTP.server.error_description)
245 return radiusd.RLM_MODULE_FAIL
247 # remove the subject confirmation if there is one
248 _assertion.subject.subject_confirmation = []
250 log(radiusd.L_DBG, "Assertion: %s" % _assertion)
253 log(radiusd.L_DBG, 'user accepted: %s' % (userName, ))
255 # We are adding to the RADIUS packet
256 # We need to set an Auth-Type.
258 # UKERNA, 25622; attribute ID is 132
259 attr = "SAML-AAA-Assertion"
260 #attr = "UKERNA-Attr-%d" % 132
261 #attr = "Vendor-%d-Attr-%d" % (25622, 132)
262 restup = (tuple([(attr, x) for x in eq_len_parts("%s" % _assertion, 248)]))
264 return radiusd.RLM_MODULE_UPDATED, restup, None
268 if __name__ == '__main__':
270 # print authorize((('User-Name', '"map"'), ('User-Password', '"abc"')))
271 print post_auth((('User-Name', '"roland"'), ('User-Password', '"one"')))