Browse Source

Adding webdav library previously developed, using the py3 version of the wsgidav project

Nonolost 8 years ago
parent
commit
2a5c09d594

+ 82 - 0
tracim/tracim/lib/webdav/__init__.py View File

@@ -0,0 +1,82 @@
1
+from sqlalchemy import create_engine
2
+from sqlalchemy.ext.declarative import declarative_base
3
+from sqlalchemy.orm import sessionmaker
4
+
5
+Base = declarative_base()
6
+
7
+some_engine = create_engine('postgresql://arnaud:wxc@localhost/template1')
8
+Session = sessionmaker(bind=some_engine)
9
+
10
+from wsgidav.compat import to_bytes
11
+
12
+from tracim.lib.content import ContentApi
13
+from tracim.model import new_revision
14
+from tracim.model.data import ActionDescription, ContentType, Content
15
+from wsgidav import util
16
+
17
+import transaction
18
+Base.metadata.create_all(some_engine)
19
+
20
+role = {
21
+    'NOT_APPLICABLE': 0,
22
+    'READER': 1,
23
+    'CONTRIBUTOR': 2,
24
+    'CONTENT_MANAGER': 4,
25
+    'WORKSPACE_MANAGER': 8
26
+}
27
+
28
+class HistoryType(object):
29
+    Deleted = 'deleted'
30
+    Archived = 'archived'
31
+    Standard = 'standard'
32
+    All = 'all'
33
+
34
+# not_applicable : nothing
35
+# reader : can only read everything in designed workspace
36
+# contributor : + create / modify files
37
+# content_manager : + delete files / create directory / delete directory
38
+# workspace_manager : + create workspace / delete workspace
39
+
40
+class MyFileStream(object):
41
+    def __init__(self, content: Content, content_api: ContentApi, file_name: str=''):
42
+        self.buflist = []
43
+        self._content = content
44
+        self._api = content_api
45
+
46
+        self._file_name = file_name if file_name != '' else self._content.file_name
47
+
48
+    def write(self, s):
49
+        self.buflist.append(s)
50
+
51
+    def close(self):
52
+        tot = to_bytes('')
53
+        for buf in self.buflist:
54
+            tot += buf
55
+
56
+        with new_revision(self._content):
57
+            self._api.update_file_data(self._content, self._file_name, util.guessMimeType(self._content.file_name), tot)
58
+            self._api.save(self._content, ActionDescription.EDITION)
59
+
60
+        transaction.commit()
61
+
62
+
63
+class MyFileStream2(object):
64
+    def __init__(self, file_name: str, content: Content, content_api: ContentApi):
65
+        self.buflist = []
66
+        self._file_name = file_name
67
+        self._content = content
68
+        self._api = content_api
69
+
70
+    def write(self, s):
71
+        self.buflist.append(s)
72
+
73
+    def close(self):
74
+        tot = to_bytes('')
75
+        for buf in self.buflist:
76
+            tot += buf
77
+
78
+        file = self._api.create(ContentType.File, self._content.workspace, self._content)
79
+        self._api.update_file_data(file, self._file_name, util.guessMimeType(self._file_name), tot)
80
+        self._api.save(file, ActionDescription.CREATION)
81
+
82
+        transaction.commit()

+ 0 - 0
tracim/tracim/lib/webdav/lock_manager.py View File


+ 276 - 0
tracim/tracim/lib/webdav/lock_storage.py View File

@@ -0,0 +1,276 @@
1
+import time
2
+
3
+from tracim.lib.webdav.sql_model import Lock, Url2Token
4
+from tracim.lib.webdav import Session
5
+from wsgidav import util
6
+from wsgidav.lock_manager import normalizeLockRoot, lockString, generateLockToken, validateLock
7
+from wsgidav.rw_lock import ReadWriteLock
8
+
9
+_logger = util.getModuleLogger(__name__)
10
+
11
+
12
+def from_dict_to_base(lock):
13
+    return Lock(
14
+        token=lock["token"],
15
+        depth=lock["depth"],
16
+        root=lock["root"],
17
+        type=lock["type"],
18
+        scopre=lock["scope"],
19
+        owner=lock["owner"],
20
+        timeout=lock["timeout"],
21
+        principal=lock["principal"],
22
+        expire=lock["expire"]
23
+    )
24
+
25
+
26
+def from_base_to_dict(lock):
27
+    return {
28
+        'token': lock.token,
29
+        'depth': lock.depth,
30
+        'root': lock.root,
31
+        'type': lock.type,
32
+        'scope': lock.scope,
33
+        'owner': lock.owner,
34
+        'timeout': lock.timeout,
35
+        'principal': lock.principal,
36
+        'expire': lock.expire
37
+    }
38
+
39
+
40
+class LockStorage(object):
41
+    LOCK_TIME_OUT_DEFAULT = 604800  # 1 week, in seconds
42
+    LOCK_TIME_OUT_MAX = 4 * 604800  # 1 month, in seconds
43
+
44
+    def __init__(self):
45
+        self._session = Session()
46
+        self._lock = ReadWriteLock()
47
+
48
+    def __repr__(self):
49
+        return "C'est bien mon verrou..."
50
+
51
+    def __del__(self):
52
+        pass
53
+
54
+    def get_lock_db_from_token(self, token):
55
+        return self._session.query(Lock).filter(Lock.token == token).one_or_none()
56
+
57
+    def _flush(self):
58
+        """Overloaded by Shelve implementation."""
59
+        pass
60
+
61
+    def open(self):
62
+        """Called before first use.
63
+
64
+        May be implemented to initialize a storage.
65
+        """
66
+        pass
67
+
68
+    def close(self):
69
+        """Called on shutdown."""
70
+        pass
71
+
72
+    def cleanup(self):
73
+        """Purge expired locks (optional)."""
74
+        pass
75
+
76
+    def clear(self):
77
+        """Delete all entries."""
78
+        self._session.query(Lock).all().delete(synchronize_session=False)
79
+        self._session.commit()
80
+
81
+    def get(self, token):
82
+        """Return a lock dictionary for a token.
83
+
84
+        If the lock does not exist or is expired, None is returned.
85
+
86
+        token:
87
+            lock token
88
+        Returns:
89
+            Lock dictionary or <None>
90
+
91
+        Side effect: if lock is expired, it will be purged and None is returned.
92
+        """
93
+        self._lock.acquireRead()
94
+        try:
95
+            lock_base = self._session.query(Lock).filter(Lock.token == token).one_or_none()
96
+            if lock_base is None:
97
+                # Lock not found: purge dangling URL2TOKEN entries
98
+                _logger.debug("Lock purged dangling: %s" % token)
99
+                self.delete(token)
100
+                return None
101
+            expire = float(lock_base.expire)
102
+            if 0 <= expire < time.time():
103
+                _logger.debug("Lock timed-out(%s): %s" % (expire, lockString(from_base_to_dict(lock_base))))
104
+                self.delete(token)
105
+                return None
106
+            return from_base_to_dict(lock_base)
107
+        finally:
108
+            self._lock.release()
109
+
110
+    def create(self, path, lock):
111
+        """Create a direct lock for a resource path.
112
+
113
+        path:
114
+            Normalized path (utf8 encoded string, no trailing '/')
115
+        lock:
116
+            lock dictionary, without a token entry
117
+        Returns:
118
+            New unique lock token.: <lock
119
+
120
+        **Note:** the lock dictionary may be modified on return:
121
+
122
+        - lock['root'] is ignored and set to the normalized <path>
123
+        - lock['timeout'] may be normalized and shorter than requested
124
+        - lock['token'] is added
125
+        """
126
+        self._lock.acquireWrite()
127
+        try:
128
+            # We expect only a lock definition, not an existing lock
129
+            assert lock.get("token") is None
130
+            assert lock.get("expire") is None, "Use timeout instead of expire"
131
+            assert path and "/" in path
132
+
133
+            # Normalize root: /foo/bar
134
+            org_path = path
135
+            path = normalizeLockRoot(path)
136
+            lock["root"] = path
137
+
138
+            # Normalize timeout from ttl to expire-date
139
+            timeout = float(lock.get("timeout"))
140
+            if timeout is None:
141
+                timeout = LockStorage.LOCK_TIME_OUT_DEFAULT
142
+            elif timeout < 0 or timeout > LockStorage.LOCK_TIME_OUT_MAX:
143
+                timeout = LockStorage.LOCK_TIME_OUT_MAX
144
+
145
+            lock["timeout"] = timeout
146
+            lock["expire"] = time.time() + timeout
147
+
148
+            validateLock(lock)
149
+
150
+            token = generateLockToken()
151
+            lock["token"] = token
152
+
153
+            # Store lock
154
+            lock_db = from_dict_to_base(lock)
155
+
156
+            self._session.add(lock_db)
157
+
158
+            # Store locked path reference
159
+            url2token = Url2Token(
160
+                path=path,
161
+                token=token
162
+            )
163
+
164
+            self._session.add(url2token)
165
+            self._session.commit()
166
+
167
+            self._flush()
168
+            _logger.debug("LockStorageDict.set(%r): %s" % (org_path, lockString(lock)))
169
+            #            print("LockStorageDict.set(%r): %s" % (org_path, lockString(lock)))
170
+            return lock
171
+        finally:
172
+            self._lock.release()
173
+
174
+    def refresh(self, token, timeout):
175
+        """Modify an existing lock's timeout.
176
+
177
+        token:
178
+            Valid lock token.
179
+        timeout:
180
+            Suggested lifetime in seconds (-1 for infinite).
181
+            The real expiration time may be shorter than requested!
182
+        Returns:
183
+            Lock dictionary.
184
+            Raises ValueError, if token is invalid.
185
+        """
186
+        lock_db = self._session.query(Lock).filter(Lock.token == token).one_or_none()
187
+        assert lock_db is not None, "Lock must exist"
188
+        assert timeout == -1 or timeout > 0
189
+        if timeout < 0 or timeout > LockStorage.LOCK_TIME_OUT_MAX:
190
+            timeout = LockStorage.LOCK_TIME_OUT_MAX
191
+
192
+        self._lock.acquireWrite()
193
+        try:
194
+            # Note: shelve dictionary returns copies, so we must reassign values:
195
+            lock_db.timeout = timeout
196
+            lock_db.expire = time.time() + timeout
197
+            self._session.commit()
198
+            self._flush()
199
+        finally:
200
+            self._lock.release()
201
+        return from_base_to_dict(lock_db)
202
+
203
+    def delete(self, token):
204
+        """Delete lock.
205
+
206
+        Returns True on success. False, if token does not exist, or is expired.
207
+        """
208
+        self._lock.acquireWrite()
209
+        try:
210
+            lock_db = self._session.query(Lock).filter(Lock.token == token).one_or_none()
211
+            _logger.debug("delete %s" % lockString(from_base_to_dict(lock_db)))
212
+            if lock_db is None:
213
+                return False
214
+            # Remove url to lock mapping
215
+            url2token = self._session.query(Url2Token).filter(
216
+                Url2Token.path == lock_db.root,
217
+                Url2Token.token == token).one_or_none()
218
+            if url2token is not None:
219
+                self._session.delete(url2token)
220
+            # Remove the lock
221
+            self._session.delete(lock_db)
222
+            self._session.commit()
223
+
224
+            self._flush()
225
+        finally:
226
+            self._lock.release()
227
+        return True
228
+
229
+    def getLockList(self, path, includeRoot, includeChildren, tokenOnly):
230
+        """Return a list of direct locks for <path>.
231
+
232
+        Expired locks are *not* returned (but may be purged).
233
+
234
+        path:
235
+            Normalized path (utf8 encoded string, no trailing '/')
236
+        includeRoot:
237
+            False: don't add <path> lock (only makes sense, when includeChildren
238
+            is True).
239
+        includeChildren:
240
+            True: Also check all sub-paths for existing locks.
241
+        tokenOnly:
242
+            True: only a list of token is returned. This may be implemented
243
+            more efficiently by some providers.
244
+        Returns:
245
+            List of valid lock dictionaries (may be empty).
246
+        """
247
+        assert path and path.startswith("/")
248
+        assert includeRoot or includeChildren
249
+
250
+        def __appendLocks(toklist):
251
+            # Since we can do this quickly, we use self.get() even if
252
+            # tokenOnly is set, so expired locks are purged.
253
+            for token in toklist:
254
+                lock_db = self.get_lock_db_from_token(token)
255
+                if lock_db:
256
+                    if tokenOnly:
257
+                        lockList.append(lock_db.token)
258
+                    else:
259
+                        lockList.append(from_base_to_dict(lock_db))
260
+
261
+        path = normalizeLockRoot(path)
262
+        self._lock.acquireRead()
263
+        try:
264
+            tokList = self._session.query(Url2Token.token).filter(Url2Token.path == path).all()
265
+            lockList = []
266
+            if includeRoot:
267
+                __appendLocks(tokList)
268
+
269
+            if includeChildren:
270
+                for url, in self._session.query(Url2Token.path).group_by(Url2Token.path):
271
+                    if util.isChildUri(path, url):
272
+                        __appendLocks(self._session.query(Url2Token.token).filter(Url2Token.path == url))
273
+
274
+            return lockList
275
+        finally:
276
+            self._lock.release()

+ 497 - 0
tracim/tracim/lib/webdav/sql_dav_provider.py View File

@@ -0,0 +1,497 @@
1
+# coding: utf8
2
+
3
+from tracim.lib.webdav import HistoryType
4
+from tracim.lib.webdav.lock_storage import LockStorage
5
+
6
+import re
7
+from os.path import basename, dirname, normpath
8
+from tracim.lib.content import ContentApi
9
+from tracim.lib.webdav import sql_resources
10
+from tracim.lib.user import UserApi
11
+from tracim.lib.workspace import WorkspaceApi
12
+from wsgidav import util
13
+from wsgidav.dav_provider import DAVProvider
14
+from wsgidav.lock_manager import LockManager
15
+from tracim.model.data import ContentType
16
+
17
+######################################
18
+
19
+__docformat__ = "reStructuredText"
20
+_logger = util.getModuleLogger(__name__)
21
+
22
+
23
+def wsgi_decode(s):
24
+    return s.encode('latin1').decode()
25
+
26
+def wsgi_encode(s):
27
+    if isinstance(s, bytes):
28
+        return s.decode('latin1')
29
+    return s.encode().decode('latin1')
30
+
31
+
32
+# ============================================================
33
+# PostgreSQLProvider
34
+# ============================================================
35
+class Provider(DAVProvider):
36
+    def __init__(self, manage_lock=True):
37
+        super(Provider, self).__init__()
38
+
39
+        if manage_lock:
40
+            self.lockManager = LockManager(LockStorage())
41
+
42
+    def __repr__(self):
43
+        return 'Provider'
44
+
45
+    #########################################################
46
+    # Everything override from DAVProvider
47
+    def getResourceInst(self, path, environ):
48
+
49
+        working_path = path
50
+
51
+        if not self.exists(path, environ):
52
+            return None
53
+
54
+        if path == "/":
55
+            return sql_resources.Root(path, environ)
56
+
57
+        norm_path = normpath(working_path)
58
+
59
+        user = UserApi(None).get_one_by_email(environ['http_authenticator.username'])
60
+        workspace_api = WorkspaceApi(user)
61
+
62
+        if dirname(norm_path) == "/":
63
+            workspace = self.get_workspace_from_path(norm_path, workspace_api)
64
+            return sql_resources.Workspace(path, environ, workspace)
65
+
66
+
67
+        api = ContentApi(user, show_archived=True, show_deleted=True)
68
+
69
+        working_path = self.reduce_path(path)
70
+
71
+        content = self.get_content_from_path(working_path, api, workspace_api)
72
+
73
+        # is archive
74
+        is_archived_folder = re.search(r'/\.archived$', norm_path) is not None
75
+
76
+        if is_archived_folder:
77
+            return sql_resources.ArchivedFolder(path, environ, content)
78
+
79
+        # is delete
80
+        is_deleted_folder = re.search(r'/\.deleted$', norm_path) is not None
81
+
82
+        if is_deleted_folder:
83
+            return sql_resources.DeletedFolder(path, environ, content)
84
+
85
+        # is history
86
+        is_history_folder = re.search(r'/\.history$', norm_path) is not None
87
+
88
+        if is_history_folder:
89
+            is_deleted_folder = re.search(r'/\.deleted/\.history$', norm_path) is not None
90
+            is_archived_folder = re.search(r'/\.archived/\.history$', norm_path) is not None
91
+
92
+            type = HistoryType.Deleted if is_deleted_folder \
93
+                else HistoryType.Archived if is_archived_folder \
94
+                else HistoryType.Standard
95
+
96
+            return sql_resources.HistoryFolder(path, environ, content, type)
97
+
98
+        # is history
99
+        is_history_file_folder = re.search(r'/\.history/([^/]+)$', norm_path) is not None
100
+
101
+        if is_history_file_folder:
102
+            return sql_resources.HistoryFileFolder(path, environ, content)
103
+
104
+        # is history
105
+        is_history_file = re.search(r'/\.history/[^/]+/(\d+)-.+', norm_path) is not None
106
+
107
+        if is_history_file:
108
+            content = api.get_one_revision2(re.search(r'/\.history/[^/]+/(\d+)-.+', norm_path).group(1))
109
+            content_revision = self.get_content_from_revision(re.search(r'/\.history/[^/]+/(\d+)-.+', norm_path).group(1), api)
110
+
111
+            if content.type == ContentType.File:
112
+                return sql_resources.HistoryFile(path, environ, content, content_revision)
113
+            else:
114
+                return sql_resources.HistoryOtherFile(path, environ, content, content_revision)
115
+
116
+        # other
117
+        if content.type == ContentType.Folder:
118
+            return sql_resources.Folder(path, environ, content)
119
+
120
+        if content.type == ContentType.File:
121
+            return sql_resources.File(path, environ, content, False)
122
+        else:
123
+            return sql_resources.OtherFile(path, environ, content)
124
+
125
+    def exists(self, path, environ):
126
+        path = normpath(path)
127
+        if path == "/":
128
+            return True
129
+        elif dirname(path) == "/":
130
+            return self.get_workspace_from_path(
131
+                path,
132
+                WorkspaceApi(UserApi(None).get_one_by_email(environ['http_authenticator.username']))
133
+            ) is not None
134
+
135
+        api = ContentApi(
136
+            current_user=UserApi(None).get_one_by_email(environ['http_authenticator.username']),
137
+            show_archived=True,
138
+            show_deleted=True
139
+        )
140
+        wapi = WorkspaceApi(UserApi(None).get_one_by_email(environ['http_authenticator.username']))
141
+
142
+        norm_path = normpath(path)
143
+
144
+        is_archived = re.search(r'/\.archived/(\.history/)?(?!\.history)[^/]*(/\.)?(history|deleted|archived)?$', norm_path) is not None
145
+
146
+        is_deleted = re.search(r'/\.deleted/(\.history/)?(?!\.history)[^/]*(/\.)?(history|deleted|archived)?$', norm_path) is not None
147
+
148
+        revision_id = re.search(r'/\.history/[^/]+/(\d+)-([^/].+)$', norm_path)
149
+
150
+        if revision_id:
151
+            revision_id = revision_id.group(1)
152
+            return self.get_content_from_revision(revision_id, api) is not None
153
+
154
+        working_path = self.reduce_path(path)
155
+
156
+        content = self.get_content_from_path(working_path, api, wapi)
157
+
158
+        return content is not None \
159
+            and content.is_deleted == is_deleted \
160
+            and content.is_archived == is_archived \
161
+            and (revision_id is None or content.revision_id == revision_id)
162
+
163
+    def reduce_path(self, path):
164
+        path = re.sub(r'/\.archived', r'', path)
165
+        path = re.sub(r'/\.deleted', r'', path)
166
+        path = re.sub(r'/\.history/[^/]+/(\d+)-.+', r'/\1', path)
167
+        path = re.sub(r'/\.history/([^/]+)', r'/\1', path)
168
+        path = re.sub(r'/\.history', r'', path)
169
+
170
+        return path
171
+
172
+    def get_content_from_path(self, path, api: ContentApi, workspace_api: WorkspaceApi):
173
+        path = normpath(path)
174
+        workspace = self.get_workspace_from_path(path, workspace_api)
175
+
176
+        if basename(dirname(path)) == workspace.label:
177
+            return api.get_one_by_label_and_parent(basename(path), workspace=workspace)
178
+        else:
179
+            parent = self.get_parent_from_path(path, api, workspace_api)
180
+            if parent is not None:
181
+                return api.get_one_by_label_and_parent(basename(path), content_parent=parent)
182
+            return None
183
+
184
+    def get_content_from_revision(self, revision_id: int, api:ContentApi):
185
+        return api.get_one_revision(revision_id)
186
+
187
+    def get_parent_from_path(self, path, api: ContentApi, workspace_api: WorkspaceApi):
188
+        return self.get_content_from_path(dirname(path), api, workspace_api)
189
+
190
+    def get_workspace_from_path(self, path: str, api: WorkspaceApi):
191
+        assert path.startswith('/')
192
+
193
+        return api.get_one_by_label(path.split('/')[1])
194
+
195
+    #########################################################
196
+    # Everything that transform path
197
+    '''
198
+    def from_id_to_name(self, path):
199
+        path_ret = ''
200
+
201
+        for item_id in path.split('/'):
202
+            if item_id == '':
203
+                pass
204
+            elif path_ret == '':
205
+                path_ret += '/' + self.get_workspace({'workspace_id': item_id}).label
206
+            else:
207
+                path_ret += '/' + self.get_item({'id': item_id}).item_name
208
+
209
+        return path_ret
210
+
211
+    def from_name_to_id(self, path):
212
+        if path == '/':
213
+            return '/'
214
+        path_ret = ""
215
+        last_id = None
216
+        workspace_id = None
217
+
218
+        for item_name in path.split("/"):
219
+            if item_name == '':
220
+                pass
221
+            elif path_ret == '':
222
+                workspace = self.get_workspace({'label': item_name})
223
+                if workspace is None:
224
+                    return None
225
+
226
+                workspace_id = workspace.workspace_id
227
+                path_ret += '/' + str(workspace_id)
228
+            else:
229
+                item = self.get_item(
230
+                    {
231
+                        'parent_id': last_id,
232
+                        'item_name': item_name,
233
+                        'workspace_id': workspace_id,
234
+                        'child_revision_id': None
235
+                    }
236
+                )
237
+
238
+                if item is None:
239
+                    return None
240
+
241
+                last_id = item.id
242
+                path_ret += '/' + str(last_id)
243
+
244
+        return path_ret
245
+
246
+    #########################################################
247
+    # Everything that check things (lol) ...
248
+    def has_right(self, username, workspace_id, expected=0):
249
+        ret = self.session.query(UserRoleInWorkspace.role).filter(
250
+            UserRoleInWorkspace.workspace_id == workspace_id,
251
+            User.user_id == UserRoleInWorkspace.user_id,
252
+            User.display_name == username
253
+        ).one_or_none()
254
+
255
+        return ret is not None and role[ret.role] >= expected
256
+
257
+    def exist_revision(self, item_name, item_id):
258
+        return self.get_item({'id': item_id, 'item_name': item_name}) is not None
259
+
260
+    @staticmethod
261
+    def is_history(path):
262
+        return normpath(path).endswith('.history')
263
+
264
+    #########################################################
265
+    # Everything that goes with "delete"
266
+    def delete_item(self, element):
267
+        self.session.delete(element)
268
+        self.session.commit()
269
+
270
+    #########################################################
271
+    # Everything that goes with "add"
272
+    def add_item(self, item_name, item_type, workspace_id, parent_id=None, parent_revision_id=None,
273
+                 child_revision_id=None, item_content=None, created=None, updated=None):
274
+
275
+        item = ItemRevision(
276
+            item_name=to_unicode(item_name),
277
+            item_type=to_unicode(item_type),
278
+            item_content=item_content,
279
+            workspace_id=workspace_id,
280
+            parent_id=parent_id,
281
+            created=created,
282
+            updated=updated,
283
+            parent_revision_id=parent_revision_id,
284
+            child_revision_id=child_revision_id
285
+        )
286
+
287
+        self.session.add(item)
288
+        self.session.commit()
289
+
290
+        return item
291
+
292
+    def add_workspace(self, environ, label):
293
+        workspace = Workspace(label=label)
294
+
295
+        self.session.add(workspace)
296
+
297
+        user = self.get_user_with_name(environ['http_authenticator.username'])
298
+
299
+        user_workspace = UserRoleInWorkspace(
300
+            role='WORKSPACE_MANAGER',
301
+            workspace_id=workspace.workspace_id,
302
+            user_id=user.user_id
303
+        )
304
+
305
+        self.session.add(user_workspace)
306
+        self.session.commit()
307
+
308
+        return workspace
309
+
310
+    #########################################################
311
+    # Everything that goes with "set"
312
+    def set_workspace_label(self, workspace, label):
313
+        workspace.label = label
314
+        self.session.commit()
315
+
316
+    #########################################################
317
+    # Everything that goes with "get"
318
+    def get_all_revisions_from_item(self, item, only_id=False):
319
+        ret = []
320
+        current_item = item
321
+        while current_item is not None:
322
+            if only_id:
323
+                ret.insert(0,current_item.id)
324
+            else:
325
+                ret.insert(0,current_item)
326
+
327
+            current_item = self.get_item(
328
+                {
329
+                    'child_revision_id': current_item.id
330
+                }
331
+            )
332
+
333
+        return ret
334
+
335
+    def get_item(self, keys_dict):
336
+        query = self.session.query(ItemRevision)
337
+
338
+        for key, value in keys_dict.items():
339
+            query = query.filter(getattr(ItemRevision, key) == value)
340
+        return query.first()
341
+
342
+    def get_item_children(self, item_id):
343
+        items_result = self.session.query(ItemRevision.id).filter(
344
+            ItemRevision.parent_id == item_id,
345
+            ItemRevision.child_revision_id.is_(None)
346
+        )
347
+
348
+        ret_id = []
349
+        for item in items_result:
350
+            ret_id.append(item.id)
351
+
352
+        return ret_id
353
+
354
+    def get_workspace_id_from_path(self, path):
355
+        return int(self.get_id_from_path('/' + path.split('/')[1]))
356
+
357
+    def get_workspace_children_id(self, workspace):
358
+        items_result = self.session.query(ItemRevision.id).filter(
359
+            ItemRevision.parent_id.is_(None),
360
+            ItemRevision.workspace_id == workspace.workspace_id
361
+        )
362
+
363
+        ret = []
364
+        for item in items_result:
365
+            ret.append(item.id)
366
+
367
+        return ret
368
+
369
+    # on workspaces
370
+    def get_workspace(self, keys_dict):
371
+        query = self.session.query(Workspace)
372
+
373
+        for key, value in keys_dict.items():
374
+            query = query.filter(getattr(Workspace, key) == value)
375
+        return query.one_or_none()
376
+
377
+    def get_all_workspaces(self, only_name=False):
378
+        retlist = []
379
+        for workspace in self.session.query(Workspace).all():
380
+            if only_name:
381
+                retlist.append(workspace.label)
382
+            else:
383
+                retlist.append(workspace)
384
+
385
+        return retlist
386
+
387
+    # on users
388
+    def get_user_with_name(self, username):
389
+        return self.session.query(User).filter(
390
+            User.display_name == username
391
+        ).one_or_none()
392
+
393
+    # on path
394
+    def get_id_from_path(self, path):
395
+        path_id = self.from_name_to_id(path)
396
+
397
+        if path_id == '/':
398
+            return None
399
+        else:
400
+            return int(basename(path_id))
401
+
402
+    def get_parent_id_from_path(self, path):
403
+        return self.get_id_from_path(dirname(path))
404
+
405
+    #########################################################
406
+    # Everything that goes with "move"
407
+    def move_item(self, item, destpath):
408
+        path = normpath(destpath)
409
+
410
+        if dirname(dirname(path)) == '/':
411
+            new_parent = None
412
+        else:
413
+            new_parent = self.get_parent_id_from_path(path)
414
+
415
+        item.parent_id = new_parent
416
+        item.workspace_id = self.get_workspace_id_from_path(path)
417
+        item.item_name = basename(path)
418
+        self.session.commit()
419
+
420
+    def move_all_revisions(self, item, destpath):
421
+        path = normpath(destpath)
422
+        new_parent = self.get_parent_id_from_path(path)
423
+        new_workspace = self.get_workspace_id_from_path(destpath)
424
+
425
+        items = self.get_all_revisions_from_item(item)
426
+
427
+        for current_item in items:
428
+            current_item.parent_id = new_parent
429
+            current_item.workspace_id = new_workspace
430
+
431
+        new_name = basename(normpath(destpath))
432
+
433
+        new_item = self.add_item(
434
+            item_name=new_name,
435
+            item_type=item.item_type,
436
+            workspace_id=item.workspace_id,
437
+            parent_id=item.parent_id,
438
+            parent_revision_id=item.id,
439
+            child_revision_id=None,
440
+            item_content=item.item_content,
441
+            created=item.created,
442
+            updated=datetime.now()
443
+        )
444
+
445
+        item.child_revision_id = new_item.id
446
+
447
+        self.session.commit()
448
+
449
+    #########################################################
450
+    # Everything that goes with "copy"
451
+    def copy_item(self, item, destpath):
452
+        path = normpath(destpath)
453
+
454
+        new_parent = self.get_parent_id_from_path(path)
455
+        new_workspace = self.get_workspace_id_from_path(path)
456
+        items = self.get_all_revisions_from_item(item)
457
+
458
+        first = True
459
+        last_item = None
460
+
461
+        for current_item in items:
462
+            new_item = self.add_item(
463
+                item_name=current_item.item_name,
464
+                item_type=current_item.item_type,
465
+                workspace_id=new_workspace,
466
+                parent_id=new_parent,
467
+                parent_revision_id=None,
468
+                child_revision_id=None,
469
+                item_content=current_item.item_content,
470
+                created=current_item.created,
471
+                updated=current_item.updated
472
+            )
473
+
474
+            if not first:
475
+                last_item.child_revision_id = new_item.id
476
+                new_item.parent_revision_id = last_item.id
477
+
478
+            first = False
479
+            last_item = new_item
480
+
481
+        new_name = basename(destpath)
482
+        
483
+        new_item = self.add_item(
484
+            item_name=new_name,
485
+            item_type=item.item_type,
486
+            workspace_id=new_workspace,
487
+            parent_id=new_parent,
488
+            parent_revision_id=last_item.id,
489
+            child_revision_id=None,
490
+            item_content=item.item_content,
491
+            created=datetime.now(),
492
+            updated=datetime.now()
493
+        )
494
+
495
+        last_item.child_revision_id = new_item.id
496
+
497
+        self.session.commit()'''

+ 41 - 0
tracim/tracim/lib/webdav/sql_domain_controller.py View File

@@ -0,0 +1,41 @@
1
+# coding: utf8
2
+
3
+from tracim.lib.user import UserApi
4
+
5
+class SQLDomainController(object):
6
+
7
+    def __init__(self, presetdomain = None, presetserver = None):
8
+        self._api = UserApi(None)
9
+
10
+    def getDomainRealm(self, inputURL, environ):
11
+        """
12
+        On va récupérer le workspace de travail pour travailler sur les droits
13
+        """
14
+        return '/'
15
+
16
+    def requireAuthentication(self, realmname, environ):
17
+        return True
18
+
19
+    def isRealmUser(self, realmname, username, environ):
20
+        """
21
+        travailler dans la bdd pour vérifier si utilisateur existe
22
+        """
23
+        try:
24
+            self._api.get_one_by_email(username)
25
+            return True
26
+        except:
27
+            return False
28
+
29
+    def getRealmUserPassword(self, realmname, username, environ):
30
+        """Retourne le mdp pour l'utilisateur pour ce real"""
31
+        try:
32
+            user = self._api.get_one_by_email(username)
33
+            return 'test'
34
+            return user.password
35
+        except:
36
+            return None
37
+
38
+    def authDomainUser(self, realmname, username, password, environ):
39
+        """Vérifier que l'utilisateur est valide pour ce domaine"""
40
+        return self.isRealmUser(realmname, username, environ) and \
41
+            self._api.get_one_by_email(username).validate_password(password)

+ 88 - 0
tracim/tracim/lib/webdav/sql_model.py View File

@@ -0,0 +1,88 @@
1
+from datetime import datetime
2
+
3
+from sqlalchemy import Column
4
+from sqlalchemy import ForeignKey
5
+from sqlalchemy import Sequence
6
+from sqlalchemy.orm import deferred
7
+from sqlalchemy.types import DateTime, Integer, LargeBinary, Unicode, UnicodeText, Float
8
+from tracim.lib.webdav import Base
9
+from wsgidav.compat import to_unicode, to_bytes
10
+
11
+# ==================================================
12
+# Content
13
+# ==================================================
14
+class Workspace(Base):
15
+    __tablename__ = 'my_workspaces'
16
+
17
+    workspace_id = Column(Integer, Sequence('my_seq__workspace__id'), autoincrement=True, primary_key=True)
18
+    label = Column(Unicode(255), unique=False, nullable=False, default=to_unicode(''))
19
+
20
+    created = Column(DateTime, unique=False, nullable=False, default=datetime.now)
21
+    updated = Column(DateTime, unique=False, nullable=False, default=datetime.now)
22
+
23
+    def __repr__(self):
24
+        return "<Workspace %s : %s>" % (self.workspace_id, self.label)
25
+
26
+
27
+class User(Base):
28
+    __tablename__ = 'my_users'
29
+
30
+    user_id = Column(Integer, Sequence('my_seq__users__id'), autoincrement=True, primary_key=True)
31
+    display_name = Column(Unicode(255), unique=True, nullable=False, default=to_unicode(''))
32
+    password = Column(Unicode(255), unique=False, nullable=False, default=to_unicode(''))
33
+
34
+    def __repr__(self):
35
+        return "<User %s : %s>" % (self.user_id, self.display_name)
36
+
37
+
38
+class UserWorkspace(Base):
39
+    __tablename__ = 'my_user_workspace'
40
+
41
+    workspace_id = Column(Integer, ForeignKey('my_workspaces.workspace_id', ondelete="CASCADE"), nullable=False, primary_key=True)
42
+    user_id = Column(Integer, ForeignKey('my_users.user_id', ondelete="CASCADE"), nullable=False, primary_key=True)
43
+    role = Column(Unicode(255), unique=False, nullable=False, default=u'NOT_APPLICABLE')
44
+
45
+    def __repr__(self):
46
+        return "<Role (W:%s, U:%s) : %s" % (self.workspace_id, self.user_id, self.role)
47
+
48
+
49
+class ItemRevision(Base):
50
+    __tablename__ = 'my_items_revisions'
51
+
52
+    id = Column(Integer, Sequence('my_seq__items__id'), autoincrement=True, primary_key=True)
53
+    workspace_id = Column(Integer, ForeignKey('my_workspaces.workspace_id', ondelete="CASCADE"), nullable=False)
54
+    parent_id = Column(Integer, ForeignKey('my_items_revisions.id', ondelete="CASCADE"), nullable=True, default=None)
55
+
56
+    item_type = Column(Unicode(32), unique=False, nullable=False)
57
+    item_name = Column(Unicode(255), unique=False, nullable=False, default=to_unicode(''))
58
+    item_content = deferred(Column(LargeBinary(), unique=False, nullable=True, default=to_bytes('')))
59
+
60
+    created = Column(DateTime, unique=False, nullable=False, default=datetime.now)
61
+    updated = Column(DateTime, unique=False, nullable=False, default=datetime.now)
62
+
63
+    parent_revision_id = Column(Integer, ForeignKey('my_items_revisions.id', ondelete="CASCADE"), nullable=True, default=None)
64
+    child_revision_id = Column(Integer, ForeignKey('my_items_revisions.id', ondelete="CASCADE"), nullable=True, default=None)
65
+
66
+    def __repr__(self):
67
+        return "<Content %s : %s in %s>" % (self.id, self.item_name, self.parent_id)
68
+
69
+
70
+class Lock(Base):
71
+    __tablename__ = 'my_locks'
72
+
73
+    token = Column(UnicodeText, primary_key=True, unique=True, nullable=False)
74
+    depth = Column(Unicode(32), unique=False, nullable=False, default=to_unicode('infinity'))
75
+    root = Column(UnicodeText, unique=False, nullable=False)
76
+    type = Column(Unicode(32), unique=False, nullable=False, default=to_unicode('write'))
77
+    scope = Column(Unicode(32), unique=False, nullable=False, default=to_unicode('exclusive'))
78
+    owner = Column(UnicodeText, unique=False, nullable=False)
79
+    expire = Column(Float, unique=False, nullable=False)
80
+    principal = Column(Unicode(255), ForeignKey('my_users.display_name', ondelete="CASCADE"))
81
+    timeout = Column(Float, unique=False, nullable=False)
82
+
83
+
84
+class Url2Token(Base):
85
+    __tablename__ = 'my_url2token'
86
+
87
+    token = Column(UnicodeText, primary_key=True, unique=True, nullable=False)
88
+    path = Column(UnicodeText, primary_key=True, unique=False, nullable=False)

File diff suppressed because it is too large
+ 1087 - 0
tracim/tracim/lib/webdav/sql_resources.py


+ 3 - 0
tracim/tracim/lib/webdav/style.css View File

@@ -0,0 +1,3 @@
1
+body {
2
+    background: red
3
+}