##// END OF EJS Templates
authentication: enabled authentication with auth_token and repository scope....
marcink -
r1510:77606b4c default
parent child Browse files
Show More
@@ -91,6 +91,11 b' class RhodeCodeAuthPluginBase(object):'
91 91 # set on authenticate() method and via set_auth_type func.
92 92 auth_type = None
93 93
94 # set on authenticate() method and via set_calling_scope_repo, this is a
95 # calling scope repository when doing authentication most likely on VCS
96 # operations
97 acl_repo_name = None
98
94 99 # List of setting names to store encrypted. Plugins may override this list
95 100 # to store settings encrypted.
96 101 _settings_encrypted = []
@@ -268,6 +273,9 b' class RhodeCodeAuthPluginBase(object):'
268 273 def set_auth_type(self, auth_type):
269 274 self.auth_type = auth_type
270 275
276 def set_calling_scope_repo(self, acl_repo_name):
277 self.acl_repo_name = acl_repo_name
278
271 279 def allows_authentication_from(
272 280 self, user, allows_non_existing_user=True,
273 281 allowed_auth_plugins=None, allowed_auth_sources=None):
@@ -520,7 +528,7 b' def get_auth_cache_manager(custom_ttl=No'
520 528
521 529
522 530 def authenticate(username, password, environ=None, auth_type=None,
523 skip_missing=False, registry=None):
531 skip_missing=False, registry=None, acl_repo_name=None):
524 532 """
525 533 Authentication function used for access control,
526 534 It tries to authenticate based on enabled authentication modules.
@@ -540,6 +548,7 b' def authenticate(username, password, env'
540 548 authn_registry = get_authn_registry(registry)
541 549 for plugin in authn_registry.get_plugins_for_authentication():
542 550 plugin.set_auth_type(auth_type)
551 plugin.set_calling_scope_repo(acl_repo_name)
543 552 user = plugin.get_user(username)
544 553 display_user = user.username if user else username
545 554
@@ -28,7 +28,7 b' from rhodecode.translation import _'
28 28 from rhodecode.authentication.base import (
29 29 RhodeCodeAuthPluginBase, VCS_TYPE, hybrid_property)
30 30 from rhodecode.authentication.routes import AuthnPluginResourceBase
31 from rhodecode.model.db import User, UserApiKeys
31 from rhodecode.model.db import User, UserApiKeys, Repository
32 32
33 33
34 34 log = logging.getLogger(__name__)
@@ -121,8 +121,15 b' class RhodeCodeAuthPlugin(RhodeCodeAuthP'
121 121
122 122 log.debug('Authenticating user with args %s', user_attrs)
123 123 if userobj.active:
124 # calling context repo for token scopes
125 scope_repo_id = None
126 if self.acl_repo_name:
127 repo = Repository.get_by_repo_name(self.acl_repo_name)
128 scope_repo_id = repo.repo_id if repo else None
129
124 130 token_match = userobj.authenticate_by_token(
125 password, roles=[UserApiKeys.ROLE_VCS])
131 password, roles=[UserApiKeys.ROLE_VCS],
132 scope_repo_id=scope_repo_id)
126 133
127 134 if userobj.username == username and token_match:
128 135 log.info(
@@ -209,11 +209,12 b' def vcs_operation_context('
209 209 class BasicAuth(AuthBasicAuthenticator):
210 210
211 211 def __init__(self, realm, authfunc, registry, auth_http_code=None,
212 initial_call_detection=False):
212 initial_call_detection=False, acl_repo_name=None):
213 213 self.realm = realm
214 214 self.initial_call = initial_call_detection
215 215 self.authfunc = authfunc
216 216 self.registry = registry
217 self.acl_repo_name = acl_repo_name
217 218 self._rc_auth_http_code = auth_http_code
218 219
219 220 def _get_response_from_code(self, http_code):
@@ -247,7 +248,7 b' class BasicAuth(AuthBasicAuthenticator):'
247 248 username, password = _parts
248 249 if self.authfunc(
249 250 username, password, environ, VCS_TYPE,
250 registry=self.registry):
251 registry=self.registry, acl_repo_name=self.acl_repo_name):
251 252 return username
252 253 if username and password:
253 254 # we mark that we actually executed authentication once, at
@@ -360,12 +360,15 b' class SimpleVCS(object):'
360 360 # try to auth based on environ, container auth methods
361 361 log.debug('Running PRE-AUTH for container based authentication')
362 362 pre_auth = authenticate(
363 '', '', environ, VCS_TYPE, registry=self.registry)
363 '', '', environ, VCS_TYPE, registry=self.registry,
364 acl_repo_name=self.acl_repo_name)
364 365 if pre_auth and pre_auth.get('username'):
365 366 username = pre_auth['username']
366 367 log.debug('PRE-AUTH got %s as username', username)
367 368
368 369 # If not authenticated by the container, running basic auth
370 # before inject the calling repo_name for special scope checks
371 self.authenticate.acl_repo_name = self.acl_repo_name
369 372 if not username:
370 373 self.authenticate.realm = get_rhodecode_realm()
371 374
@@ -623,7 +623,7 b' class User(Base, BaseModel):'
623 623 UserApiKeys.role == UserApiKeys.ROLE_ALL))
624 624 return tokens.all()
625 625
626 def authenticate_by_token(self, auth_token, roles=None):
626 def authenticate_by_token(self, auth_token, roles=None, scope_repo_id=None):
627 627 from rhodecode.lib import auth
628 628
629 629 log.debug('Trying to authenticate user: %s via auth-token, '
@@ -646,6 +646,17 b' class User(Base, BaseModel):'
646 646 hash_tokens = []
647 647
648 648 for token in tokens_q.all():
649 # verify scope first
650 if token.repo_id:
651 # token has a scope, we need to verify it
652 if scope_repo_id != token.repo_id:
653 log.debug(
654 'Scope mismatch: token has a set repo scope: %s, '
655 'and calling scope is:%s, skipping further checks',
656 token.repo, scope_repo_id)
657 # token has a scope, and it doesn't match, skip token
658 continue
659
649 660 if token.api_key.startswith(crypto_backend.ENC_PREF):
650 661 hash_tokens.append(token.api_key)
651 662 else:
@@ -656,7 +667,7 b' class User(Base, BaseModel):'
656 667 return True
657 668
658 669 for hashed in hash_tokens:
659 # marcink: this is expensive to calculate, but the most secure
670 # TODO(marcink): this is expensive to calculate, but most secure
660 671 match = crypto_backend.hash_check(auth_token, hashed)
661 672 if match:
662 673 return True
@@ -132,11 +132,14 b' def repos(request, pylonsapp):'
132 132
133 133
134 134 @pytest.fixture(scope="module")
135 def rc_web_server_config(pylons_config):
135 def rc_web_server_config(testini_factory):
136 136 """
137 137 Configuration file used for the fixture `rc_web_server`.
138 138 """
139 return pylons_config
139 CUSTOM_PARAMS = [
140 {'handler_console': {'level': 'DEBUG'}},
141 ]
142 return testini_factory(CUSTOM_PARAMS)
140 143
141 144
142 145 @pytest.fixture(scope="module")
@@ -150,7 +153,8 b' def rc_web_server('
150 153 env = os.environ.copy()
151 154 env['RC_NO_TMP_PATH'] = '1'
152 155
153 server_out = open(RC_LOG, 'w')
156 rc_log = RC_LOG
157 server_out = open(rc_log, 'w')
154 158
155 159 # TODO: Would be great to capture the output and err of the subprocess
156 160 # and make it available in a section of the py.test report in case of an
@@ -158,11 +162,11 b' def rc_web_server('
158 162
159 163 host_url = 'http://' + get_host_url(rc_web_server_config)
160 164 assert_no_running_instance(host_url)
161 command = ['rcserver', rc_web_server_config]
165 command = ['pserve', rc_web_server_config]
162 166
163 167 print('Starting rcserver: {}'.format(host_url))
164 168 print('Command: {}'.format(command))
165 print('Logfile: {}'.format(RC_LOG))
169 print('Logfile: {}'.format(rc_log))
166 170
167 171 proc = subprocess32.Popen(
168 172 command, bufsize=0, env=env, stdout=server_out, stderr=server_out)
@@ -173,8 +177,9 b' def rc_web_server('
173 177 def stop_web_server():
174 178 # TODO: Find out how to integrate with the reporting of py.test to
175 179 # make this information available.
176 print "\nServer log file written to %s" % (RC_LOG, )
180 print("\nServer log file written to %s" % (rc_log, ))
177 181 proc.kill()
182 server_out.flush()
178 183 server_out.close()
179 184
180 185 return RcWebServer(rc_web_server_config)
@@ -210,12 +215,17 b' def enable_auth_plugins(request, pylonsa'
210 215 override = override or {}
211 216 params = {
212 217 'auth_plugins': ','.join(plugins_list),
213 'csrf_token': csrf_token,
218 }
219
220 # helper translate some names to others
221 name_map = {
222 'token': 'authtoken'
214 223 }
215 224
216 225 for module in plugins_list:
217 plugin = rhodecode.authentication.base.loadplugin(module)
218 plugin_name = plugin.name
226 plugin_name = module.partition('#')[-1]
227 if plugin_name in name_map:
228 plugin_name = name_map[plugin_name]
219 229 enabled_plugin = 'auth_%s_enabled' % plugin_name
220 230 cache_ttl = 'auth_%s_cache_ttl' % plugin_name
221 231
@@ -35,6 +35,7 b' import pytest'
35 35
36 36 from rhodecode.lib.vcs.backends.git.repository import GitRepository
37 37 from rhodecode.lib.vcs.nodes import FileNode
38 from rhodecode.model.auth_token import AuthTokenModel
38 39 from rhodecode.model.db import Repository, UserIpMap, CacheKey
39 40 from rhodecode.model.meta import Session
40 41 from rhodecode.model.user import UserModel
@@ -46,7 +47,7 b' from rhodecode.tests.other.vcs_operation'
46 47
47 48
48 49 @pytest.mark.usefixtures("disable_locking")
49 class TestVCSOperations:
50 class TestVCSOperations(object):
50 51
51 52 def test_clone_hg_repo_by_admin(self, rc_web_server, tmpdir):
52 53 clone_url = rc_web_server.repo_clone_url(HG_REPO)
@@ -322,6 +323,115 b' class TestVCSOperations:'
322 323 cmd.assert_returncode_success()
323 324 _check_proper_clone(stdout, stderr, 'git')
324 325
326 def test_clone_by_auth_token(
327 self, rc_web_server, tmpdir, user_util, enable_auth_plugins):
328 enable_auth_plugins(['egg:rhodecode-enterprise-ce#token',
329 'egg:rhodecode-enterprise-ce#rhodecode'])
330
331 user = user_util.create_user()
332 token = user.auth_tokens[1]
333
334 clone_url = rc_web_server.repo_clone_url(
335 HG_REPO, user=user.username, passwd=token)
336
337 stdout, stderr = Command('/tmp').execute(
338 'hg clone', clone_url, tmpdir.strpath)
339 _check_proper_clone(stdout, stderr, 'hg')
340
341 def test_clone_by_auth_token_expired(
342 self, rc_web_server, tmpdir, user_util, enable_auth_plugins):
343 enable_auth_plugins(['egg:rhodecode-enterprise-ce#token',
344 'egg:rhodecode-enterprise-ce#rhodecode'])
345
346 user = user_util.create_user()
347 auth_token = AuthTokenModel().create(
348 user.user_id, 'test-token', -10, AuthTokenModel.cls.ROLE_VCS)
349 token = auth_token.api_key
350
351 clone_url = rc_web_server.repo_clone_url(
352 HG_REPO, user=user.username, passwd=token)
353
354 stdout, stderr = Command('/tmp').execute(
355 'hg clone', clone_url, tmpdir.strpath)
356 assert 'abort: authorization failed' in stderr
357
358 def test_clone_by_auth_token_bad_role(
359 self, rc_web_server, tmpdir, user_util, enable_auth_plugins):
360 enable_auth_plugins(['egg:rhodecode-enterprise-ce#token',
361 'egg:rhodecode-enterprise-ce#rhodecode'])
362
363 user = user_util.create_user()
364 auth_token = AuthTokenModel().create(
365 user.user_id, 'test-token', -1, AuthTokenModel.cls.ROLE_API)
366 token = auth_token.api_key
367
368 clone_url = rc_web_server.repo_clone_url(
369 HG_REPO, user=user.username, passwd=token)
370
371 stdout, stderr = Command('/tmp').execute(
372 'hg clone', clone_url, tmpdir.strpath)
373 assert 'abort: authorization failed' in stderr
374
375 def test_clone_by_auth_token_user_disabled(
376 self, rc_web_server, tmpdir, user_util, enable_auth_plugins):
377 enable_auth_plugins(['egg:rhodecode-enterprise-ce#token',
378 'egg:rhodecode-enterprise-ce#rhodecode'])
379 user = user_util.create_user()
380 user.active = False
381 Session().add(user)
382 Session().commit()
383 token = user.auth_tokens[1]
384
385 clone_url = rc_web_server.repo_clone_url(
386 HG_REPO, user=user.username, passwd=token)
387
388 stdout, stderr = Command('/tmp').execute(
389 'hg clone', clone_url, tmpdir.strpath)
390 assert 'abort: authorization failed' in stderr
391
392
393 def test_clone_by_auth_token_with_scope(
394 self, rc_web_server, tmpdir, user_util, enable_auth_plugins):
395 enable_auth_plugins(['egg:rhodecode-enterprise-ce#token',
396 'egg:rhodecode-enterprise-ce#rhodecode'])
397 user = user_util.create_user()
398 auth_token = AuthTokenModel().create(
399 user.user_id, 'test-token', -1, AuthTokenModel.cls.ROLE_VCS)
400 token = auth_token.api_key
401
402 # manually set scope
403 auth_token.repo = Repository.get_by_repo_name(HG_REPO)
404 Session().add(auth_token)
405 Session().commit()
406
407 clone_url = rc_web_server.repo_clone_url(
408 HG_REPO, user=user.username, passwd=token)
409
410 stdout, stderr = Command('/tmp').execute(
411 'hg clone', clone_url, tmpdir.strpath)
412 _check_proper_clone(stdout, stderr, 'hg')
413
414 def test_clone_by_auth_token_with_wrong_scope(
415 self, rc_web_server, tmpdir, user_util, enable_auth_plugins):
416 enable_auth_plugins(['egg:rhodecode-enterprise-ce#token',
417 'egg:rhodecode-enterprise-ce#rhodecode'])
418 user = user_util.create_user()
419 auth_token = AuthTokenModel().create(
420 user.user_id, 'test-token', -1, AuthTokenModel.cls.ROLE_VCS)
421 token = auth_token.api_key
422
423 # manually set scope
424 auth_token.repo = Repository.get_by_repo_name(GIT_REPO)
425 Session().add(auth_token)
426 Session().commit()
427
428 clone_url = rc_web_server.repo_clone_url(
429 HG_REPO, user=user.username, passwd=token)
430
431 stdout, stderr = Command('/tmp').execute(
432 'hg clone', clone_url, tmpdir.strpath)
433 assert 'abort: authorization failed' in stderr
434
325 435
326 436 def test_git_sets_default_branch_if_not_master(
327 437 backend_git, tmpdir, disable_locking, rc_web_server):
General Comments 0
You need to be logged in to leave comments. Login now