##// END OF EJS Templates
Improve LDAP authentication...
Thayne Harbaugh -
r991:b232a36c issue-108
parent child Browse files
Show More
@@ -48,15 +48,33 b' log = logging.getLogger(__name__)'
48
48
49 class LdapSettingsController(BaseController):
49 class LdapSettingsController(BaseController):
50
50
51 search_scope_choices = [('BASE', _('BASE'),),
52 ('ONELEVEL', _('ONELEVEL'),),
53 ('SUBTREE', _('SUBTREE'),),
54 ]
55 search_scope_default = 'SUBTREE'
56
57 tls_reqcert_choices = [('NEVER', _('NEVER'),),
58 ('ALLOW', _('ALLOW'),),
59 ('TRY', _('TRY'),),
60 ('DEMAND', _('DEMAND'),),
61 ('HARD', _('HARD'),),
62 ]
63 tls_reqcert_default = 'DEMAND'
64
51 @LoginRequired()
65 @LoginRequired()
52 @HasPermissionAllDecorator('hg.admin')
66 @HasPermissionAllDecorator('hg.admin')
53 def __before__(self):
67 def __before__(self):
54 c.admin_user = session.get('admin_user')
68 c.admin_user = session.get('admin_user')
55 c.admin_username = session.get('admin_username')
69 c.admin_username = session.get('admin_username')
70 c.search_scope_choices = self.search_scope_choices
71 c.tls_reqcert_choices = self.tls_reqcert_choices
56 super(LdapSettingsController, self).__before__()
72 super(LdapSettingsController, self).__before__()
57
73
58 def index(self):
74 def index(self):
59 defaults = SettingsModel().get_ldap_settings()
75 defaults = SettingsModel().get_ldap_settings()
76 c.search_scope_cur = defaults.get('ldap_search_scope')
77 c.tls_reqcert_cur = defaults.get('ldap_tls_reqcert')
60
78
61 return htmlfill.render(
79 return htmlfill.render(
62 render('admin/ldap/ldap.html'),
80 render('admin/ldap/ldap.html'),
@@ -68,7 +86,8 b' class LdapSettingsController(BaseControl'
68 """POST ldap create and store ldap settings"""
86 """POST ldap create and store ldap settings"""
69
87
70 settings_model = SettingsModel()
88 settings_model = SettingsModel()
71 _form = LdapSettingsForm()()
89 _form = LdapSettingsForm([x[0] for x in self.tls_reqcert_choices],
90 [x[0] for x in self.search_scope_choices])()
72
91
73 try:
92 try:
74 form_result = _form.to_python(dict(request.POST))
93 form_result = _form.to_python(dict(request.POST))
@@ -91,6 +110,9 b' class LdapSettingsController(BaseControl'
91
110
92 except formencode.Invalid, errors:
111 except formencode.Invalid, errors:
93
112
113 c.search_scope_cur = self.search_scope_default
114 c.tls_reqcert_cur = self.search_scope_default
115
94 return htmlfill.render(
116 return htmlfill.render(
95 render('admin/ldap/ldap.html'),
117 render('admin/ldap/ldap.html'),
96 defaults=errors.value,
118 defaults=errors.value,
@@ -103,7 +103,7 b' def authenticate(username, password):'
103 user = user_model.get_by_username(username, cache=False)
103 user = user_model.get_by_username(username, cache=False)
104
104
105 log.debug('Authenticating user using RhodeCode account')
105 log.debug('Authenticating user using RhodeCode account')
106 if user is not None and user.is_ldap is False:
106 if user is not None and not user.ldap_dn:
107 if user.active:
107 if user.active:
108
108
109 if user.username == 'default' and user.active:
109 if user.username == 'default' and user.active:
@@ -122,7 +122,7 b' def authenticate(username, password):'
122 user_obj = user_model.get_by_username(username, cache=False,
122 user_obj = user_model.get_by_username(username, cache=False,
123 case_insensitive=True)
123 case_insensitive=True)
124
124
125 if user_obj is not None and user_obj.is_ldap is False:
125 if user_obj is not None and not user_obj.ldap_dn:
126 log.debug('this user already exists as non ldap')
126 log.debug('this user already exists as non ldap')
127 return False
127 return False
128
128
@@ -141,15 +141,25 b' def authenticate(username, password):'
141 'bind_dn':ldap_settings.get('ldap_dn_user'),
141 'bind_dn':ldap_settings.get('ldap_dn_user'),
142 'bind_pass':ldap_settings.get('ldap_dn_pass'),
142 'bind_pass':ldap_settings.get('ldap_dn_pass'),
143 'use_ldaps':ldap_settings.get('ldap_ldaps'),
143 'use_ldaps':ldap_settings.get('ldap_ldaps'),
144 'tls_reqcert':ldap_settings.get('ldap_tls_reqcert'),
145 'ldap_filter':ldap_settings.get('ldap_filter'),
146 'search_scope':ldap_settings.get('ldap_search_scope'),
147 'attr_login':ldap_settings.get('ldap_attr_login'),
144 'ldap_version':3,
148 'ldap_version':3,
145 }
149 }
146 log.debug('Checking for ldap authentication')
150 log.debug('Checking for ldap authentication')
147 try:
151 try:
148 aldap = AuthLdap(**kwargs)
152 aldap = AuthLdap(**kwargs)
149 res = aldap.authenticate_ldap(username, password)
153 (user_dn, ldap_attrs) = aldap.authenticate_ldap(username, password)
150 log.debug('Got ldap response %s', res)
154 log.debug('Got ldap DN response %s', user_dn)
151
155
152 if user_model.create_ldap(username, password):
156 user_attrs = {
157 'name' : ldap_attrs[ldap_settings.get('ldap_attr_firstname')][0],
158 'lastname' : ldap_attrs[ldap_settings.get('ldap_attr_lastname')][0],
159 'email' : ldap_attrs[ldap_settings.get('ldap_attr_email')][0],
160 }
161
162 if user_model.create_ldap(username, password, user_dn, user_attrs):
153 log.info('created new ldap user')
163 log.info('created new ldap user')
154
164
155 return True
165 return True
@@ -36,11 +36,15 b' except ImportError:'
36 class AuthLdap(object):
36 class AuthLdap(object):
37
37
38 def __init__(self, server, base_dn, port=389, bind_dn='', bind_pass='',
38 def __init__(self, server, base_dn, port=389, bind_dn='', bind_pass='',
39 use_ldaps=False, ldap_version=3):
39 use_ldaps=False, tls_reqcert='DEMAND', ldap_version=3,
40 ldap_filter='(&(objectClass=user)(!(objectClass=computer)))',
41 search_scope='SUBTREE',
42 attr_login='uid'):
40 self.ldap_version = ldap_version
43 self.ldap_version = ldap_version
41 if use_ldaps:
44 if use_ldaps:
42 port = port or 689
45 port = port or 689
43 self.LDAP_USE_LDAPS = use_ldaps
46 self.LDAP_USE_LDAPS = use_ldaps
47 self.TLS_REQCERT = ldap.__dict__['OPT_X_TLS_' + tls_reqcert]
44 self.LDAP_SERVER_ADDRESS = server
48 self.LDAP_SERVER_ADDRESS = server
45 self.LDAP_SERVER_PORT = port
49 self.LDAP_SERVER_PORT = port
46
50
@@ -55,6 +59,10 b' class AuthLdap(object):'
55 self.LDAP_SERVER_PORT)
59 self.LDAP_SERVER_PORT)
56
60
57 self.BASE_DN = base_dn
61 self.BASE_DN = base_dn
62 self.LDAP_FILTER = ldap_filter
63 self.SEARCH_SCOPE = ldap.__dict__['SCOPE_' + search_scope]
64 self.attr_login = attr_login
65
58
66
59 def authenticate_ldap(self, username, password):
67 def authenticate_ldap(self, username, password):
60 """Authenticate a user via LDAP and return his/her LDAP properties.
68 """Authenticate a user via LDAP and return his/her LDAP properties.
@@ -74,7 +82,13 b' class AuthLdap(object):'
74 raise LdapUsernameError("invalid character in username: ,")
82 raise LdapUsernameError("invalid character in username: ,")
75 try:
83 try:
76 ldap.set_option(ldap.OPT_X_TLS_CACERTDIR, '/etc/openldap/cacerts')
84 ldap.set_option(ldap.OPT_X_TLS_CACERTDIR, '/etc/openldap/cacerts')
85 ldap.set_option(ldap.OPT_REFERRALS, ldap.OPT_OFF)
86 ldap.set_option(ldap.OPT_RESTART, ldap.OPT_ON)
87 ldap.set_option(ldap.OPT_TIMEOUT, 20)
77 ldap.set_option(ldap.OPT_NETWORK_TIMEOUT, 10)
88 ldap.set_option(ldap.OPT_NETWORK_TIMEOUT, 10)
89 ldap.set_option(ldap.OPT_TIMELIMIT, 15)
90 if self.LDAP_USE_LDAPS:
91 ldap.set_option(ldap.OPT_X_TLS_REQUIRE_CERT, self.TLS_REQCERT)
78 server = ldap.initialize(self.LDAP_SERVER)
92 server = ldap.initialize(self.LDAP_SERVER)
79 if self.ldap_version == 2:
93 if self.ldap_version == 2:
80 server.protocol = ldap.VERSION2
94 server.protocol = ldap.VERSION2
@@ -84,21 +98,29 b' class AuthLdap(object):'
84 if self.LDAP_BIND_DN and self.LDAP_BIND_PASS:
98 if self.LDAP_BIND_DN and self.LDAP_BIND_PASS:
85 server.simple_bind_s(self.LDAP_BIND_DN, self.LDAP_BIND_PASS)
99 server.simple_bind_s(self.LDAP_BIND_DN, self.LDAP_BIND_PASS)
86
100
87 dn = self.BASE_DN % {'user':uid}
101 filt = '(&%s(%s=%s))' % (self.LDAP_FILTER, self.attr_login, username)
88 log.debug("Authenticating %r at %s", dn, self.LDAP_SERVER)
102 log.debug("Authenticating %r filt %s at %s", self.BASE_DN, filt, self.LDAP_SERVER)
89 server.simple_bind_s(dn, password)
103 lobjects = server.search_ext_s(self.BASE_DN, self.SEARCH_SCOPE, filt)
104
105 if not lobjects:
106 raise ldap.NO_SUCH_OBJECT()
90
107
91 properties = server.search_s(dn, ldap.SCOPE_SUBTREE)
108 for (dn, attrs) in lobjects:
92 if not properties:
109 try:
93 raise ldap.NO_SUCH_OBJECT()
110 server.simple_bind_s(dn, password)
111 break
112
113 except ldap.INVALID_CREDENTIALS, e:
114 log.debug("LDAP rejected password for user '%s' (%s): %s", uid, username, dn)
115
116 else:
117 log.debug("No matching LDAP objecs for authentication of '%s' (%s)", uid, username)
118 raise LdapPasswordError()
119
94 except ldap.NO_SUCH_OBJECT, e:
120 except ldap.NO_SUCH_OBJECT, e:
95 log.debug("LDAP says no such user '%s' (%s)", uid, username)
121 log.debug("LDAP says no such user '%s' (%s)", uid, username)
96 raise LdapUsernameError()
122 raise LdapUsernameError()
97 except ldap.INVALID_CREDENTIALS, e:
98 log.debug("LDAP rejected password for user '%s' (%s)", uid, username)
99 raise LdapPasswordError()
100 except ldap.SERVER_DOWN, e:
123 except ldap.SERVER_DOWN, e:
101 raise LdapConnectionError("LDAP can't access authentication server")
124 raise LdapConnectionError("LDAP can't access authentication server")
102
125
103 return properties[0]
126 return (dn, attrs)
104
@@ -336,7 +336,10 b' class DbManage(object):'
336
336
337 try:
337 try:
338 for k in ['ldap_active', 'ldap_host', 'ldap_port', 'ldap_ldaps',
338 for k in ['ldap_active', 'ldap_host', 'ldap_port', 'ldap_ldaps',
339 'ldap_dn_user', 'ldap_dn_pass', 'ldap_base_dn']:
339 'ldap_tls_reqcert', 'ldap_dn_user', 'ldap_dn_pass',
340 'ldap_base_dn', 'ldap_filter', 'ldap_search_scope',
341 'ldap_attr_login', 'ldap_attr_firstname', 'ldap_attr_lastname',
342 'ldap_attr_email']:
340
343
341 setting = RhodeCodeSettings(k, '')
344 setting = RhodeCodeSettings(k, '')
342 self.sa.add(setting)
345 self.sa.add(setting)
@@ -105,7 +105,7 b' class User(Base, BaseModel):'
105 lastname = Column("lastname", String(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
105 lastname = Column("lastname", String(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
106 email = Column("email", String(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
106 email = Column("email", String(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
107 last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None)
107 last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None)
108 is_ldap = Column("is_ldap", Boolean(), nullable=False, unique=None, default=False)
108 ldap_dn = Column("ldap_dn", String(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
109
109
110 user_log = relationship('UserLog', cascade='all')
110 user_log = relationship('UserLog', cascade='all')
111 user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all')
111 user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all')
@@ -334,23 +334,14 b' class LdapLibValidator(formencode.valida'
334 raise LdapImportError
334 raise LdapImportError
335 return value
335 return value
336
336
337 class BaseDnValidator(formencode.validators.FancyValidator):
337 class AttrLoginValidator(formencode.validators.FancyValidator):
338
338
339 def to_python(self, value, state):
339 def to_python(self, value, state):
340
340
341 try:
341 if not value or not isinstance(value, (str, unicode)):
342 value % {'user':'valid'}
342 raise formencode.Invalid(_("The LDAP Login attribute of the CN must be specified "
343
343 "- this is the name of the attribute that is equivalent to 'username'"),
344 if value.find('%(user)s') == -1:
344 value, state)
345 raise formencode.Invalid(_("You need to specify %(user)s in "
346 "template for example uid=%(user)s "
347 ",dc=company...") ,
348 value, state)
349
350 except KeyError:
351 raise formencode.Invalid(_("Wrong template used, only %(user)s "
352 "is an valid entry") ,
353 value, state)
354
345
355 return value
346 return value
356
347
@@ -521,7 +512,7 b' def DefaultPermissionsForm(perms_choices'
521 return _DefaultPermissionsForm
512 return _DefaultPermissionsForm
522
513
523
514
524 def LdapSettingsForm():
515 def LdapSettingsForm(tls_reqcert_choices, search_scope_choices):
525 class _LdapSettingsForm(formencode.Schema):
516 class _LdapSettingsForm(formencode.Schema):
526 allow_extra_fields = True
517 allow_extra_fields = True
527 filter_extra_fields = True
518 filter_extra_fields = True
@@ -530,8 +521,15 b' def LdapSettingsForm():'
530 ldap_host = UnicodeString(strip=True,)
521 ldap_host = UnicodeString(strip=True,)
531 ldap_port = Number(strip=True,)
522 ldap_port = Number(strip=True,)
532 ldap_ldaps = StringBoolean(if_missing=False)
523 ldap_ldaps = StringBoolean(if_missing=False)
524 ldap_tls_reqcert = OneOf(tls_reqcert_choices)
533 ldap_dn_user = UnicodeString(strip=True,)
525 ldap_dn_user = UnicodeString(strip=True,)
534 ldap_dn_pass = UnicodeString(strip=True,)
526 ldap_dn_pass = UnicodeString(strip=True,)
535 ldap_base_dn = All(BaseDnValidator, UnicodeString(strip=True,))
527 ldap_base_dn = UnicodeString(strip=True,)
528 ldap_filter = UnicodeString(strip=True,)
529 ldap_search_scope = OneOf(search_scope_choices)
530 ldap_attr_login = All(AttrLoginValidator, UnicodeString(strip=True,))
531 ldap_attr_firstname = UnicodeString(strip=True,)
532 ldap_attr_lastname = UnicodeString(strip=True,)
533 ldap_attr_email = UnicodeString(strip=True,)
536
534
537 return _LdapSettingsForm
535 return _LdapSettingsForm
@@ -72,10 +72,18 b' class SettingsModel(BaseModel):'
72 ldap_host
72 ldap_host
73 ldap_port
73 ldap_port
74 ldap_ldaps
74 ldap_ldaps
75 ldap_tls_reqcert
75 ldap_dn_user
76 ldap_dn_user
76 ldap_dn_pass
77 ldap_dn_pass
77 ldap_base_dn
78 ldap_base_dn
79 ldap_filter
80 ldap_search_scope
81 ldap_attr_login
82 ldap_attr_firstname
83 ldap_attr_lastname
84 ldap_attr_email
78 """
85 """
86 # ldap_search_scope
79
87
80 r = self.sa.query(RhodeCodeSettings)\
88 r = self.sa.query(RhodeCodeSettings)\
81 .filter(RhodeCodeSettings.app_settings_name\
89 .filter(RhodeCodeSettings.app_settings_name\
@@ -75,25 +75,27 b' class UserModel(BaseModel):'
75 self.sa.rollback()
75 self.sa.rollback()
76 raise
76 raise
77
77
78 def create_ldap(self, username, password):
78 def create_ldap(self, username, password, user_dn, attrs):
79 """
79 """
80 Checks if user is in database, if not creates this user marked
80 Checks if user is in database, if not creates this user marked
81 as ldap user
81 as ldap user
82 :param username:
82 :param username:
83 :param password:
83 :param password:
84 :param user_dn:
85 :param attrs:
84 """
86 """
85 from rhodecode.lib.auth import get_crypt_password
87 from rhodecode.lib.auth import get_crypt_password
86 log.debug('Checking for such ldap account in RhodeCode database')
88 log.debug('Checking for such ldap account in RhodeCode database')
87 if self.get_by_username(username, case_insensitive=True) is None:
89 if self.get_by_username(username, case_insensitive=True) is None:
88 try:
90 try:
89 new_user = User()
91 new_user = User()
90 new_user.username = username.lower()#add ldap account always lowercase
92 new_user.username = username.lower() # add ldap account always lowercase
91 new_user.password = get_crypt_password(password)
93 new_user.password = get_crypt_password(password)
92 new_user.email = '%s@ldap.server' % username
94 new_user.email = attrs['email']
93 new_user.active = True
95 new_user.active = True
94 new_user.is_ldap = True
96 new_user.ldap_dn = user_dn
95 new_user.name = '%s@ldap' % username
97 new_user.name = attrs['name']
96 new_user.lastname = ''
98 new_user.lastname = attrs['lastname']
97
99
98
100
99 self.sa.add(new_user)
101 self.sa.add(new_user)
@@ -21,13 +21,13 b''
21 <div class="title">
21 <div class="title">
22 ${self.breadcrumbs()}
22 ${self.breadcrumbs()}
23 </div>
23 </div>
24 <h3>${_('LDAP administration')}</h3>
25 ${h.form(url('ldap_settings'))}
24 ${h.form(url('ldap_settings'))}
26 <div class="form">
25 <div class="form">
27 <div class="fields">
26 <div class="fields">
28
27
28 <h3>${_('Connection settings')}</h3>
29 <div class="field">
29 <div class="field">
30 <div class="label label-checkbox"><label for="ldap_active">${_('Enable ldap')}</label></div>
30 <div class="label label-checkbox"><label for="ldap_active">${_('Enable LDAP')}</label></div>
31 <div class="checkboxes"><div class="checkbox">${h.checkbox('ldap_active',True,class_='small')}</div></div>
31 <div class="checkboxes"><div class="checkbox">${h.checkbox('ldap_active',True,class_='small')}</div></div>
32 </div>
32 </div>
33 <div class="field">
33 <div class="field">
@@ -39,10 +39,6 b''
39 <div class="input">${h.text('ldap_port',class_='small')}</div>
39 <div class="input">${h.text('ldap_port',class_='small')}</div>
40 </div>
40 </div>
41 <div class="field">
41 <div class="field">
42 <div class="label label-checkbox"><label for="ldap_ldaps">${_('Enable LDAPS')}</label></div>
43 <div class="checkboxes"><div class="checkbox">${h.checkbox('ldap_ldaps',True,class_='small')}</div></div>
44 </div>
45 <div class="field">
46 <div class="label"><label for="ldap_dn_user">${_('Account')}</label></div>
42 <div class="label"><label for="ldap_dn_user">${_('Account')}</label></div>
47 <div class="input">${h.text('ldap_dn_user',class_='small')}</div>
43 <div class="input">${h.text('ldap_dn_user',class_='small')}</div>
48 </div>
44 </div>
@@ -51,9 +47,43 b''
51 <div class="input">${h.password('ldap_dn_pass',class_='small')}</div>
47 <div class="input">${h.password('ldap_dn_pass',class_='small')}</div>
52 </div>
48 </div>
53 <div class="field">
49 <div class="field">
50 <div class="label label-checkbox"><label for="ldap_ldaps">${_('Enable LDAPS')}</label></div>
51 <div class="checkboxes"><div class="checkbox">${h.checkbox('ldap_ldaps',True,class_='small')}</div></div>
52 </div>
53 <div class="field">
54 <div class="label"><label for="ldap_tls_reqcert">${_('Certificate Checks')}</label></div>
55 <div class="select">${h.select('ldap_tls_reqcert',c.tls_reqcert_cur,c.tls_reqcert_choices,class_='small')}</div>
56 </div>
57 <h3>${_('Search settings')}</h3>
58 <div class="field">
54 <div class="label"><label for="ldap_base_dn">${_('Base DN')}</label></div>
59 <div class="label"><label for="ldap_base_dn">${_('Base DN')}</label></div>
55 <div class="input">${h.text('ldap_base_dn',class_='small')}</div>
60 <div class="input">${h.text('ldap_base_dn',class_='small')}</div>
56 </div>
61 </div>
62 <div class="field">
63 <div class="label"><label for="ldap_filter">${_('LDAP Filter')}</label></div>
64 <div class="input">${h.text('ldap_filter',class_='small')}</div>
65 </div>
66 <div class="field">
67 <div class="label"><label for="ldap_search_scope">${_('LDAP Search Scope')}</label></div>
68 <div class="select">${h.select('ldap_search_scope',c.search_scope_cur,c.search_scope_choices,class_='small')}</div>
69 </div>
70 <h3>${_('Attribute mappings')}</h3>
71 <div class="field">
72 <div class="label"><label for="ldap_attr_login">${_('Login Attribute')}</label></div>
73 <div class="input">${h.text('ldap_attr_login',class_='small')}</div>
74 </div>
75 <div class="field">
76 <div class="label"><label for="ldap_attr_firstname">${_('First Name Attribute')}</label></div>
77 <div class="input">${h.text('ldap_attr_firstname',class_='small')}</div>
78 </div>
79 <div class="field">
80 <div class="label"><label for="ldap_attr_lastname">${_('Last Name Attribute')}</label></div>
81 <div class="input">${h.text('ldap_attr_lastname',class_='small')}</div>
82 </div>
83 <div class="field">
84 <div class="label"><label for="ldap_attr_email">${_('E-mail Attribute')}</label></div>
85 <div class="input">${h.text('ldap_attr_email',class_='small')}</div>
86 </div>
57
87
58 <div class="buttons">
88 <div class="buttons">
59 ${h.submit('save','Save',class_="ui-button")}
89 ${h.submit('save','Save',class_="ui-button")}
@@ -49,6 +49,15 b''
49
49
50 <div class="field">
50 <div class="field">
51 <div class="label">
51 <div class="label">
52 <label for="ldap_dn">${_('LDAP DN')}:</label>
53 </div>
54 <div class="input">
55 ${h.text('ldap_dn',class_='small')}
56 </div>
57 </div>
58
59 <div class="field">
60 <div class="label">
52 <label for="new_password">${_('New password')}:</label>
61 <label for="new_password">${_('New password')}:</label>
53 </div>
62 </div>
54 <div class="input">
63 <div class="input">
@@ -231,4 +240,4 b''
231 });
240 });
232 </script>
241 </script>
233 </div>
242 </div>
234 </%def> No newline at end of file
243 </%def>
@@ -49,7 +49,7 b''
49 <td>${user.last_login}</td>
49 <td>${user.last_login}</td>
50 <td>${h.bool2icon(user.active)}</td>
50 <td>${h.bool2icon(user.active)}</td>
51 <td>${h.bool2icon(user.admin)}</td>
51 <td>${h.bool2icon(user.admin)}</td>
52 <td>${h.bool2icon(user.is_ldap)}</td>
52 <td>${h.bool2icon(bool(user.ldap_dn))}</td>
53 <td>
53 <td>
54 ${h.form(url('user', id=user.user_id),method='delete')}
54 ${h.form(url('user', id=user.user_id),method='delete')}
55 ${h.submit('remove_','delete',id="remove_user_%s" % user.user_id,
55 ${h.submit('remove_','delete',id="remove_user_%s" % user.user_id,
General Comments 0
You need to be logged in to leave comments. Login now