Projet

Général

Profil

Télécharger (13,5 ko) Statistiques
| Branche: | Révision:

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

1
import libxml2
2
import urllib
3
import urlparse
4
import httplib
5
import re
6
import os
7
import socket
8
import base64
9

    
10
from quixote import get_request, get_response, get_session, redirect, get_publisher
11
from quixote.directory import Directory
12
from quixote.http_request import parse_header
13

    
14
import lasso
15

    
16
from qommon import get_logger
17
from qommon.form import *
18
from qommon.errors import ConnectionError, ConfigurationError, LoginError
19
from qommon.misc import http_post_request, http_get_page
20
from qommon.template import *
21

    
22
from larpe.plugins import site_authentication_plugins
23

    
24
import misc
25
from users import User
26
from federations import Federation
27

    
28
class SiteAuthentication:
29

    
30
    output_filters = []
31

    
32
    def __init__(self, host):
33
        self.host = host
34

    
35
    def federate(self, username, password, provider_id, cookies, select):
36
        user = get_session().get_user(provider_id)
37
        if user is not None:
38
            Federation(username, password, self.host.id, user.name_identifiers[0], cookies, select).store()
39

    
40
    def sso_local_login(self, federation):
41
        status, data = self.local_auth_check_dispatch(
42
            federation.username, federation.password, federation.select_fields)
43
        success, return_content = self.check_auth(status, data)
44
        if success:
45
            session = get_session()
46
            if hasattr(session, 'cookies'):
47
                federation.set_cookies(session.cookies)
48
                federation.store()
49
            return return_content
50
        else:
51
            return redirect('local_auth')
52

    
53
    def local_auth [html] (self, first_time=True):
54
        response = get_response()
55
        response.set_content_type('text/html')
56

    
57
        if hasattr(get_response(), str('breadcrumb')):
58
            del get_response().breadcrumb
59

    
60
        get_response().filter['default_org'] = '%s - %s' % (self.host.label, _('Local authentication'))
61
        get_response().filter['body_class'] = 'login'
62

    
63
        form = self.form_local_auth()
64
        form.add_submit('submit', _('Submit'))
65
        #form.add_submit('cancel', _('Cancel'))
66

    
67
#        if form.get_widget('cancel').parse():
68
#            return redirect('.')
69
        authentication_failure = None
70
        if form.is_submitted() and not form.has_errors():
71
            try:
72
                return self.submit_local_auth_form(form)
73
            except LoginError:
74
                authentication_failure = _('Authentication failure')
75
                get_logger().info('local auth page : %s' % authentication_failure)
76
            except ConnectionError, err:
77
                authentication_failure = _('Connection failed : %s') % err
78
                get_logger().info('local auth page : %s' % authentication_failure)
79
            except ConfigurationError, err:
80
                authentication_failure = _('This service provider is not fully configured : %s') % err
81
                get_logger().info('local auth page : %s' % authentication_failure)
82
            except Exception, err:
83
                authentication_failure = _('Unknown error : %s' % err)
84
                get_logger().info('local auth page : %s' % authentication_failure)
85

    
86
        if authentication_failure:
87
            '<div class="errornotice">%s</div>' % authentication_failure
88
        '<p>'
89
        _('Please type your login and password for this Service Provider.')
90
        _('Your local account will be federated with your Liberty Alliance account.')
91
        '</p>'
92

    
93
        form.render()
94

    
95
    # Also used in admin/hosts.ptl
96
    def form_local_auth(self):
97
        form = Form(enctype='multipart/form-data')
98
        form.add(StringWidget, 'username', title = _('Username'), required = True,
99
                size = 30)
100
        form.add(PasswordWidget, 'password', title = _('Password'), required = True,
101
                size = 30)
102
        for name, values in self.host.select_fields.iteritems():
103
            options = []
104
            if values:
105
                for value in values:
106
                    options.append(value)
107
                form.add(SingleSelectWidget, name, title = name.capitalize(),
108
                    value = values[0], options = options)
109
        return form
110

    
111
    def submit_local_auth_form(self, form):
112
        username = form.get_widget('username').parse()
113
        password = form.get_widget('password').parse()
114
        select = {}
115
        for name, values in self.host.select_fields.iteritems():
116
            if form.get_widget(name):
117
                select[name] = form.get_widget(name).parse()
118
        return self.local_auth_check(username, password, select)
119

    
120
    def local_auth_check(self, username, password, select=None):
121
        select = select or {}
122
        status, data = self.local_auth_check_dispatch(username, password, select)
123
        if status == 0:
124
            raise
125
        success, return_content = self.check_auth(status, data)
126
        if success:
127
            if misc.get_current_protocol() == lasso.PROTOCOL_SAML_2_0:
128
                provider_id = self.host.saml2_provider_id
129
            else:
130
                provider_id = self.host.provider_id
131
            session = get_session()
132

    
133
            if hasattr(session, 'cookies'):
134
                self.federate(username, password, provider_id, session.cookies, select)
135
            else:
136
                self.federate(username, password, provider_id, None, select)
137
            return return_content
138
        raise LoginError()
139

    
140
    def local_auth_check_dispatch(self, username, password, select=None):
141
        select = select or {}
142
        if self.host.auth_mode == 'http_basic':
143
            return self.local_auth_check_http_basic(username, password)
144
        elif self.host.auth_mode == 'form' and hasattr(self.host, 'auth_check_url') \
145
                and self.host.auth_check_url is not None:
146
            return self.local_auth_check_post(username, password, select)
147
        else:
148
            raise ConfigurationError('No authentication form was found')
149

    
150
    def local_auth_check_post(self, username, password, select=None):
151
        select = select or {}
152
        url = self.host.auth_check_url
153

    
154
        # Build request body
155
        if self.host.post_parameters:
156
            body_params = {}
157
            # Login field
158
            if self.host.post_parameters[self.host.login_field_name]['enabled'] is True:
159
                body_params[self.host.login_field_name] = username
160
            # Password field
161
            if self.host.post_parameters[self.host.password_field_name]['enabled'] is True:
162
                body_params[self.host.password_field_name] = password
163
            # Select fields
164
            for name, value in select.iteritems():
165
                if self.host.post_parameters[name]['enabled'] is True:
166
                    body_params[name] = value
167
            # Other fields (hidden, submit and custom)
168
            for name, value in self.host.other_fields.iteritems():
169
                if self.host.post_parameters[name]['enabled'] is True:
170
                    body_params[name] = self.host.post_parameters[name]['value']
171
            body = urllib.urlencode(body_params)
172
        else:
173
            # XXX: Legacy (to be removed later) Send all parameters for sites configured with a previous version of Larpe
174
            body = '%s=%s&%s=%s' % (self.host.login_field_name, username, self.host.password_field_name, password)
175
            # Add select fields to the body
176
            for name, value in select.iteritems():
177
                body += '&%s=%s' % (name, value)
178
            # Add hidden fields to the body
179
            if self.host.send_hidden_fields:
180
                for name, value in self.host.other_fields.iteritems():
181
                    body += '&%s=%s' % (name, value)
182

    
183
        # Build request HTTP headers
184
        if self.host.http_headers:
185
            headers = {}
186
            if self.host.http_headers['X-Forwarded-For']['enabled'] is True:
187
                headers['X-Forwarded-For'] = get_request().get_environ('REMOTE_ADDR', '-')
188
            for name, value in self.host.http_headers.iteritems():
189
                if value['enabled'] is True and value['immutable'] is False:
190
                    headers[name] = value['value']
191
        else:
192
            # XXX: (to be removed later) Send default headers for sites configured with a previous version of Larpe
193
            headers = { 'Content-Type': 'application/x-www-form-urlencoded',
194
                        'X-Forwarded-For': get_request().get_environ('REMOTE_ADDR', '-'),
195
                        'X-Forwarded-Host': self.host.reversed_hostname }
196

    
197
        # Send request
198
        response, status, data, auth_headers = http_post_request(url, body, headers, self.host.use_proxy)
199

    
200
        cookies = response.getheader('Set-Cookie', None)
201
        self.host.cookies = []
202
        if cookies is not None:
203
            cookies_list = []
204
            cookies_set_list = []
205
            for cookie in cookies.split(', '):
206
                # Drop the path and other attributes
207
                cookie_only = cookie.split('; ')[0]
208
                regexp = re.compile('=')
209
                if regexp.search(cookie_only) is None:
210
                    continue
211
                # Split name and value
212
                cookie_split = cookie_only.split('=')
213
                cookie_name = cookie_split[0]
214
                cookie_value = cookie_split[1]
215
                cookies_list.append('%s=%s' % (cookie_name, cookie_value))
216
                set_cookie = '%s=%s; path=/' % (cookie_name, cookie_value)
217
                cookies_set_list.append(set_cookie)
218
                self.host.cookies.append(cookie_name)
219
            cookies_headers = '\r\nSet-Cookie: '.join(cookies_set_list)
220
            get_response().set_header('Set-Cookie', cookies_headers)
221
            self.host.store()
222
            get_session().cookies = '; '.join(cookies_list)
223
        else:
224
            get_logger().warn('No cookie from local authentication')
225

    
226
        return response.status, data
227

    
228
    def local_auth_check_http_basic(self, username, password):
229
        url = self.host.auth_form_url
230
        hostname, query = urllib.splithost(url[5:])
231
        conn = httplib.HTTPConnection(hostname)
232

    
233
        auth_header = 'Basic %s' % base64.encodestring('%s:%s' % (username, password))
234

    
235
        try:
236
            conn.request('GET', query, headers={'Authorization': auth_header})
237
        except socket.gaierror, err:
238
            print err
239
            conn.close()
240
            return 0, None
241
        else:
242
            response = conn.getresponse()
243
            conn.close()
244
            return response.status, response.read()
245

    
246
    def check_auth(self, status, data):
247
        success = False
248
        return_content = ''
249

    
250
        # If status is 500, fail without checking other criterias
251
        if status // 100 == 5:
252
            success = False
253
            return_content = redirect(self.host.get_return_url())
254

    
255

    
256
        # For http auth, only check status code
257
        elif self.host.auth_mode == 'http_basic':
258
            # If failed, status code should be 401
259
            if status // 100 == 2 or status // 100 == 3:
260
                success = True
261
                return_content = redirect(self.host.get_return_url())
262

    
263
        else:
264
            if self.host.auth_system == 'password':
265
                # If there is a password field, authentication probably failed
266
                regexp = re.compile("""<input[^>]*?type=["']?password["']?[^>]*?>""", re.DOTALL | re.IGNORECASE)
267
                if not regexp.findall(data):
268
                    success = True
269
                    return_content = redirect(self.host.get_return_url())
270
            elif self.host.auth_system == 'status':
271
                match_status = int(self.host.auth_match_status)
272
                if match_status == status:
273
                    success = True
274
                    return_content = redirect(self.host.get_return_url())
275
            elif self.host.auth_system == 'match_text':
276
                # If the auth_match_text is not matched, it means the authentication is successful
277
                regexp = re.compile(self.host.auth_match_text, re.DOTALL)
278
                if not regexp.findall(data):
279
                    success = True
280
                    return_content = redirect(self.host.get_return_url())
281

    
282
        return success, return_content
283

    
284
    def local_logout(self, federation=None, user=None, cookies=None):
285
        if cookies is None and federation is None and user is not None:
286
            federations = Federation.select(lambda x: user.name_identifiers[0] in x.name_identifiers)
287
            if federations:
288
                cookies = federations[0].cookies
289

    
290
        # Logout request to the site
291
        url = self.host.logout_url
292
        if url is not None and cookies is not None:
293
            try:
294
                http_get_page(url, {'Cookie': cookies})
295
            except ConnectionError, err:
296
                get_logger().warning(_("%s logout failed") % url)
297
                get_logger().debug(err)
298

    
299
        # Remove cookies from the browser
300
        # TODO: this should be removed because this only works
301
        # with a 'direct' logout
302
        if hasattr(self.host, 'cookies'):
303
            for cookie in self.host.cookies:
304
                get_response().expire_cookie(cookie, path='/')
305

    
306
    def local_defederate(self, session, provider_id):
307
        if session is None:
308
            return
309
        user = session.get_user(provider_id)
310
        if user is not None:
311
            federations = Federation.select(lambda x: user.name_identifiers[0] in x.name_identifiers)
312
            for federation in federations:
313
                self.local_logout(provider_id, federation)
314
                federation.remove_name_identifier(user.name_identifiers[0])
315
                federation.store()
316

    
317
def get_site_authentication(host):
318
    if host.site_authentication_plugin is None:
319
        return SiteAuthentication(host)
320
    return site_authentication_plugins.get(host.site_authentication_plugin)(host)
321

    
(18-18/19)