The following example is used to login to Roundup via an LDAP authentication process. User profiles are also stored in Roundup database, to minimize code modifications. Also :
- - user can also be stored and authenticated only internally into Roundup database - user's profile is automatically created if authentication is OK but profile is missing
In your 'extensions' directory, create a file 'ldap.py' with::
1 import ldap
2 from roundup.cgi.actions import LoginAction
3 from roundup.i18n import _
4
5 class LdapLoginAction(LoginAction):
6
7 ldap_attrs = (
8 ( 'realname', ['cn'] ),
9 ( 'username', ['uid'] ),
10 )
11 ldap_server = 'ldaps://example.com'
12 ldap_base = 'dc=example, dc=com'
13 email_suffix = '@example.com'
14
15 def verifyLocalPassword(self, password):
16 ''' Verify the password that the user has supplied '''
17 stored = self.db.user.get(self.client.userid, 'password')
18 if password == stored:
19 return 1
20 if not password and not stored:
21 return 1
22 return 0
23
24 def local_login (self, password):
25 ''' Local authentication '''
26 # make sure the user exists
27 try:
28 self.client.userid = self.db.user.lookup(self.client.user)
29 except KeyError:
30 self.client.error_message.append(_('Unknown user "%s"')%self.client.user)
31 return 0
32 # verify the password
33 if not self.verifyLocalPassword(password):
34 self.client.error_message.append(_('Invalid password'))
35 return 0
36 return 1
37
38 def ldap_login (self, password):
39 ''' Authentication via LDAP '''
40 # connect to LDAP host
41 ldapcn = ldap.initialize(self.ldap_server)
42 # make sure that user exists
43 try:
44 ldaps = ldapcn.search_s(self.ldap_base, ldap.SCOPE_SUBTREE,'uid=%s'%self.client.user)
45 self.ldapdn,self.attrs = ldaps[0][0],ldaps[0][1]
46 except:
47 name = self.client.user
48 self.client.error_message.append (_('Unknown LDAP account "%(name)s"')%locals())
49 return 0
50 # verify the password
51 try:
52 ldapcn.bind_s (self.ldapdn, password)
53 except:
54 self.client.error_message.append (_('Invalid password !'))
55 return 0
56 return 1
57
58 def verifyLogin(self, username, password):
59 # try to login throught LDAP or with local account
60 ldap_ok = None
61 if not self.local_login(password):
62 ldap_ok = self.ldap_login(password)
63 if not ldap_ok:
64 self.client.make_user_anonymous ()
65 return
66 self.client.error_message = []
67 # reload user profile, or create it automatically if missing
68 try:
69 self.client.userid = self.db.user.lookup(self.client.user)
70 except:
71 if ldap_ok:
72 props = {}
73 for user_attr,ldap_attr in self.ldap_attrs:
74 props[user_attr] = ' '.join([self.attrs.get (attr,['',''])[0] for attr in ldap_attr])
75 props['address'] = self.attrs['uid'][0]+self.email_suffix
76 self.journaltag = 'admin'
77 cl = self.db.user
78 props['roles'] = self.db.config.NEW_WEB_USER_ROLES
79 self.userid = cl.create (**props)
80 self.db.commit ()
81 self.client.userid = self.db.user.lookup(self.client.user)
82 else:
83 self.client.make_user_anonymous()
84 self.client.error_message.append(_("No account created without LDAP account"))
85 return
86
87
88 def init(instance):
89 instance.registerAction('login', LdapLoginAction)
As said before, you could easily forbid actions such as allowing authentication throught local Roundup profile, or auto-creation of user's profile. It's also possible, via LDAP, to check that the logged in user is member of a given group, or has a given attribute with a given value. The only thing which have to be done here is complete the "ldap_login" method, and return 0 and an error message when invalid conditions are encountered.
**From: Denis Shaposhnikov**
**Date: Sat, 5 Jan 2008 21:37:56 +0300**
I want to share my expirience with configuring Roundup to use LDAP for authentication. I've found that the LDAPLogin (see above) is not exactly what I need. It creates new Roundup user after successfull LDAP authentication but use empty password. And it checks local password first. So, after first LDAP authentication anyone can login with empty password.
OK, I've done some modifications and you can find 'ldaplogin.py' below. Just configure it and put to 'extenstions' directory. This module can do local authentication too but it disabled by default, see above for the reason. My module do the next steps for authentication:
1. Try to bind with user's DN (without search).
2. Check that user is a member of configured group.
- This check is optional.
3. If no Roundup's user in the database, create it.
- It creates a user with password and with some fields from LDAP.
Here the 'ldaplogin.py' module::
1 """Authentication an user via LDAP."""
2
3 import ldap
4 from roundup import password as PW
5 from roundup.cgi import exceptions
6 from roundup.cgi.actions import LoginAction
7 from roundup.i18n import _
8
9 class LDAPLoginAction(LoginAction):
10 """Do LDAP authentication.
11
12 Do LDAP (and may be local) authentication and create new roundup
13 user if it is not exists. If property `ldap_req_group` have any
14 value the user should be a memeber of that LDAP group.
15
16 Set `use_local_auth` if you want to use local authentication and
17 LDAP next. If not, it authenticates by LDAP only.
18 """
19
20 ldap_attrs = (
21 ('address', 'mail'),
22 ('organisation', 'o'),
23 ('realname', 'cn'),
24 ('phone', 'telephoneNumber'),
25 ('username', 'uid'))
26 ldap_dn = 'uid=%s,ou=people,dc=example,dc=com'
27 ldap_group_attr = 'memberUid'
28 ldap_req_group = 'cn=Roundup Users,ou=groups,dc=example,dc=com'
29 ldap_server = 'ldap://127.0.0.1/'
30
31 use_local_auth = False # LDAP only
32
33 def local_login (self, password):
34 """Try local authentication."""
35 # make sure the user exists
36 try:
37 self.client.userid = self.db.user.lookup(self.client.user)
38 except KeyError:
39 self.client.error_message.append(
40 _('Unknown user "%s"') % self.client.user)
41 return 0
42
43 # verify the password
44 if not self.verifyPassword(self.client.userid, password):
45 self.client.error_message.append(_('Invalid password'))
46 return 0
47
48 # Determine whether the user has permission to log in.
49 # Base behaviour is to check the user has "Web Access".
50 if not self.hasPermission("Web Access"):
51 raise exceptions.LoginError, self._(
52 "You do not have permission to login")
53
54 return 1
55
56 def ldap_login(self, password):
57 """Try LDAP authentication."""
58 ldapconn = ldap.initialize(self.ldap_server)
59 # verify the password
60 try:
61 ldapconn.bind_s(self.ldap_dn % self.client.user, password)
62 except:
63 self.client.error_message.append (_('Invalid password'))
64 return 0
65
66 if self.ldap_req_group:
67 # verify the group membership
68 member = None
69 try:
70 member = ldapconn.compare_s(
71 self.ldap_req_group, self.ldap_group_attr, self.client.user)
72 except:
73 member = None
74 if not member:
75 self.client.error_message.append (
76 _("You do not have permission to login"))
77 return 0
78
79 return 1
80
81 def verifyLogin(self, username, password):
82 # try to login throught LDAP or with local account
83 ldap_ok = None
84 if self.use_local_auth:
85 if not self.local_login(password):
86 ldap_ok = self.ldap_login(password)
87 if not ldap_ok:
88 raise exceptions.LoginError
89 else:
90 ldap_ok = self.ldap_login(password)
91 if not ldap_ok:
92 raise exceptions.LoginError
93 self.client.error_message = []
94 # reload user profile, or create it automatically if missing
95 try:
96 self.client.userid = self.db.user.lookup(self.client.user)
97 except:
98 if ldap_ok:
99 ldapconn = ldap.initialize(self.ldap_server)
100 ldaps = ldapconn.search_s(
101 self.ldap_dn % self.client.user, ldap.SCOPE_BASE)
102 attrs = ldaps[0][1]
103 props = {}
104 for user_attr,ldap_attr in self.ldap_attrs:
105 props[user_attr] = attrs.get(ldap_attr,('',))[0]
106 props['password'] = PW.Password(password)
107 self.journaltag = 'admin'
108 cl = self.db.user
109 props['roles'] = self.db.config.NEW_WEB_USER_ROLES
110 self.userid = cl.create(**props)
111 self.db.commit()
112 self.client.userid = self.db.user.lookup(self.client.user)
113 else:
114 raise exceptions.LoginError, _(
115 "No account created without LDAP account")
116
117 def init(instance):
118 instance.registerAction('login', LDAPLoginAction)