Browse Source

LDAP: Disable feature on externalized user (+fields)

Bastien Sevajol 9 years ago
parent
commit
e1e8efa8a4

+ 2 - 0
tracim/tracim/controllers/__init__.py View File

@@ -126,6 +126,7 @@ class TIMRestController(RestController, BaseController):
126 126
         :param kw:
127 127
         :return:
128 128
         """
129
+        super()._before(*args, **kw)
129 130
         TIMRestPathContextSetup.current_user()
130 131
 
131 132
 
@@ -468,6 +469,7 @@ class StandardController(BaseController):
468 469
         :param kw:
469 470
         :return:
470 471
         """
472
+        super()._before(*args, **kw)
471 473
         TIMRestPathContextSetup.current_user()
472 474
 
473 475
     @classmethod

+ 32 - 1
tracim/tracim/controllers/user.py View File

@@ -1,4 +1,5 @@
1 1
 # -*- coding: utf-8 -*-
2
+from webob.exc import HTTPForbidden
2 3
 
3 4
 from tracim import model  as pm
4 5
 
@@ -93,11 +94,17 @@ class UserPasswordRestController(TIMRestController):
93 94
 
94 95
     @tg.expose('tracim.templates.user_password_edit_me')
95 96
     def edit(self):
97
+        if not tg.config.get('auth_is_internal'):
98
+            raise HTTPForbidden()
99
+
96 100
         dictified_user = Context(CTX.USER).toDict(tmpl_context.current_user, 'user')
97 101
         return DictLikeClass(result = dictified_user)
98 102
 
99 103
     @tg.expose()
100 104
     def put(self, current_password, new_password1, new_password2):
105
+        if not tg.config.get('auth_is_internal'):
106
+            raise HTTPForbidden()
107
+
101 108
         # FIXME - Allow only self password or operation for managers
102 109
         current_user = tmpl_context.current_user
103 110
 
@@ -130,6 +137,10 @@ class UserRestController(TIMRestController):
130 137
     password = UserPasswordRestController()
131 138
     workspaces = UserWorkspaceRestController()
132 139
 
140
+    def __init__(self, *args, **kwargs):
141
+        super().__init__(*args, **kwargs)
142
+        self._auth_instance = tg.config.get('auth_instance')
143
+
133 144
     @classmethod
134 145
     def current_item_id_key_in_context(cls):
135 146
         return 'user_id'
@@ -174,9 +185,29 @@ class UserRestController(TIMRestController):
174 185
         current_user = tmpl_context.current_user
175 186
         assert user_id==current_user.user_id
176 187
 
188
+        # Only keep allowed field update
189
+        updated_fields = self._clean_update_fields({
190
+            'name': name,
191
+            'email': email
192
+        })
193
+
177 194
         api = UserApi(tmpl_context.current_user)
178
-        api.update(current_user, name, email, True)
195
+        api.update(current_user, do_save=True, **updated_fields)
179 196
         tg.flash(_('profile updated.'))
180 197
         if next_url:
181 198
             tg.redirect(tg.url(next_url))
182 199
         tg.redirect(self.url())
200
+
201
+    def _clean_update_fields(self, fields: dict):
202
+        """
203
+        Remove field key who are not allowed to be updated
204
+        :param fields: dict with field name key to be cleaned
205
+        :rtype fields: dict
206
+        :return:
207
+        """
208
+        if not self._auth_instance.is_internal:
209
+            externalized_fields_names = self._auth_instance.managed_fields
210
+            for externalized_field_name in externalized_fields_names:
211
+                if externalized_field_name in fields:
212
+                    fields.pop(externalized_field_name)
213
+        return fields

+ 15 - 1
tracim/tracim/lib/auth/base.py View File

@@ -29,16 +29,30 @@ class Auth:
29 29
     """ Auth strategy must be named: .ini config will use this name in auth_type parameter """
30 30
     name = NotImplemented
31 31
 
32
+    """ When Auth is not internal, user account management are disabled (forgotten password, etc.) """
33
+    _internal = NotImplemented
34
+
32 35
     def __init__(self, config):
33 36
         self._config = config
37
+        self._managed_fields = []
38
+
39
+    @property
40
+    def is_internal(self):
41
+        return bool(self._internal)
34 42
 
35
-    def wrap_config(self):
43
+    @property
44
+    def managed_fields(self):
45
+        return self._managed_fields
46
+
47
+    def feed_config(self):
36 48
         """
37 49
         Fill config with auth needed. You must overload with whild implementation.
38 50
         :return:
39 51
         """
40 52
         self._config['sa_auth'] = _get_clean_sa_auth(self._config)
41 53
 
54
+        self._config['auth_is_internal'] = self.is_internal
55
+
42 56
         # override this if you would like to provide a different who plugin for
43 57
         # managing login and logout of your application
44 58
         self._config['sa_auth'].form_plugin = None

+ 3 - 3
tracim/tracim/lib/auth/internal.py View File

@@ -9,14 +9,14 @@ from tracim.model import DBSession, User
9 9
 class InternalAuth(Auth):
10 10
 
11 11
     name = 'internal'
12
+    _internal = True
12 13
 
13
-    def wrap_config(self):
14
+    def feed_config(self):
14 15
         """
15 16
         Fill config with internal (database) auth information.
16 17
         :return:
17 18
         """
18
-        super().wrap_config()
19
-
19
+        super().feed_config()
20 20
         self._config['sa_auth'].user_class = User
21 21
         self._config['auth_backend'] = 'sqlalchemy'
22 22
         self._config['sa_auth'].dbsession = DBSession

+ 11 - 2
tracim/tracim/lib/auth/ldap.py View File

@@ -16,6 +16,7 @@ class LDAPAuth(Auth):
16 16
 
17 17
     """
18 18
     name = 'ldap'
19
+    _internal = False
19 20
 
20 21
     def __init__(self, config):
21 22
         super().__init__(config)
@@ -23,9 +24,10 @@ class LDAPAuth(Auth):
23 24
         self.ldap_user_provider = self._get_ldap_user_provider()
24 25
         if ini_conf_to_bool(self._config.get('ldap_group_enabled', False)):
25 26
             self.ldap_groups_provider = self._get_ldap_groups_provider()
27
+        self._managed_fields = self.ldap_user_provider.local_fields
26 28
 
27
-    def wrap_config(self):
28
-        super().wrap_config()
29
+    def feed_config(self):
30
+        super().feed_config()
29 31
         self._config['auth_backend'] = 'ldapauth'
30 32
         self._config['sa_auth'].authenticators = [('ldapauth', self.ldap_auth)]
31 33
 
@@ -155,3 +157,10 @@ class LDAPAttributesPlugin(BaseLDAPAttributesPlugin):
155 157
         super().add_metadata(environ, identity)
156 158
         # TODO - B.S. - 20160212: identity contains now som information from LDAP what we can save in local database
157 159
         identity[self.name] = self._user_api.get_one_by_email(identity.get('repoze.who.userid'))
160
+
161
+    @property
162
+    def local_fields(self):
163
+        """
164
+        :return: list of ldap side managed field names
165
+        """
166
+        return list(self._attributes_map.values())

+ 10 - 12
tracim/tracim/lib/auth/wrapper.py View File

@@ -6,21 +6,19 @@ from tracim.lib.auth.ldap import LDAPAuth
6 6
 class AuthConfigWrapper:
7 7
 
8 8
     # TODO: Dynamic load, like plugins ?
9
-    AUTH_WRAPPERS = (InternalAuth, LDAPAuth)
10
-    EXTERNAL_AUTHS = (LDAPAuth,)
9
+    AUTH_CLASSES = (InternalAuth, LDAPAuth)
11 10
 
12 11
     @classmethod
13 12
     def wrap(cls, config):
14
-        wrapper_class = cls._get_wrapper_class(config)
15
-        wrapper = wrapper_class(config)
16
-        wrapper.wrap_config()
17
-        return config
13
+        auth_class = cls._get_auth_class(config)
14
+        config['auth_instance'] = auth_class(config)
15
+        config['auth_instance'].feed_config()
18 16
 
19 17
     @classmethod
20
-    def _get_wrapper_class(cls, config):
21
-        for wrapper_class in cls.AUTH_WRAPPERS:
22
-            if wrapper_class.name is NotImplemented:
23
-                raise Exception("\"name\" attribute of %s is required" % str(wrapper_class))
24
-            if config.get('auth_type') == wrapper_class.name:
25
-                return wrapper_class
18
+    def _get_auth_class(cls, config):
19
+        for auth_class in cls.AUTH_CLASSES:
20
+            if auth_class.name is NotImplemented:
21
+                raise Exception("\"name\" attribute of %s is required" % str(auth_class))
22
+            if config.get('auth_type') == auth_class.name:
23
+                return auth_class
26 24
         raise Exception("No auth config wrapper found for \"%s\" auth_type config" % config.get('auth_type'))

+ 4 - 0
tracim/tracim/lib/base.py View File

@@ -31,6 +31,10 @@ class BaseController(TGController):
31 31
         tmpl_context.identity = request.identity
32 32
         return TGController.__call__(self, environ, context)
33 33
 
34
+    def _before(self, *args, **kwargs):
35
+        tmpl_context.auth_is_internal = tg.config.get('auth_is_internal')
36
+        tmpl_context.auth_instance = tg.config.get('auth_instance')
37
+
34 38
     @property
35 39
     def parent_controller(self):
36 40
         possible_parent = None

+ 6 - 0
tracim/tracim/lib/helpers.py View File

@@ -210,3 +210,9 @@ def ini_conf_to_bool(value):
210 210
     if value in ('False', 'false', '0', 'off', 'no'):
211 211
         return False
212 212
     return bool(value)
213
+
214
+
215
+def is_user_externalized_field(field_name):
216
+    if not tg.config.get('auth_instance').is_internal:
217
+        return field_name in tg.config.get('auth_instance').managed_fields
218
+    return False

+ 7 - 3
tracim/tracim/lib/user.py View File

@@ -27,9 +27,13 @@ class UserApi(object):
27 27
     def get_one_by_email(self, email: str):
28 28
         return self._base_query().filter(User.email==email).one()
29 29
 
30
-    def update(self, user: User, name: str, email: str, do_save):
31
-        user.display_name = name
32
-        user.email = email
30
+    def update(self, user: User, name: str=None, email: str=None, do_save=True):
31
+        if name is not None:
32
+            user.display_name = name
33
+
34
+        if email is not None:
35
+            user.email = email
36
+
33 37
         if do_save:
34 38
             self.save(user)
35 39
 

+ 10 - 2
tracim/tracim/templates/user_toolbars.mak View File

@@ -15,7 +15,11 @@
15 15
                 endif
16 16
             %>
17 17
         <a title="${_('Edit current user')}" class="btn btn-default" data-toggle="modal" data-target="#user-edit-modal-dialog" data-remote="${user_edit_url}" >${ICON.FA('fa-edit t-less-visible')} ${_('Edit user')}</a>
18
-            <a title="${_('Change password')}" class="btn btn-default" data-toggle="modal" data-target="#user-edit-password-modal-dialog" data-remote="${user_password_edit_url}" >${ICON.FA('fa-key t-less-visible')} ${_('Password')}</a>
18
+
19
+            % if auth_is_internal:
20
+                <a title="${_('Change password')}" class="btn btn-default" data-toggle="modal" data-target="#user-edit-password-modal-dialog" data-remote="${user_password_edit_url}" >${ICON.FA('fa-key t-less-visible')} ${_('Password')}</a>
21
+            % endif
22
+
19 23
         </div>
20 24
         <p></p>
21 25
         % if current_user.profile.id>2 and current_user.id!=user.id:
@@ -40,7 +44,11 @@
40 44
                 user_password_edit_url = tg.url('/user/{}/password/edit'.format(current_user.id))
41 45
             %>
42 46
             <a title="${_('Edit my profile')}" class="btn btn-default" data-toggle="modal" data-target="#user-edit-modal-dialog" data-remote="${user_edit_url}" >${ICON.FA('fa-edit t-less-visible')} ${_('Edit my profile')}</a>
43
-            <a title="${_('Change password')}" class="btn btn-default" data-toggle="modal" data-target="#user-edit-password-modal-dialog" data-remote="${user_password_edit_url}" >${ICON.FA('fa-key t-less-visible')} ${_('Password')}</a>
47
+
48
+            % if auth_is_internal:
49
+                <a title="${_('Change password')}" class="btn btn-default" data-toggle="modal" data-target="#user-edit-password-modal-dialog" data-remote="${user_password_edit_url}" >${ICON.FA('fa-key t-less-visible')} ${_('Password')}</a>
50
+            % endif
51
+
44 52
         </div>
45 53
         <p></p>
46 54
         <h3 class="t-spacer-above" style="margin-top: 1em;">

+ 2 - 2
tracim/tracim/templates/user_workspace_forms.mak View File

@@ -98,11 +98,11 @@
98 98
         <div class="modal-body">
99 99
             <div class="form-group">
100 100
                 <label for="name">${_('Name')}</label>
101
-                <input name="name" type="text" class="form-control" id="name" placeholder="${_('Name')}" value="${user.name}">
101
+                <input name="name" type="text" class="form-control" id="name" placeholder="${_('Name')}" value="${user.name}" ${'readonly="readonly"' if h.is_user_externalized_field('name') else ''}>
102 102
             </div>
103 103
             <div class="form-group">
104 104
                 <label for="email">${_('Email')}</label>
105
-                <input name="email" type="text" class="form-control" id="email" placeholder="${_('Name')}" value="${user.email}">
105
+                <input name="email" type="text" class="form-control" id="email" placeholder="${_('Name')}" value="${user.email}" ${'readonly="readonly"' if h.is_user_externalized_field('email') else ''}>
106 106
             </div>
107 107
         </div>
108 108
         <div class="modal-footer">