浏览代码

Merge pull request #50 from buxx/dev/_/41/radicale

Tracim 9 年前
父节点
当前提交
38ce4ea2d9

+ 1 - 0
.travis.yml 查看文件

34
 
34
 
35
 after_success:
35
 after_success:
36
 - coveralls
36
 - coveralls
37
+

+ 1 - 0
install/requirements.txt 查看文件

52
 unicode-slugify==0.1.3
52
 unicode-slugify==0.1.3
53
 Radicale==1.1.1
53
 Radicale==1.1.1
54
 icalendar==3.10
54
 icalendar==3.10
55
+caldav==0.4.0

+ 9 - 2
tracim/test.ini 查看文件

9
 # email_to = you@yourdomain.com
9
 # email_to = you@yourdomain.com
10
 smtp_server = localhost
10
 smtp_server = localhost
11
 error_email_from = turbogears@localhost
11
 error_email_from = turbogears@localhost
12
+radicale.server.port = 15232
13
+radicale.client.port = 15232
14
+radicale.server.filesystem.folder = /tmp/tracim_tests_radicale_fs
12
 
15
 
13
 [server:main]
16
 [server:main]
14
 use = egg:gearbox#wsgiref
17
 use = egg:gearbox#wsgiref
16
 port = 8080
19
 port = 8080
17
 
20
 
18
 [app:main]
21
 [app:main]
19
-sqlalchemy.url = postgresql://postgres:dummy@127.0.0.1:5432/tracim_test?client_encoding=utf8
22
+sqlalchemy.url = mysql+oursql://tracim:tracim@localhost/tracim_test
20
 use = config:development.ini
23
 use = config:development.ini
21
 
24
 
22
 [app:main_without_authn]
25
 [app:main_without_authn]
24
 skip_authentication = True
27
 skip_authentication = True
25
 
28
 
26
 [app:ldap]
29
 [app:ldap]
27
-sqlalchemy.url = postgresql://postgres:dummy@127.0.0.1:5432/tracim_test?client_encoding=utf8
30
+sqlalchemy.url = mysql+oursql://tracim:tracim@localhost/tracim_test
28
 auth_type = ldap
31
 auth_type = ldap
29
 ldap_url = ldap://localhost:3333
32
 ldap_url = ldap://localhost:3333
30
 ldap_base_dn = dc=directory,dc=fsf,dc=org
33
 ldap_base_dn = dc=directory,dc=fsf,dc=org
36
 ldap_group_enabled = False
39
 ldap_group_enabled = False
37
 use = config:development.ini
40
 use = config:development.ini
38
 
41
 
42
+[app:radicale]
43
+sqlalchemy.url = mysql+oursql://tracim:tracim@localhost/tracim_test
44
+use = config:development.ini
45
+
39
 # Add additional test specific configuration options as necessary.
46
 # Add additional test specific configuration options as necessary.

+ 3 - 1
tracim/tracim/config/app_cfg.py 查看文件

217
         ]
217
         ]
218
 
218
 
219
         self.RADICALE_SERVER_HOST = tg.config.get('radicale.server.host', '0.0.0.0')
219
         self.RADICALE_SERVER_HOST = tg.config.get('radicale.server.host', '0.0.0.0')
220
-        self.RADICALE_SERVER_PORT = tg.config.get('radicale.server.port', 5232)
220
+        self.RADICALE_SERVER_PORT = int(
221
+            tg.config.get('radicale.server.port', 5232)
222
+        )
221
         # Note: Other parameters needed to work in SSL (cert file, etc)
223
         # Note: Other parameters needed to work in SSL (cert file, etc)
222
         self.RADICALE_SERVER_SSL = asbool(tg.config.get('radicale.server.ssl', False))
224
         self.RADICALE_SERVER_SSL = asbool(tg.config.get('radicale.server.ssl', False))
223
         self.RADICALE_SERVER_FILE_SYSTEM_FOLDER = tg.config.get(
225
         self.RADICALE_SERVER_FILE_SYSTEM_FOLDER = tg.config.get(

+ 3 - 3
tracim/tracim/fixtures/ldap.py 查看文件

46
         },
46
         },
47
         {
47
         {
48
             'objectclass': ['account', 'top'],
48
             'objectclass': ['account', 'top'],
49
-            'dn': 'cn=lawrence-not-real-email@fsf.org,ou=people,dc=directory,dc=fsf,dc=org',
49
+            'dn': 'cn=lawrence-not-real-email@fsf.local,ou=people,dc=directory,dc=fsf,dc=org',
50
             'attributes': {
50
             'attributes': {
51
-                'uid': 'lawrence-not-real-email@fsf.org',
51
+                'uid': 'lawrence-not-real-email@fsf.local',
52
                 'userPassword': 'foobarbaz',
52
                 'userPassword': 'foobarbaz',
53
-                'mail': 'lawrence-not-real-email@fsf.org',
53
+                'mail': 'lawrence-not-real-email@fsf.local',
54
                 'pubname': 'Lawrence Lessig',
54
                 'pubname': 'Lawrence Lessig',
55
             }
55
             }
56
         },
56
         },

+ 8 - 1
tracim/tracim/fixtures/users_and_groups.py 查看文件

43
 
43
 
44
         lawrence = model.User()
44
         lawrence = model.User()
45
         lawrence.display_name = 'Lawrence L.'
45
         lawrence.display_name = 'Lawrence L.'
46
-        lawrence.email = 'lawrence-not-real-email@fsf.org'
46
+        lawrence.email = 'lawrence-not-real-email@fsf.local'
47
         lawrence.password = 'foobarbaz'
47
         lawrence.password = 'foobarbaz'
48
         self._session.add(lawrence)
48
         self._session.add(lawrence)
49
         g2.users.append(lawrence)
49
         g2.users.append(lawrence)
50
+
51
+        bob = model.User()
52
+        bob.display_name = 'Bob i.'
53
+        bob.email = 'bob@fsf.local'
54
+        bob.password = 'foobarbaz'
55
+        self._session.add(bob)
56
+        g2.users.append(bob)

+ 49 - 3
tracim/tracim/lib/calendar.py 查看文件

24
 CALENDAR_TYPE_USER = UserCalendar
24
 CALENDAR_TYPE_USER = UserCalendar
25
 CALENDAR_TYPE_WORKSPACE = WorkspaceCalendar
25
 CALENDAR_TYPE_WORKSPACE = WorkspaceCalendar
26
 
26
 
27
+CALENDAR_BASE_URL_TEMPLATE = '{proto}://{domain}:{port}'
27
 CALENDAR_USER_URL_TEMPLATE = \
28
 CALENDAR_USER_URL_TEMPLATE = \
28
-    '{proto}://{domain}:{port}/user/{id}.ics#{slug}'
29
+    CALENDAR_BASE_URL_TEMPLATE + '/user/{id}.ics{extra}/'
29
 CALENDAR_WORKSPACE_URL_TEMPLATE = \
30
 CALENDAR_WORKSPACE_URL_TEMPLATE = \
30
-    '{proto}://{domain}:{port}/workspace/{id}.ics#{slug}'
31
+    CALENDAR_BASE_URL_TEMPLATE + '/workspace/{id}.ics{extra}/'
31
 
32
 
32
 
33
 
33
 class CalendarManager(object):
34
 class CalendarManager(object):
35
+    @staticmethod
36
+    def get_base_url():
37
+        from tracim.config.app_cfg import CFG
38
+        cfg = CFG.get_instance()
39
+
40
+        return CALENDAR_BASE_URL_TEMPLATE.format(
41
+            proto='https' if cfg.RADICALE_CLIENT_SSL else 'http',
42
+            domain=cfg.RADICALE_CLIENT_HOST or '127.0.0.1',
43
+            port=str(cfg.RADICALE_CLIENT_PORT)
44
+        )
45
+
46
+    @staticmethod
47
+    def get_user_calendar_url(user_id: int, extra: str=''):
48
+        from tracim.config.app_cfg import CFG
49
+        cfg = CFG.get_instance()
50
+
51
+        return CALENDAR_USER_URL_TEMPLATE.format(
52
+            proto='https' if cfg.RADICALE_CLIENT_SSL else 'http',
53
+            domain=cfg.RADICALE_CLIENT_HOST or '127.0.0.1',
54
+            port=str(cfg.RADICALE_CLIENT_PORT),
55
+            id=str(user_id),
56
+            extra=extra,
57
+        )
58
+
59
+    @staticmethod
60
+    def get_workspace_calendar_url(workspace_id: int, extra: str=''):
61
+        from tracim.config.app_cfg import CFG
62
+        cfg = CFG.get_instance()
63
+
64
+        return CALENDAR_WORKSPACE_URL_TEMPLATE.format(
65
+            proto='https' if cfg.RADICALE_CLIENT_SSL else 'http',
66
+            domain=cfg.RADICALE_CLIENT_HOST or '127.0.0.1',
67
+            port=str(cfg.RADICALE_CLIENT_PORT),
68
+            id=str(workspace_id),
69
+            extra=extra,
70
+        )
71
+
34
     def __init__(self, user: User):
72
     def __init__(self, user: User):
35
         self._user = user
73
         self._user = user
36
 
74
 
223
             content: Content,
261
             content: Content,
224
             event: iCalendarEvent,
262
             event: iCalendarEvent,
225
             event_name: str,
263
             event_name: str,
226
-    ) -> Content:
264
+    ) -> None:
265
+        """
266
+        Populate Content content instance from iCalendarEvent event attributes.
267
+        :param content: content to populate
268
+        :param event: event with data to insert in content
269
+        :param event_name: Event name (ID) like
270
+        20160602T083511Z-18100-1001-1-71_Bastien-20160602T083516Z.ics
271
+        :return: given content
272
+        """
227
         content.label = event.get('summary')
273
         content.label = event.get('summary')
228
         content.description = event.get('description')
274
         content.description = event.get('description')
229
         content.properties = {
275
         content.properties = {

+ 81 - 7
tracim/tracim/lib/daemons.py 查看文件

1
 import threading
1
 import threading
2
 from wsgiref.simple_server import make_server
2
 from wsgiref.simple_server import make_server
3
-
4
 import signal
3
 import signal
5
 
4
 
5
+import collections
6
+import transaction
7
+
6
 from radicale import Application as RadicaleApplication
8
 from radicale import Application as RadicaleApplication
7
-from radicale import HTTPServer as RadicaleHTTPServer
8
-from radicale import HTTPSServer as RadicaleHTTPSServer
9
+from radicale import HTTPServer as BaseRadicaleHTTPServer
10
+from radicale import HTTPSServer as BaseRadicaleHTTPSServer
9
 from radicale import RequestHandler as RadicaleRequestHandler
11
 from radicale import RequestHandler as RadicaleRequestHandler
10
 from radicale import config as radicale_config
12
 from radicale import config as radicale_config
11
-from tg import TGApp
12
 
13
 
13
 from tracim.lib.base import logger
14
 from tracim.lib.base import logger
14
 from tracim.lib.exceptions import AlreadyRunningDaemon
15
 from tracim.lib.exceptions import AlreadyRunningDaemon
60
 
61
 
61
     def stop_all(self, *args, **kwargs) -> None:
62
     def stop_all(self, *args, **kwargs) -> None:
62
         """
63
         """
63
-        Stop all started daemons and w<ait for them.
64
+        Stop all started daemons and wait for them.
64
         """
65
         """
65
         logger.info(self, 'Stopping all daemons')
66
         logger.info(self, 'Stopping all daemons')
66
         for name, daemon in self._running_daemons.items():
67
         for name, daemon in self._running_daemons.items():
73
 
74
 
74
         self._running_daemons = {}
75
         self._running_daemons = {}
75
 
76
 
77
+    def execute_in_thread(self, thread_name, callback):
78
+        self._running_daemons[thread_name].append_thread_callback(callback)
79
+
80
+
81
+class TracimSocketServerMixin(object):
82
+    """
83
+    Mixin to use with socketserver.BaseServer who add _after_serve_actions
84
+    method executed after end of server execution.
85
+    """
86
+    def __init__(self, *args, **kwargs):
87
+        super().__init__(*args, **kwargs)
88
+        self._daemon_execute_callbacks = []
89
+
90
+    def append_thread_callback(self, callback: collections.Callable) -> None:
91
+        """
92
+        Add callback to self._daemon_execute_callbacks. See service_actions
93
+        function to their usages.
94
+        :param callback: callback to execute in daemon
95
+        """
96
+        self._daemon_execute_callbacks.append(callback)
97
+
98
+    def serve_forever(self, *args, **kwargs):
99
+        super().serve_forever(*args, **kwargs)
100
+        # After serving (in case of stop) do following:
101
+        self._after_serve_actions()
102
+
103
+    def _after_serve_actions(self):
104
+        """
105
+        Override (and call super if needed) to execute actions when server
106
+        finish it's job.
107
+        """
108
+        pass
109
+
110
+    def service_actions(self):
111
+        if len(self._daemon_execute_callbacks):
112
+            try:
113
+                while True:
114
+                    self._daemon_execute_callbacks.pop()()
115
+            except IndexError:
116
+                pass  # Finished to iter
117
+
76
 
118
 
77
 class Daemon(threading.Thread):
119
 class Daemon(threading.Thread):
78
     """
120
     """
79
     Thread who contains daemon. You must implement start and stop methods to
121
     Thread who contains daemon. You must implement start and stop methods to
80
     manage daemon life correctly.
122
     manage daemon life correctly.
81
     """
123
     """
82
-    def run(self):
124
+    def run(self) -> None:
125
+        """
126
+        Place here code who have to be executed in Daemon.
127
+        """
83
         raise NotImplementedError()
128
         raise NotImplementedError()
84
 
129
 
85
-    def stop(self):
130
+    def stop(self) -> None:
131
+        """
132
+        Place here code who stop your daemon
133
+        """
86
         raise NotImplementedError()
134
         raise NotImplementedError()
87
 
135
 
136
+    def append_thread_callback(self, callback: collections.Callable) -> None:
137
+        """
138
+        Place here the logic who permit to execute a callback in your daemon.
139
+        To get an exemple of that, take a look at
140
+        socketserver.BaseServer#service_actions  and how we use it in
141
+        tracim.lib.daemons.TracimSocketServerMixin#service_actions .
142
+        :param callback: callback to execute in your thread.
143
+        """
144
+        raise NotImplementedError()
145
+
146
+
147
+class RadicaleHTTPSServer(TracimSocketServerMixin, BaseRadicaleHTTPSServer):
148
+    pass
149
+
150
+
151
+class RadicaleHTTPServer(TracimSocketServerMixin, BaseRadicaleHTTPServer):
152
+    pass
153
+
88
 
154
 
89
 class RadicaleDaemon(Daemon):
155
 class RadicaleDaemon(Daemon):
90
     def __init__(self, *args, **kwargs):
156
     def __init__(self, *args, **kwargs):
132
             RadicaleHTTPSServer if cfg.RADICALE_SERVER_SSL else RadicaleHTTPServer,
198
             RadicaleHTTPSServer if cfg.RADICALE_SERVER_SSL else RadicaleHTTPServer,
133
             RadicaleRequestHandler
199
             RadicaleRequestHandler
134
         )
200
         )
201
+
202
+    def append_thread_callback(self, callback: collections.Callable) -> None:
203
+        """
204
+        Give the callback to running server through
205
+        tracim.lib.daemons.TracimSocketServerMixin#append_thread_callback
206
+        :param callback: callback to execute in daemon
207
+        """
208
+        self._server.append_thread_callback(callback)

+ 1 - 1
tracim/tracim/model/auth.py 查看文件

159
             domain=cfg.RADICALE_CLIENT_HOST or request.domain,
159
             domain=cfg.RADICALE_CLIENT_HOST or request.domain,
160
             port=cfg.RADICALE_CLIENT_PORT,
160
             port=cfg.RADICALE_CLIENT_PORT,
161
             id=self.user_id,
161
             id=self.user_id,
162
-            slug=slugify(self.get_display_name(
162
+            extra='#' + slugify(self.get_display_name(
163
                 remove_email_part=True
163
                 remove_email_part=True
164
             ), only_ascii=True)
164
             ), only_ascii=True)
165
         )
165
         )

+ 1 - 1
tracim/tracim/model/data.py 查看文件

78
             domain=cfg.RADICALE_CLIENT_HOST or tg.request.domain,
78
             domain=cfg.RADICALE_CLIENT_HOST or tg.request.domain,
79
             port=cfg.RADICALE_CLIENT_PORT,
79
             port=cfg.RADICALE_CLIENT_PORT,
80
             id=self.workspace_id,
80
             id=self.workspace_id,
81
-            slug=slugify(self.label)
81
+            extra='#' + slugify(self.label),
82
         )
82
         )
83
 
83
 
84
     def get_user_role(self, user: User) -> int:
84
     def get_user_role(self, user: User) -> int:

+ 50 - 1
tracim/tracim/tests/__init__.py 查看文件

3
 import argparse
3
 import argparse
4
 import os
4
 import os
5
 import time
5
 import time
6
+
7
+import shutil
6
 from os import getcwd
8
 from os import getcwd
7
 
9
 
8
 import ldap3
10
 import ldap3
11
 from gearbox.commands.setup_app import SetupAppCommand
13
 from gearbox.commands.setup_app import SetupAppCommand
12
 from ldap_test import LdapServer
14
 from ldap_test import LdapServer
13
 from nose.tools import eq_
15
 from nose.tools import eq_
16
+from nose.tools import make_decorator
14
 from nose.tools import ok_
17
 from nose.tools import ok_
15
 from paste.deploy import loadapp
18
 from paste.deploy import loadapp
16
 from sqlalchemy.engine import reflection
19
 from sqlalchemy.engine import reflection
28
 
31
 
29
 from tracim.fixtures import FixturesLoader
32
 from tracim.fixtures import FixturesLoader
30
 from tracim.fixtures.users_and_groups import Base as BaseFixture
33
 from tracim.fixtures.users_and_groups import Base as BaseFixture
34
+from tracim.fixtures.users_and_groups import Test as TestFixture
35
+from tracim.config.app_cfg import daemons
31
 from tracim.lib.base import logger
36
 from tracim.lib.base import logger
32
 from tracim.lib.content import ContentApi
37
 from tracim.lib.content import ContentApi
33
 from tracim.lib.workspace import WorkspaceApi
38
 from tracim.lib.workspace import WorkspaceApi
34
 from tracim.model import DBSession, Content
39
 from tracim.model import DBSession, Content
35
-from tracim.model.data import Workspace, ContentType, ContentRevisionRO
40
+from tracim.model.data import Workspace
41
+from tracim.model.data import ContentType
36
 
42
 
37
 __all__ = ['setup_app', 'setup_db', 'teardown_db', 'TestController']
43
 __all__ = ['setup_app', 'setup_db', 'teardown_db', 'TestController']
38
 
44
 
239
     def tearDown(self):
245
     def tearDown(self):
240
         """Tear down test fixture for each functional test method."""
246
         """Tear down test fixture for each functional test method."""
241
         DBSession.close()
247
         DBSession.close()
248
+        daemons.execute_in_thread('radicale', lambda: transaction.commit())
242
         teardown_db()
249
         teardown_db()
243
 
250
 
244
 
251
 
344
                                                parent=folder,
351
                                                parent=folder,
345
                                                owner=user)
352
                                                owner=user)
346
         return thread
353
         return thread
354
+
355
+
356
+class TestCalendar(TestController):
357
+    fixtures = [BaseFixture, TestFixture]
358
+    application_under_test = 'radicale'
359
+
360
+    def setUp(self):
361
+        super().setUp()
362
+        self._clear_radicale_fs()
363
+
364
+    @staticmethod
365
+    def _clear_radicale_fs():
366
+        radicale_fs_path = config.radicale.server.filesystem.folder
367
+        try:
368
+            files = os.listdir(radicale_fs_path)
369
+            for file in files:
370
+                shutil.rmtree('{0}/{1}'.format(radicale_fs_path, file))
371
+        except FileNotFoundError:
372
+            pass  # Dir not exists yet, no need to clear it
373
+
374
+
375
+def not_raises(*exceptions):
376
+    """
377
+    Test must not raise one of expected exceptions to pass.
378
+    """
379
+    valid = ' or '.join([e.__name__ for e in exceptions])
380
+
381
+    def decorate(func):
382
+        name = func.__name__
383
+
384
+        def newfunc(*arg, **kw):
385
+            try:
386
+                func(*arg, **kw)
387
+            except exceptions as exc:
388
+                message = '{0} raise {1} exception and should be not'\
389
+                    .format(name, exc)
390
+                raise AssertionError(message)
391
+            except:
392
+                raise
393
+        newfunc = make_decorator(func)(newfunc)
394
+        return newfunc
395
+    return decorate

+ 205 - 0
tracim/tracim/tests/functional/test_calendar.py 查看文件

1
+import time
2
+
3
+import caldav
4
+import transaction
5
+from caldav.lib.error import AuthorizationError
6
+from nose.tools import eq_
7
+from nose.tools import ok_
8
+from nose.tools import raises
9
+import requests
10
+from requests.exceptions import ConnectionError
11
+from sqlalchemy.orm.exc import NoResultFound
12
+
13
+from tracim.config.app_cfg import daemons
14
+from tracim.lib.calendar import CalendarManager
15
+from tracim.lib.workspace import WorkspaceApi
16
+from tracim.model import DBSession
17
+from tracim.tests import TestCalendar as BaseTestCalendar
18
+from tracim.tests import not_raises
19
+from tracim.model.auth import User
20
+from tracim.model.data import Content
21
+
22
+
23
+class TestCalendar(BaseTestCalendar):
24
+    def setUp(self):
25
+        super().setUp()
26
+        time.sleep(3)  # TODO - 20160606 - Bastien: sleep to wait ...
27
+        # ... radicale daemon started. We should lock something somewhere !
28
+
29
+    def test_func__radicale_connectivity__ok__nominal_case(self):
30
+        radicale_base_url = CalendarManager.get_base_url()
31
+
32
+        try:
33
+            response = requests.get(radicale_base_url)
34
+            eq_(response.status_code, 401, 'Radicale http response should be '
35
+                                           '401, its {0}'
36
+                .format(response.status_code))
37
+        except ConnectionError as exc:
38
+            ok_(False, 'Unable to contact radicale on HTTP: {0}'.format(exc))
39
+
40
+    @not_raises(AuthorizationError)
41
+    def test_func__radicale_auth__ok__as_lawrence(self):
42
+        radicale_base_url = CalendarManager.get_base_url()
43
+        client = caldav.DAVClient(
44
+            radicale_base_url,
45
+            username='lawrence-not-real-email@fsf.local',
46
+            password='foobarbaz'
47
+        )
48
+        client.propfind()
49
+
50
+    @raises(AuthorizationError)
51
+    def test_func__radicale_auth__fail__as_john_doe(self):
52
+        radicale_base_url = CalendarManager.get_base_url()
53
+        client = caldav.DAVClient(
54
+            radicale_base_url,
55
+            username='john.doe@foo.local',
56
+            password='nopasswd'
57
+        )
58
+        client.propfind()
59
+
60
+    @not_raises(AuthorizationError)
61
+    def test_func__rights_read_user_calendar__ok__as_lawrence(self):
62
+        radicale_base_url = CalendarManager.get_base_url()
63
+        client = caldav.DAVClient(
64
+            radicale_base_url,
65
+            username='lawrence-not-real-email@fsf.local',
66
+            password='foobarbaz'
67
+        )
68
+        user = DBSession.query(User).filter(
69
+            User.email == 'lawrence-not-real-email@fsf.local'
70
+        ).one()
71
+        user_calendar_url = CalendarManager.get_user_calendar_url(user.user_id)
72
+        caldav.Calendar(
73
+            parent=client,
74
+            client=client,
75
+            url=user_calendar_url
76
+        ).events()
77
+
78
+    @raises(AuthorizationError)
79
+    def test_func__rights_read_user_calendar__fail__as_john_doe(self):
80
+        radicale_base_url = CalendarManager.get_base_url()
81
+        client = caldav.DAVClient(
82
+            radicale_base_url,
83
+            username='john.doe@foo.local',
84
+            password='nopasswd'
85
+        )
86
+        other_user = DBSession.query(User).filter(
87
+            User.email == 'admin@admin.admin'
88
+        ).one()
89
+        user_calendar_url = CalendarManager.get_user_calendar_url(other_user.user_id)
90
+        caldav.Calendar(
91
+            parent=client,
92
+            client=client,
93
+            url=user_calendar_url
94
+        ).events()
95
+
96
+    @not_raises(AuthorizationError)
97
+    def test_func__rights_read_workspace_calendar__ok__as_owner(self):
98
+        lawrence = DBSession.query(User).filter(
99
+            User.email == 'lawrence-not-real-email@fsf.local'
100
+        ).one()
101
+        workspace = WorkspaceApi(lawrence).create_workspace(
102
+            'workspace_1',
103
+            save_now=False
104
+        )
105
+        workspace.calendar_enabled = True
106
+        DBSession.flush()
107
+
108
+        workspace_calendar_url = CalendarManager.get_workspace_calendar_url(
109
+            workspace.workspace_id
110
+        )
111
+
112
+        transaction.commit()
113
+
114
+        radicale_base_url = CalendarManager.get_base_url()
115
+        client = caldav.DAVClient(
116
+            radicale_base_url,
117
+            username='lawrence-not-real-email@fsf.local',
118
+            password='foobarbaz'
119
+        )
120
+        caldav.Calendar(
121
+            parent=client,
122
+            client=client,
123
+            url=workspace_calendar_url
124
+        ).events()
125
+
126
+    @raises(AuthorizationError)
127
+    def test_func__rights_read_workspace_calendar__fail__as_unauthorized(self):
128
+        lawrence = DBSession.query(User).filter(
129
+            User.email == 'lawrence-not-real-email@fsf.local'
130
+        ).one()
131
+        workspace = WorkspaceApi(lawrence).create_workspace(
132
+            'workspace_1',
133
+            save_now=False
134
+        )
135
+        workspace.calendar_enabled = True
136
+        DBSession.flush()
137
+
138
+        workspace_calendar_url = CalendarManager.get_workspace_calendar_url(
139
+            workspace.workspace_id
140
+        )
141
+
142
+        transaction.commit()
143
+
144
+        radicale_base_url = CalendarManager.get_base_url()
145
+        client = caldav.DAVClient(
146
+            radicale_base_url,
147
+            username='bob@fsf.local',
148
+            password='foobarbaz'
149
+        )
150
+        caldav.Calendar(
151
+            parent=client,
152
+            client=client,
153
+            url=workspace_calendar_url
154
+        ).events()
155
+
156
+    def test_func__event_create__ok__nominal_case(self):
157
+        lawrence = DBSession.query(User).filter(
158
+            User.email == 'lawrence-not-real-email@fsf.local'
159
+        ).one()
160
+        radicale_base_url = CalendarManager.get_base_url()
161
+        client = caldav.DAVClient(
162
+            radicale_base_url,
163
+            username='lawrence-not-real-email@fsf.local',
164
+            password='foobarbaz'
165
+        )
166
+        user_calendar_url = CalendarManager.get_user_calendar_url(
167
+            lawrence.user_id
168
+        )
169
+        user_calendar = caldav.Calendar(
170
+            parent=client,
171
+            client=client,
172
+            url=user_calendar_url
173
+        )
174
+
175
+        event_ics = """BEGIN:VCALENDAR
176
+VERSION:2.0
177
+PRODID:-//Example Corp.//CalDAV Client//EN
178
+BEGIN:VEVENT
179
+UID:1234567890
180
+DTSTAMP:20100510T182145Z
181
+DTSTART:20100512T170000Z
182
+DTEND:20100512T180000Z
183
+SUMMARY:This is an event
184
+LOCATION:Here
185
+END:VEVENT
186
+END:VCALENDAR
187
+"""
188
+        user_calendar.add_event(event_ics)
189
+        user_calendar.save()
190
+
191
+        daemons.execute_in_thread('radicale', lambda: transaction.commit())
192
+        # TODO - 20160606 - Bastien: lock should be better here ?
193
+        time.sleep(3)  # Wait for be sure transaction commited in daemon
194
+        transaction.commit()
195
+        try:
196
+            event = DBSession.query(Content).filter(
197
+                Content.label == 'This is an event'
198
+            ).one()
199
+        except NoResultFound:
200
+            ok_(False, 'Content record should exist for '
201
+                       '"This is an event" label')
202
+
203
+        eq_(event.properties['location'], 'Here')
204
+        eq_(event.properties['start'], '2010-05-12 18:00:00+0000')
205
+        eq_(event.properties['end'], '2010-05-12 17:00:00+0000')

+ 4 - 4
tracim/tracim/tests/functional/test_ldap_authentication.py 查看文件

44
 
44
 
45
     def test_ldap_attributes_sync(self):
45
     def test_ldap_attributes_sync(self):
46
         # User is already know in database
46
         # User is already know in database
47
-        eq_(1, DBSession.query(User).filter(User.email == 'lawrence-not-real-email@fsf.org').count())
47
+        eq_(1, DBSession.query(User).filter(User.email == 'lawrence-not-real-email@fsf.local').count())
48
 
48
 
49
         # His display name is Lawrence L.
49
         # His display name is Lawrence L.
50
-        lawrence = DBSession.query(User).filter(User.email == 'lawrence-not-real-email@fsf.org').one()
50
+        lawrence = DBSession.query(User).filter(User.email == 'lawrence-not-real-email@fsf.local').one()
51
         eq_('Lawrence L.', lawrence.display_name)
51
         eq_('Lawrence L.', lawrence.display_name)
52
 
52
 
53
         # After connexion with LDAP, his display_name is updated (see ldap fixtures)
53
         # After connexion with LDAP, his display_name is updated (see ldap fixtures)
54
-        self._connect_user('lawrence-not-real-email@fsf.org', 'foobarbaz')
55
-        lawrence = DBSession.query(User).filter(User.email == 'lawrence-not-real-email@fsf.org').one()
54
+        self._connect_user('lawrence-not-real-email@fsf.local', 'foobarbaz')
55
+        lawrence = DBSession.query(User).filter(User.email == 'lawrence-not-real-email@fsf.local').one()
56
         eq_('Lawrence Lessig', lawrence.display_name)
56
         eq_('Lawrence Lessig', lawrence.display_name)

+ 7 - 7
tracim/tracim/tests/functional/test_ldap_restrictions.py 查看文件

21
         Password change is disabled
21
         Password change is disabled
22
         :return:
22
         :return:
23
         """
23
         """
24
-        lawrence = DBSession.query(User).filter(User.email == 'lawrence-not-real-email@fsf.org').one()
25
-        self._connect_user('lawrence-not-real-email@fsf.org', 'foobarbaz')
24
+        lawrence = DBSession.query(User).filter(User.email == 'lawrence-not-real-email@fsf.local').one()
25
+        self._connect_user('lawrence-not-real-email@fsf.local', 'foobarbaz')
26
         home = self.app.get('/home/',)
26
         home = self.app.get('/home/',)
27
 
27
 
28
         # HTML button is not here
28
         # HTML button is not here
45
         Some fields (email) are not editable on user interface: they are managed by LDAP
45
         Some fields (email) are not editable on user interface: they are managed by LDAP
46
         :return:
46
         :return:
47
         """
47
         """
48
-        lawrence = DBSession.query(User).filter(User.email == 'lawrence-not-real-email@fsf.org').one()
49
-        self._connect_user('lawrence-not-real-email@fsf.org', 'foobarbaz')
48
+        lawrence = DBSession.query(User).filter(User.email == 'lawrence-not-real-email@fsf.local').one()
49
+        self._connect_user('lawrence-not-real-email@fsf.local', 'foobarbaz')
50
 
50
 
51
         edit = self.app.get('/user/5/edit')
51
         edit = self.app.get('/user/5/edit')
52
 
52
 
60
         ok_('readonly' not in name_input.attrs)
60
         ok_('readonly' not in name_input.attrs)
61
 
61
 
62
         # If we force edit of user, "email" field will be not updated
62
         # If we force edit of user, "email" field will be not updated
63
-        eq_(lawrence.email, 'lawrence-not-real-email@fsf.org')
63
+        eq_(lawrence.email, 'lawrence-not-real-email@fsf.local')
64
         eq_(lawrence.display_name, 'Lawrence L.')
64
         eq_(lawrence.display_name, 'Lawrence L.')
65
 
65
 
66
         try_post_user = self.app.post(
66
         try_post_user = self.app.post(
73
 
73
 
74
         eq_(try_post_user.status_code, 302, "Code should be 302, but is %d" % try_post_user.status_code)
74
         eq_(try_post_user.status_code, 302, "Code should be 302, but is %d" % try_post_user.status_code)
75
 
75
 
76
-        lawrence = DBSession.query(User).filter(User.email == 'lawrence-not-real-email@fsf.org').one()
77
-        eq_(lawrence.email, 'lawrence-not-real-email@fsf.org', "email should be unmodified")
76
+        lawrence = DBSession.query(User).filter(User.email == 'lawrence-not-real-email@fsf.local').one()
77
+        eq_(lawrence.email, 'lawrence-not-real-email@fsf.local', "email should be unmodified")
78
         eq_(lawrence.display_name, 'Lawrence Lessig YEAH', "Name should be updated")
78
         eq_(lawrence.display_name, 'Lawrence Lessig YEAH', "Name should be updated")

+ 1 - 2
tracim/tracim/tests/library/test_helpers.py 查看文件

17
 
17
 
18
 from tracim.tests import TestStandard
18
 from tracim.tests import TestStandard
19
 
19
 
20
-config = CFG.get_instance()
21
-
22
 
20
 
23
 class TestHelpers(TestStandard):
21
 class TestHelpers(TestStandard):
24
 
22
 
25
     def test_is_item_still_editable(self):
23
     def test_is_item_still_editable(self):
24
+        config = CFG.get_instance()
26
         item = DictLikeClass()
25
         item = DictLikeClass()
27
 
26
 
28
         config.DATA_UPDATE_ALLOWED_DURATION = 0
27
         config.DATA_UPDATE_ALLOWED_DURATION = 0

+ 4 - 4
tracim/tracim/tests/library/test_ldap_without_ldap_groups.py 查看文件

68
         LDAP don't manage groups here: We must retrieve internal groups of tested user
68
         LDAP don't manage groups here: We must retrieve internal groups of tested user
69
         :return:
69
         :return:
70
         """
70
         """
71
-        lawrence = DBSession.query(User).filter(User.email == 'lawrence-not-real-email@fsf.org').one()
71
+        lawrence = DBSession.query(User).filter(User.email == 'lawrence-not-real-email@fsf.local').one()
72
         managers = DBSession.query(Group).filter(Group.group_name == 'managers').one()
72
         managers = DBSession.query(Group).filter(Group.group_name == 'managers').one()
73
         lawrence_identity = {'user': lawrence}
73
         lawrence_identity = {'user': lawrence}
74
 
74
 
75
         # Lawrence is in fixtures: he is in managers group
75
         # Lawrence is in fixtures: he is in managers group
76
-        self._check_db_user('lawrence-not-real-email@fsf.org', 1)
76
+        self._check_db_user('lawrence-not-real-email@fsf.local', 1)
77
         assert lawrence in managers.users
77
         assert lawrence in managers.users
78
         assert False is ini_conf_to_bool(config.get('ldap_group_enabled', False))
78
         assert False is ini_conf_to_bool(config.get('ldap_group_enabled', False))
79
         assert ['managers'] == config.get('sa_auth').authmetadata.get_groups(
79
         assert ['managers'] == config.get('sa_auth').authmetadata.get_groups(
95
         LDAP don't manage groups here: We must retrieve internal groups permission of tested user
95
         LDAP don't manage groups here: We must retrieve internal groups permission of tested user
96
         :return:
96
         :return:
97
         """
97
         """
98
-        lawrence = DBSession.query(User).filter(User.email == 'lawrence-not-real-email@fsf.org').one()
98
+        lawrence = DBSession.query(User).filter(User.email == 'lawrence-not-real-email@fsf.local').one()
99
         managers = DBSession.query(Group).filter(Group.group_name == 'managers').one()
99
         managers = DBSession.query(Group).filter(Group.group_name == 'managers').one()
100
         lawrence_identity = {'user': lawrence}
100
         lawrence_identity = {'user': lawrence}
101
 
101
 
102
         # Lawrence is in fixtures: he is in managers group
102
         # Lawrence is in fixtures: he is in managers group
103
-        self._check_db_user('lawrence-not-real-email@fsf.org', 1)
103
+        self._check_db_user('lawrence-not-real-email@fsf.local', 1)
104
         assert lawrence in managers.users
104
         assert lawrence in managers.users
105
         assert False is ini_conf_to_bool(config.get('ldap_group_enabled', False))
105
         assert False is ini_conf_to_bool(config.get('ldap_group_enabled', False))
106
 
106