Browse Source

Take care of email already exist in db case and fix user creation endpoint

Guénaël Muller 6 years ago
parent
commit
9ea5d70ad4

+ 4 - 0
backend/tracim_backend/exceptions.py View File

@@ -211,3 +211,7 @@ class TooShortAutocompleteString(TracimException):
211 211
 
212 212
 class PageNotFound(TracimException):
213 213
     pass
214
+
215
+
216
+class EmailAlreadyExistInDb(TracimException):
217
+    pass

+ 31 - 9
backend/tracim_backend/lib/core/user.py View File

@@ -13,6 +13,7 @@ from tracim_backend.config import CFG
13 13
 from tracim_backend.models.auth import User
14 14
 from tracim_backend.models.auth import Group
15 15
 from tracim_backend.exceptions import NoUserSetted
16
+from tracim_backend.exceptions import EmailAlreadyExistInDb
16 17
 from tracim_backend.exceptions import TooShortAutocompleteString
17 18
 from tracim_backend.exceptions import PasswordDoNotMatch
18 19
 from tracim_backend.exceptions import EmailValidationFailed
@@ -266,7 +267,33 @@ class UserApi(object):
266 267
         )
267 268
         return user
268 269
 
269
-    def _check_email(self, email: str) -> bool:
270
+    def _check_email(self, email) -> bool:
271
+        """
272
+        Check if email is completely ok to be used in user db table
273
+        """
274
+        is_email_correct = self._check_email_correctness(email)
275
+        if not is_email_correct:
276
+            raise EmailValidationFailed(
277
+                'Email given form {} is uncorrect'.format(email))  # nopep8
278
+        email_already_exist_in_db = self._check_email_already_in_db(email)
279
+        if email_already_exist_in_db:
280
+            raise EmailAlreadyExistInDb(
281
+                'Email given {} already exist, please choose something else'.format(email)  # nopep8
282
+            )
283
+        return True
284
+
285
+    def _check_email_already_in_db(self, email: str) -> bool:
286
+        """
287
+        Verify if given email does not already exist in db
288
+        """
289
+        return self._session.query(User.email).filter(User.email==email).all() != []  # nopep8
290
+
291
+    def _check_email_correctness(self, email: str) -> bool:
292
+        """
293
+           Verify if given email is correct:
294
+           - check format
295
+           - futur active check for email ? (dns based ?)
296
+           """
270 297
         # TODO - G.M - 2018-07-05 - find a better way to check email
271 298
         if not email:
272 299
             return False
@@ -288,10 +315,8 @@ class UserApi(object):
288 315
         if name is not None:
289 316
             user.display_name = name
290 317
 
291
-        if email is not None:
292
-            email_exist = self._check_email(email)
293
-            if not email_exist:
294
-                raise EmailValidationFailed('Email given form {} is uncorrect'.format(email))  # nopep8
318
+        if email is not None and email != user.email:
319
+            self._check_email(email)
295 320
             user.email = email
296 321
 
297 322
         if password is not None:
@@ -354,11 +379,8 @@ class UserApi(object):
354 379
             save_now=False
355 380
     ) -> User:
356 381
         """Previous create_user method"""
382
+        self._check_email(email)
357 383
         user = User()
358
-
359
-        email_exist = self._check_email(email)
360
-        if not email_exist:
361
-            raise EmailValidationFailed('Email given form {} is uncorrect'.format(email))  # nopep8
362 384
         user.email = email
363 385
         user.display_name = email.split('@')[0]
364 386
 

+ 11 - 9
backend/tracim_backend/models/context_models.py View File

@@ -8,10 +8,12 @@ from sqlalchemy.orm import Session
8 8
 from tracim_backend.config import CFG
9 9
 from tracim_backend.config import PreviewDim
10 10
 from tracim_backend.lib.utils.utils import get_root_frontend_url
11
+from tracim_backend.lib.utils.utils import password_generator
11 12
 from tracim_backend.lib.utils.utils import CONTENT_FRONTEND_URL_SCHEMA
12 13
 from tracim_backend.lib.utils.utils import WORKSPACE_FRONTEND_URL_SCHEMA
13 14
 from tracim_backend.models import User
14 15
 from tracim_backend.models.auth import Profile
16
+from tracim_backend.models.auth import Group
15 17
 from tracim_backend.models.data import Content
16 18
 from tracim_backend.models.data import ContentRevisionRO
17 19
 from tracim_backend.models.data import Workspace
@@ -99,17 +101,17 @@ class UserCreation(object):
99 101
     def __init__(
100 102
             self,
101 103
             email: str,
102
-            password: str,
103
-            public_name: str,
104
-            timezone: str,
105
-            profile: str,
106
-            email_notification: str,
104
+            password: str = None,
105
+            public_name: str = None,
106
+            timezone: str = None,
107
+            profile: str = None,
108
+            email_notification: bool = True,
107 109
     ) -> None:
108 110
         self.email = email
109
-        self.password = password
110
-        self.public_name = public_name
111
-        self.timezone = timezone
112
-        self.profile = profile
111
+        self.password = password or password_generator()
112
+        self.public_name = public_name or None
113
+        self.timezone = timezone or ''
114
+        self.profile = profile or Group.TIM_USER_GROUPNAME
113 115
         self.email_notification = email_notification
114 116
 
115 117
 

+ 362 - 3
backend/tracim_backend/tests/functional/test_user.py View File

@@ -4,6 +4,7 @@ Tests for /api/v2/users subpath endpoints.
4 4
 """
5 5
 from time import sleep
6 6
 import pytest
7
+import requests
7 8
 import transaction
8 9
 
9 10
 from tracim_backend import models
@@ -2479,9 +2480,8 @@ class TestUserWorkspaceEndpoint(FunctionalTest):
2479 2480
 
2480 2481
 
2481 2482
 class TestUserEndpoint(FunctionalTest):
2482
-    # -*- coding: utf-8 -*-
2483 2483
     """
2484
-    Tests for GET /api/v2/users/{user_id}
2484
+    Tests for GET/POST /api/v2/users/{user_id}
2485 2485
     """
2486 2486
     fixtures = [BaseFixture]
2487 2487
 
@@ -2634,11 +2634,308 @@ class TestUserEndpoint(FunctionalTest):
2634 2634
             status=403
2635 2635
         )
2636 2636
 
2637
+    def test_api__create_user__ok_200__full_admin(self):
2638
+        self.testapp.authorization = (
2639
+            'Basic',
2640
+            (
2641
+                'admin@admin.admin',
2642
+                'admin@admin.admin'
2643
+            )
2644
+        )
2645
+        params = {
2646
+            'email': 'test@test.test',
2647
+            'password': 'mysuperpassword',
2648
+            'profile': 'users',
2649
+            'timezone': 'Europe/Paris',
2650
+            'public_name': 'test user',
2651
+            'email_notification': False,
2652
+        }
2653
+        res = self.testapp.post_json(
2654
+            '/api/v2/users',
2655
+            status=200,
2656
+            params=params,
2657
+        )
2658
+        res = res.json_body
2659
+        assert res['user_id']
2660
+        user_id = res['user_id']
2661
+        assert res['created']
2662
+        assert res['is_active'] is True
2663
+        assert res['profile'] == 'users'
2664
+        assert res['email'] == 'test@test.test'
2665
+        assert res['public_name'] == 'test user'
2666
+        assert res['timezone'] == 'Europe/Paris'
2667
+
2668
+        dbsession = get_tm_session(self.session_factory, transaction.manager)
2669
+        admin = dbsession.query(models.User) \
2670
+            .filter(models.User.email == 'admin@admin.admin') \
2671
+            .one()
2672
+        uapi = UserApi(
2673
+            current_user=admin,
2674
+            session=dbsession,
2675
+            config=self.app_config,
2676
+        )
2677
+        user = uapi.get_one(user_id)
2678
+        assert user.email == 'test@test.test'
2679
+        assert user.validate_password('mysuperpassword')
2680
+
2681
+    def test_api__create_user__ok_200__limited_admin(self):
2682
+        self.testapp.authorization = (
2683
+            'Basic',
2684
+            (
2685
+                'admin@admin.admin',
2686
+                'admin@admin.admin'
2687
+            )
2688
+        )
2689
+        params = {
2690
+            'email': 'test@test.test',
2691
+            'email_notification': False,
2692
+        }
2693
+        res = self.testapp.post_json(
2694
+            '/api/v2/users',
2695
+            status=200,
2696
+            params=params,
2697
+        )
2698
+        res = res.json_body
2699
+        assert res['user_id']
2700
+        user_id = res['user_id']
2701
+        assert res['created']
2702
+        assert res['is_active'] is True
2703
+        assert res['profile'] == 'users'
2704
+        assert res['email'] == 'test@test.test'
2705
+        assert res['public_name'] == 'test'
2706
+        assert res['timezone'] == ''
2707
+
2708
+        dbsession = get_tm_session(self.session_factory, transaction.manager)
2709
+        admin = dbsession.query(models.User) \
2710
+            .filter(models.User.email == 'admin@admin.admin') \
2711
+            .one()
2712
+        uapi = UserApi(
2713
+            current_user=admin,
2714
+            session=dbsession,
2715
+            config=self.app_config,
2716
+        )
2717
+        user = uapi.get_one(user_id)
2718
+        assert user.email == 'test@test.test'
2719
+        assert user.password
2720
+
2721
+    def test_api__create_user__err_400__email_already_in_db(self):
2722
+        dbsession = get_tm_session(self.session_factory, transaction.manager)
2723
+        admin = dbsession.query(models.User) \
2724
+            .filter(models.User.email == 'admin@admin.admin') \
2725
+            .one()
2726
+        uapi = UserApi(
2727
+            current_user=admin,
2728
+            session=dbsession,
2729
+            config=self.app_config,
2730
+        )
2731
+        gapi = GroupApi(
2732
+            current_user=admin,
2733
+            session=dbsession,
2734
+            config=self.app_config,
2735
+        )
2736
+        groups = [gapi.get_one_with_name('users')]
2737
+        test_user = uapi.create_user(
2738
+            email='test@test.test',
2739
+            password='pass',
2740
+            name='bob',
2741
+            groups=groups,
2742
+            timezone='Europe/Paris',
2743
+            do_save=True,
2744
+            do_notify=False,
2745
+        )
2746
+        uapi.save(test_user)
2747
+        transaction.commit()
2748
+        self.testapp.authorization = (
2749
+            'Basic',
2750
+            (
2751
+                'admin@admin.admin',
2752
+                'admin@admin.admin'
2753
+            )
2754
+        )
2755
+        params = {
2756
+            'email': 'test@test.test',
2757
+            'password': 'mysuperpassword',
2758
+            'profile': 'users',
2759
+            'timezone': 'Europe/Paris',
2760
+            'public_name': 'test user',
2761
+            'email_notification': False,
2762
+        }
2763
+        res = self.testapp.post_json(
2764
+            '/api/v2/users',
2765
+            status=400,
2766
+            params=params,
2767
+        )
2768
+
2769
+    def test_api__create_user__err_403__other_user(self):
2770
+        dbsession = get_tm_session(self.session_factory, transaction.manager)
2771
+        admin = dbsession.query(models.User) \
2772
+            .filter(models.User.email == 'admin@admin.admin') \
2773
+            .one()
2774
+        uapi = UserApi(
2775
+            current_user=admin,
2776
+            session=dbsession,
2777
+            config=self.app_config,
2778
+        )
2779
+        gapi = GroupApi(
2780
+            current_user=admin,
2781
+            session=dbsession,
2782
+            config=self.app_config,
2783
+        )
2784
+        groups = [gapi.get_one_with_name('users')]
2785
+        test_user = uapi.create_user(
2786
+            email='test@test.test',
2787
+            password='pass',
2788
+            name='bob',
2789
+            groups=groups,
2790
+            timezone='Europe/Paris',
2791
+            do_save=True,
2792
+            do_notify=False,
2793
+        )
2794
+        uapi.save(test_user)
2795
+        transaction.commit()
2796
+        self.testapp.authorization = (
2797
+            'Basic',
2798
+            (
2799
+                'test@test.test',
2800
+                'pass',
2801
+            )
2802
+        )
2803
+        params = {
2804
+            'email': 'test2@test2.test2',
2805
+            'password': 'mysuperpassword',
2806
+            'profile': 'users',
2807
+            'timezone': 'Europe/Paris',
2808
+            'public_name': 'test user',
2809
+            'email_notification': False,
2810
+        }
2811
+        res = self.testapp.post_json(
2812
+            '/api/v2/users',
2813
+            status=403,
2814
+            params=params,
2815
+        )
2816
+
2817
+
2818
+class TestUserWithNotificationEndpoint(FunctionalTest):
2819
+    """
2820
+    Tests for POST /api/v2/users/{user_id}
2821
+    """
2822
+    config_section = 'functional_test_with_mail_test_sync'
2823
+
2824
+    def test_api__create_user__ok_200__full_admin_with_notif(self):
2825
+        requests.delete('http://127.0.0.1:8025/api/v1/messages')
2826
+        self.testapp.authorization = (
2827
+            'Basic',
2828
+            (
2829
+                'admin@admin.admin',
2830
+                'admin@admin.admin'
2831
+            )
2832
+        )
2833
+        params = {
2834
+            'email': 'test@test.test',
2835
+            'password': 'mysuperpassword',
2836
+            'profile': 'users',
2837
+            'timezone': 'Europe/Paris',
2838
+            'public_name': 'test user',
2839
+            'email_notification': True,
2840
+        }
2841
+        res = self.testapp.post_json(
2842
+            '/api/v2/users',
2843
+            status=200,
2844
+            params=params,
2845
+        )
2846
+        res = res.json_body
2847
+        assert res['user_id']
2848
+        user_id = res['user_id']
2849
+        assert res['created']
2850
+        assert res['is_active'] is True
2851
+        assert res['profile'] == 'users'
2852
+        assert res['email'] == 'test@test.test'
2853
+        assert res['public_name'] == 'test user'
2854
+        assert res['timezone'] == 'Europe/Paris'
2855
+
2856
+        dbsession = get_tm_session(self.session_factory, transaction.manager)
2857
+        admin = dbsession.query(models.User) \
2858
+            .filter(models.User.email == 'admin@admin.admin') \
2859
+            .one()
2860
+        uapi = UserApi(
2861
+            current_user=admin,
2862
+            session=dbsession,
2863
+            config=self.app_config,
2864
+        )
2865
+        user = uapi.get_one(user_id)
2866
+        assert user.email == 'test@test.test'
2867
+        assert user.validate_password('mysuperpassword')
2868
+
2869
+        # check mail received
2870
+        response = requests.get('http://127.0.0.1:8025/api/v1/messages')
2871
+        response = response.json()
2872
+        assert len(response) == 1
2873
+        headers = response[0]['Content']['Headers']
2874
+        assert headers['From'][0] == 'Tracim Notifications <test_user_from+0@localhost>'  # nopep8
2875
+        assert headers['To'][0] == 'test user <test@test.test>'
2876
+        assert headers['Subject'][0] == '[TRACIM] Created account'
2877
+
2878
+        # TODO - G.M - 2018-08-02 - Place cleanup outside of the test
2879
+        requests.delete('http://127.0.0.1:8025/api/v1/messages')
2880
+
2881
+    def test_api__create_user__ok_200__limited_admin_with_notif(self):
2882
+        requests.delete('http://127.0.0.1:8025/api/v1/messages')
2883
+        self.testapp.authorization = (
2884
+            'Basic',
2885
+            (
2886
+                'admin@admin.admin',
2887
+                'admin@admin.admin'
2888
+            )
2889
+        )
2890
+        params = {
2891
+            'email': 'test@test.test',
2892
+            'email_notification': True,
2893
+        }
2894
+        res = self.testapp.post_json(
2895
+            '/api/v2/users',
2896
+            status=200,
2897
+            params=params,
2898
+        )
2899
+        res = res.json_body
2900
+        assert res['user_id']
2901
+        user_id = res['user_id']
2902
+        assert res['created']
2903
+        assert res['is_active'] is True
2904
+        assert res['profile'] == 'users'
2905
+        assert res['email'] == 'test@test.test'
2906
+        assert res['public_name'] == 'test'
2907
+        assert res['timezone'] == ''
2908
+
2909
+        dbsession = get_tm_session(self.session_factory, transaction.manager)
2910
+        admin = dbsession.query(models.User) \
2911
+            .filter(models.User.email == 'admin@admin.admin') \
2912
+            .one()
2913
+        uapi = UserApi(
2914
+            current_user=admin,
2915
+            session=dbsession,
2916
+            config=self.app_config,
2917
+        )
2918
+        user = uapi.get_one(user_id)
2919
+        assert user.email == 'test@test.test'
2920
+        assert user.password
2921
+
2922
+        # check mail received
2923
+        response = requests.get('http://127.0.0.1:8025/api/v1/messages')
2924
+        response = response.json()
2925
+        assert len(response) == 1
2926
+        headers = response[0]['Content']['Headers']
2927
+        assert headers['From'][0] == 'Tracim Notifications <test_user_from+0@localhost>'  # nopep8
2928
+        assert headers['To'][0] == 'test <test@test.test>'
2929
+        assert headers['Subject'][0] == '[TRACIM] Created account'
2930
+
2931
+        # TODO - G.M - 2018-08-02 - Place cleanup outside of the test
2932
+        requests.delete('http://127.0.0.1:8025/api/v1/messages')
2933
+
2637 2934
 
2638 2935
 class TestUsersEndpoint(FunctionalTest):
2639 2936
     # -*- coding: utf-8 -*-
2640 2937
     """
2641
-    Tests for GET /api/v2/users/{user_id}
2938
+    Tests for GET /api/v2/users
2642 2939
     """
2643 2940
     fixtures = [BaseFixture]
2644 2941
 
@@ -3088,6 +3385,68 @@ class TestSetEmailEndpoint(FunctionalTest):
3088 3385
         res = res.json_body
3089 3386
         assert res['email'] == 'mysuperemail@email.fr'
3090 3387
 
3388
+    def test_api__set_user_email__err_400__admin_same_email(self):
3389
+        dbsession = get_tm_session(self.session_factory, transaction.manager)
3390
+        admin = dbsession.query(models.User) \
3391
+            .filter(models.User.email == 'admin@admin.admin') \
3392
+            .one()
3393
+        uapi = UserApi(
3394
+            current_user=admin,
3395
+            session=dbsession,
3396
+            config=self.app_config,
3397
+        )
3398
+        gapi = GroupApi(
3399
+            current_user=admin,
3400
+            session=dbsession,
3401
+            config=self.app_config,
3402
+        )
3403
+        groups = [gapi.get_one_with_name('users')]
3404
+        test_user = uapi.create_user(
3405
+            email='test@test.test',
3406
+            password='pass',
3407
+            name='bob',
3408
+            groups=groups,
3409
+            timezone='Europe/Paris',
3410
+            do_save=True,
3411
+            do_notify=False,
3412
+        )
3413
+        uapi.save(test_user)
3414
+        transaction.commit()
3415
+        user_id = int(test_user.user_id)
3416
+
3417
+        self.testapp.authorization = (
3418
+            'Basic',
3419
+            (
3420
+                'admin@admin.admin',
3421
+                'admin@admin.admin'
3422
+            )
3423
+        )
3424
+        # check before
3425
+        res = self.testapp.get(
3426
+            '/api/v2/users/{}'.format(user_id),
3427
+            status=200
3428
+        )
3429
+        res = res.json_body
3430
+        assert res['email'] == 'test@test.test'
3431
+
3432
+        # Set password
3433
+        params = {
3434
+            'email': 'admin@admin.admin',
3435
+            'loggedin_user_password': 'admin@admin.admin',
3436
+        }
3437
+        self.testapp.put_json(
3438
+            '/api/v2/users/{}/email'.format(user_id),
3439
+            params=params,
3440
+            status=400,
3441
+        )
3442
+        # Check After
3443
+        res = self.testapp.get(
3444
+            '/api/v2/users/{}'.format(user_id),
3445
+            status=200
3446
+        )
3447
+        res = res.json_body
3448
+        assert res['email'] == 'test@test.test'
3449
+
3091 3450
     def test_api__set_user_email__err_403__admin_wrong_password(self):
3092 3451
         dbsession = get_tm_session(self.session_factory, transaction.manager)
3093 3452
         admin = dbsession.query(models.User) \

+ 33 - 6
backend/tracim_backend/views/core_api/schemas.py View File

@@ -7,6 +7,7 @@ from marshmallow.validate import Range
7 7
 
8 8
 from tracim_backend.lib.utils.utils import DATETIME_FORMAT
9 9
 from tracim_backend.models.auth import Profile
10
+from tracim_backend.models.auth import Group
10 11
 from tracim_backend.models.contents import GlobalStatus
11 12
 from tracim_backend.models.contents import CONTENT_STATUS
12 13
 from tracim_backend.models.contents import CONTENT_TYPES
@@ -154,12 +155,38 @@ class UserProfileSchema(marshmallow.Schema):
154 155
         return UserProfile(**data)
155 156
 
156 157
 
157
-class UserCreationSchema(
158
-    SetEmailSchema,
159
-    SetPasswordSchema,
160
-    UserInfosSchema,
161
-    UserProfileSchema
162
-):
158
+class UserCreationSchema(marshmallow.Schema):
159
+    email = marshmallow.fields.Email(
160
+        required=True,
161
+        example='suri.cate@algoo.fr'
162
+    )
163
+    password = marshmallow.fields.String(
164
+        example='8QLa$<w',
165
+        required=False,
166
+    )
167
+    profile = marshmallow.fields.String(
168
+        attribute='profile',
169
+        validate=OneOf(Profile._NAME),
170
+        example='managers',
171
+        required=False,
172
+        default=Group.TIM_USER_GROUPNAME
173
+    )
174
+    timezone = marshmallow.fields.String(
175
+        example="Europe/Paris",
176
+        required=False,
177
+        default=''
178
+    )
179
+    public_name = marshmallow.fields.String(
180
+        example='Suri Cate',
181
+        required=False,
182
+        default=None,
183
+    )
184
+    email_notification = marshmallow.fields.Bool(
185
+        example=True,
186
+        required=False,
187
+        default=True,
188
+    )
189
+
163 190
     @post_load
164 191
     def create_user(self, data):
165 192
         return UserCreation(**data)

+ 4 - 1
backend/tracim_backend/views/core_api/user_controller.py View File

@@ -1,4 +1,5 @@
1 1
 from pyramid.config import Configurator
2
+from tracim_backend.lib.utils.utils import password_generator
2 3
 
3 4
 try:  # Python 3.5+
4 5
     from http import HTTPStatus
@@ -16,6 +17,7 @@ from tracim_backend.views.controllers import Controller
16 17
 from tracim_backend.lib.utils.authorization import require_same_user_or_profile
17 18
 from tracim_backend.lib.utils.authorization import require_profile
18 19
 from tracim_backend.exceptions import WrongUserPassword
20
+from tracim_backend.exceptions import EmailAlreadyExistInDb
19 21
 from tracim_backend.exceptions import PasswordDoNotMatch
20 22
 from tracim_backend.views.core_api.schemas import UserSchema
21 23
 from tracim_backend.views.core_api.schemas import AutocompleteQuerySchema
@@ -120,6 +122,7 @@ class UserController(Controller):
120 122
 
121 123
     @hapic.with_api_doc(tags=[SWAGGER_TAG__USER_ENDPOINTS])
122 124
     @hapic.handle_exception(WrongUserPassword, HTTPStatus.FORBIDDEN)
125
+    @hapic.handle_exception(EmailAlreadyExistInDb, HTTPStatus.BAD_REQUEST)
123 126
     @require_same_user_or_profile(Group.TIM_ADMIN)
124 127
     @hapic.input_body(SetEmailSchema())
125 128
     @hapic.input_path(UserIdPathSchema())
@@ -192,8 +195,8 @@ class UserController(Controller):
192 195
         return uapi.get_user_with_context(user)
193 196
 
194 197
     @hapic.with_api_doc(tags=[SWAGGER_TAG__USER_ENDPOINTS])
198
+    @hapic.handle_exception(EmailAlreadyExistInDb, HTTPStatus.BAD_REQUEST)
195 199
     @require_profile(Group.TIM_ADMIN)
196
-    @hapic.input_path(UserIdPathSchema())
197 200
     @hapic.input_body(UserCreationSchema())
198 201
     @hapic.output_body(UserSchema())
199 202
     def create_user(self, context, request: TracimRequest, hapic_data=None):

+ 1 - 1
backend/tracim_backend/views/core_api/workspace_controller.py View File

@@ -215,7 +215,7 @@ class WorkspaceController(Controller):
215 215
                 # notification for creation
216 216
                 user = uapi.create_user(
217 217
                     email=hapic_data.body.user_email_or_public_name,
218
-                    password= password_generator(),
218
+                    password=password_generator(),
219 219
                     do_notify=True
220 220
                 )  # nopep8
221 221
                 newly_created = True