Browse Source

remove webdav digest_auth

Guénaël Muller 6 years ago
parent
commit
c714f22ae6

+ 4 - 0
doc/apache.md View File

109
     
109
     
110
     root_path = ''
110
     root_path = ''
111
     
111
     
112
+    # Tracim doesn't support digest auth for webdav
113
+    acceptbasic = True
114
+    acceptdigest = False
115
+    defaultdigest = False
112
     #===============================================================================
116
     #===============================================================================
113
     # Lock Manager
117
     # Lock Manager
114
     #
118
     #

+ 26 - 0
tracim/migration/versions/2b4043fa2502_remove_webdav_right_digest_response_.py View File

1
+"""remove webdav_right_digest_response_hash from database
2
+
3
+Revision ID: 2b4043fa2502
4
+Revises: f3852e1349c4
5
+Create Date: 2018-03-13 14:41:38.590375
6
+
7
+"""
8
+
9
+# revision identifiers, used by Alembic.
10
+revision = '2b4043fa2502'
11
+down_revision = 'f3852e1349c4'
12
+
13
+from alembic import op
14
+from sqlalchemy import Column, Unicode
15
+
16
+
17
+def upgrade():
18
+    with op.batch_alter_table('users') as batch_op:
19
+        batch_op.drop_column('webdav_left_digest_response_hash')
20
+
21
+
22
+def downgrade():
23
+    with op.batch_alter_table('users') as batch_op:
24
+        batch_op.add_column(
25
+            Column('webdav_left_digest_response_hash', Unicode(128))
26
+        )

+ 2 - 2
tracim/tracim/lib/daemons.py View File

390
             print(
390
             print(
391
                 "WARNING: Could not import lxml: using xml instead (slower). Consider installing lxml from http://codespeak.net/lxml/.")
391
                 "WARNING: Could not import lxml: using xml instead (slower). Consider installing lxml from http://codespeak.net/lxml/.")
392
         from wsgidav.dir_browser import WsgiDavDirBrowser
392
         from wsgidav.dir_browser import WsgiDavDirBrowser
393
-        from tracim.lib.webdav.tracim_http_authenticator import TracimHTTPAuthenticator
393
+        from wsgidav.http_authenticator import HTTPAuthenticator
394
         from wsgidav.error_printer import ErrorPrinter
394
         from wsgidav.error_printer import ErrorPrinter
395
         from tracim.lib.webdav.utils import TracimWsgiDavDebugFilter
395
         from tracim.lib.webdav.utils import TracimWsgiDavDebugFilter
396
 
396
 
397
         config['middleware_stack'] = [
397
         config['middleware_stack'] = [
398
             TracimEnforceHTTPS,
398
             TracimEnforceHTTPS,
399
             WsgiDavDirBrowser,
399
             WsgiDavDirBrowser,
400
-            TracimHTTPAuthenticator,
400
+            HTTPAuthenticator,
401
             ErrorPrinter,
401
             ErrorPrinter,
402
             TracimWsgiDavDebugFilter,
402
             TracimWsgiDavDebugFilter,
403
         ]
403
         ]

+ 7 - 17
tracim/tracim/lib/webdav/sql_domain_controller.py View File

2
 
2
 
3
 from tracim.lib.user import UserApi
3
 from tracim.lib.user import UserApi
4
 
4
 
5
+class DigestAuthNotImplemented(Exception):
6
+    pass
7
+
5
 class TracimDomainController(object):
8
 class TracimDomainController(object):
6
     """
9
     """
7
     The domain controller is used by http_authenticator to authenticate the user every time a request is
10
     The domain controller is used by http_authenticator to authenticate the user every time a request is
15
 
18
 
16
     def getRealmUserPassword(self, realmname, username, environ):
19
     def getRealmUserPassword(self, realmname, username, environ):
17
         """
20
         """
18
-        Warning ! This return hashed version of password !
21
+        This method is normally only use for digest auth. wsgidav need
22
+        plain password to deal with it. as we didn't
23
+        provide support for this kind of auth, this method raise an exception.
19
         """
24
         """
20
-        try:
21
-            user = self._api.get_one_by_email(username)
22
-            return user.password
23
-        except:
24
-            return False
25
+        raise DigestAuthNotImplemented
25
 
26
 
26
     def requireAuthentication(self, realmname, environ):
27
     def requireAuthentication(self, realmname, environ):
27
         return True
28
         return True
37
         except:
38
         except:
38
             return False
39
             return False
39
 
40
 
40
-    def get_left_digest_response_hash(self, realmname, username, environ):
41
-        """
42
-        Called by our http_authenticator to get the hashed md5 digest for the current user that is also sent by
43
-        the webdav client
44
-        """
45
-        try:
46
-            user = self._api.get_one_by_email(username)
47
-            return user.webdav_left_digest_response_hash
48
-        except:
49
-            return None
50
-
51
     def authDomainUser(self, realmname, username, password, environ):
41
     def authDomainUser(self, realmname, username, password, environ):
52
         """
42
         """
53
         If you ever feel the need to send a request al-mano with a curl, this is the function that'll be called by
43
         If you ever feel the need to send a request al-mano with a curl, this is the function that'll be called by

+ 0 - 76
tracim/tracim/lib/webdav/tracim_http_authenticator.py View File

1
-from wsgidav.http_authenticator import HTTPAuthenticator
2
-from wsgidav import util
3
-
4
-_logger = util.getModuleLogger(__name__, True)
5
-
6
-
7
-class TracimHTTPAuthenticator(HTTPAuthenticator):
8
-    def computeDigestResponse(
9
-            self,
10
-            username,
11
-            realm,
12
-            password,
13
-            method,
14
-            uri,
15
-            nonce,
16
-            cnonce,
17
-            qop,
18
-            nc
19
-    ):
20
-        """
21
-        Override standard computeDigestResponse : as user password is already
22
-        hashed in database, we need to use left_digest_response_hash
23
-        to have correctly hashed digest_response.
24
-        """
25
-
26
-        # TODO - G.M - 13-03-2018 Check if environ is useful
27
-        # for get_left_digest_response. If true, find a solution
28
-        # to obtain it here without recopy-paste whole authDigestAuthRequest
29
-        # method.
30
-        left_digest_response_hash = self._domaincontroller.get_left_digest_response_hash(realm, username, None)  # nopep8
31
-        if left_digest_response_hash:
32
-            return self.tracim_compute_digest_response(
33
-                left_digest_response_hash=left_digest_response_hash,
34
-                method=method,
35
-                uri=uri,
36
-                nonce=nonce,
37
-                cnonce=cnonce,
38
-                qop=qop,
39
-                nc=nc,
40
-            )
41
-        else:
42
-            return None
43
-
44
-    def tracim_compute_digest_response(
45
-            self,
46
-            left_digest_response_hash,
47
-            method,
48
-            uri,
49
-            nonce,
50
-            cnonce,
51
-            qop,
52
-            nc
53
-    ):
54
-        # TODO : Rename A to something more correct
55
-        A = "{method}:{uri}".format(method=method, uri=uri)
56
-        if qop:
57
-            right_digest_response_hash = "{nonce}:{nc}:{cnonce}:{qop}:{A}".format(  # nopep8
58
-                nonce=nonce,
59
-                nc=nc,
60
-                cnonce=cnonce,
61
-                qop=qop,
62
-                method=method,
63
-                uri=uri,
64
-                A=self.md5h(A),
65
-            )
66
-        else:
67
-            right_digest_response_hash = "{nonce}:{A}".format(
68
-                nonce=nonce,
69
-                A=self.md5h(A),
70
-            )
71
-        digestresp = self.md5kd(
72
-            left_digest_response_hash,
73
-            right_digest_response_hash,
74
-        )
75
-
76
-        return digestresp

+ 0 - 25
tracim/tracim/model/auth.py View File

128
     is_active = Column(Boolean, default=True, nullable=False)
128
     is_active = Column(Boolean, default=True, nullable=False)
129
     imported_from = Column(Unicode(32), nullable=True)
129
     imported_from = Column(Unicode(32), nullable=True)
130
     timezone = Column(Unicode(255), nullable=False, server_default='')
130
     timezone = Column(Unicode(255), nullable=False, server_default='')
131
-    _webdav_left_digest_response_hash = Column('webdav_left_digest_response_hash', Unicode(128))
132
     auth_token = Column(Unicode(255))
131
     auth_token = Column(Unicode(255))
133
     auth_token_created = Column(DateTime)
132
     auth_token_created = Column(DateTime)
134
 
133
 
202
 
201
 
203
         Hash cleartext password on the fly,
202
         Hash cleartext password on the fly,
204
         Store its ciphertext version,
203
         Store its ciphertext version,
205
-        Update the WebDAV hash as well.
206
         """
204
         """
207
         self._password = self._hash_password(cleartext_password)
205
         self._password = self._hash_password(cleartext_password)
208
-        self.update_webdav_digest_auth(cleartext_password)
209
 
206
 
210
     def _get_password(self) -> str:
207
     def _get_password(self) -> str:
211
         """Return the hashed version of the password."""
208
         """Return the hashed version of the password."""
214
     password = synonym('_password', descriptor=property(_get_password,
211
     password = synonym('_password', descriptor=property(_get_password,
215
                                                         _set_password))
212
                                                         _set_password))
216
 
213
 
217
-    @classmethod
218
-    def _hash_digest(cls, digest):
219
-        return md5(bytes(digest, 'utf-8')).hexdigest()
220
-
221
-    def _set_hash_digest(self, digest):
222
-        self._webdav_left_digest_response_hash = self._hash_digest(digest)
223
-
224
-    def _get_hash_digest(self):
225
-        return self._webdav_left_digest_response_hash
226
-
227
-    webdav_left_digest_response_hash = synonym('_webdav_left_digest_response_hash',
228
-                                               descriptor=property(_get_hash_digest,
229
-                                                                   _set_hash_digest))
230
-
231
-    def update_webdav_digest_auth(self, cleartext_password: str) -> None:
232
-        self.webdav_left_digest_response_hash \
233
-            = '{username}:/:{cleartext_password}'.format(
234
-                username=self.email,
235
-                cleartext_password=cleartext_password,
236
-            )
237
 
214
 
238
     def validate_password(self, cleartext_password: str) -> bool:
215
     def validate_password(self, cleartext_password: str) -> bool:
239
         """
216
         """
252
             hash = sha256()
229
             hash = sha256()
253
             hash.update((cleartext_password + self.password[:64]).encode('utf-8'))
230
             hash.update((cleartext_password + self.password[:64]).encode('utf-8'))
254
             result = self.password[64:] == hash.hexdigest()
231
             result = self.password[64:] == hash.hexdigest()
255
-            if result and not self.webdav_left_digest_response_hash:
256
-                self.update_webdav_digest_auth(cleartext_password)
257
         return result
232
         return result
258
 
233
 
259
     def get_display_name(self, remove_email_part: bool=False) -> str:
234
     def get_display_name(self, remove_email_part: bool=False) -> str:

+ 0 - 6
tracim/tracim/tests/command/user.py View File

14
         # Check webdav digest exist for this user
14
         # Check webdav digest exist for this user
15
         user = DBSession.query(User)\
15
         user = DBSession.query(User)\
16
             .filter(User.email == 'new-user@algoo.fr').one()
16
             .filter(User.email == 'new-user@algoo.fr').one()
17
-        ok_(user.webdav_left_digest_response_hash)
18
 
17
 
19
     def test_update_password(self):
18
     def test_update_password(self):
20
         self._create_user('new-user@algoo.fr', 'toor')
19
         self._create_user('new-user@algoo.fr', 'toor')
22
         # Grab webdav digest
21
         # Grab webdav digest
23
         user = DBSession.query(User) \
22
         user = DBSession.query(User) \
24
             .filter(User.email == 'new-user@algoo.fr').one()
23
             .filter(User.email == 'new-user@algoo.fr').one()
25
-        webdav_digest = user.webdav_left_digest_response_hash
26
 
24
 
27
         self._execute_command(
25
         self._execute_command(
28
             UpdateUserCommand,
26
             UpdateUserCommand,
35
         # Grab new webdav digest to compare it
33
         # Grab new webdav digest to compare it
36
         user = DBSession.query(User) \
34
         user = DBSession.query(User) \
37
             .filter(User.email == 'new-user@algoo.fr').one()
35
             .filter(User.email == 'new-user@algoo.fr').one()
38
-        ok_(
39
-            webdav_digest != user.webdav_left_digest_response_hash,
40
-            msg='Webdav digest should be different',
41
-        )
42
 
36
 
43
     def test_create_with_group(self):
37
     def test_create_with_group(self):
44
         more_args = ['--add-to-group', 'managers', '--add-to-group', 'administrators']
38
         more_args = ['--add-to-group', 'managers', '--add-to-group', 'administrators']

+ 0 - 8
tracim/tracim/tests/functional/test_admin.py View File

43
         ok_(user, msg="User should exist now")
43
         ok_(user, msg="User should exist now")
44
         ok_(user.validate_password('password'))
44
         ok_(user.validate_password('password'))
45
 
45
 
46
-        # User must have webdav digest
47
-        ok_(user.webdav_left_digest_response_hash)
48
-
49
     def test_update_user_password(self):
46
     def test_update_user_password(self):
50
         self._connect_user(
47
         self._connect_user(
51
             'admin@admin.admin',
48
             'admin@admin.admin',
67
 
64
 
68
         user = DBSession.query(User) \
65
         user = DBSession.query(User) \
69
             .filter(User.email == 'an-other-email@test.local').one()
66
             .filter(User.email == 'an-other-email@test.local').one()
70
-        webdav_digest = user.webdav_left_digest_response_hash
71
 
67
 
72
         self.app.post(
68
         self.app.post(
73
             '/admin/users/{user_id}/password?_method=PUT'.format(
69
             '/admin/users/{user_id}/password?_method=PUT'.format(
82
         user = DBSession.query(User) \
78
         user = DBSession.query(User) \
83
             .filter(User.email == 'an-other-email@test.local').one()
79
             .filter(User.email == 'an-other-email@test.local').one()
84
         ok_(user.validate_password('new-password'))
80
         ok_(user.validate_password('new-password'))
85
-        ok_(
86
-            webdav_digest != user.webdav_left_digest_response_hash,
87
-            msg='Webdav digest should be updated',
88
-        )

+ 0 - 5
tracim/tracim/tests/functional/test_user.py View File

22
 
22
 
23
         user = DBSession.query(User) \
23
         user = DBSession.query(User) \
24
             .filter(User.email == 'lawrence-not-real-email@fsf.local').one()
24
             .filter(User.email == 'lawrence-not-real-email@fsf.local').one()
25
-        webdav_digest = user.webdav_left_digest_response_hash
26
 
25
 
27
         try_post_user = self.app.post(
26
         try_post_user = self.app.post(
28
             '/user/{user_id}/password?_method=PUT'.format(
27
             '/user/{user_id}/password?_method=PUT'.format(
40
         user = DBSession.query(User) \
39
         user = DBSession.query(User) \
41
             .filter(User.email == 'lawrence-not-real-email@fsf.local').one()
40
             .filter(User.email == 'lawrence-not-real-email@fsf.local').one()
42
         ok_(user.validate_password('new-password'))
41
         ok_(user.validate_password('new-password'))
43
-        ok_(
44
-            webdav_digest != user.webdav_left_digest_response_hash,
45
-            msg='Webdav digest should be updated',
46
-        )

+ 4 - 0
tracim/wsgidav.conf.sample View File

25
 
25
 
26
 root_path = ''
26
 root_path = ''
27
 
27
 
28
+# Tracim doesn't support digest auth for webdav
29
+acceptbasic = True
30
+acceptdigest = False
31
+defaultdigest = False
28
 #===============================================================================
32
 #===============================================================================
29
 # Lock Manager
33
 # Lock Manager
30
 #
34
 #