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 |
|
|
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( |
|
|
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 |
|
|
|
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 = [' |
|
|
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( |
|
|
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 |
|
|
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 |
|
|
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