Projet

Général

Profil

Télécharger (19,3 ko) Statistiques
| Branche: | Révision:

root / larpe / tags / release-1.1.1 / larpe / saml2.ptl @ d03cb81c

1
import os
2
import sys
3
import urlparse
4

    
5
try:
6
    import lasso
7
except ImportError:
8
    print >> sys.stderr, 'Missing Lasso module, SAMLv2 support disabled'
9

    
10
from quixote import get_publisher, get_request, get_response, get_session, get_session_manager, redirect
11

    
12
from qommon.liberty import SOAPException, soap_call
13
from qommon.saml2 import Saml2Directory
14
from qommon import template
15
from qommon import get_logger
16

    
17
import misc
18
from users import User
19
from hosts import Host
20
from federations import Federation
21
import site_authentication
22

    
23
class Saml2(Saml2Directory):
24
    _q_exports = Saml2Directory._q_exports + ['local_auth']
25

    
26
    def login(self):
27
        return self.perform_login()
28

    
29
    def perform_login(self, idp = None):
30
        server = misc.get_lasso_server(protocol = 'saml2')
31
        if not server:
32
            return template.error_page(_('SAML 2.0 support not yet configured.'))
33
        login = lasso.Login(server)
34
        login.initAuthnRequest(idp, lasso.HTTP_METHOD_REDIRECT)
35
        login.request.nameIDPolicy.format = lasso.SAML2_NAME_IDENTIFIER_FORMAT_PERSISTENT
36
        login.request.nameIDPolicy.allowCreate = True
37
        login.request.forceAuthn = False
38
        login.request.isPassive = False
39
        login.request.consent = 'urn:oasis:names:tc:SAML:2.0:consent:current-implicit'
40
        login.buildAuthnRequestMsg()
41
        return redirect(login.msgUrl)
42

    
43
    def singleSignOnArtifact(self):
44
        server = misc.get_lasso_server(protocol = 'saml2')
45
        if not server:
46
            return template.error_page(_('SAML 2.0 support not yet configured.'))
47
        login = lasso.Login(server)
48
        request = get_request()
49
        try:
50
            login.initRequest(request.get_query(), lasso.HTTP_METHOD_ARTIFACT_GET)
51
        except lasso.Error, error:
52
            if error[0] == lasso.PROFILE_ERROR_MISSING_ARTIFACT:
53
                return template.error_page(_('Missing SAML Artifact'))
54
            else:
55
                raise
56

    
57
        login.buildRequestMsg()
58
        #remote_provider_cfg = get_cfg('idp', {}).get(misc.get_provider_key(login.remoteProviderId))
59
        #client_cert = remote_provider_cfg.get('clientcertificate')
60

    
61
        try:
62
            soap_answer = soap_call(login.msgUrl, login.msgBody)
63
        except SOAPException:
64
            return template.error_page(_('Failure to communicate with identity provider'))
65

    
66
        try:
67
            login.processResponseMsg(soap_answer)
68
        except lasso.Error, error:
69
            if error[0] == lasso.LOGIN_ERROR_STATUS_NOT_SUCCESS:
70
                return template.error_page(_('Unknown authentication failure'))
71
            if error[0] == lasso.LOGIN_ERROR_UNKNOWN_PRINCIPAL:
72
                return template.error_page(_('Authentication failure; unknown principal'))
73
            if error[0] == lasso.LOGIN_ERROR_FEDERATION_NOT_FOUND:
74
                return template.error_page('there was no federation')
75
            raise
76

    
77
        return self.sso_after_response(login)
78

    
79
    def sso_after_response(self, login):
80
        providerId = login.server.providerId
81
        try:
82
            assertion = login.response.assertion[0]
83
            if assertion.subject.subjectConfirmation.subjectConfirmationData.recipient != \
84
                        get_request().get_url():
85
                return template.error_page('SubjectConfirmation Recipient Mismatch')
86
        except:
87
                return template.error_page('SubjectConfirmation Recipient Mismatch')
88

    
89
        assertions_dir = os.path.join(get_publisher().app_dir, 'assertions')
90
        if not os.path.exists(assertions_dir):
91
            os.mkdir(assertions_dir)
92

    
93
        assertion_fn = os.path.join(assertions_dir, assertion.iD)
94
        if os.path.exists(assertion_fn):
95
            return template.error_page('Assertion replay')
96

    
97
        try:
98
            if assertion.subject.subjectConfirmation.method != \
99
                        'urn:oasis:names:tc:SAML:2.0:cm:bearer':
100
                return template.error_page('Unknown SubjectConfirmation Method')
101
        except:
102
            return template.error_page('Unknown SubjectConfirmation Method')
103

    
104
        try:
105
            audience_ok = False
106
            for audience_restriction in assertion.conditions.audienceRestriction:
107
                if audience_restriction.audience != providerId:
108
                    return template.error_page('Incorrect AudienceRestriction')
109
                audience_ok = True
110
            if not audience_ok:
111
                return template.error_page('Incorrect AudienceRestriction')
112
        except:
113
            return template.error_page('Incorrect AudienceRestriction')
114

    
115
#        try:
116
#            current_time = time.strftime('%Y-%m-%dT%H:%M:%SZ', time.gmtime())
117
#            not_before = assertion.subject.subjectConfirmation.subjectConfirmationData.notBefore
118
#            not_on_or_after = assertion.subject.subjectConfirmation.subjectConfirmationData.notOnOrAfter
119
#            if not_before and current_time < not_before:
120
#                return template.error_page('Assertion received too early')
121
#            if not_on_or_after and current_time > not_on_or_after:
122
#                return template.error_page('Assertion expired')
123
#        except:
124
#            return template.error_page('Error checking Assertion Time')
125

    
126
        # TODO: check for unknown conditions
127

    
128
        login.acceptSso()
129

    
130
        session = get_session()
131
        if login.isSessionDirty:
132
            if login.session:
133
                session.lasso_session_dumps[providerId] = login.session.dump()
134
                session.lasso_session_indexes[providerId] = assertion.authnStatement[0].sessionIndex
135
                session.lasso_session_name_identifiers[providerId] = login.nameIdentifier.content
136
            else:
137
                session.lasso_session_dumps[login.server.providerId] = None
138

    
139
        if assertion.authnStatement[0].sessionIndex:
140
            session.lasso_session_index = assertion.authnStatement[0].sessionIndex
141

    
142
        user = self.lookup_user(session, login)
143

    
144
        # Check if it is for Larpe administration or token
145
        host = Host.get_host_from_url()
146
        if host is None:
147
            return redirect('%s/' % get_request().environ['SCRIPT_NAME'])
148
        if host.name == 'larpe':
149
            if user:
150
                session.set_user(user.id, login.server.providerId)
151
            else:
152
                session.name_identifier = login.nameIdentifier.content
153
                session.lasso_anonymous_identity_dump = login.identity.dump()
154
                session.provider_id = login.server.providerId
155

    
156
            if session.after_url:
157
                # Access to an admin page or token url with parameter
158
                after_url = session.after_url
159
                session.after_url = None
160
                return redirect(after_url)
161

    
162
            if user and user.is_admin:
163
                return redirect('%s/admin/' % get_request().environ['SCRIPT_NAME'])
164
            else:
165
                return redirect('%s/token' % get_request().environ['SCRIPT_NAME'])
166

    
167
        # Set session user
168
        if not user:
169
            user = User()
170
        user.name_identifiers = [ login.nameIdentifier.content ]
171
        user.lasso_dumps = [ login.identity.dump() ]
172
        user.store()
173
        session.set_user(user.id, login.server.providerId)
174

    
175
        # Check if a federation already exist
176
        federations = Federation.select(lambda x: host.id == x.host_id \
177
                                        and user.name_identifiers[0] in x.name_identifiers)
178

    
179
        if federations:
180
            return site_authentication.get_site_authentication(host).sso_local_login(federations[0])
181
        else:
182
            # Build response redirection
183
            response = get_response()
184
            if session.after_url:
185
                after_url = session.after_url
186
                session.after_url = None
187
                return redirect(after_url)
188
            response.set_status(303)
189
            response.headers['location'] = urlparse.urljoin(get_request().get_url(), str('local_auth'))
190
            response.content_type = 'text/plain'
191
            return 'Your browser should redirect you'
192

    
193
    def lookup_user(self, session, login):
194
        found_users = list(User.select(lambda x: login.nameIdentifier.content in x.name_identifiers, ignore_errors = True))
195
        if found_users:
196
            return found_users[0]
197
        return None
198

    
199
    def slo_sp(self, method = None):
200
        host = Host.get_host_from_url()
201
        if host is None:
202
            return redirect('%s/' % get_request().environ['SCRIPT_NAME'])
203

    
204
        if method is None:
205
            method = lasso.HTTP_METHOD_REDIRECT
206

    
207
        logout = lasso.Logout(misc.get_lasso_server(protocol = 'saml2'))
208
        session = get_session()
209

    
210
        if not session.id or not session.users.has_key(logout.server.providerId) \
211
                or not session.lasso_session_dumps.has_key(logout.server.providerId):
212
            get_session_manager().expire_session(logout.server.providerId)
213
            return redirect(host.get_root_url())
214
        logout.setSessionFromDump(session.lasso_session_dumps[logout.server.providerId])
215
        user = session.get_user(logout.server.providerId)
216

    
217
        if host.name != 'larpe' and user:
218
            site_authentication.get_site_authentication(host).local_logout(user=user)
219

    
220
        if user and user.lasso_dumps:
221
            logout.setIdentityFromDump(user.lasso_dumps[0])
222
        else:
223
            get_session_manager().expire_session(logout.server.providerId)
224
            return redirect(host.get_root_url())
225

    
226
        if method == lasso.HTTP_METHOD_REDIRECT:
227
            return self.slo_sp_redirect(logout)
228

    
229
        # Not implemented yet
230
        if method == lasso.HTTP_METHOD_SOAP:
231
            return self.slo_sp_soap(logout)
232

    
233
    def slo_sp_redirect(self, logout):
234
        session = get_session()
235
        try:
236
            logout.initRequest(None, lasso.HTTP_METHOD_REDIRECT)
237
        except lasso.Error, error:
238
            if error[0] == lasso.PROFILE_ERROR_NAME_IDENTIFIER_NOT_FOUND:
239
                get_session_manager().expire_session(logout.server.providerId)
240
                return redirect(host.get_root_url())
241
            if error[0] == lasso.PROFILE_ERROR_SESSION_NOT_FOUND:
242
                get_session_manager().expire_session(logout.server.providerId)
243
                return redirect(host.get_root_url())
244
            raise
245

    
246
        logout.buildRequestMsg()
247
        return redirect(logout.msgUrl)
248

    
249
    def singleLogoutReturn(self):
250
        host = Host.get_host_from_url()
251
        if host is None:
252
            return redirect('%s/' % get_request().environ['SCRIPT_NAME'])
253

    
254
        logout = lasso.Logout(misc.get_lasso_server(protocol = 'saml2'))
255
        session = get_session()
256

    
257
        if not session.id or not session.users.has_key(logout.server.providerId) \
258
                or not session.lasso_session_dumps.has_key(logout.server.providerId):
259
            get_session_manager().expire_session(logout.server.providerId)
260
            return redirect(host.get_root_url())
261
        logout.setSessionFromDump(session.lasso_session_dumps[logout.server.providerId])
262

    
263
        message = get_request().get_query()
264
        return self.slo_return(logout, message)
265

    
266
    def slo_return(self, logout, message):
267
        host = Host.get_host_from_url()
268

    
269
        session = get_session()
270

    
271
        try:
272
            logout.processResponseMsg(message)
273
        except lasso.Error, error:
274
            if error[0] == lasso.PROFILE_ERROR_INVALID_QUERY:
275
                get_logger().warn('Invalid response')
276
            elif error[0] == lasso.DS_ERROR_INVALID_SIGNATURE:
277
                get_logger().warn('Failed to check single logout request signature')
278
            elif error[0] == lasso.LOGOUT_ERROR_REQUEST_DENIED:
279
                get_logger().warn('Request Denied')
280
            elif error[0] == lasso.LOGOUT_ERROR_UNKNOWN_PRINCIPAL:
281
                get_logger().warn('Unknown principal on logout, probably session stopped already on IdP')
282
            else:
283
                get_logger().error('Unknown Lasso exception on logout return: ' + repr(error))
284
        except Exception, exception:
285
            get_logger().error('Unknown exception on logout return: ' + repr(exception))
286

    
287
        get_session_manager().expire_session(logout.server.providerId)
288

    
289
        return redirect(host.get_root_url())
290

    
291
    def singleLogoutSOAP(self):
292
        try:
293
            soap_message = self.get_soap_message()
294
        except:
295
            return
296

    
297
        response = get_response()
298
        response.set_content_type('text/xml')
299

    
300
        request_type = lasso.getRequestTypeFromSoapMsg(soap_message)
301

    
302
        if request_type != lasso.REQUEST_TYPE_LOGOUT:
303
            get_logger().warn('SOAP message on single logout url not a slo message')
304
            return
305

    
306
        logout = lasso.Logout(misc.get_lasso_server(protocol = 'saml2'))
307
        providerId = logout.server.providerId
308
        logout.processRequestMsg(soap_message)
309
        name_identifier = logout.nameIdentifier.content
310
        # find one session matching the name identifier, and eventually the request
311
        for session in get_session_manager().values():
312
            session_index = session.lasso_session_indexes.get(providerId)
313
            name_identifier = session.lasso_session_name_identifiers.get(providerId)
314
            request_name_identifier = logout.nameIdentifier.content
315
            request_session_index = logout.request.sessionIndex
316
            if request_name_identifier == name_identifier and \
317
                    (not session_index or request_session_index == session_index) \
318
                        and session.lasso_session_dumps.get(providerId):
319
                get_logger().info('SLO/SOAP from %s' % logout.remoteProviderId)
320
                break
321
        else:
322
            # no session, build straight failure answer
323
            logout.buildResponseMsg()
324
            return logout.msgBody
325

    
326
        return self.slo_idp(logout, session)
327

    
328
    def singleLogout(self):
329
        logout = lasso.Logout(misc.get_lasso_server(protocol = 'saml2'))
330
        try:
331
            logout.processRequestMsg(get_request().get_query())
332
        except lasso.Error, error:
333
            if error[0] == lasso.DS_ERROR_INVALID_SIGNATURE:
334
                return template.error_page(_('Failed to check single logout request signature.'))
335
            raise
336
        session = get_session()
337
        if not session.id:
338
            # session has not been found, this may be because the user has
339
            # its browser configured so that cookies are not sent for
340
            # remote queries and IdP is using image-based SLO.
341
            # so we look up a session with the appropriate name identifier
342
            # find a matching 
343
            for session in get_session_manager().values():
344
                session_index = session.lasso_session_indexes.get(providerId)
345
                name_identifier = session.lasso_session_name_identifiers.get(providerId)
346
                request_name_identifier = logout.nameIdentifier.content
347
                request_session_index = logout.request.sessionIndex
348
                if request_name_identifier == name_identifier and \
349
                        (not session_index or request_session_index == session_index) \
350
                        and session.lasso_session_dump.get(providerId):
351
                    get_logger().info('SLO/SOAP from %s' % logout.remoteProviderId)
352
                    break
353
            else:
354
                # no session, build straight failure answer
355
                logout.buildResponseMsg()
356
                return logout.msgBody
357

    
358
        return self.slo_idp(logout, session)
359

    
360
    def slo_idp(self, logout, session):
361
        # This block differs from qommon
362
        if session.lasso_session_dumps.has_key(logout.server.providerId):
363
            logout.setSessionFromDump(session.lasso_session_dumps[logout.server.providerId])
364
        user = session.get_user(logout.server.providerId)
365
        if user and user.lasso_dumps:
366
            logout.setIdentityFromDump(user.lasso_dumps[0])
367

    
368
        if user and logout.nameIdentifier.content not in user.name_identifiers:
369
            raise 'no appropriate name identifier in session (%s and %s)' % (
370
                    logout.nameIdentifier.content, session.name_identifier)
371

    
372
        try:
373
            assertion = logout.session.getAssertions(logout.remoteProviderId)[0]
374
            if logout.request.sessionIndex and (
375
                    assertion.authnStatement[0].sessionIndex != logout.request.sessionIndex):
376
                logout.setSessionFromDump('<Session />')
377
        except:
378
            pass
379

    
380
        try:
381
            logout.validateRequest()
382
        except lasso.Error, error:
383
            if error[0] == lasso.PROFILE_ERROR_SESSION_NOT_FOUND:
384
                pass
385
            elif error[0] == lasso.PROFILE_ERROR_IDENTITY_NOT_FOUND:
386
                pass
387
            elif error[0] == lasso.PROFILE_ERROR_MISSING_ASSERTION:
388
                pass
389
            elif error[0] == lasso.SERVER_ERROR_PROVIDER_NOT_FOUND:
390
                pass
391
            elif error[0] == lasso.NAME_IDENTIFIER_NOT_FOUND:
392
                pass
393
            else:
394
                raise
395
        else:
396
            providerId = logout.server.providerId
397
            session_index = logout.request.sessionIndex
398
            name_identifier = logout.nameIdentifier.content
399
            # Remove reference to local authentication on this SP in the session
400
            # if a user is present, try a local logout
401
            for session2 in get_session_manager().values():
402
                if session2.lasso_session_name_identifiers.get(providerId) == name_identifier \
403
                        and ( not session_index
404
                                or session2.lasso_session_indexes.get(providerId) == session_index):
405
                    if session2.users.has_key(providerId):
406
                        # local logout
407
                        site_auth = site_authentication.get_site_authentication(Host.get_host_from_url())
408
                        site_auth.local_logout(user=session2.get_user(providerId),
409
                                cookies=getattr(session2,'cookies', None))
410
                        del session2.users[providerId]
411
                    if session2.lasso_session_dumps.has_key(providerId):
412
                        del session2.lasso_session_dumps[providerId]
413
                    if session2.lasso_session_indexes.has_key(providerId):
414
                        del session2.lasso_session_indexes[providerId]
415
                    if session2.lasso_session_name_identifiers.has_key(providerId):
416
                        del session2.lasso_session_name_identifiers[providerId]
417
                    session2.store()
418
            get_session_manager().expire_session(logout.server.providerId)
419

    
420
        logout.buildResponseMsg()
421
        if logout.msgBody: # soap answer
422
            return logout.msgBody
423
        else:
424
            return redirect(logout.msgUrl)
425

    
426
    def local_auth(self):
427
        host = Host.get_host_from_url()
428
        if host is None:
429
            return redirect('%s/' % get_request().environ['SCRIPT_NAME'])
430
        return site_authentication.get_site_authentication(host).local_auth
431
    local_auth = property(local_auth)
432

    
433
    def metadata(self):
434
        host = Host.get_host_from_url()
435
        if host is None:
436
            return redirect('%s/' % get_request().environ['SCRIPT_NAME'])
437
        get_response().set_content_type('text/xml', 'utf-8')
438
        metadata = unicode(open(host.saml2_metadata).read(), 'utf-8')
439
        return metadata
440

    
441
    def public_key(self):
442
        host = Host.get_host_from_url()
443
        if host is None:
444
            return redirect('%s/' % get_request().environ['SCRIPT_NAME'])
445
        get_response().set_content_type('text/plain')
446
        public_key = open(host.public_key).read()
447
        return public_key
448

    
(16-16/19)