浏览代码

ldap support, dev.

Bastien Sevajol 9 年前
父节点
当前提交
803fd4e630

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

13
 WebTest==1.4.2
13
 WebTest==1.4.2
14
 alembic==0.8.4
14
 alembic==0.8.4
15
 argparse==1.2.1
15
 argparse==1.2.1
16
-backlash==0.0.6
16
+backlash==0.0.7
17
 beautifulsoup4==4.3.2
17
 beautifulsoup4==4.3.2
18
 cliff==1.8.0
18
 cliff==1.8.0
19
 cmd2==0.6.7
19
 cmd2==0.6.7
47
 zope.sqlalchemy==0.7.6
47
 zope.sqlalchemy==0.7.6
48
 tgapp-resetpassword==0.1.3
48
 tgapp-resetpassword==0.1.3
49
 lxml
49
 lxml
50
+python-ldap-test==0.2.0
51
+who-ldap==3.1.0

+ 22 - 0
tracim/migration/versions/b73e57760b36_add_user_field_for_imported_profiles.py 查看文件

1
+"""Add User field for imported profiles
2
+
3
+Revision ID: b73e57760b36
4
+Revises: 43a323cc661
5
+Create Date: 2016-02-09 11:00:22.694054
6
+
7
+"""
8
+
9
+# revision identifiers, used by Alembic.
10
+revision = 'b73e57760b36'
11
+down_revision = '43a323cc661'
12
+
13
+import sqlalchemy as sa
14
+from alembic import op
15
+
16
+
17
+def upgrade():
18
+    op.add_column('users', sa.Column('imported_from', sa.Unicode(length=32), nullable=True))
19
+
20
+
21
+def downgrade():
22
+    op.drop_column('users', 'imported_from')

+ 59 - 0
tracim/s.py 查看文件

1
+from time import sleep
2
+
3
+from ldap_test import LdapServer
4
+
5
+server = LdapServer({
6
+            'port': 3333,
7
+            'password': 'toor',
8
+
9
+            'bind_dn': 'cn=admin,dc=directory,dc=fsf,dc=org',
10
+            'base': {
11
+                'objectclass': ['dcObject', 'organization'],
12
+                'dn': 'dc=directory,dc=fsf,dc=org',
13
+                'attributes': {
14
+                    'o': 'Free Software Foundation',
15
+                    'dc': 'directory'
16
+                }
17
+            },
18
+
19
+            'entries': [
20
+                {
21
+                    'objectclass': ['organizationalRole'],
22
+                    'dn': 'cn=admin,dc=directory,dc=fsf,dc=org',
23
+                    'attributes': {
24
+                        'cn': 'admin'
25
+                    }
26
+                },
27
+                {
28
+                    'objectclass': ['organizationalUnit'],
29
+                    'dn': 'ou=people,dc=directory,dc=fsf,dc=org',
30
+                    'attributes': {
31
+                        'ou': 'people',
32
+                    }
33
+                },
34
+                {
35
+                    'objectclass': ['organizationalUnit'],
36
+                    'dn': 'ou=groups,dc=directory,dc=fsf,dc=org',
37
+                    'attributes': {
38
+                        'ou': 'groups',
39
+                    }
40
+                },
41
+                {
42
+                    'objectclass': ['account', 'top'],
43
+                    'dn': 'cn=richard-not-real-email@fsf.org,ou=people,dc=directory,dc=fsf,dc=org',
44
+                    'attributes': {
45
+                        'uid': 'richard-not-real-email@fsf.org',
46
+                        'userPassword': 'rms',
47
+                        'givenName': 'Richard',
48
+                        'sn': 'Stallman',
49
+                        'mail': 'richard-not-real-email@fsf.org'
50
+                    }
51
+                },
52
+            ]
53
+        })
54
+
55
+server.start()
56
+try:
57
+    sleep(999999999)
58
+except KeyboardInterrupt:
59
+    pass

+ 2 - 0
tracim/setup.py 查看文件

39
     "sqlalchemy",
39
     "sqlalchemy",
40
     "alembic",
40
     "alembic",
41
     "repoze.who",
41
     "repoze.who",
42
+    "who-ldap==3.1.0",
43
+    "python-ldap-test==0.2.0",
42
     ]
44
     ]
43
 
45
 
44
 setup(
46
 setup(

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

23
 use = main
23
 use = main
24
 skip_authentication = True
24
 skip_authentication = True
25
 
25
 
26
+[app:ldap]
27
+sqlalchemy.url = postgresql://postgres:dummy@127.0.0.1:5432/tracim_test?client_encoding=utf8
28
+auth_type = 'ldap'
29
+ldap_url = 'ldaps://ad.my-company.org'
30
+ldap_base_dn = 'ou=users,dc=ad,dc=my-company,dc=com'
31
+ldap_bind_dn = 'cn=bind,cn=users,dc=ad,dc=my-company,dc=com'
32
+ldap_bind_pass = 'toor2'
33
+use = config:development.ini
34
+
26
 # Add additional test specific configuration options as necessary.
35
 # Add additional test specific configuration options as necessary.

+ 12 - 51
tracim/tracim/config/app_cfg.py 查看文件

25
 import tracim
25
 import tracim
26
 from tracim import model
26
 from tracim import model
27
 from tracim.lib import app_globals, helpers
27
 from tracim.lib import app_globals, helpers
28
+from tracim.lib.auth.wrapper import AuthConfigWrapper
28
 from tracim.lib.base import logger
29
 from tracim.lib.base import logger
29
 from tracim.model.data import ActionDescription
30
 from tracim.model.data import ActionDescription
30
 from tracim.model.data import ContentType
31
 from tracim.model.data import ContentType
72
 # YOU MUST CHANGE THIS VALUE IN PRODUCTION TO SECURE YOUR APP 
73
 # YOU MUST CHANGE THIS VALUE IN PRODUCTION TO SECURE YOUR APP 
73
 base_config.sa_auth.cookie_secret = "3283411b-1904-4554-b0e1-883863b53080"
74
 base_config.sa_auth.cookie_secret = "3283411b-1904-4554-b0e1-883863b53080"
74
 
75
 
75
-base_config.auth_backend = 'sqlalchemy'
76
+base_config.auth_type = 'ldap'
76
 
77
 
77
-# what is the class you want to use to search for users in the database
78
-base_config.sa_auth.user_class = model.User
78
+# ldap_base_dn = 'ou=users,dc=ad,dc=snake-oil-company,dc=com'
79
+# ldap_bind_dn = 'cn=bind,cn=users,dc=ad,dc=snake-oil-company,dc=com'
79
 
80
 
80
-from tg.configuration.auth import TGAuthMetadata
81
+base_config.ldap_url = 'ldap://localhost:3333'
82
+base_config.ldap_base_dn = 'dc=directory,dc=fsf,dc=org'
83
+base_config.ldap_bind_dn = 'cn=admin,dc=directory,dc=fsf,dc=org'
84
+base_config.ldap_bind_pass = 'toor'
85
+base_config.ldap_ldap_naming_attribute = 'uid'
86
+base_config.ldap_user_attributes = 'mail=email'
87
+base_config.ldap_tls = False
81
 
88
 
82
-from sqlalchemy import and_
83
-#This tells to TurboGears how to retrieve the data for your user
84
-class ApplicationAuthMetadata(TGAuthMetadata):
85
-
86
-    def __init__(self, sa_auth):
87
-        self.sa_auth = sa_auth
88
-
89
-    def authenticate(self, environ, identity):
90
-        user = self.sa_auth.dbsession.query(self.sa_auth.user_class).filter(and_(
91
-            self.sa_auth.user_class.is_active==True,
92
-            self.sa_auth.user_class.email==identity['login']
93
-        )).first()
94
-
95
-        if user and user.validate_password(identity['password']):
96
-            return identity['login']
97
-    def get_user(self, identity, userid):
98
-        return self.sa_auth.dbsession.query(self.sa_auth.user_class).filter(and_(self.sa_auth.user_class.is_active==True, self.sa_auth.user_class.email==userid)).first()
99
-    def get_groups(self, identity, userid):
100
-        return [g.group_name for g in identity['user'].groups]
101
-    def get_permissions(self, identity, userid):
102
-        return [p.permission_name for p in identity['user'].permissions]
103
-
104
-base_config.sa_auth.dbsession = model.DBSession
105
-
106
-base_config.sa_auth.authmetadata = ApplicationAuthMetadata(base_config.sa_auth)
107
-
108
-# You can use a different repoze.who Authenticator if you want to
109
-# change the way users can login
110
-#base_config.sa_auth.authenticators = [('myauth', SomeAuthenticator()]
111
-
112
-# You can add more repoze.who metadata providers to fetch
113
-# user metadata.
114
-# Remember to set base_config.sa_auth.authmetadata to None
115
-# to disable authmetadata and use only your own metadata providers
116
-#base_config.sa_auth.mdproviders = [('myprovider', SomeMDProvider()]
117
-
118
-# override this if you would like to provide a different who plugin for
119
-# managing login and logout of your application
120
-base_config.sa_auth.form_plugin = None
121
-
122
-# You may optionally define a page where you want users to be redirected to
123
-# on login:
124
-base_config.sa_auth.post_login_url = '/post_login'
125
-
126
-# You may optionally define a page where you want users to be redirected to
127
-# on logout:
128
-base_config.sa_auth.post_logout_url = '/post_logout'
89
+AuthConfigWrapper.wrap(base_config)
129
 
90
 
130
 # INFO - This is the way to specialize the resetpassword email properties
91
 # INFO - This is the way to specialize the resetpassword email properties
131
 # plug(base_config, 'resetpassword', None, mail_subject=reset_password_email_subject)
92
 # plug(base_config, 'resetpassword', None, mail_subject=reset_password_email_subject)

+ 1 - 0
tracim/tracim/lib/auth/__init__.py 查看文件

1
+from tracim.lib.auth.base import Auth

+ 22 - 0
tracim/tracim/lib/auth/base.py 查看文件

1
+# -*- coding: utf-8 -*-
2
+
3
+
4
+class Auth:
5
+
6
+    name = NotImplemented
7
+
8
+    def __init__(self, config):
9
+        self._config = config
10
+
11
+    def wrap_config(self):
12
+        # override this if you would like to provide a different who plugin for
13
+        # managing login and logout of your application
14
+        self._config.sa_auth.form_plugin = None
15
+
16
+        # You may optionally define a page where you want users to be redirected to
17
+        # on login:
18
+        self._config.sa_auth.post_login_url = '/post_login'
19
+
20
+        # You may optionally define a page where you want users to be redirected to
21
+        # on logout:
22
+        self._config.sa_auth.post_logout_url = '/post_logout'

+ 43 - 0
tracim/tracim/lib/auth/internal.py 查看文件

1
+# -*- coding: utf-8 -*-
2
+from sqlalchemy import and_
3
+from tg.configuration.auth import TGAuthMetadata
4
+
5
+from tracim.lib.auth.base import Auth
6
+from tracim.model import DBSession, User
7
+
8
+
9
+class InternalAuth(Auth):
10
+
11
+    name = 'internal'
12
+
13
+    def wrap_config(self):
14
+        super().wrap_config()
15
+
16
+        self._config.sa_auth.user_class = User
17
+        self._config.auth_backend = 'sqlalchemy'
18
+        self._config.sa_auth.dbsession = DBSession
19
+        self._config.sa_auth.authmetadata = InternalApplicationAuthMetadata(self._config.sa_auth)
20
+
21
+
22
+class InternalApplicationAuthMetadata(TGAuthMetadata):
23
+    def __init__(self, sa_auth):
24
+        self.sa_auth = sa_auth
25
+
26
+    def authenticate(self, environ, identity):
27
+        user = self.sa_auth.dbsession.query(self.sa_auth.user_class).filter(and_(
28
+            self.sa_auth.user_class.is_active == True,
29
+            self.sa_auth.user_class.email == identity['login']
30
+        )).first()
31
+
32
+        if user and user.validate_password(identity['password']):
33
+            return identity['login']
34
+
35
+    def get_user(self, identity, userid):
36
+        return self.sa_auth.dbsession.query(self.sa_auth.user_class).filter(
37
+            and_(self.sa_auth.user_class.is_active == True, self.sa_auth.user_class.email == userid)).first()
38
+
39
+    def get_groups(self, identity, userid):
40
+        return [g.group_name for g in identity['user'].groups]
41
+
42
+    def get_permissions(self, identity, userid):
43
+        return [p.permission_name for p in identity['user'].permissions]

+ 135 - 0
tracim/tracim/lib/auth/ldap.py 查看文件

1
+# -*- coding: utf-8 -*-
2
+from tg.configuration.auth import TGAuthMetadata
3
+from who_ldap import LDAPAttributesPlugin, LDAPGroupsPlugin
4
+from who_ldap import LDAPSearchAuthenticatorPlugin as BaseLDAPSearchAuthenticatorPlugin
5
+
6
+from tracim.lib.auth.base import Auth
7
+from tracim.lib.user import UserApi
8
+from tracim.model import auth, DBSession, User
9
+
10
+
11
+class LDAPAuth(Auth):
12
+    """
13
+    LDAP auth management.
14
+    TODO: Group connection
15
+
16
+    """
17
+    name = 'ldap'
18
+
19
+    def __init__(self, config):
20
+        super().__init__(config)
21
+        self.ldap_auth = self._get_ldap_auth()
22
+        self.ldap_user_provider = self._get_ldap_user_provider()
23
+        if self._config.get('ldap_group_enabled', False):
24
+            self.ldap_groups_provider = self._get_ldap_groups_provider()
25
+
26
+    def wrap_config(self):
27
+        super().wrap_config()
28
+        self._config.auth_backend = 'ldapauth'
29
+        self._config.sa_auth.authenticators = [('ldapauth', self.ldap_auth)]
30
+
31
+        mdproviders = [('ldapuser', self.ldap_user_provider)]
32
+        if self._config.get('ldap_group_enabled', False):
33
+            mdproviders.append(('ldapgroups', self.ldap_groups_provider))
34
+        self._config.sa_auth.mdproviders = mdproviders
35
+
36
+        self._config.sa_auth.authmetadata = LDAPApplicationAuthMetadata(self._config.sa_auth)
37
+
38
+    def _get_ldap_auth(self):
39
+        auth_plug = LDAPSearchAuthenticatorPlugin(
40
+            url=self._config.get('ldap_url'),
41
+            base_dn=self._config.get('ldap_base_dn'),
42
+            bind_dn=self._config.get('ldap_bind_dn'),
43
+            bind_pass=self._config.get('ldap_bind_pass'),
44
+            returned_id='login',
45
+            # the LDAP attribute that holds the user name:
46
+            naming_attribute=self._config.get('ldap_naming_attribute'),
47
+            start_tls=self._config.get('ldap_tls', False),
48
+        )
49
+        auth_plug.set_auth(self)
50
+        return auth_plug
51
+
52
+    def _get_ldap_user_provider(self):
53
+        return LDAPAttributesPlugin(
54
+            url=self._config.get('ldap_url'),
55
+            bind_dn=self._config.get('ldap_bind_dn'),
56
+            bind_pass=self._config.get('ldap_bind_pass'),
57
+            name='user',
58
+            # map from LDAP attributes to TurboGears user attributes:
59
+            attributes=self._config.get('ldap_user_attributes', 'mail=email'),
60
+            flatten=True,
61
+            start_tls=self._config.get('ldap_tls', False)
62
+        )
63
+
64
+    def _get_ldap_groups_provider(self):
65
+        return LDAPGroupsPlugin(
66
+            url=self._config.get('ldap_url'),
67
+            base_dn=self._config.get('ldap_base_dn'),
68
+            bind_dn=self._config.get('ldap_bind_dn'),
69
+            bind_pass=self._config.get('ldap_bind_pass'),
70
+            filterstr=self._config.get('ldap_group_filter', '(&(objectClass=group)(member=%(dn)s))'),
71
+            name='groups',
72
+            start_tls=self._config.get('ldap_tls', False)
73
+        )
74
+
75
+
76
+class LDAPSearchAuthenticatorPlugin(BaseLDAPSearchAuthenticatorPlugin):
77
+
78
+    def __init__(self, *args, **kwargs):
79
+        super().__init__(*args, **kwargs)
80
+        self._auth = None
81
+        self._user_api = UserApi(None)
82
+
83
+    def set_auth(self, auth):
84
+        self._auth = auth
85
+
86
+    def authenticate(self, environ, identity):
87
+        # Note: super().authenticate return None if already authenticated or not found
88
+        email = super().authenticate(environ, identity)
89
+        if email:
90
+            self._sync_ldap_user(email)
91
+        return email
92
+
93
+    def _sync_ldap_user(self, email):
94
+        if not self._user_api.user_with_email_exists(email):
95
+            user = User(email=email, imported_from=LDAPAuth.name)
96
+            DBSession.add(user)
97
+            import transaction
98
+            transaction.commit()
99
+
100
+            # TODO - B.S. - 20160208: Voir avec Damien, si je ne fait pas de transaction.commit()
101
+            # manuellement la donnée n'est pas en base.
102
+            # self._user_api.create_user(email=email, save_now=True)
103
+
104
+
105
+class LDAPApplicationAuthMetadata(TGAuthMetadata):
106
+
107
+    # map from LDAP group names to TurboGears group names
108
+    group_map = {'operators': 'managers'}
109
+
110
+    # set of permissions for all mapped groups
111
+    permissions_for_groups = {'managers': {'manage'}}
112
+
113
+    def __init__(self, sa_auth):
114
+        self.sa_auth = sa_auth
115
+
116
+    def get_user(self, identity, userid):
117
+        user = identity.get('user')
118
+        if user:
119
+            name = '{email}'.format(**user).strip()
120
+            user.update(user_name=userid, display_name=name)
121
+        return user
122
+
123
+    def get_groups(self, identity, userid):
124
+        get_group = self.group_map.get
125
+        return [get_group(g, g) for g in identity.get('groups', [])]
126
+
127
+    def get_permissions_for_group(self, group):
128
+        return self.permissions_for_groups.get(group, set())
129
+
130
+    def get_permissions(self, identity, userid):
131
+        permissions = set()
132
+        get_permissions = self.get_permissions_for_group
133
+        for group in self.get_groups(identity, userid):
134
+            permissions |= get_permissions(group)
135
+        return permissions

+ 26 - 0
tracim/tracim/lib/auth/wrapper.py 查看文件

1
+# -*- coding: utf-8 -*-
2
+from tracim.lib.auth.internal import InternalAuth
3
+from tracim.lib.auth.ldap import LDAPAuth
4
+
5
+
6
+class AuthConfigWrapper:
7
+
8
+    # TODO: Dynamic load, like plugins ?
9
+    AUTH_WRAPPERS = (InternalAuth, LDAPAuth)
10
+    EXTERNAL_AUTHS = (LDAPAuth,)
11
+
12
+    @classmethod
13
+    def wrap(cls, config):
14
+        wrapper_class = cls._get_wrapper_class(config)
15
+        wrapper = wrapper_class(config)
16
+        wrapper.wrap_config()
17
+        return config
18
+
19
+    @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.auth_type == wrapper_class.name:
25
+                return wrapper_class
26
+        raise Exception("No auth config wrapper found for \"%s\" auth_type config" % config.auth_type)

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

119
     _password = Column('password', Unicode(128))
119
     _password = Column('password', Unicode(128))
120
     created = Column(DateTime, default=datetime.now)
120
     created = Column(DateTime, default=datetime.now)
121
     is_active = Column(Boolean, default=True, nullable=False)
121
     is_active = Column(Boolean, default=True, nullable=False)
122
+    imported_from = Column(Unicode(32), nullable=True)
122
 
123
 
123
     @hybrid_property
124
     @hybrid_property
124
     def email_address(self):
125
     def email_address(self):

+ 56 - 0
tracim/tracim/tests/__init__.py 查看文件

3
 
3
 
4
 from os import getcwd, path
4
 from os import getcwd, path
5
 
5
 
6
+import ldap3
7
+from ldap_test import LdapServer
8
+from nose.tools import ok_
6
 from paste.deploy import loadapp
9
 from paste.deploy import loadapp
7
 from webtest import TestApp
10
 from webtest import TestApp
8
 
11
 
23
 from sqlalchemy.schema import Table
26
 from sqlalchemy.schema import Table
24
 
27
 
25
 import transaction
28
 import transaction
29
+from who_ldap import make_connection
26
 
30
 
27
 from tracim.lib.base import logger
31
 from tracim.lib.base import logger
28
 
32
 
191
         """Tear down test fixture for each functional test method."""
195
         """Tear down test fixture for each functional test method."""
192
         # model.DBSession.remove()
196
         # model.DBSession.remove()
193
         teardown_db()
197
         teardown_db()
198
+
199
+
200
+class TracimTestController(TestController):
201
+
202
+    def _connect_user(self, login, password):
203
+        # Going to the login form voluntarily:
204
+        resp = self.app.get('/', status=200)
205
+        form = resp.form
206
+        # Submitting the login form:
207
+        form['login'] = login
208
+        form['password'] = password
209
+        return form.submit(status=302)
210
+
211
+
212
+class LDAPTest:
213
+
214
+    """
215
+    Server fixtures, see https://github.com/zoldar/python-ldap-test
216
+    """
217
+    ldap_server_data = NotImplemented
218
+
219
+    def __init__(self, *args, **kwargs):
220
+        super().__init__(*args, **kwargs)
221
+        self._ldap_test_server = None
222
+        self._ldap_connection = None
223
+
224
+    def setUp(self):
225
+        super().setUp()
226
+        self._ldap_test_server = LdapServer(self.ldap_server_data)
227
+        self._ldap_test_server.start()
228
+        ldap3_server = ldap3.Server('localhost', port=self._ldap_test_server.config['port'])
229
+        self._ldap_connection = ldap3.Connection(
230
+            ldap3_server,
231
+            user=self._ldap_test_server.config['bind_dn'],
232
+            password=self._ldap_test_server.config['password'],
233
+            auto_bind=True
234
+        )
235
+
236
+    def tearDown(self):
237
+        super().tearDown()
238
+        self._ldap_test_server.stop()
239
+
240
+    def test_ldap_connectivity(self):
241
+        with make_connection(
242
+                'ldap://%s:%d' % ('localhost', self._ldap_test_server.config['port']),
243
+                self._ldap_test_server.config['bind_dn'],
244
+                'toor'
245
+        ) as conn:
246
+            if not conn.bind():
247
+                ok_(False, "Cannot establish connection with LDAP test server")
248
+            else:
249
+                ok_(True)

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

1
+# -*- coding: utf-8 -*-
2
+"""
3
+Integration tests for the ldap authentication sub-system.
4
+"""
5
+from nose.tools import eq_
6
+from tg import config
7
+
8
+from tracim.model import DBSession, User
9
+from tracim.tests import LDAPTest, TracimTestController
10
+
11
+
12
+class TestAuthentication(LDAPTest, TracimTestController):
13
+    application_under_test = 'ldap'
14
+    ldap_server_data = {
15
+            'port': 3333,
16
+            'password': 'toor',
17
+
18
+            'bind_dn': 'cn=admin,dc=directory,dc=fsf,dc=org',
19
+            'base': {
20
+                'objectclass': ['dcObject', 'organization'],
21
+                'dn': 'dc=directory,dc=fsf,dc=org',
22
+                'attributes': {
23
+                    'o': 'Free Software Foundation',
24
+                    'dc': 'directory'
25
+                }
26
+            },
27
+
28
+            'entries': [
29
+                {
30
+                    'objectclass': ['organizationalRole'],
31
+                    'dn': 'cn=admin,dc=directory,dc=fsf,dc=org',
32
+                    'attributes': {
33
+                        'cn': 'admin'
34
+                    }
35
+                },
36
+                {
37
+                    'objectclass': ['organizationalUnit'],
38
+                    'dn': 'ou=people,dc=directory,dc=fsf,dc=org',
39
+                    'attributes': {
40
+                        'ou': 'people',
41
+                    }
42
+                },
43
+                {
44
+                    'objectclass': ['organizationalUnit'],
45
+                    'dn': 'ou=groups,dc=directory,dc=fsf,dc=org',
46
+                    'attributes': {
47
+                        'ou': 'groups',
48
+                    }
49
+                },
50
+                {
51
+                    'objectclass': ['account', 'top'],
52
+                    'dn': 'cn=richard-not-real-email@fsf.org,ou=people,dc=directory,dc=fsf,dc=org',
53
+                    'attributes': {
54
+                        'uid': 'richard-not-real-email@fsf.org',
55
+                        'userPassword': 'rms',
56
+                        'mail': 'richard-not-real-email@fsf.org'
57
+                    }
58
+                },
59
+            ]
60
+        }
61
+
62
+    def test_ldap_auth_fail(self):
63
+        # User is unknown in tracim database
64
+        eq_(0, DBSession.query(User).filter(User.email == 'unknown-user@fsf.org').count())
65
+
66
+        self._connect_user('unknown-user@fsf.org', 'no-pass')
67
+
68
+        # User is registered in tracim database
69
+        eq_(0, DBSession.query(User).filter(User.email == 'unknown-user@fsf.org').count())
70
+        DBSession.close()
71
+
72
+    def test_ldap_auth_sync(self):
73
+        # User is unknown in tracim database
74
+        eq_(0, DBSession.query(User).filter(User.email == 'richard-not-real-email@fsf.org').count())
75
+
76
+        self._connect_user('richard-not-real-email@fsf.org', 'rms')
77
+
78
+        # User is registered in tracim database
79
+        eq_(1, DBSession.query(User).filter(User.email == 'richard-not-real-email@fsf.org').count())
80
+        DBSession.close()

+ 30 - 29
tracim/tracim/websetup/bootstrap.py 查看文件

13
     # <websetup.bootstrap.before.auth
13
     # <websetup.bootstrap.before.auth
14
     from sqlalchemy.exc import IntegrityError
14
     from sqlalchemy.exc import IntegrityError
15
     try:
15
     try:
16
-        u = model.User()
17
-        u.display_name = 'Global manager'
18
-        u.email = 'admin@admin.admin'
19
-        u.password = 'admin@admin.admin'
20
-        model.DBSession.add(u)
21
-
22
-        g1 = model.Group()
23
-        g1.group_id = 1
24
-        g1.group_name = 'users'
25
-        g1.display_name = 'Users'
26
-        g1.users.append(u)
27
-        model.DBSession.add(g1)
28
-
29
-        g2 = model.Group()
30
-        g2.group_id = 2
31
-        g2.group_name = 'managers'
32
-        g2.display_name = 'Global Managers'
33
-        g2.users.append(u)
34
-        model.DBSession.add(g2)
35
-
36
-        g3 = model.Group()
37
-        g3.group_id = 3
38
-        g3.group_name = 'administrators'
39
-        g3.display_name = 'Administrators'
40
-        g3.users.append(u)
41
-        model.DBSession.add(g3)
42
-
43
-        model.DBSession.flush()
44
-        transaction.commit()
16
+        # u = model.User()
17
+        # u.display_name = 'Global manager'
18
+        # u.email = 'admin@admin.admin'
19
+        # u.password = 'admin@admin.admin'
20
+        # model.DBSession.add(u)
21
+        #
22
+        # g1 = model.Group()
23
+        # g1.group_id = 1
24
+        # g1.group_name = 'users'
25
+        # g1.display_name = 'Users'
26
+        # g1.users.append(u)
27
+        # model.DBSession.add(g1)
28
+        #
29
+        # g2 = model.Group()
30
+        # g2.group_id = 2
31
+        # g2.group_name = 'managers'
32
+        # g2.display_name = 'Global Managers'
33
+        # g2.users.append(u)
34
+        # model.DBSession.add(g2)
35
+        #
36
+        # g3 = model.Group()
37
+        # g3.group_id = 3
38
+        # g3.group_name = 'administrators'
39
+        # g3.display_name = 'Administrators'
40
+        # g3.users.append(u)
41
+        # model.DBSession.add(g3)
42
+        #
43
+        # model.DBSession.flush()
44
+        # transaction.commit()
45
+        pass
45
 
46
 
46
     except IntegrityError:
47
     except IntegrityError:
47
         print('Warning, there was a problem adding your auth data, it may have already been added:')
48
         print('Warning, there was a problem adding your auth data, it may have already been added:')

+ 2 - 1
tracim/tracim/websetup/schema.py 查看文件

174
     display_name character varying(255),
174
     display_name character varying(255),
175
     password character varying(128),
175
     password character varying(128),
176
     created timestamp without time zone,
176
     created timestamp without time zone,
177
-    is_active boolean DEFAULT true NOT NULL
177
+    is_active boolean DEFAULT true NOT NULL,
178
+    imported_from character varying(32)
178
 );
179
 );
179
 
180
 
180
 CREATE TABLE user_group (
181
 CREATE TABLE user_group (