##// 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 # set on authenticate() method and via set_auth_type func.
91 # set on authenticate() method and via set_auth_type func.
92 auth_type = None
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 # List of setting names to store encrypted. Plugins may override this list
99 # List of setting names to store encrypted. Plugins may override this list
95 # to store settings encrypted.
100 # to store settings encrypted.
96 _settings_encrypted = []
101 _settings_encrypted = []
@@ -268,6 +273,9 b' class RhodeCodeAuthPluginBase(object):'
268 def set_auth_type(self, auth_type):
273 def set_auth_type(self, auth_type):
269 self.auth_type = auth_type
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 def allows_authentication_from(
279 def allows_authentication_from(
272 self, user, allows_non_existing_user=True,
280 self, user, allows_non_existing_user=True,
273 allowed_auth_plugins=None, allowed_auth_sources=None):
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 def authenticate(username, password, environ=None, auth_type=None,
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 Authentication function used for access control,
533 Authentication function used for access control,
526 It tries to authenticate based on enabled authentication modules.
534 It tries to authenticate based on enabled authentication modules.
@@ -540,6 +548,7 b' def authenticate(username, password, env'
540 authn_registry = get_authn_registry(registry)
548 authn_registry = get_authn_registry(registry)
541 for plugin in authn_registry.get_plugins_for_authentication():
549 for plugin in authn_registry.get_plugins_for_authentication():
542 plugin.set_auth_type(auth_type)
550 plugin.set_auth_type(auth_type)
551 plugin.set_calling_scope_repo(acl_repo_name)
543 user = plugin.get_user(username)
552 user = plugin.get_user(username)
544 display_user = user.username if user else username
553 display_user = user.username if user else username
545
554
@@ -28,7 +28,7 b' from rhodecode.translation import _'
28 from rhodecode.authentication.base import (
28 from rhodecode.authentication.base import (
29 RhodeCodeAuthPluginBase, VCS_TYPE, hybrid_property)
29 RhodeCodeAuthPluginBase, VCS_TYPE, hybrid_property)
30 from rhodecode.authentication.routes import AuthnPluginResourceBase
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 log = logging.getLogger(__name__)
34 log = logging.getLogger(__name__)
@@ -121,8 +121,15 b' class RhodeCodeAuthPlugin(RhodeCodeAuthP'
121
121
122 log.debug('Authenticating user with args %s', user_attrs)
122 log.debug('Authenticating user with args %s', user_attrs)
123 if userobj.active:
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 token_match = userobj.authenticate_by_token(
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 if userobj.username == username and token_match:
134 if userobj.username == username and token_match:
128 log.info(
135 log.info(
@@ -209,11 +209,12 b' def vcs_operation_context('
209 class BasicAuth(AuthBasicAuthenticator):
209 class BasicAuth(AuthBasicAuthenticator):
210
210
211 def __init__(self, realm, authfunc, registry, auth_http_code=None,
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 self.realm = realm
213 self.realm = realm
214 self.initial_call = initial_call_detection
214 self.initial_call = initial_call_detection
215 self.authfunc = authfunc
215 self.authfunc = authfunc
216 self.registry = registry
216 self.registry = registry
217 self.acl_repo_name = acl_repo_name
217 self._rc_auth_http_code = auth_http_code
218 self._rc_auth_http_code = auth_http_code
218
219
219 def _get_response_from_code(self, http_code):
220 def _get_response_from_code(self, http_code):
@@ -247,7 +248,7 b' class BasicAuth(AuthBasicAuthenticator):'
247 username, password = _parts
248 username, password = _parts
248 if self.authfunc(
249 if self.authfunc(
249 username, password, environ, VCS_TYPE,
250 username, password, environ, VCS_TYPE,
250 registry=self.registry):
251 registry=self.registry, acl_repo_name=self.acl_repo_name):
251 return username
252 return username
252 if username and password:
253 if username and password:
253 # we mark that we actually executed authentication once, at
254 # we mark that we actually executed authentication once, at
@@ -360,12 +360,15 b' class SimpleVCS(object):'
360 # try to auth based on environ, container auth methods
360 # try to auth based on environ, container auth methods
361 log.debug('Running PRE-AUTH for container based authentication')
361 log.debug('Running PRE-AUTH for container based authentication')
362 pre_auth = authenticate(
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 if pre_auth and pre_auth.get('username'):
365 if pre_auth and pre_auth.get('username'):
365 username = pre_auth['username']
366 username = pre_auth['username']
366 log.debug('PRE-AUTH got %s as username', username)
367 log.debug('PRE-AUTH got %s as username', username)
367
368
368 # If not authenticated by the container, running basic auth
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 if not username:
372 if not username:
370 self.authenticate.realm = get_rhodecode_realm()
373 self.authenticate.realm = get_rhodecode_realm()
371
374
@@ -623,7 +623,7 b' class User(Base, BaseModel):'
623 UserApiKeys.role == UserApiKeys.ROLE_ALL))
623 UserApiKeys.role == UserApiKeys.ROLE_ALL))
624 return tokens.all()
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 from rhodecode.lib import auth
627 from rhodecode.lib import auth
628
628
629 log.debug('Trying to authenticate user: %s via auth-token, '
629 log.debug('Trying to authenticate user: %s via auth-token, '
@@ -646,6 +646,17 b' class User(Base, BaseModel):'
646 hash_tokens = []
646 hash_tokens = []
647
647
648 for token in tokens_q.all():
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 if token.api_key.startswith(crypto_backend.ENC_PREF):
660 if token.api_key.startswith(crypto_backend.ENC_PREF):
650 hash_tokens.append(token.api_key)
661 hash_tokens.append(token.api_key)
651 else:
662 else:
@@ -656,7 +667,7 b' class User(Base, BaseModel):'
656 return True
667 return True
657
668
658 for hashed in hash_tokens:
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 match = crypto_backend.hash_check(auth_token, hashed)
671 match = crypto_backend.hash_check(auth_token, hashed)
661 if match:
672 if match:
662 return True
673 return True
@@ -132,11 +132,14 b' def repos(request, pylonsapp):'
132
132
133
133
134 @pytest.fixture(scope="module")
134 @pytest.fixture(scope="module")
135 def rc_web_server_config(pylons_config):
135 def rc_web_server_config(testini_factory):
136 """
136 """
137 Configuration file used for the fixture `rc_web_server`.
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 @pytest.fixture(scope="module")
145 @pytest.fixture(scope="module")
@@ -150,7 +153,8 b' def rc_web_server('
150 env = os.environ.copy()
153 env = os.environ.copy()
151 env['RC_NO_TMP_PATH'] = '1'
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 # TODO: Would be great to capture the output and err of the subprocess
159 # TODO: Would be great to capture the output and err of the subprocess
156 # and make it available in a section of the py.test report in case of an
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 host_url = 'http://' + get_host_url(rc_web_server_config)
163 host_url = 'http://' + get_host_url(rc_web_server_config)
160 assert_no_running_instance(host_url)
164 assert_no_running_instance(host_url)
161 command = ['rcserver', rc_web_server_config]
165 command = ['pserve', rc_web_server_config]
162
166
163 print('Starting rcserver: {}'.format(host_url))
167 print('Starting rcserver: {}'.format(host_url))
164 print('Command: {}'.format(command))
168 print('Command: {}'.format(command))
165 print('Logfile: {}'.format(RC_LOG))
169 print('Logfile: {}'.format(rc_log))
166
170
167 proc = subprocess32.Popen(
171 proc = subprocess32.Popen(
168 command, bufsize=0, env=env, stdout=server_out, stderr=server_out)
172 command, bufsize=0, env=env, stdout=server_out, stderr=server_out)
@@ -173,8 +177,9 b' def rc_web_server('
173 def stop_web_server():
177 def stop_web_server():
174 # TODO: Find out how to integrate with the reporting of py.test to
178 # TODO: Find out how to integrate with the reporting of py.test to
175 # make this information available.
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 proc.kill()
181 proc.kill()
182 server_out.flush()
178 server_out.close()
183 server_out.close()
179
184
180 return RcWebServer(rc_web_server_config)
185 return RcWebServer(rc_web_server_config)
@@ -210,12 +215,17 b' def enable_auth_plugins(request, pylonsa'
210 override = override or {}
215 override = override or {}
211 params = {
216 params = {
212 'auth_plugins': ','.join(plugins_list),
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 for module in plugins_list:
225 for module in plugins_list:
217 plugin = rhodecode.authentication.base.loadplugin(module)
226 plugin_name = module.partition('#')[-1]
218 plugin_name = plugin.name
227 if plugin_name in name_map:
228 plugin_name = name_map[plugin_name]
219 enabled_plugin = 'auth_%s_enabled' % plugin_name
229 enabled_plugin = 'auth_%s_enabled' % plugin_name
220 cache_ttl = 'auth_%s_cache_ttl' % plugin_name
230 cache_ttl = 'auth_%s_cache_ttl' % plugin_name
221
231
@@ -35,6 +35,7 b' import pytest'
35
35
36 from rhodecode.lib.vcs.backends.git.repository import GitRepository
36 from rhodecode.lib.vcs.backends.git.repository import GitRepository
37 from rhodecode.lib.vcs.nodes import FileNode
37 from rhodecode.lib.vcs.nodes import FileNode
38 from rhodecode.model.auth_token import AuthTokenModel
38 from rhodecode.model.db import Repository, UserIpMap, CacheKey
39 from rhodecode.model.db import Repository, UserIpMap, CacheKey
39 from rhodecode.model.meta import Session
40 from rhodecode.model.meta import Session
40 from rhodecode.model.user import UserModel
41 from rhodecode.model.user import UserModel
@@ -46,7 +47,7 b' from rhodecode.tests.other.vcs_operation'
46
47
47
48
48 @pytest.mark.usefixtures("disable_locking")
49 @pytest.mark.usefixtures("disable_locking")
49 class TestVCSOperations:
50 class TestVCSOperations(object):
50
51
51 def test_clone_hg_repo_by_admin(self, rc_web_server, tmpdir):
52 def test_clone_hg_repo_by_admin(self, rc_web_server, tmpdir):
52 clone_url = rc_web_server.repo_clone_url(HG_REPO)
53 clone_url = rc_web_server.repo_clone_url(HG_REPO)
@@ -322,6 +323,115 b' class TestVCSOperations:'
322 cmd.assert_returncode_success()
323 cmd.assert_returncode_success()
323 _check_proper_clone(stdout, stderr, 'git')
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 def test_git_sets_default_branch_if_not_master(
436 def test_git_sets_default_branch_if_not_master(
327 backend_git, tmpdir, disable_locking, rc_web_server):
437 backend_git, tmpdir, disable_locking, rc_web_server):
General Comments 0
You need to be logged in to leave comments. Login now