##// END OF EJS Templates
logging: changed module in logs extra as it is a reserved keyword
super-admin -
r4818:6ebd323b default
parent child Browse files
Show More
@@ -1,232 +1,232 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2012-2020 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 import colander
22 22 import logging
23 23
24 24 from rhodecode.translation import _
25 25 from rhodecode.authentication.base import (
26 26 RhodeCodeExternalAuthPlugin, hybrid_property)
27 27 from rhodecode.authentication.schema import AuthnPluginSettingsSchemaBase
28 28 from rhodecode.authentication.routes import AuthnPluginResourceBase
29 29 from rhodecode.lib.colander_utils import strip_whitespace
30 30 from rhodecode.lib.utils2 import str2bool, safe_unicode
31 31 from rhodecode.model.db import User
32 32
33 33
34 34 log = logging.getLogger(__name__)
35 35
36 36
37 37 def plugin_factory(plugin_id, *args, **kwargs):
38 38 """
39 39 Factory function that is called during plugin discovery.
40 40 It returns the plugin instance.
41 41 """
42 42 plugin = RhodeCodeAuthPlugin(plugin_id)
43 43 return plugin
44 44
45 45
46 46 class HeadersAuthnResource(AuthnPluginResourceBase):
47 47 pass
48 48
49 49
50 50 class HeadersSettingsSchema(AuthnPluginSettingsSchemaBase):
51 51 header = colander.SchemaNode(
52 52 colander.String(),
53 53 default='REMOTE_USER',
54 54 description=_('Header to extract the user from'),
55 55 preparer=strip_whitespace,
56 56 title=_('Header'),
57 57 widget='string')
58 58 fallback_header = colander.SchemaNode(
59 59 colander.String(),
60 60 default='HTTP_X_FORWARDED_USER',
61 61 description=_('Header to extract the user from when main one fails'),
62 62 preparer=strip_whitespace,
63 63 title=_('Fallback header'),
64 64 widget='string')
65 65 clean_username = colander.SchemaNode(
66 66 colander.Boolean(),
67 67 default=True,
68 68 description=_('Perform cleaning of user, if passed user has @ in '
69 69 'username then first part before @ is taken. '
70 70 'If there\'s \\ in the username only the part after '
71 71 ' \\ is taken'),
72 72 missing=False,
73 73 title=_('Clean username'),
74 74 widget='bool')
75 75
76 76
77 77 class RhodeCodeAuthPlugin(RhodeCodeExternalAuthPlugin):
78 78 uid = 'headers'
79 79
80 80 def includeme(self, config):
81 81 config.add_authn_plugin(self)
82 82 config.add_authn_resource(self.get_id(), HeadersAuthnResource(self))
83 83 config.add_view(
84 84 'rhodecode.authentication.views.AuthnPluginViewBase',
85 85 attr='settings_get',
86 86 renderer='rhodecode:templates/admin/auth/plugin_settings.mako',
87 87 request_method='GET',
88 88 route_name='auth_home',
89 89 context=HeadersAuthnResource)
90 90 config.add_view(
91 91 'rhodecode.authentication.views.AuthnPluginViewBase',
92 92 attr='settings_post',
93 93 renderer='rhodecode:templates/admin/auth/plugin_settings.mako',
94 94 request_method='POST',
95 95 route_name='auth_home',
96 96 context=HeadersAuthnResource)
97 97
98 98 def get_display_name(self, load_from_settings=False):
99 99 return _('Headers')
100 100
101 101 def get_settings_schema(self):
102 102 return HeadersSettingsSchema()
103 103
104 104 @hybrid_property
105 105 def name(self):
106 106 return u"headers"
107 107
108 108 @property
109 109 def is_headers_auth(self):
110 110 return True
111 111
112 112 def use_fake_password(self):
113 113 return True
114 114
115 115 def user_activation_state(self):
116 116 def_user_perms = User.get_default_user().AuthUser().permissions['global']
117 117 return 'hg.extern_activate.auto' in def_user_perms
118 118
119 119 def _clean_username(self, username):
120 120 # Removing realm and domain from username
121 121 username = username.split('@')[0]
122 122 username = username.rsplit('\\')[-1]
123 123 return username
124 124
125 125 def _get_username(self, environ, settings):
126 126 username = None
127 127 environ = environ or {}
128 128 if not environ:
129 129 log.debug('got empty environ: %s', environ)
130 130
131 131 settings = settings or {}
132 132 if settings.get('header'):
133 133 header = settings.get('header')
134 134 username = environ.get(header)
135 135 log.debug('extracted %s:%s', header, username)
136 136
137 137 # fallback mode
138 138 if not username and settings.get('fallback_header'):
139 139 header = settings.get('fallback_header')
140 140 username = environ.get(header)
141 141 log.debug('extracted %s:%s', header, username)
142 142
143 143 if username and str2bool(settings.get('clean_username')):
144 144 log.debug('Received username `%s` from headers', username)
145 145 username = self._clean_username(username)
146 146 log.debug('New cleanup user is:%s', username)
147 147 return username
148 148
149 149 def get_user(self, username=None, **kwargs):
150 150 """
151 151 Helper method for user fetching in plugins, by default it's using
152 152 simple fetch by username, but this method can be custimized in plugins
153 153 eg. headers auth plugin to fetch user by environ params
154 154 :param username: username if given to fetch
155 155 :param kwargs: extra arguments needed for user fetching.
156 156 """
157 157 environ = kwargs.get('environ') or {}
158 158 settings = kwargs.get('settings') or {}
159 159 username = self._get_username(environ, settings)
160 160 # we got the username, so use default method now
161 161 return super(RhodeCodeAuthPlugin, self).get_user(username)
162 162
163 163 def auth(self, userobj, username, password, settings, **kwargs):
164 164 """
165 165 Get's the headers_auth username (or email). It tries to get username
166 166 from REMOTE_USER if this plugin is enabled, if that fails
167 167 it tries to get username from HTTP_X_FORWARDED_USER if fallback header
168 168 is set. clean_username extracts the username from this data if it's
169 169 having @ in it.
170 170 Return None on failure. On success, return a dictionary of the form:
171 171
172 172 see: RhodeCodeAuthPluginBase.auth_func_attrs
173 173
174 174 :param userobj:
175 175 :param username:
176 176 :param password:
177 177 :param settings:
178 178 :param kwargs:
179 179 """
180 180 environ = kwargs.get('environ')
181 181 if not environ:
182 182 log.debug('Empty environ data skipping...')
183 183 return None
184 184
185 185 if not userobj:
186 186 userobj = self.get_user('', environ=environ, settings=settings)
187 187
188 188 # we don't care passed username/password for headers auth plugins.
189 189 # only way to log in is using environ
190 190 username = None
191 191 if userobj:
192 192 username = getattr(userobj, 'username')
193 193
194 194 if not username:
195 195 # we don't have any objects in DB user doesn't exist extract
196 196 # username from environ based on the settings
197 197 username = self._get_username(environ, settings)
198 198
199 199 # if cannot fetch username, it's a no-go for this plugin to proceed
200 200 if not username:
201 201 return None
202 202
203 203 # old attrs fetched from RhodeCode database
204 204 admin = getattr(userobj, 'admin', False)
205 205 active = getattr(userobj, 'active', True)
206 206 email = getattr(userobj, 'email', '')
207 207 firstname = getattr(userobj, 'firstname', '')
208 208 lastname = getattr(userobj, 'lastname', '')
209 209 extern_type = getattr(userobj, 'extern_type', '')
210 210
211 211 user_attrs = {
212 212 'username': username,
213 213 'firstname': safe_unicode(firstname or username),
214 214 'lastname': safe_unicode(lastname or ''),
215 215 'groups': [],
216 216 'user_group_sync': False,
217 217 'email': email or '',
218 218 'admin': admin or False,
219 219 'active': active,
220 220 'active_from_extern': True,
221 221 'extern_name': username,
222 222 'extern_type': extern_type,
223 223 }
224 224
225 225 log.info('user `%s` authenticated correctly', user_attrs['username'],
226 extra={"action": "user_auth_ok", "module": "auth_headers", "username": user_attrs["username"]})
226 extra={"action": "user_auth_ok", "auth_module": "auth_headers", "username": user_attrs["username"]})
227 227 return user_attrs
228 228
229 229
230 230 def includeme(config):
231 231 plugin_id = 'egg:rhodecode-enterprise-ce#{}'.format(RhodeCodeAuthPlugin.uid)
232 232 plugin_factory(plugin_id).includeme(config)
@@ -1,552 +1,552 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2020 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 """
22 22 RhodeCode authentication plugin for LDAP
23 23 """
24 24
25 25 import logging
26 26 import traceback
27 27
28 28 import colander
29 29 from rhodecode.translation import _
30 30 from rhodecode.authentication.base import (
31 31 RhodeCodeExternalAuthPlugin, AuthLdapBase, hybrid_property)
32 32 from rhodecode.authentication.schema import AuthnPluginSettingsSchemaBase
33 33 from rhodecode.authentication.routes import AuthnPluginResourceBase
34 34 from rhodecode.lib.colander_utils import strip_whitespace
35 35 from rhodecode.lib.exceptions import (
36 36 LdapConnectionError, LdapUsernameError, LdapPasswordError, LdapImportError
37 37 )
38 38 from rhodecode.lib.utils2 import safe_unicode, safe_str
39 39 from rhodecode.model.db import User
40 40 from rhodecode.model.validators import Missing
41 41
42 42 log = logging.getLogger(__name__)
43 43
44 44 try:
45 45 import ldap
46 46 except ImportError:
47 47 # means that python-ldap is not installed, we use Missing object to mark
48 48 # ldap lib is Missing
49 49 ldap = Missing
50 50
51 51
52 52 class LdapError(Exception):
53 53 pass
54 54
55 55
56 56 def plugin_factory(plugin_id, *args, **kwargs):
57 57 """
58 58 Factory function that is called during plugin discovery.
59 59 It returns the plugin instance.
60 60 """
61 61 plugin = RhodeCodeAuthPlugin(plugin_id)
62 62 return plugin
63 63
64 64
65 65 class LdapAuthnResource(AuthnPluginResourceBase):
66 66 pass
67 67
68 68
69 69 class AuthLdap(AuthLdapBase):
70 70 default_tls_cert_dir = '/etc/openldap/cacerts'
71 71
72 72 scope_labels = {
73 73 ldap.SCOPE_BASE: 'SCOPE_BASE',
74 74 ldap.SCOPE_ONELEVEL: 'SCOPE_ONELEVEL',
75 75 ldap.SCOPE_SUBTREE: 'SCOPE_SUBTREE',
76 76 }
77 77
78 78 def __init__(self, server, base_dn, port=389, bind_dn='', bind_pass='',
79 79 tls_kind='PLAIN', tls_reqcert='DEMAND', tls_cert_file=None,
80 80 tls_cert_dir=None, ldap_version=3,
81 81 search_scope='SUBTREE', attr_login='uid',
82 82 ldap_filter='', timeout=None):
83 83 if ldap == Missing:
84 84 raise LdapImportError("Missing or incompatible ldap library")
85 85
86 86 self.debug = False
87 87 self.timeout = timeout or 60 * 5
88 88 self.ldap_version = ldap_version
89 89 self.ldap_server_type = 'ldap'
90 90
91 91 self.TLS_KIND = tls_kind
92 92
93 93 if self.TLS_KIND == 'LDAPS':
94 94 port = port or 636
95 95 self.ldap_server_type += 's'
96 96
97 97 OPT_X_TLS_DEMAND = 2
98 98 self.TLS_REQCERT = getattr(ldap, 'OPT_X_TLS_%s' % tls_reqcert, OPT_X_TLS_DEMAND)
99 99 self.TLS_CERT_FILE = tls_cert_file or ''
100 100 self.TLS_CERT_DIR = tls_cert_dir or self.default_tls_cert_dir
101 101
102 102 # split server into list
103 103 self.SERVER_ADDRESSES = self._get_server_list(server)
104 104 self.LDAP_SERVER_PORT = port
105 105
106 106 # USE FOR READ ONLY BIND TO LDAP SERVER
107 107 self.attr_login = attr_login
108 108
109 109 self.LDAP_BIND_DN = safe_str(bind_dn)
110 110 self.LDAP_BIND_PASS = safe_str(bind_pass)
111 111
112 112 self.SEARCH_SCOPE = getattr(ldap, 'SCOPE_%s' % search_scope)
113 113 self.BASE_DN = safe_str(base_dn)
114 114 self.LDAP_FILTER = safe_str(ldap_filter)
115 115
116 116 def _get_ldap_conn(self):
117 117
118 118 if self.debug:
119 119 ldap.set_option(ldap.OPT_DEBUG_LEVEL, 255)
120 120
121 121 if self.TLS_CERT_FILE and hasattr(ldap, 'OPT_X_TLS_CACERTFILE'):
122 122 ldap.set_option(ldap.OPT_X_TLS_CACERTFILE, self.TLS_CERT_FILE)
123 123
124 124 elif hasattr(ldap, 'OPT_X_TLS_CACERTDIR'):
125 125 ldap.set_option(ldap.OPT_X_TLS_CACERTDIR, self.TLS_CERT_DIR)
126 126
127 127 if self.TLS_KIND != 'PLAIN':
128 128 ldap.set_option(ldap.OPT_X_TLS_REQUIRE_CERT, self.TLS_REQCERT)
129 129
130 130 ldap.set_option(ldap.OPT_REFERRALS, ldap.OPT_OFF)
131 131 ldap.set_option(ldap.OPT_RESTART, ldap.OPT_ON)
132 132
133 133 # init connection now
134 134 ldap_servers = self._build_servers(
135 135 self.ldap_server_type, self.SERVER_ADDRESSES, self.LDAP_SERVER_PORT)
136 136 log.debug('initializing LDAP connection to:%s', ldap_servers)
137 137 ldap_conn = ldap.initialize(ldap_servers)
138 138 ldap_conn.set_option(ldap.OPT_NETWORK_TIMEOUT, self.timeout)
139 139 ldap_conn.set_option(ldap.OPT_TIMEOUT, self.timeout)
140 140 ldap_conn.timeout = self.timeout
141 141
142 142 if self.ldap_version == 2:
143 143 ldap_conn.protocol = ldap.VERSION2
144 144 else:
145 145 ldap_conn.protocol = ldap.VERSION3
146 146
147 147 if self.TLS_KIND == 'START_TLS':
148 148 ldap_conn.start_tls_s()
149 149
150 150 if self.LDAP_BIND_DN and self.LDAP_BIND_PASS:
151 151 log.debug('Trying simple_bind with password and given login DN: %r',
152 152 self.LDAP_BIND_DN)
153 153 ldap_conn.simple_bind_s(self.LDAP_BIND_DN, self.LDAP_BIND_PASS)
154 154 log.debug('simple_bind successful')
155 155 return ldap_conn
156 156
157 157 def fetch_attrs_from_simple_bind(self, ldap_conn, dn, username, password):
158 158 scope = ldap.SCOPE_BASE
159 159 scope_label = self.scope_labels.get(scope)
160 160 ldap_filter = '(objectClass=*)'
161 161
162 162 try:
163 163 log.debug('Trying authenticated search bind with dn: %r SCOPE: %s (and filter: %s)',
164 164 dn, scope_label, ldap_filter)
165 165 ldap_conn.simple_bind_s(dn, safe_str(password))
166 166 response = ldap_conn.search_ext_s(dn, scope, ldap_filter, attrlist=['*', '+'])
167 167
168 168 if not response:
169 169 log.error('search bind returned empty results: %r', response)
170 170 return {}
171 171 else:
172 172 _dn, attrs = response[0]
173 173 return attrs
174 174
175 175 except ldap.INVALID_CREDENTIALS:
176 176 log.debug("LDAP rejected password for user '%s': %s, org_exc:",
177 177 username, dn, exc_info=True)
178 178
179 179 def authenticate_ldap(self, username, password):
180 180 """
181 181 Authenticate a user via LDAP and return his/her LDAP properties.
182 182
183 183 Raises AuthenticationError if the credentials are rejected, or
184 184 EnvironmentError if the LDAP server can't be reached.
185 185
186 186 :param username: username
187 187 :param password: password
188 188 """
189 189
190 190 uid = self.get_uid(username, self.SERVER_ADDRESSES)
191 191 user_attrs = {}
192 192 dn = ''
193 193
194 194 self.validate_password(username, password)
195 195 self.validate_username(username)
196 196 scope_label = self.scope_labels.get(self.SEARCH_SCOPE)
197 197
198 198 ldap_conn = None
199 199 try:
200 200 ldap_conn = self._get_ldap_conn()
201 201 filter_ = '(&%s(%s=%s))' % (
202 202 self.LDAP_FILTER, self.attr_login, username)
203 203 log.debug("Authenticating %r filter %s and scope: %s",
204 204 self.BASE_DN, filter_, scope_label)
205 205
206 206 ldap_objects = ldap_conn.search_ext_s(
207 207 self.BASE_DN, self.SEARCH_SCOPE, filter_, attrlist=['*', '+'])
208 208
209 209 if not ldap_objects:
210 210 log.debug("No matching LDAP objects for authentication "
211 211 "of UID:'%s' username:(%s)", uid, username)
212 212 raise ldap.NO_SUCH_OBJECT()
213 213
214 214 log.debug('Found %s matching ldap object[s], trying to authenticate on each one now...', len(ldap_objects))
215 215 for (dn, _attrs) in ldap_objects:
216 216 if dn is None:
217 217 continue
218 218
219 219 user_attrs = self.fetch_attrs_from_simple_bind(
220 220 ldap_conn, dn, username, password)
221 221
222 222 if user_attrs:
223 223 log.debug('Got authenticated user attributes from DN:%s', dn)
224 224 break
225 225 else:
226 226 raise LdapPasswordError(
227 227 'Failed to authenticate user `{}` with given password'.format(username))
228 228
229 229 except ldap.NO_SUCH_OBJECT:
230 230 log.debug("LDAP says no such user '%s' (%s), org_exc:",
231 231 uid, username, exc_info=True)
232 232 raise LdapUsernameError('Unable to find user')
233 233 except ldap.SERVER_DOWN:
234 234 org_exc = traceback.format_exc()
235 235 raise LdapConnectionError(
236 236 "LDAP can't access authentication server, org_exc:%s" % org_exc)
237 237 finally:
238 238 if ldap_conn:
239 239 log.debug('ldap: connection release')
240 240 try:
241 241 ldap_conn.unbind_s()
242 242 except Exception:
243 243 # for any reason this can raise exception we must catch it
244 244 # to not crush the server
245 245 pass
246 246
247 247 return dn, user_attrs
248 248
249 249
250 250 class LdapSettingsSchema(AuthnPluginSettingsSchemaBase):
251 251 tls_kind_choices = ['PLAIN', 'LDAPS', 'START_TLS']
252 252 tls_reqcert_choices = ['NEVER', 'ALLOW', 'TRY', 'DEMAND', 'HARD']
253 253 search_scope_choices = ['BASE', 'ONELEVEL', 'SUBTREE']
254 254
255 255 host = colander.SchemaNode(
256 256 colander.String(),
257 257 default='',
258 258 description=_('Host[s] of the LDAP Server \n'
259 259 '(e.g., 192.168.2.154, or ldap-server.domain.com.\n '
260 260 'Multiple servers can be specified using commas'),
261 261 preparer=strip_whitespace,
262 262 title=_('LDAP Host'),
263 263 widget='string')
264 264 port = colander.SchemaNode(
265 265 colander.Int(),
266 266 default=389,
267 267 description=_('Custom port that the LDAP server is listening on. '
268 268 'Default value is: 389, use 636 for LDAPS (SSL)'),
269 269 preparer=strip_whitespace,
270 270 title=_('Port'),
271 271 validator=colander.Range(min=0, max=65536),
272 272 widget='int')
273 273
274 274 timeout = colander.SchemaNode(
275 275 colander.Int(),
276 276 default=60 * 5,
277 277 description=_('Timeout for LDAP connection'),
278 278 preparer=strip_whitespace,
279 279 title=_('Connection timeout'),
280 280 validator=colander.Range(min=1),
281 281 widget='int')
282 282
283 283 dn_user = colander.SchemaNode(
284 284 colander.String(),
285 285 default='',
286 286 description=_('Optional user DN/account to connect to LDAP if authentication is required. \n'
287 287 'e.g., cn=admin,dc=mydomain,dc=com, or '
288 288 'uid=root,cn=users,dc=mydomain,dc=com, or admin@mydomain.com'),
289 289 missing='',
290 290 preparer=strip_whitespace,
291 291 title=_('Bind account'),
292 292 widget='string')
293 293 dn_pass = colander.SchemaNode(
294 294 colander.String(),
295 295 default='',
296 296 description=_('Password to authenticate for given user DN.'),
297 297 missing='',
298 298 preparer=strip_whitespace,
299 299 title=_('Bind account password'),
300 300 widget='password')
301 301 tls_kind = colander.SchemaNode(
302 302 colander.String(),
303 303 default=tls_kind_choices[0],
304 304 description=_('TLS Type'),
305 305 title=_('Connection Security'),
306 306 validator=colander.OneOf(tls_kind_choices),
307 307 widget='select')
308 308 tls_reqcert = colander.SchemaNode(
309 309 colander.String(),
310 310 default=tls_reqcert_choices[0],
311 311 description=_('Require Cert over TLS?. Self-signed and custom '
312 312 'certificates can be used when\n `RhodeCode Certificate` '
313 313 'found in admin > settings > system info page is extended.'),
314 314 title=_('Certificate Checks'),
315 315 validator=colander.OneOf(tls_reqcert_choices),
316 316 widget='select')
317 317 tls_cert_file = colander.SchemaNode(
318 318 colander.String(),
319 319 default='',
320 320 description=_('This specifies the PEM-format file path containing '
321 321 'certificates for use in TLS connection.\n'
322 322 'If not specified `TLS Cert dir` will be used'),
323 323 title=_('TLS Cert file'),
324 324 missing='',
325 325 widget='string')
326 326 tls_cert_dir = colander.SchemaNode(
327 327 colander.String(),
328 328 default=AuthLdap.default_tls_cert_dir,
329 329 description=_('This specifies the path of a directory that contains individual '
330 330 'CA certificates in separate files.'),
331 331 title=_('TLS Cert dir'),
332 332 widget='string')
333 333 base_dn = colander.SchemaNode(
334 334 colander.String(),
335 335 default='',
336 336 description=_('Base DN to search. Dynamic bind is supported. Add `$login` marker '
337 337 'in it to be replaced with current user username \n'
338 338 '(e.g., dc=mydomain,dc=com, or ou=Users,dc=mydomain,dc=com)'),
339 339 missing='',
340 340 preparer=strip_whitespace,
341 341 title=_('Base DN'),
342 342 widget='string')
343 343 filter = colander.SchemaNode(
344 344 colander.String(),
345 345 default='',
346 346 description=_('Filter to narrow results \n'
347 347 '(e.g., (&(objectCategory=Person)(objectClass=user)), or \n'
348 348 '(memberof=cn=rc-login,ou=groups,ou=company,dc=mydomain,dc=com)))'),
349 349 missing='',
350 350 preparer=strip_whitespace,
351 351 title=_('LDAP Search Filter'),
352 352 widget='string')
353 353
354 354 search_scope = colander.SchemaNode(
355 355 colander.String(),
356 356 default=search_scope_choices[2],
357 357 description=_('How deep to search LDAP. If unsure set to SUBTREE'),
358 358 title=_('LDAP Search Scope'),
359 359 validator=colander.OneOf(search_scope_choices),
360 360 widget='select')
361 361 attr_login = colander.SchemaNode(
362 362 colander.String(),
363 363 default='uid',
364 364 description=_('LDAP Attribute to map to user name (e.g., uid, or sAMAccountName)'),
365 365 preparer=strip_whitespace,
366 366 title=_('Login Attribute'),
367 367 missing_msg=_('The LDAP Login attribute of the CN must be specified'),
368 368 widget='string')
369 369 attr_email = colander.SchemaNode(
370 370 colander.String(),
371 371 default='',
372 372 description=_('LDAP Attribute to map to email address (e.g., mail).\n'
373 373 'Emails are a crucial part of RhodeCode. \n'
374 374 'If possible add a valid email attribute to ldap users.'),
375 375 missing='',
376 376 preparer=strip_whitespace,
377 377 title=_('Email Attribute'),
378 378 widget='string')
379 379 attr_firstname = colander.SchemaNode(
380 380 colander.String(),
381 381 default='',
382 382 description=_('LDAP Attribute to map to first name (e.g., givenName)'),
383 383 missing='',
384 384 preparer=strip_whitespace,
385 385 title=_('First Name Attribute'),
386 386 widget='string')
387 387 attr_lastname = colander.SchemaNode(
388 388 colander.String(),
389 389 default='',
390 390 description=_('LDAP Attribute to map to last name (e.g., sn)'),
391 391 missing='',
392 392 preparer=strip_whitespace,
393 393 title=_('Last Name Attribute'),
394 394 widget='string')
395 395
396 396
397 397 class RhodeCodeAuthPlugin(RhodeCodeExternalAuthPlugin):
398 398 uid = 'ldap'
399 399 # used to define dynamic binding in the
400 400 DYNAMIC_BIND_VAR = '$login'
401 401 _settings_unsafe_keys = ['dn_pass']
402 402
403 403 def includeme(self, config):
404 404 config.add_authn_plugin(self)
405 405 config.add_authn_resource(self.get_id(), LdapAuthnResource(self))
406 406 config.add_view(
407 407 'rhodecode.authentication.views.AuthnPluginViewBase',
408 408 attr='settings_get',
409 409 renderer='rhodecode:templates/admin/auth/plugin_settings.mako',
410 410 request_method='GET',
411 411 route_name='auth_home',
412 412 context=LdapAuthnResource)
413 413 config.add_view(
414 414 'rhodecode.authentication.views.AuthnPluginViewBase',
415 415 attr='settings_post',
416 416 renderer='rhodecode:templates/admin/auth/plugin_settings.mako',
417 417 request_method='POST',
418 418 route_name='auth_home',
419 419 context=LdapAuthnResource)
420 420
421 421 def get_settings_schema(self):
422 422 return LdapSettingsSchema()
423 423
424 424 def get_display_name(self, load_from_settings=False):
425 425 return _('LDAP')
426 426
427 427 @classmethod
428 428 def docs(cls):
429 429 return "https://docs.rhodecode.com/RhodeCode-Enterprise/auth/auth-ldap.html"
430 430
431 431 @hybrid_property
432 432 def name(self):
433 433 return u"ldap"
434 434
435 435 def use_fake_password(self):
436 436 return True
437 437
438 438 def user_activation_state(self):
439 439 def_user_perms = User.get_default_user().AuthUser().permissions['global']
440 440 return 'hg.extern_activate.auto' in def_user_perms
441 441
442 442 def try_dynamic_binding(self, username, password, current_args):
443 443 """
444 444 Detects marker inside our original bind, and uses dynamic auth if
445 445 present
446 446 """
447 447
448 448 org_bind = current_args['bind_dn']
449 449 passwd = current_args['bind_pass']
450 450
451 451 def has_bind_marker(username):
452 452 if self.DYNAMIC_BIND_VAR in username:
453 453 return True
454 454
455 455 # we only passed in user with "special" variable
456 456 if org_bind and has_bind_marker(org_bind) and not passwd:
457 457 log.debug('Using dynamic user/password binding for ldap '
458 458 'authentication. Replacing `%s` with username',
459 459 self.DYNAMIC_BIND_VAR)
460 460 current_args['bind_dn'] = org_bind.replace(
461 461 self.DYNAMIC_BIND_VAR, username)
462 462 current_args['bind_pass'] = password
463 463
464 464 return current_args
465 465
466 466 def auth(self, userobj, username, password, settings, **kwargs):
467 467 """
468 468 Given a user object (which may be null), username, a plaintext password,
469 469 and a settings object (containing all the keys needed as listed in
470 470 settings()), authenticate this user's login attempt.
471 471
472 472 Return None on failure. On success, return a dictionary of the form:
473 473
474 474 see: RhodeCodeAuthPluginBase.auth_func_attrs
475 475 This is later validated for correctness
476 476 """
477 477
478 478 if not username or not password:
479 479 log.debug('Empty username or password skipping...')
480 480 return None
481 481
482 482 ldap_args = {
483 483 'server': settings.get('host', ''),
484 484 'base_dn': settings.get('base_dn', ''),
485 485 'port': settings.get('port'),
486 486 'bind_dn': settings.get('dn_user'),
487 487 'bind_pass': settings.get('dn_pass'),
488 488 'tls_kind': settings.get('tls_kind'),
489 489 'tls_reqcert': settings.get('tls_reqcert'),
490 490 'tls_cert_file': settings.get('tls_cert_file'),
491 491 'tls_cert_dir': settings.get('tls_cert_dir'),
492 492 'search_scope': settings.get('search_scope'),
493 493 'attr_login': settings.get('attr_login'),
494 494 'ldap_version': 3,
495 495 'ldap_filter': settings.get('filter'),
496 496 'timeout': settings.get('timeout')
497 497 }
498 498
499 499 ldap_attrs = self.try_dynamic_binding(username, password, ldap_args)
500 500
501 501 log.debug('Checking for ldap authentication.')
502 502
503 503 try:
504 504 aldap = AuthLdap(**ldap_args)
505 505 (user_dn, ldap_attrs) = aldap.authenticate_ldap(username, password)
506 506 log.debug('Got ldap DN response %s', user_dn)
507 507
508 508 def get_ldap_attr(k):
509 509 return ldap_attrs.get(settings.get(k), [''])[0]
510 510
511 511 # old attrs fetched from RhodeCode database
512 512 admin = getattr(userobj, 'admin', False)
513 513 active = getattr(userobj, 'active', True)
514 514 email = getattr(userobj, 'email', '')
515 515 username = getattr(userobj, 'username', username)
516 516 firstname = getattr(userobj, 'firstname', '')
517 517 lastname = getattr(userobj, 'lastname', '')
518 518 extern_type = getattr(userobj, 'extern_type', '')
519 519
520 520 groups = []
521 521
522 522 user_attrs = {
523 523 'username': username,
524 524 'firstname': safe_unicode(get_ldap_attr('attr_firstname') or firstname),
525 525 'lastname': safe_unicode(get_ldap_attr('attr_lastname') or lastname),
526 526 'groups': groups,
527 527 'user_group_sync': False,
528 528 'email': get_ldap_attr('attr_email') or email,
529 529 'admin': admin,
530 530 'active': active,
531 531 'active_from_extern': None,
532 532 'extern_name': user_dn,
533 533 'extern_type': extern_type,
534 534 }
535 535
536 536 log.debug('ldap user: %s', user_attrs)
537 537 log.info('user `%s` authenticated correctly', user_attrs['username'],
538 extra={"action": "user_auth_ok", "module": "auth_ldap", "username": user_attrs["username"]})
538 extra={"action": "user_auth_ok", "auth_module": "auth_ldap", "username": user_attrs["username"]})
539 539
540 540 return user_attrs
541 541
542 542 except (LdapUsernameError, LdapPasswordError, LdapImportError):
543 543 log.exception("LDAP related exception")
544 544 return None
545 545 except (Exception,):
546 546 log.exception("Other exception")
547 547 return None
548 548
549 549
550 550 def includeme(config):
551 551 plugin_id = 'egg:rhodecode-enterprise-ce#{}'.format(RhodeCodeAuthPlugin.uid)
552 552 plugin_factory(plugin_id).includeme(config)
@@ -1,172 +1,172 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2012-2020 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 """
22 22 RhodeCode authentication library for PAM
23 23 """
24 24
25 25 import colander
26 26 import grp
27 27 import logging
28 28 import pam
29 29 import pwd
30 30 import re
31 31 import socket
32 32
33 33 from rhodecode.translation import _
34 34 from rhodecode.authentication.base import (
35 35 RhodeCodeExternalAuthPlugin, hybrid_property)
36 36 from rhodecode.authentication.schema import AuthnPluginSettingsSchemaBase
37 37 from rhodecode.authentication.routes import AuthnPluginResourceBase
38 38 from rhodecode.lib.colander_utils import strip_whitespace
39 39
40 40 log = logging.getLogger(__name__)
41 41
42 42
43 43 def plugin_factory(plugin_id, *args, **kwargs):
44 44 """
45 45 Factory function that is called during plugin discovery.
46 46 It returns the plugin instance.
47 47 """
48 48 plugin = RhodeCodeAuthPlugin(plugin_id)
49 49 return plugin
50 50
51 51
52 52 class PamAuthnResource(AuthnPluginResourceBase):
53 53 pass
54 54
55 55
56 56 class PamSettingsSchema(AuthnPluginSettingsSchemaBase):
57 57 service = colander.SchemaNode(
58 58 colander.String(),
59 59 default='login',
60 60 description=_('PAM service name to use for authentication.'),
61 61 preparer=strip_whitespace,
62 62 title=_('PAM service name'),
63 63 widget='string')
64 64 gecos = colander.SchemaNode(
65 65 colander.String(),
66 66 default='(?P<last_name>.+),\s*(?P<first_name>\w+)',
67 67 description=_('Regular expression for extracting user name/email etc. '
68 68 'from Unix userinfo.'),
69 69 preparer=strip_whitespace,
70 70 title=_('Gecos Regex'),
71 71 widget='string')
72 72
73 73
74 74 class RhodeCodeAuthPlugin(RhodeCodeExternalAuthPlugin):
75 75 uid = 'pam'
76 76 # PAM authentication can be slow. Repository operations involve a lot of
77 77 # auth calls. Little caching helps speedup push/pull operations significantly
78 78 AUTH_CACHE_TTL = 4
79 79
80 80 def includeme(self, config):
81 81 config.add_authn_plugin(self)
82 82 config.add_authn_resource(self.get_id(), PamAuthnResource(self))
83 83 config.add_view(
84 84 'rhodecode.authentication.views.AuthnPluginViewBase',
85 85 attr='settings_get',
86 86 renderer='rhodecode:templates/admin/auth/plugin_settings.mako',
87 87 request_method='GET',
88 88 route_name='auth_home',
89 89 context=PamAuthnResource)
90 90 config.add_view(
91 91 'rhodecode.authentication.views.AuthnPluginViewBase',
92 92 attr='settings_post',
93 93 renderer='rhodecode:templates/admin/auth/plugin_settings.mako',
94 94 request_method='POST',
95 95 route_name='auth_home',
96 96 context=PamAuthnResource)
97 97
98 98 def get_display_name(self, load_from_settings=False):
99 99 return _('PAM')
100 100
101 101 @classmethod
102 102 def docs(cls):
103 103 return "https://docs.rhodecode.com/RhodeCode-Enterprise/auth/auth-pam.html"
104 104
105 105 @hybrid_property
106 106 def name(self):
107 107 return u"pam"
108 108
109 109 def get_settings_schema(self):
110 110 return PamSettingsSchema()
111 111
112 112 def use_fake_password(self):
113 113 return True
114 114
115 115 def auth(self, userobj, username, password, settings, **kwargs):
116 116 if not username or not password:
117 117 log.debug('Empty username or password skipping...')
118 118 return None
119 119 _pam = pam.pam()
120 120 auth_result = _pam.authenticate(username, password, settings["service"])
121 121
122 122 if not auth_result:
123 123 log.error("PAM was unable to authenticate user: %s", username)
124 124 return None
125 125
126 126 log.debug('Got PAM response %s', auth_result)
127 127
128 128 # old attrs fetched from RhodeCode database
129 129 default_email = "%s@%s" % (username, socket.gethostname())
130 130 admin = getattr(userobj, 'admin', False)
131 131 active = getattr(userobj, 'active', True)
132 132 email = getattr(userobj, 'email', '') or default_email
133 133 username = getattr(userobj, 'username', username)
134 134 firstname = getattr(userobj, 'firstname', '')
135 135 lastname = getattr(userobj, 'lastname', '')
136 136 extern_type = getattr(userobj, 'extern_type', '')
137 137
138 138 user_attrs = {
139 139 'username': username,
140 140 'firstname': firstname,
141 141 'lastname': lastname,
142 142 'groups': [g.gr_name for g in grp.getgrall()
143 143 if username in g.gr_mem],
144 144 'user_group_sync': True,
145 145 'email': email,
146 146 'admin': admin,
147 147 'active': active,
148 148 'active_from_extern': None,
149 149 'extern_name': username,
150 150 'extern_type': extern_type,
151 151 }
152 152
153 153 try:
154 154 user_data = pwd.getpwnam(username)
155 155 regex = settings["gecos"]
156 156 match = re.search(regex, user_data.pw_gecos)
157 157 if match:
158 158 user_attrs["firstname"] = match.group('first_name')
159 159 user_attrs["lastname"] = match.group('last_name')
160 160 except Exception:
161 161 log.warning("Cannot extract additional info for PAM user")
162 162 pass
163 163
164 164 log.debug("pamuser: %s", user_attrs)
165 165 log.info('user `%s` authenticated correctly', user_attrs['username'],
166 extra={"action": "user_auth_ok", "module": "auth_pam", "username": user_attrs["username"]})
166 extra={"action": "user_auth_ok", "auth_module": "auth_pam", "username": user_attrs["username"]})
167 167 return user_attrs
168 168
169 169
170 170 def includeme(config):
171 171 plugin_id = 'egg:rhodecode-enterprise-ce#{}'.format(RhodeCodeAuthPlugin.uid)
172 172 plugin_factory(plugin_id).includeme(config)
@@ -1,222 +1,222 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2012-2020 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 """
22 22 RhodeCode authentication plugin for built in internal auth
23 23 """
24 24
25 25 import logging
26 26
27 27 import colander
28 28
29 29 from rhodecode.translation import _
30 30 from rhodecode.lib.utils2 import safe_str
31 31 from rhodecode.model.db import User
32 32 from rhodecode.authentication.schema import AuthnPluginSettingsSchemaBase
33 33 from rhodecode.authentication.base import (
34 34 RhodeCodeAuthPluginBase, hybrid_property, HTTP_TYPE, VCS_TYPE)
35 35 from rhodecode.authentication.routes import AuthnPluginResourceBase
36 36
37 37 log = logging.getLogger(__name__)
38 38
39 39
40 40 def plugin_factory(plugin_id, *args, **kwargs):
41 41 plugin = RhodeCodeAuthPlugin(plugin_id)
42 42 return plugin
43 43
44 44
45 45 class RhodecodeAuthnResource(AuthnPluginResourceBase):
46 46 pass
47 47
48 48
49 49 class RhodeCodeAuthPlugin(RhodeCodeAuthPluginBase):
50 50 uid = 'rhodecode'
51 51 AUTH_RESTRICTION_NONE = 'user_all'
52 52 AUTH_RESTRICTION_SUPER_ADMIN = 'user_super_admin'
53 53 AUTH_RESTRICTION_SCOPE_ALL = 'scope_all'
54 54 AUTH_RESTRICTION_SCOPE_HTTP = 'scope_http'
55 55 AUTH_RESTRICTION_SCOPE_VCS = 'scope_vcs'
56 56
57 57 def includeme(self, config):
58 58 config.add_authn_plugin(self)
59 59 config.add_authn_resource(self.get_id(), RhodecodeAuthnResource(self))
60 60 config.add_view(
61 61 'rhodecode.authentication.views.AuthnPluginViewBase',
62 62 attr='settings_get',
63 63 renderer='rhodecode:templates/admin/auth/plugin_settings.mako',
64 64 request_method='GET',
65 65 route_name='auth_home',
66 66 context=RhodecodeAuthnResource)
67 67 config.add_view(
68 68 'rhodecode.authentication.views.AuthnPluginViewBase',
69 69 attr='settings_post',
70 70 renderer='rhodecode:templates/admin/auth/plugin_settings.mako',
71 71 request_method='POST',
72 72 route_name='auth_home',
73 73 context=RhodecodeAuthnResource)
74 74
75 75 def get_settings_schema(self):
76 76 return RhodeCodeSettingsSchema()
77 77
78 78 def get_display_name(self, load_from_settings=False):
79 79 return _('RhodeCode Internal')
80 80
81 81 @classmethod
82 82 def docs(cls):
83 83 return "https://docs.rhodecode.com/RhodeCode-Enterprise/auth/auth.html"
84 84
85 85 @hybrid_property
86 86 def name(self):
87 87 return u"rhodecode"
88 88
89 89 def user_activation_state(self):
90 90 def_user_perms = User.get_default_user().AuthUser().permissions['global']
91 91 return 'hg.register.auto_activate' in def_user_perms
92 92
93 93 def allows_authentication_from(
94 94 self, user, allows_non_existing_user=True,
95 95 allowed_auth_plugins=None, allowed_auth_sources=None):
96 96 """
97 97 Custom method for this auth that doesn't accept non existing users.
98 98 We know that user exists in our database.
99 99 """
100 100 allows_non_existing_user = False
101 101 return super(RhodeCodeAuthPlugin, self).allows_authentication_from(
102 102 user, allows_non_existing_user=allows_non_existing_user)
103 103
104 104 def auth(self, userobj, username, password, settings, **kwargs):
105 105 if not userobj:
106 106 log.debug('userobj was:%s skipping', userobj)
107 107 return None
108 108
109 109 if userobj.extern_type != self.name:
110 110 log.warning("userobj:%s extern_type mismatch got:`%s` expected:`%s`",
111 111 userobj, userobj.extern_type, self.name)
112 112 return None
113 113
114 114 # check scope of auth
115 115 scope_restriction = settings.get('scope_restriction', '')
116 116
117 117 if scope_restriction == self.AUTH_RESTRICTION_SCOPE_HTTP \
118 118 and self.auth_type != HTTP_TYPE:
119 119 log.warning("userobj:%s tried scope type %s and scope restriction is set to %s",
120 120 userobj, self.auth_type, scope_restriction)
121 121 return None
122 122
123 123 if scope_restriction == self.AUTH_RESTRICTION_SCOPE_VCS \
124 124 and self.auth_type != VCS_TYPE:
125 125 log.warning("userobj:%s tried scope type %s and scope restriction is set to %s",
126 126 userobj, self.auth_type, scope_restriction)
127 127 return None
128 128
129 129 # check super-admin restriction
130 130 auth_restriction = settings.get('auth_restriction', '')
131 131
132 132 if auth_restriction == self.AUTH_RESTRICTION_SUPER_ADMIN \
133 133 and userobj.admin is False:
134 134 log.warning("userobj:%s is not super-admin and auth restriction is set to %s",
135 135 userobj, auth_restriction)
136 136 return None
137 137
138 138 user_attrs = {
139 139 "username": userobj.username,
140 140 "firstname": userobj.firstname,
141 141 "lastname": userobj.lastname,
142 142 "groups": [],
143 143 'user_group_sync': False,
144 144 "email": userobj.email,
145 145 "admin": userobj.admin,
146 146 "active": userobj.active,
147 147 "active_from_extern": userobj.active,
148 148 "extern_name": userobj.user_id,
149 149 "extern_type": userobj.extern_type,
150 150 }
151 151
152 152 log.debug("User attributes:%s", user_attrs)
153 153 if userobj.active:
154 154 from rhodecode.lib import auth
155 155 crypto_backend = auth.crypto_backend()
156 156 password_encoded = safe_str(password)
157 157 password_match, new_hash = crypto_backend.hash_check_with_upgrade(
158 158 password_encoded, userobj.password or '')
159 159
160 160 if password_match and new_hash:
161 161 log.debug('user %s properly authenticated, but '
162 162 'requires hash change to bcrypt', userobj)
163 163 # if password match, and we use OLD deprecated hash,
164 164 # we should migrate this user hash password to the new hash
165 165 # we store the new returned by hash_check_with_upgrade function
166 166 user_attrs['_hash_migrate'] = new_hash
167 167
168 168 if userobj.username == User.DEFAULT_USER and userobj.active:
169 169 log.info('user `%s` authenticated correctly as anonymous user',
170 170 userobj.username,
171 extra={"action": "user_auth_ok", "module": "auth_rhodecode_anon", "username": userobj.username})
171 extra={"action": "user_auth_ok", "auth_module": "auth_rhodecode_anon", "username": userobj.username})
172 172 return user_attrs
173 173
174 174 elif userobj.username == username and password_match:
175 175 log.info('user `%s` authenticated correctly', userobj.username,
176 extra={"action": "user_auth_ok", "module": "auth_rhodecode", "username": userobj.username})
176 extra={"action": "user_auth_ok", "auth_module": "auth_rhodecode", "username": userobj.username})
177 177 return user_attrs
178 178 log.warning("user `%s` used a wrong password when "
179 179 "authenticating on this plugin", userobj.username)
180 180 return None
181 181 else:
182 182 log.warning('user `%s` failed to authenticate via %s, reason: account not '
183 183 'active.', username, self.name)
184 184 return None
185 185
186 186
187 187 class RhodeCodeSettingsSchema(AuthnPluginSettingsSchemaBase):
188 188
189 189 auth_restriction_choices = [
190 190 (RhodeCodeAuthPlugin.AUTH_RESTRICTION_NONE, 'All users'),
191 191 (RhodeCodeAuthPlugin.AUTH_RESTRICTION_SUPER_ADMIN, 'Super admins only'),
192 192 ]
193 193
194 194 auth_scope_choices = [
195 195 (RhodeCodeAuthPlugin.AUTH_RESTRICTION_SCOPE_ALL, 'HTTP and VCS'),
196 196 (RhodeCodeAuthPlugin.AUTH_RESTRICTION_SCOPE_HTTP, 'HTTP only'),
197 197 ]
198 198
199 199 auth_restriction = colander.SchemaNode(
200 200 colander.String(),
201 201 default=auth_restriction_choices[0],
202 202 description=_('Allowed user types for authentication using this plugin.'),
203 203 title=_('User restriction'),
204 204 validator=colander.OneOf([x[0] for x in auth_restriction_choices]),
205 205 widget='select_with_labels',
206 206 choices=auth_restriction_choices
207 207 )
208 208 scope_restriction = colander.SchemaNode(
209 209 colander.String(),
210 210 default=auth_scope_choices[0],
211 211 description=_('Allowed protocols for authentication using this plugin. '
212 212 'VCS means GIT/HG/SVN. HTTP is web based login.'),
213 213 title=_('Scope restriction'),
214 214 validator=colander.OneOf([x[0] for x in auth_scope_choices]),
215 215 widget='select_with_labels',
216 216 choices=auth_scope_choices
217 217 )
218 218
219 219
220 220 def includeme(config):
221 221 plugin_id = 'egg:rhodecode-enterprise-ce#{}'.format(RhodeCodeAuthPlugin.uid)
222 222 plugin_factory(plugin_id).includeme(config)
General Comments 0
You need to be logged in to leave comments. Login now