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
         :param kw:
126
         :param kw:
127
         :return:
127
         :return:
128
         """
128
         """
129
+        super()._before(*args, **kw)
129
         TIMRestPathContextSetup.current_user()
130
         TIMRestPathContextSetup.current_user()
130
 
131
 
131
 
132
 
468
         :param kw:
469
         :param kw:
469
         :return:
470
         :return:
470
         """
471
         """
472
+        super()._before(*args, **kw)
471
         TIMRestPathContextSetup.current_user()
473
         TIMRestPathContextSetup.current_user()
472
 
474
 
473
     @classmethod
475
     @classmethod

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

1
 # -*- coding: utf-8 -*-
1
 # -*- coding: utf-8 -*-
2
+from webob.exc import HTTPForbidden
2
 
3
 
3
 from tracim import model  as pm
4
 from tracim import model  as pm
4
 
5
 
93
 
94
 
94
     @tg.expose('tracim.templates.user_password_edit_me')
95
     @tg.expose('tracim.templates.user_password_edit_me')
95
     def edit(self):
96
     def edit(self):
97
+        if not tg.config.get('auth_is_internal'):
98
+            raise HTTPForbidden()
99
+
96
         dictified_user = Context(CTX.USER).toDict(tmpl_context.current_user, 'user')
100
         dictified_user = Context(CTX.USER).toDict(tmpl_context.current_user, 'user')
97
         return DictLikeClass(result = dictified_user)
101
         return DictLikeClass(result = dictified_user)
98
 
102
 
99
     @tg.expose()
103
     @tg.expose()
100
     def put(self, current_password, new_password1, new_password2):
104
     def put(self, current_password, new_password1, new_password2):
105
+        if not tg.config.get('auth_is_internal'):
106
+            raise HTTPForbidden()
107
+
101
         # FIXME - Allow only self password or operation for managers
108
         # FIXME - Allow only self password or operation for managers
102
         current_user = tmpl_context.current_user
109
         current_user = tmpl_context.current_user
103
 
110
 
130
     password = UserPasswordRestController()
137
     password = UserPasswordRestController()
131
     workspaces = UserWorkspaceRestController()
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
     @classmethod
144
     @classmethod
134
     def current_item_id_key_in_context(cls):
145
     def current_item_id_key_in_context(cls):
135
         return 'user_id'
146
         return 'user_id'
174
         current_user = tmpl_context.current_user
185
         current_user = tmpl_context.current_user
175
         assert user_id==current_user.user_id
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
         api = UserApi(tmpl_context.current_user)
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
         tg.flash(_('profile updated.'))
196
         tg.flash(_('profile updated.'))
180
         if next_url:
197
         if next_url:
181
             tg.redirect(tg.url(next_url))
198
             tg.redirect(tg.url(next_url))
182
         tg.redirect(self.url())
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
     """ Auth strategy must be named: .ini config will use this name in auth_type parameter """
29
     """ Auth strategy must be named: .ini config will use this name in auth_type parameter """
30
     name = NotImplemented
30
     name = NotImplemented
31
 
31
 
32
+    """ When Auth is not internal, user account management are disabled (forgotten password, etc.) """
33
+    _internal = NotImplemented
34
+
32
     def __init__(self, config):
35
     def __init__(self, config):
33
         self._config = config
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
         Fill config with auth needed. You must overload with whild implementation.
49
         Fill config with auth needed. You must overload with whild implementation.
38
         :return:
50
         :return:
39
         """
51
         """
40
         self._config['sa_auth'] = _get_clean_sa_auth(self._config)
52
         self._config['sa_auth'] = _get_clean_sa_auth(self._config)
41
 
53
 
54
+        self._config['auth_is_internal'] = self.is_internal
55
+
42
         # override this if you would like to provide a different who plugin for
56
         # override this if you would like to provide a different who plugin for
43
         # managing login and logout of your application
57
         # managing login and logout of your application
44
         self._config['sa_auth'].form_plugin = None
58
         self._config['sa_auth'].form_plugin = None

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

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

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

16
 
16
 
17
     """
17
     """
18
     name = 'ldap'
18
     name = 'ldap'
19
+    _internal = False
19
 
20
 
20
     def __init__(self, config):
21
     def __init__(self, config):
21
         super().__init__(config)
22
         super().__init__(config)
23
         self.ldap_user_provider = self._get_ldap_user_provider()
24
         self.ldap_user_provider = self._get_ldap_user_provider()
24
         if ini_conf_to_bool(self._config.get('ldap_group_enabled', False)):
25
         if ini_conf_to_bool(self._config.get('ldap_group_enabled', False)):
25
             self.ldap_groups_provider = self._get_ldap_groups_provider()
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
         self._config['auth_backend'] = 'ldapauth'
31
         self._config['auth_backend'] = 'ldapauth'
30
         self._config['sa_auth'].authenticators = [('ldapauth', self.ldap_auth)]
32
         self._config['sa_auth'].authenticators = [('ldapauth', self.ldap_auth)]
31
 
33
 
155
         super().add_metadata(environ, identity)
157
         super().add_metadata(environ, identity)
156
         # TODO - B.S. - 20160212: identity contains now som information from LDAP what we can save in local database
158
         # TODO - B.S. - 20160212: identity contains now som information from LDAP what we can save in local database
157
         identity[self.name] = self._user_api.get_one_by_email(identity.get('repoze.who.userid'))
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
 class AuthConfigWrapper:
6
 class AuthConfigWrapper:
7
 
7
 
8
     # TODO: Dynamic load, like plugins ?
8
     # TODO: Dynamic load, like plugins ?
9
-    AUTH_WRAPPERS = (InternalAuth, LDAPAuth)
10
-    EXTERNAL_AUTHS = (LDAPAuth,)
9
+    AUTH_CLASSES = (InternalAuth, LDAPAuth)
11
 
10
 
12
     @classmethod
11
     @classmethod
13
     def wrap(cls, config):
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
     @classmethod
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
         raise Exception("No auth config wrapper found for \"%s\" auth_type config" % config.get('auth_type'))
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
         tmpl_context.identity = request.identity
31
         tmpl_context.identity = request.identity
32
         return TGController.__call__(self, environ, context)
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
     @property
38
     @property
35
     def parent_controller(self):
39
     def parent_controller(self):
36
         possible_parent = None
40
         possible_parent = None

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

210
     if value in ('False', 'false', '0', 'off', 'no'):
210
     if value in ('False', 'false', '0', 'off', 'no'):
211
         return False
211
         return False
212
     return bool(value)
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
     def get_one_by_email(self, email: str):
27
     def get_one_by_email(self, email: str):
28
         return self._base_query().filter(User.email==email).one()
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
         if do_save:
37
         if do_save:
34
             self.save(user)
38
             self.save(user)
35
 
39
 

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

15
                 endif
15
                 endif
16
             %>
16
             %>
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>
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
         </div>
23
         </div>
20
         <p></p>
24
         <p></p>
21
         % if current_user.profile.id>2 and current_user.id!=user.id:
25
         % if current_user.profile.id>2 and current_user.id!=user.id:
40
                 user_password_edit_url = tg.url('/user/{}/password/edit'.format(current_user.id))
44
                 user_password_edit_url = tg.url('/user/{}/password/edit'.format(current_user.id))
41
             %>
45
             %>
42
             <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>
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
         </div>
52
         </div>
45
         <p></p>
53
         <p></p>
46
         <h3 class="t-spacer-above" style="margin-top: 1em;">
54
         <h3 class="t-spacer-above" style="margin-top: 1em;">

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

98
         <div class="modal-body">
98
         <div class="modal-body">
99
             <div class="form-group">
99
             <div class="form-group">
100
                 <label for="name">${_('Name')}</label>
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
             </div>
102
             </div>
103
             <div class="form-group">
103
             <div class="form-group">
104
                 <label for="email">${_('Email')}</label>
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
             </div>
106
             </div>
107
         </div>
107
         </div>
108
         <div class="modal-footer">
108
         <div class="modal-footer">