##// END OF EJS Templates
auth: allow binding the whitelist views to specific tokens
marcink -
r1995:11838527 default
parent child Browse files
Show More
@@ -1,529 +1,554 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2017 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 urlparse
22 22
23 23 import mock
24 24 import pytest
25 25
26 26 from rhodecode.tests import (
27 27 assert_session_flash, HG_REPO, TEST_USER_ADMIN_LOGIN,
28 28 no_newline_id_generator)
29 29 from rhodecode.tests.fixture import Fixture
30 30 from rhodecode.lib.auth import check_password
31 31 from rhodecode.lib import helpers as h
32 32 from rhodecode.model.auth_token import AuthTokenModel
33 33 from rhodecode.model import validators
34 34 from rhodecode.model.db import User, Notification, UserApiKeys
35 35 from rhodecode.model.meta import Session
36 36
37 37 fixture = Fixture()
38 38
39 39 whitelist_view = ['RepoCommitsView:repo_commit_raw']
40 40
41 41
42 42 def route_path(name, params=None, **kwargs):
43 43 import urllib
44 44 from rhodecode.apps._base import ADMIN_PREFIX
45 45
46 46 base_url = {
47 47 'login': ADMIN_PREFIX + '/login',
48 48 'logout': ADMIN_PREFIX + '/logout',
49 49 'register': ADMIN_PREFIX + '/register',
50 50 'reset_password':
51 51 ADMIN_PREFIX + '/password_reset',
52 52 'reset_password_confirmation':
53 53 ADMIN_PREFIX + '/password_reset_confirmation',
54 54
55 55 'admin_permissions_application':
56 56 ADMIN_PREFIX + '/permissions/application',
57 57 'admin_permissions_application_update':
58 58 ADMIN_PREFIX + '/permissions/application/update',
59 59
60 60 'repo_commit_raw': '/{repo_name}/raw-changeset/{commit_id}'
61 61
62 62 }[name].format(**kwargs)
63 63
64 64 if params:
65 65 base_url = '{}?{}'.format(base_url, urllib.urlencode(params))
66 66 return base_url
67 67
68 68
69 69 @pytest.mark.usefixtures('app')
70 70 class TestLoginController(object):
71 71 destroy_users = set()
72 72
73 73 @classmethod
74 74 def teardown_class(cls):
75 75 fixture.destroy_users(cls.destroy_users)
76 76
77 77 def teardown_method(self, method):
78 78 for n in Notification.query().all():
79 79 Session().delete(n)
80 80
81 81 Session().commit()
82 82 assert Notification.query().all() == []
83 83
84 84 def test_index(self):
85 85 response = self.app.get(route_path('login'))
86 86 assert response.status == '200 OK'
87 87 # Test response...
88 88
89 89 def test_login_admin_ok(self):
90 90 response = self.app.post(route_path('login'),
91 91 {'username': 'test_admin',
92 92 'password': 'test12'})
93 93 assert response.status == '302 Found'
94 94 session = response.get_session_from_response()
95 95 username = session['rhodecode_user'].get('username')
96 96 assert username == 'test_admin'
97 97 response = response.follow()
98 98 response.mustcontain('/%s' % HG_REPO)
99 99
100 100 def test_login_regular_ok(self):
101 101 response = self.app.post(route_path('login'),
102 102 {'username': 'test_regular',
103 103 'password': 'test12'})
104 104
105 105 assert response.status == '302 Found'
106 106 session = response.get_session_from_response()
107 107 username = session['rhodecode_user'].get('username')
108 108 assert username == 'test_regular'
109 109 response = response.follow()
110 110 response.mustcontain('/%s' % HG_REPO)
111 111
112 112 def test_login_ok_came_from(self):
113 113 test_came_from = '/_admin/users?branch=stable'
114 114 _url = '{}?came_from={}'.format(route_path('login'), test_came_from)
115 115 response = self.app.post(
116 116 _url, {'username': 'test_admin', 'password': 'test12'})
117 117 assert response.status == '302 Found'
118 118 assert 'branch=stable' in response.location
119 119 response = response.follow()
120 120
121 121 assert response.status == '200 OK'
122 122 response.mustcontain('Users administration')
123 123
124 124 def test_redirect_to_login_with_get_args(self):
125 125 with fixture.anon_access(False):
126 126 kwargs = {'branch': 'stable'}
127 127 response = self.app.get(
128 128 h.route_path('repo_summary', repo_name=HG_REPO, _query=kwargs))
129 129 assert response.status == '302 Found'
130 130
131 131 response_query = urlparse.parse_qsl(response.location)
132 132 assert 'branch=stable' in response_query[0][1]
133 133
134 134 def test_login_form_with_get_args(self):
135 135 _url = '{}?came_from=/_admin/users,branch=stable'.format(route_path('login'))
136 136 response = self.app.get(_url)
137 137 assert 'branch%3Dstable' in response.form.action
138 138
139 139 @pytest.mark.parametrize("url_came_from", [
140 140 'data:text/html,<script>window.alert("xss")</script>',
141 141 'mailto:test@rhodecode.org',
142 142 'file:///etc/passwd',
143 143 'ftp://some.ftp.server',
144 144 'http://other.domain',
145 145 '/\r\nX-Forwarded-Host: http://example.org',
146 146 ], ids=no_newline_id_generator)
147 147 def test_login_bad_came_froms(self, url_came_from):
148 148 _url = '{}?came_from={}'.format(route_path('login'), url_came_from)
149 149 response = self.app.post(
150 150 _url,
151 151 {'username': 'test_admin', 'password': 'test12'})
152 152 assert response.status == '302 Found'
153 153 response = response.follow()
154 154 assert response.status == '200 OK'
155 155 assert response.request.path == '/'
156 156
157 157 def test_login_short_password(self):
158 158 response = self.app.post(route_path('login'),
159 159 {'username': 'test_admin',
160 160 'password': 'as'})
161 161 assert response.status == '200 OK'
162 162
163 163 response.mustcontain('Enter 3 characters or more')
164 164
165 165 def test_login_wrong_non_ascii_password(self, user_regular):
166 166 response = self.app.post(
167 167 route_path('login'),
168 168 {'username': user_regular.username,
169 169 'password': u'invalid-non-asci\xe4'.encode('utf8')})
170 170
171 171 response.mustcontain('invalid user name')
172 172 response.mustcontain('invalid password')
173 173
174 174 def test_login_with_non_ascii_password(self, user_util):
175 175 password = u'valid-non-ascii\xe4'
176 176 user = user_util.create_user(password=password)
177 177 response = self.app.post(
178 178 route_path('login'),
179 179 {'username': user.username,
180 180 'password': password.encode('utf-8')})
181 181 assert response.status_code == 302
182 182
183 183 def test_login_wrong_username_password(self):
184 184 response = self.app.post(route_path('login'),
185 185 {'username': 'error',
186 186 'password': 'test12'})
187 187
188 188 response.mustcontain('invalid user name')
189 189 response.mustcontain('invalid password')
190 190
191 191 def test_login_admin_ok_password_migration(self, real_crypto_backend):
192 192 from rhodecode.lib import auth
193 193
194 194 # create new user, with sha256 password
195 195 temp_user = 'test_admin_sha256'
196 196 user = fixture.create_user(temp_user)
197 197 user.password = auth._RhodeCodeCryptoSha256().hash_create(
198 198 b'test123')
199 199 Session().add(user)
200 200 Session().commit()
201 201 self.destroy_users.add(temp_user)
202 202 response = self.app.post(route_path('login'),
203 203 {'username': temp_user,
204 204 'password': 'test123'})
205 205
206 206 assert response.status == '302 Found'
207 207 session = response.get_session_from_response()
208 208 username = session['rhodecode_user'].get('username')
209 209 assert username == temp_user
210 210 response = response.follow()
211 211 response.mustcontain('/%s' % HG_REPO)
212 212
213 213 # new password should be bcrypted, after log-in and transfer
214 214 user = User.get_by_username(temp_user)
215 215 assert user.password.startswith('$')
216 216
217 217 # REGISTRATIONS
218 218 def test_register(self):
219 219 response = self.app.get(route_path('register'))
220 220 response.mustcontain('Create an Account')
221 221
222 222 def test_register_err_same_username(self):
223 223 uname = 'test_admin'
224 224 response = self.app.post(
225 225 route_path('register'),
226 226 {
227 227 'username': uname,
228 228 'password': 'test12',
229 229 'password_confirmation': 'test12',
230 230 'email': 'goodmail@domain.com',
231 231 'firstname': 'test',
232 232 'lastname': 'test'
233 233 }
234 234 )
235 235
236 236 assertr = response.assert_response()
237 237 msg = validators.ValidUsername()._messages['username_exists']
238 238 msg = msg % {'username': uname}
239 239 assertr.element_contains('#username+.error-message', msg)
240 240
241 241 def test_register_err_same_email(self):
242 242 response = self.app.post(
243 243 route_path('register'),
244 244 {
245 245 'username': 'test_admin_0',
246 246 'password': 'test12',
247 247 'password_confirmation': 'test12',
248 248 'email': 'test_admin@mail.com',
249 249 'firstname': 'test',
250 250 'lastname': 'test'
251 251 }
252 252 )
253 253
254 254 assertr = response.assert_response()
255 255 msg = validators.UniqSystemEmail()()._messages['email_taken']
256 256 assertr.element_contains('#email+.error-message', msg)
257 257
258 258 def test_register_err_same_email_case_sensitive(self):
259 259 response = self.app.post(
260 260 route_path('register'),
261 261 {
262 262 'username': 'test_admin_1',
263 263 'password': 'test12',
264 264 'password_confirmation': 'test12',
265 265 'email': 'TesT_Admin@mail.COM',
266 266 'firstname': 'test',
267 267 'lastname': 'test'
268 268 }
269 269 )
270 270 assertr = response.assert_response()
271 271 msg = validators.UniqSystemEmail()()._messages['email_taken']
272 272 assertr.element_contains('#email+.error-message', msg)
273 273
274 274 def test_register_err_wrong_data(self):
275 275 response = self.app.post(
276 276 route_path('register'),
277 277 {
278 278 'username': 'xs',
279 279 'password': 'test',
280 280 'password_confirmation': 'test',
281 281 'email': 'goodmailm',
282 282 'firstname': 'test',
283 283 'lastname': 'test'
284 284 }
285 285 )
286 286 assert response.status == '200 OK'
287 287 response.mustcontain('An email address must contain a single @')
288 288 response.mustcontain('Enter a value 6 characters long or more')
289 289
290 290 def test_register_err_username(self):
291 291 response = self.app.post(
292 292 route_path('register'),
293 293 {
294 294 'username': 'error user',
295 295 'password': 'test12',
296 296 'password_confirmation': 'test12',
297 297 'email': 'goodmailm',
298 298 'firstname': 'test',
299 299 'lastname': 'test'
300 300 }
301 301 )
302 302
303 303 response.mustcontain('An email address must contain a single @')
304 304 response.mustcontain(
305 305 'Username may only contain '
306 306 'alphanumeric characters underscores, '
307 307 'periods or dashes and must begin with '
308 308 'alphanumeric character')
309 309
310 310 def test_register_err_case_sensitive(self):
311 311 usr = 'Test_Admin'
312 312 response = self.app.post(
313 313 route_path('register'),
314 314 {
315 315 'username': usr,
316 316 'password': 'test12',
317 317 'password_confirmation': 'test12',
318 318 'email': 'goodmailm',
319 319 'firstname': 'test',
320 320 'lastname': 'test'
321 321 }
322 322 )
323 323
324 324 assertr = response.assert_response()
325 325 msg = validators.ValidUsername()._messages['username_exists']
326 326 msg = msg % {'username': usr}
327 327 assertr.element_contains('#username+.error-message', msg)
328 328
329 329 def test_register_special_chars(self):
330 330 response = self.app.post(
331 331 route_path('register'),
332 332 {
333 333 'username': 'xxxaxn',
334 334 'password': 'Δ…Δ‡ΕΊΕΌΔ…Ε›Ε›Ε›Ε›',
335 335 'password_confirmation': 'Δ…Δ‡ΕΊΕΌΔ…Ε›Ε›Ε›Ε›',
336 336 'email': 'goodmailm@test.plx',
337 337 'firstname': 'test',
338 338 'lastname': 'test'
339 339 }
340 340 )
341 341
342 342 msg = validators.ValidPassword()._messages['invalid_password']
343 343 response.mustcontain(msg)
344 344
345 345 def test_register_password_mismatch(self):
346 346 response = self.app.post(
347 347 route_path('register'),
348 348 {
349 349 'username': 'xs',
350 350 'password': '123qwe',
351 351 'password_confirmation': 'qwe123',
352 352 'email': 'goodmailm@test.plxa',
353 353 'firstname': 'test',
354 354 'lastname': 'test'
355 355 }
356 356 )
357 357 msg = validators.ValidPasswordsMatch()._messages['password_mismatch']
358 358 response.mustcontain(msg)
359 359
360 360 def test_register_ok(self):
361 361 username = 'test_regular4'
362 362 password = 'qweqwe'
363 363 email = 'marcin@test.com'
364 364 name = 'testname'
365 365 lastname = 'testlastname'
366 366
367 367 response = self.app.post(
368 368 route_path('register'),
369 369 {
370 370 'username': username,
371 371 'password': password,
372 372 'password_confirmation': password,
373 373 'email': email,
374 374 'firstname': name,
375 375 'lastname': lastname,
376 376 'admin': True
377 377 }
378 378 ) # This should be overriden
379 379 assert response.status == '302 Found'
380 380 assert_session_flash(
381 381 response, 'You have successfully registered with RhodeCode')
382 382
383 383 ret = Session().query(User).filter(
384 384 User.username == 'test_regular4').one()
385 385 assert ret.username == username
386 386 assert check_password(password, ret.password)
387 387 assert ret.email == email
388 388 assert ret.name == name
389 389 assert ret.lastname == lastname
390 390 assert ret.auth_tokens is not None
391 391 assert not ret.admin
392 392
393 393 def test_forgot_password_wrong_mail(self):
394 394 bad_email = 'marcin@wrongmail.org'
395 395 response = self.app.post(
396 396 route_path('reset_password'), {'email': bad_email, }
397 397 )
398 398 assert_session_flash(response,
399 399 'If such email exists, a password reset link was sent to it.')
400 400
401 401 def test_forgot_password(self, user_util):
402 402 response = self.app.get(route_path('reset_password'))
403 403 assert response.status == '200 OK'
404 404
405 405 user = user_util.create_user()
406 406 user_id = user.user_id
407 407 email = user.email
408 408
409 409 response = self.app.post(route_path('reset_password'), {'email': email, })
410 410
411 411 assert_session_flash(response,
412 412 'If such email exists, a password reset link was sent to it.')
413 413
414 414 # BAD KEY
415 415 confirm_url = '{}?key={}'.format(route_path('reset_password_confirmation'), 'badkey')
416 416 response = self.app.get(confirm_url)
417 417 assert response.status == '302 Found'
418 418 assert response.location.endswith(route_path('reset_password'))
419 419 assert_session_flash(response, 'Given reset token is invalid')
420 420
421 421 response.follow() # cleanup flash
422 422
423 423 # GOOD KEY
424 424 key = UserApiKeys.query()\
425 425 .filter(UserApiKeys.user_id == user_id)\
426 426 .filter(UserApiKeys.role == UserApiKeys.ROLE_PASSWORD_RESET)\
427 427 .first()
428 428
429 429 assert key
430 430
431 431 confirm_url = '{}?key={}'.format(route_path('reset_password_confirmation'), key.api_key)
432 432 response = self.app.get(confirm_url)
433 433 assert response.status == '302 Found'
434 434 assert response.location.endswith(route_path('login'))
435 435
436 436 assert_session_flash(
437 437 response,
438 438 'Your password reset was successful, '
439 439 'a new password has been sent to your email')
440 440
441 441 response.follow()
442 442
443 443 def _get_api_whitelist(self, values=None):
444 444 config = {'api_access_controllers_whitelist': values or []}
445 445 return config
446 446
447 447 @pytest.mark.parametrize("test_name, auth_token", [
448 448 ('none', None),
449 449 ('empty_string', ''),
450 450 ('fake_number', '123456'),
451 451 ('proper_auth_token', None)
452 452 ])
453 453 def test_access_not_whitelisted_page_via_auth_token(
454 454 self, test_name, auth_token, user_admin):
455 455
456 456 whitelist = self._get_api_whitelist([])
457 457 with mock.patch.dict('rhodecode.CONFIG', whitelist):
458 458 assert [] == whitelist['api_access_controllers_whitelist']
459 459 if test_name == 'proper_auth_token':
460 460 # use builtin if api_key is None
461 461 auth_token = user_admin.api_key
462 462
463 463 with fixture.anon_access(False):
464 464 self.app.get(
465 465 route_path('repo_commit_raw',
466 466 repo_name=HG_REPO, commit_id='tip',
467 467 params=dict(api_key=auth_token)),
468 468 status=302)
469 469
470 470 @pytest.mark.parametrize("test_name, auth_token, code", [
471 471 ('none', None, 302),
472 472 ('empty_string', '', 302),
473 473 ('fake_number', '123456', 302),
474 474 ('proper_auth_token', None, 200)
475 475 ])
476 476 def test_access_whitelisted_page_via_auth_token(
477 477 self, test_name, auth_token, code, user_admin):
478 478
479 479 whitelist = self._get_api_whitelist(whitelist_view)
480 480
481 481 with mock.patch.dict('rhodecode.CONFIG', whitelist):
482 482 assert whitelist_view == whitelist['api_access_controllers_whitelist']
483 483
484 484 if test_name == 'proper_auth_token':
485 485 auth_token = user_admin.api_key
486 486 assert auth_token
487 487
488 488 with fixture.anon_access(False):
489 489 self.app.get(
490 490 route_path('repo_commit_raw',
491 491 repo_name=HG_REPO, commit_id='tip',
492 492 params=dict(api_key=auth_token)),
493 493 status=code)
494 494
495 @pytest.mark.parametrize("test_name, auth_token, code", [
496 ('proper_auth_token', None, 200),
497 ('wrong_auth_token', '123456', 302),
498 ])
499 def test_access_whitelisted_page_via_auth_token_bound_to_token(
500 self, test_name, auth_token, code, user_admin):
501
502 expected_token = auth_token
503 if test_name == 'proper_auth_token':
504 auth_token = user_admin.api_key
505 expected_token = auth_token
506 assert auth_token
507
508 whitelist = self._get_api_whitelist([
509 'RepoCommitsView:repo_commit_raw@{}'.format(expected_token)])
510
511 with mock.patch.dict('rhodecode.CONFIG', whitelist):
512
513 with fixture.anon_access(False):
514 self.app.get(
515 route_path('repo_commit_raw',
516 repo_name=HG_REPO, commit_id='tip',
517 params=dict(api_key=auth_token)),
518 status=code)
519
495 520 def test_access_page_via_extra_auth_token(self):
496 521 whitelist = self._get_api_whitelist(whitelist_view)
497 522 with mock.patch.dict('rhodecode.CONFIG', whitelist):
498 523 assert whitelist_view == \
499 524 whitelist['api_access_controllers_whitelist']
500 525
501 526 new_auth_token = AuthTokenModel().create(
502 527 TEST_USER_ADMIN_LOGIN, 'test')
503 528 Session().commit()
504 529 with fixture.anon_access(False):
505 530 self.app.get(
506 531 route_path('repo_commit_raw',
507 532 repo_name=HG_REPO, commit_id='tip',
508 533 params=dict(api_key=new_auth_token.api_key)),
509 534 status=200)
510 535
511 536 def test_access_page_via_expired_auth_token(self):
512 537 whitelist = self._get_api_whitelist(whitelist_view)
513 538 with mock.patch.dict('rhodecode.CONFIG', whitelist):
514 539 assert whitelist_view == \
515 540 whitelist['api_access_controllers_whitelist']
516 541
517 542 new_auth_token = AuthTokenModel().create(
518 543 TEST_USER_ADMIN_LOGIN, 'test')
519 544 Session().commit()
520 545 # patch the api key and make it expired
521 546 new_auth_token.expires = 0
522 547 Session().add(new_auth_token)
523 548 Session().commit()
524 549 with fixture.anon_access(False):
525 550 self.app.get(
526 551 route_path('repo_commit_raw',
527 552 repo_name=HG_REPO, commit_id='tip',
528 553 params=dict(api_key=new_auth_token.api_key)),
529 554 status=302)
@@ -1,1997 +1,2004 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2017 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 authentication and permission libraries
23 23 """
24 24
25 25 import os
26 26 import inspect
27 27 import collections
28 28 import fnmatch
29 29 import hashlib
30 30 import itertools
31 31 import logging
32 32 import random
33 33 import traceback
34 34 from functools import wraps
35 35
36 36 import ipaddress
37 37 from pyramid.httpexceptions import HTTPForbidden, HTTPFound, HTTPNotFound
38 38 from pylons.i18n.translation import _
39 39 # NOTE(marcink): this has to be removed only after pyramid migration,
40 40 # replace with _ = request.translate
41 41 from sqlalchemy.orm.exc import ObjectDeletedError
42 42 from sqlalchemy.orm import joinedload
43 43 from zope.cachedescriptors.property import Lazy as LazyProperty
44 44
45 45 import rhodecode
46 46 from rhodecode.model import meta
47 47 from rhodecode.model.meta import Session
48 48 from rhodecode.model.user import UserModel
49 49 from rhodecode.model.db import (
50 50 User, Repository, Permission, UserToPerm, UserGroupToPerm, UserGroupMember,
51 51 UserIpMap, UserApiKeys, RepoGroup)
52 52 from rhodecode.lib import caches
53 53 from rhodecode.lib.utils2 import safe_unicode, aslist, safe_str, md5
54 54 from rhodecode.lib.utils import (
55 55 get_repo_slug, get_repo_group_slug, get_user_group_slug)
56 56 from rhodecode.lib.caching_query import FromCache
57 57
58 58
59 59 if rhodecode.is_unix:
60 60 import bcrypt
61 61
62 62 log = logging.getLogger(__name__)
63 63
64 64 csrf_token_key = "csrf_token"
65 65
66 66
67 67 class PasswordGenerator(object):
68 68 """
69 69 This is a simple class for generating password from different sets of
70 70 characters
71 71 usage::
72 72
73 73 passwd_gen = PasswordGenerator()
74 74 #print 8-letter password containing only big and small letters
75 75 of alphabet
76 76 passwd_gen.gen_password(8, passwd_gen.ALPHABETS_BIG_SMALL)
77 77 """
78 78 ALPHABETS_NUM = r'''1234567890'''
79 79 ALPHABETS_SMALL = r'''qwertyuiopasdfghjklzxcvbnm'''
80 80 ALPHABETS_BIG = r'''QWERTYUIOPASDFGHJKLZXCVBNM'''
81 81 ALPHABETS_SPECIAL = r'''`-=[]\;',./~!@#$%^&*()_+{}|:"<>?'''
82 82 ALPHABETS_FULL = ALPHABETS_BIG + ALPHABETS_SMALL \
83 83 + ALPHABETS_NUM + ALPHABETS_SPECIAL
84 84 ALPHABETS_ALPHANUM = ALPHABETS_BIG + ALPHABETS_SMALL + ALPHABETS_NUM
85 85 ALPHABETS_BIG_SMALL = ALPHABETS_BIG + ALPHABETS_SMALL
86 86 ALPHABETS_ALPHANUM_BIG = ALPHABETS_BIG + ALPHABETS_NUM
87 87 ALPHABETS_ALPHANUM_SMALL = ALPHABETS_SMALL + ALPHABETS_NUM
88 88
89 89 def __init__(self, passwd=''):
90 90 self.passwd = passwd
91 91
92 92 def gen_password(self, length, type_=None):
93 93 if type_ is None:
94 94 type_ = self.ALPHABETS_FULL
95 95 self.passwd = ''.join([random.choice(type_) for _ in xrange(length)])
96 96 return self.passwd
97 97
98 98
99 99 class _RhodeCodeCryptoBase(object):
100 100 ENC_PREF = None
101 101
102 102 def hash_create(self, str_):
103 103 """
104 104 hash the string using
105 105
106 106 :param str_: password to hash
107 107 """
108 108 raise NotImplementedError
109 109
110 110 def hash_check_with_upgrade(self, password, hashed):
111 111 """
112 112 Returns tuple in which first element is boolean that states that
113 113 given password matches it's hashed version, and the second is new hash
114 114 of the password, in case this password should be migrated to new
115 115 cipher.
116 116 """
117 117 checked_hash = self.hash_check(password, hashed)
118 118 return checked_hash, None
119 119
120 120 def hash_check(self, password, hashed):
121 121 """
122 122 Checks matching password with it's hashed value.
123 123
124 124 :param password: password
125 125 :param hashed: password in hashed form
126 126 """
127 127 raise NotImplementedError
128 128
129 129 def _assert_bytes(self, value):
130 130 """
131 131 Passing in an `unicode` object can lead to hard to detect issues
132 132 if passwords contain non-ascii characters. Doing a type check
133 133 during runtime, so that such mistakes are detected early on.
134 134 """
135 135 if not isinstance(value, str):
136 136 raise TypeError(
137 137 "Bytestring required as input, got %r." % (value, ))
138 138
139 139
140 140 class _RhodeCodeCryptoBCrypt(_RhodeCodeCryptoBase):
141 141 ENC_PREF = ('$2a$10', '$2b$10')
142 142
143 143 def hash_create(self, str_):
144 144 self._assert_bytes(str_)
145 145 return bcrypt.hashpw(str_, bcrypt.gensalt(10))
146 146
147 147 def hash_check_with_upgrade(self, password, hashed):
148 148 """
149 149 Returns tuple in which first element is boolean that states that
150 150 given password matches it's hashed version, and the second is new hash
151 151 of the password, in case this password should be migrated to new
152 152 cipher.
153 153
154 154 This implements special upgrade logic which works like that:
155 155 - check if the given password == bcrypted hash, if yes then we
156 156 properly used password and it was already in bcrypt. Proceed
157 157 without any changes
158 158 - if bcrypt hash check is not working try with sha256. If hash compare
159 159 is ok, it means we using correct but old hashed password. indicate
160 160 hash change and proceed
161 161 """
162 162
163 163 new_hash = None
164 164
165 165 # regular pw check
166 166 password_match_bcrypt = self.hash_check(password, hashed)
167 167
168 168 # now we want to know if the password was maybe from sha256
169 169 # basically calling _RhodeCodeCryptoSha256().hash_check()
170 170 if not password_match_bcrypt:
171 171 if _RhodeCodeCryptoSha256().hash_check(password, hashed):
172 172 new_hash = self.hash_create(password) # make new bcrypt hash
173 173 password_match_bcrypt = True
174 174
175 175 return password_match_bcrypt, new_hash
176 176
177 177 def hash_check(self, password, hashed):
178 178 """
179 179 Checks matching password with it's hashed value.
180 180
181 181 :param password: password
182 182 :param hashed: password in hashed form
183 183 """
184 184 self._assert_bytes(password)
185 185 try:
186 186 return bcrypt.hashpw(password, hashed) == hashed
187 187 except ValueError as e:
188 188 # we're having a invalid salt here probably, we should not crash
189 189 # just return with False as it would be a wrong password.
190 190 log.debug('Failed to check password hash using bcrypt %s',
191 191 safe_str(e))
192 192
193 193 return False
194 194
195 195
196 196 class _RhodeCodeCryptoSha256(_RhodeCodeCryptoBase):
197 197 ENC_PREF = '_'
198 198
199 199 def hash_create(self, str_):
200 200 self._assert_bytes(str_)
201 201 return hashlib.sha256(str_).hexdigest()
202 202
203 203 def hash_check(self, password, hashed):
204 204 """
205 205 Checks matching password with it's hashed value.
206 206
207 207 :param password: password
208 208 :param hashed: password in hashed form
209 209 """
210 210 self._assert_bytes(password)
211 211 return hashlib.sha256(password).hexdigest() == hashed
212 212
213 213
214 214 class _RhodeCodeCryptoMd5(_RhodeCodeCryptoBase):
215 215 ENC_PREF = '_'
216 216
217 217 def hash_create(self, str_):
218 218 self._assert_bytes(str_)
219 219 return hashlib.md5(str_).hexdigest()
220 220
221 221 def hash_check(self, password, hashed):
222 222 """
223 223 Checks matching password with it's hashed value.
224 224
225 225 :param password: password
226 226 :param hashed: password in hashed form
227 227 """
228 228 self._assert_bytes(password)
229 229 return hashlib.md5(password).hexdigest() == hashed
230 230
231 231
232 232 def crypto_backend():
233 233 """
234 234 Return the matching crypto backend.
235 235
236 236 Selection is based on if we run tests or not, we pick md5 backend to run
237 237 tests faster since BCRYPT is expensive to calculate
238 238 """
239 239 if rhodecode.is_test:
240 240 RhodeCodeCrypto = _RhodeCodeCryptoMd5()
241 241 else:
242 242 RhodeCodeCrypto = _RhodeCodeCryptoBCrypt()
243 243
244 244 return RhodeCodeCrypto
245 245
246 246
247 247 def get_crypt_password(password):
248 248 """
249 249 Create the hash of `password` with the active crypto backend.
250 250
251 251 :param password: The cleartext password.
252 252 :type password: unicode
253 253 """
254 254 password = safe_str(password)
255 255 return crypto_backend().hash_create(password)
256 256
257 257
258 258 def check_password(password, hashed):
259 259 """
260 260 Check if the value in `password` matches the hash in `hashed`.
261 261
262 262 :param password: The cleartext password.
263 263 :type password: unicode
264 264
265 265 :param hashed: The expected hashed version of the password.
266 266 :type hashed: The hash has to be passed in in text representation.
267 267 """
268 268 password = safe_str(password)
269 269 return crypto_backend().hash_check(password, hashed)
270 270
271 271
272 272 def generate_auth_token(data, salt=None):
273 273 """
274 274 Generates API KEY from given string
275 275 """
276 276
277 277 if salt is None:
278 278 salt = os.urandom(16)
279 279 return hashlib.sha1(safe_str(data) + salt).hexdigest()
280 280
281 281
282 282 class CookieStoreWrapper(object):
283 283
284 284 def __init__(self, cookie_store):
285 285 self.cookie_store = cookie_store
286 286
287 287 def __repr__(self):
288 288 return 'CookieStore<%s>' % (self.cookie_store)
289 289
290 290 def get(self, key, other=None):
291 291 if isinstance(self.cookie_store, dict):
292 292 return self.cookie_store.get(key, other)
293 293 elif isinstance(self.cookie_store, AuthUser):
294 294 return self.cookie_store.__dict__.get(key, other)
295 295
296 296
297 297 def _cached_perms_data(user_id, scope, user_is_admin,
298 298 user_inherit_default_permissions, explicit, algo):
299 299
300 300 permissions = PermissionCalculator(
301 301 user_id, scope, user_is_admin, user_inherit_default_permissions,
302 302 explicit, algo)
303 303 return permissions.calculate()
304 304
305 305
306 306 class PermOrigin(object):
307 307 ADMIN = 'superadmin'
308 308
309 309 REPO_USER = 'user:%s'
310 310 REPO_USERGROUP = 'usergroup:%s'
311 311 REPO_OWNER = 'repo.owner'
312 312 REPO_DEFAULT = 'repo.default'
313 313 REPO_PRIVATE = 'repo.private'
314 314
315 315 REPOGROUP_USER = 'user:%s'
316 316 REPOGROUP_USERGROUP = 'usergroup:%s'
317 317 REPOGROUP_OWNER = 'group.owner'
318 318 REPOGROUP_DEFAULT = 'group.default'
319 319
320 320 USERGROUP_USER = 'user:%s'
321 321 USERGROUP_USERGROUP = 'usergroup:%s'
322 322 USERGROUP_OWNER = 'usergroup.owner'
323 323 USERGROUP_DEFAULT = 'usergroup.default'
324 324
325 325
326 326 class PermOriginDict(dict):
327 327 """
328 328 A special dict used for tracking permissions along with their origins.
329 329
330 330 `__setitem__` has been overridden to expect a tuple(perm, origin)
331 331 `__getitem__` will return only the perm
332 332 `.perm_origin_stack` will return the stack of (perm, origin) set per key
333 333
334 334 >>> perms = PermOriginDict()
335 335 >>> perms['resource'] = 'read', 'default'
336 336 >>> perms['resource']
337 337 'read'
338 338 >>> perms['resource'] = 'write', 'admin'
339 339 >>> perms['resource']
340 340 'write'
341 341 >>> perms.perm_origin_stack
342 342 {'resource': [('read', 'default'), ('write', 'admin')]}
343 343 """
344 344
345 345 def __init__(self, *args, **kw):
346 346 dict.__init__(self, *args, **kw)
347 347 self.perm_origin_stack = {}
348 348
349 349 def __setitem__(self, key, (perm, origin)):
350 350 self.perm_origin_stack.setdefault(key, []).append((perm, origin))
351 351 dict.__setitem__(self, key, perm)
352 352
353 353
354 354 class PermissionCalculator(object):
355 355
356 356 def __init__(
357 357 self, user_id, scope, user_is_admin,
358 358 user_inherit_default_permissions, explicit, algo):
359 359 self.user_id = user_id
360 360 self.user_is_admin = user_is_admin
361 361 self.inherit_default_permissions = user_inherit_default_permissions
362 362 self.explicit = explicit
363 363 self.algo = algo
364 364
365 365 scope = scope or {}
366 366 self.scope_repo_id = scope.get('repo_id')
367 367 self.scope_repo_group_id = scope.get('repo_group_id')
368 368 self.scope_user_group_id = scope.get('user_group_id')
369 369
370 370 self.default_user_id = User.get_default_user(cache=True).user_id
371 371
372 372 self.permissions_repositories = PermOriginDict()
373 373 self.permissions_repository_groups = PermOriginDict()
374 374 self.permissions_user_groups = PermOriginDict()
375 375 self.permissions_global = set()
376 376
377 377 self.default_repo_perms = Permission.get_default_repo_perms(
378 378 self.default_user_id, self.scope_repo_id)
379 379 self.default_repo_groups_perms = Permission.get_default_group_perms(
380 380 self.default_user_id, self.scope_repo_group_id)
381 381 self.default_user_group_perms = \
382 382 Permission.get_default_user_group_perms(
383 383 self.default_user_id, self.scope_user_group_id)
384 384
385 385 def calculate(self):
386 386 if self.user_is_admin:
387 387 return self._admin_permissions()
388 388
389 389 self._calculate_global_default_permissions()
390 390 self._calculate_global_permissions()
391 391 self._calculate_default_permissions()
392 392 self._calculate_repository_permissions()
393 393 self._calculate_repository_group_permissions()
394 394 self._calculate_user_group_permissions()
395 395 return self._permission_structure()
396 396
397 397 def _admin_permissions(self):
398 398 """
399 399 admin user have all default rights for repositories
400 400 and groups set to admin
401 401 """
402 402 self.permissions_global.add('hg.admin')
403 403 self.permissions_global.add('hg.create.write_on_repogroup.true')
404 404
405 405 # repositories
406 406 for perm in self.default_repo_perms:
407 407 r_k = perm.UserRepoToPerm.repository.repo_name
408 408 p = 'repository.admin'
409 409 self.permissions_repositories[r_k] = p, PermOrigin.ADMIN
410 410
411 411 # repository groups
412 412 for perm in self.default_repo_groups_perms:
413 413 rg_k = perm.UserRepoGroupToPerm.group.group_name
414 414 p = 'group.admin'
415 415 self.permissions_repository_groups[rg_k] = p, PermOrigin.ADMIN
416 416
417 417 # user groups
418 418 for perm in self.default_user_group_perms:
419 419 u_k = perm.UserUserGroupToPerm.user_group.users_group_name
420 420 p = 'usergroup.admin'
421 421 self.permissions_user_groups[u_k] = p, PermOrigin.ADMIN
422 422
423 423 return self._permission_structure()
424 424
425 425 def _calculate_global_default_permissions(self):
426 426 """
427 427 global permissions taken from the default user
428 428 """
429 429 default_global_perms = UserToPerm.query()\
430 430 .filter(UserToPerm.user_id == self.default_user_id)\
431 431 .options(joinedload(UserToPerm.permission))
432 432
433 433 for perm in default_global_perms:
434 434 self.permissions_global.add(perm.permission.permission_name)
435 435
436 436 def _calculate_global_permissions(self):
437 437 """
438 438 Set global system permissions with user permissions or permissions
439 439 taken from the user groups of the current user.
440 440
441 441 The permissions include repo creating, repo group creating, forking
442 442 etc.
443 443 """
444 444
445 445 # now we read the defined permissions and overwrite what we have set
446 446 # before those can be configured from groups or users explicitly.
447 447
448 448 # TODO: johbo: This seems to be out of sync, find out the reason
449 449 # for the comment below and update it.
450 450
451 451 # In case we want to extend this list we should be always in sync with
452 452 # User.DEFAULT_USER_PERMISSIONS definitions
453 453 _configurable = frozenset([
454 454 'hg.fork.none', 'hg.fork.repository',
455 455 'hg.create.none', 'hg.create.repository',
456 456 'hg.usergroup.create.false', 'hg.usergroup.create.true',
457 457 'hg.repogroup.create.false', 'hg.repogroup.create.true',
458 458 'hg.create.write_on_repogroup.false',
459 459 'hg.create.write_on_repogroup.true',
460 460 'hg.inherit_default_perms.false', 'hg.inherit_default_perms.true'
461 461 ])
462 462
463 463 # USER GROUPS comes first user group global permissions
464 464 user_perms_from_users_groups = Session().query(UserGroupToPerm)\
465 465 .options(joinedload(UserGroupToPerm.permission))\
466 466 .join((UserGroupMember, UserGroupToPerm.users_group_id ==
467 467 UserGroupMember.users_group_id))\
468 468 .filter(UserGroupMember.user_id == self.user_id)\
469 469 .order_by(UserGroupToPerm.users_group_id)\
470 470 .all()
471 471
472 472 # need to group here by groups since user can be in more than
473 473 # one group, so we get all groups
474 474 _explicit_grouped_perms = [
475 475 [x, list(y)] for x, y in
476 476 itertools.groupby(user_perms_from_users_groups,
477 477 lambda _x: _x.users_group)]
478 478
479 479 for gr, perms in _explicit_grouped_perms:
480 480 # since user can be in multiple groups iterate over them and
481 481 # select the lowest permissions first (more explicit)
482 482 # TODO: marcink: do this^^
483 483
484 484 # group doesn't inherit default permissions so we actually set them
485 485 if not gr.inherit_default_permissions:
486 486 # NEED TO IGNORE all previously set configurable permissions
487 487 # and replace them with explicitly set from this user
488 488 # group permissions
489 489 self.permissions_global = self.permissions_global.difference(
490 490 _configurable)
491 491 for perm in perms:
492 492 self.permissions_global.add(perm.permission.permission_name)
493 493
494 494 # user explicit global permissions
495 495 user_perms = Session().query(UserToPerm)\
496 496 .options(joinedload(UserToPerm.permission))\
497 497 .filter(UserToPerm.user_id == self.user_id).all()
498 498
499 499 if not self.inherit_default_permissions:
500 500 # NEED TO IGNORE all configurable permissions and
501 501 # replace them with explicitly set from this user permissions
502 502 self.permissions_global = self.permissions_global.difference(
503 503 _configurable)
504 504 for perm in user_perms:
505 505 self.permissions_global.add(perm.permission.permission_name)
506 506
507 507 def _calculate_default_permissions(self):
508 508 """
509 509 Set default user permissions for repositories, repository groups
510 510 taken from the default user.
511 511
512 512 Calculate inheritance of object permissions based on what we have now
513 513 in GLOBAL permissions. We check if .false is in GLOBAL since this is
514 514 explicitly set. Inherit is the opposite of .false being there.
515 515
516 516 .. note::
517 517
518 518 the syntax is little bit odd but what we need to check here is
519 519 the opposite of .false permission being in the list so even for
520 520 inconsistent state when both .true/.false is there
521 521 .false is more important
522 522
523 523 """
524 524 user_inherit_object_permissions = not ('hg.inherit_default_perms.false'
525 525 in self.permissions_global)
526 526
527 527 # defaults for repositories, taken from `default` user permissions
528 528 # on given repo
529 529 for perm in self.default_repo_perms:
530 530 r_k = perm.UserRepoToPerm.repository.repo_name
531 531 o = PermOrigin.REPO_DEFAULT
532 532 if perm.Repository.private and not (
533 533 perm.Repository.user_id == self.user_id):
534 534 # disable defaults for private repos,
535 535 p = 'repository.none'
536 536 o = PermOrigin.REPO_PRIVATE
537 537 elif perm.Repository.user_id == self.user_id:
538 538 # set admin if owner
539 539 p = 'repository.admin'
540 540 o = PermOrigin.REPO_OWNER
541 541 else:
542 542 p = perm.Permission.permission_name
543 543 # if we decide this user isn't inheriting permissions from
544 544 # default user we set him to .none so only explicit
545 545 # permissions work
546 546 if not user_inherit_object_permissions:
547 547 p = 'repository.none'
548 548 self.permissions_repositories[r_k] = p, o
549 549
550 550 # defaults for repository groups taken from `default` user permission
551 551 # on given group
552 552 for perm in self.default_repo_groups_perms:
553 553 rg_k = perm.UserRepoGroupToPerm.group.group_name
554 554 o = PermOrigin.REPOGROUP_DEFAULT
555 555 if perm.RepoGroup.user_id == self.user_id:
556 556 # set admin if owner
557 557 p = 'group.admin'
558 558 o = PermOrigin.REPOGROUP_OWNER
559 559 else:
560 560 p = perm.Permission.permission_name
561 561
562 562 # if we decide this user isn't inheriting permissions from default
563 563 # user we set him to .none so only explicit permissions work
564 564 if not user_inherit_object_permissions:
565 565 p = 'group.none'
566 566 self.permissions_repository_groups[rg_k] = p, o
567 567
568 568 # defaults for user groups taken from `default` user permission
569 569 # on given user group
570 570 for perm in self.default_user_group_perms:
571 571 u_k = perm.UserUserGroupToPerm.user_group.users_group_name
572 572 o = PermOrigin.USERGROUP_DEFAULT
573 573 if perm.UserGroup.user_id == self.user_id:
574 574 # set admin if owner
575 575 p = 'usergroup.admin'
576 576 o = PermOrigin.USERGROUP_OWNER
577 577 else:
578 578 p = perm.Permission.permission_name
579 579
580 580 # if we decide this user isn't inheriting permissions from default
581 581 # user we set him to .none so only explicit permissions work
582 582 if not user_inherit_object_permissions:
583 583 p = 'usergroup.none'
584 584 self.permissions_user_groups[u_k] = p, o
585 585
586 586 def _calculate_repository_permissions(self):
587 587 """
588 588 Repository permissions for the current user.
589 589
590 590 Check if the user is part of user groups for this repository and
591 591 fill in the permission from it. `_choose_permission` decides of which
592 592 permission should be selected based on selected method.
593 593 """
594 594
595 595 # user group for repositories permissions
596 596 user_repo_perms_from_user_group = Permission\
597 597 .get_default_repo_perms_from_user_group(
598 598 self.user_id, self.scope_repo_id)
599 599
600 600 multiple_counter = collections.defaultdict(int)
601 601 for perm in user_repo_perms_from_user_group:
602 602 r_k = perm.UserGroupRepoToPerm.repository.repo_name
603 603 ug_k = perm.UserGroupRepoToPerm.users_group.users_group_name
604 604 multiple_counter[r_k] += 1
605 605 p = perm.Permission.permission_name
606 606 o = PermOrigin.REPO_USERGROUP % ug_k
607 607
608 608 if perm.Repository.user_id == self.user_id:
609 609 # set admin if owner
610 610 p = 'repository.admin'
611 611 o = PermOrigin.REPO_OWNER
612 612 else:
613 613 if multiple_counter[r_k] > 1:
614 614 cur_perm = self.permissions_repositories[r_k]
615 615 p = self._choose_permission(p, cur_perm)
616 616 self.permissions_repositories[r_k] = p, o
617 617
618 618 # user explicit permissions for repositories, overrides any specified
619 619 # by the group permission
620 620 user_repo_perms = Permission.get_default_repo_perms(
621 621 self.user_id, self.scope_repo_id)
622 622 for perm in user_repo_perms:
623 623 r_k = perm.UserRepoToPerm.repository.repo_name
624 624 o = PermOrigin.REPO_USER % perm.UserRepoToPerm.user.username
625 625 # set admin if owner
626 626 if perm.Repository.user_id == self.user_id:
627 627 p = 'repository.admin'
628 628 o = PermOrigin.REPO_OWNER
629 629 else:
630 630 p = perm.Permission.permission_name
631 631 if not self.explicit:
632 632 cur_perm = self.permissions_repositories.get(
633 633 r_k, 'repository.none')
634 634 p = self._choose_permission(p, cur_perm)
635 635 self.permissions_repositories[r_k] = p, o
636 636
637 637 def _calculate_repository_group_permissions(self):
638 638 """
639 639 Repository group permissions for the current user.
640 640
641 641 Check if the user is part of user groups for repository groups and
642 642 fill in the permissions from it. `_choose_permmission` decides of which
643 643 permission should be selected based on selected method.
644 644 """
645 645 # user group for repo groups permissions
646 646 user_repo_group_perms_from_user_group = Permission\
647 647 .get_default_group_perms_from_user_group(
648 648 self.user_id, self.scope_repo_group_id)
649 649
650 650 multiple_counter = collections.defaultdict(int)
651 651 for perm in user_repo_group_perms_from_user_group:
652 652 g_k = perm.UserGroupRepoGroupToPerm.group.group_name
653 653 ug_k = perm.UserGroupRepoGroupToPerm.users_group.users_group_name
654 654 o = PermOrigin.REPOGROUP_USERGROUP % ug_k
655 655 multiple_counter[g_k] += 1
656 656 p = perm.Permission.permission_name
657 657 if perm.RepoGroup.user_id == self.user_id:
658 658 # set admin if owner, even for member of other user group
659 659 p = 'group.admin'
660 660 o = PermOrigin.REPOGROUP_OWNER
661 661 else:
662 662 if multiple_counter[g_k] > 1:
663 663 cur_perm = self.permissions_repository_groups[g_k]
664 664 p = self._choose_permission(p, cur_perm)
665 665 self.permissions_repository_groups[g_k] = p, o
666 666
667 667 # user explicit permissions for repository groups
668 668 user_repo_groups_perms = Permission.get_default_group_perms(
669 669 self.user_id, self.scope_repo_group_id)
670 670 for perm in user_repo_groups_perms:
671 671 rg_k = perm.UserRepoGroupToPerm.group.group_name
672 672 u_k = perm.UserRepoGroupToPerm.user.username
673 673 o = PermOrigin.REPOGROUP_USER % u_k
674 674
675 675 if perm.RepoGroup.user_id == self.user_id:
676 676 # set admin if owner
677 677 p = 'group.admin'
678 678 o = PermOrigin.REPOGROUP_OWNER
679 679 else:
680 680 p = perm.Permission.permission_name
681 681 if not self.explicit:
682 682 cur_perm = self.permissions_repository_groups.get(
683 683 rg_k, 'group.none')
684 684 p = self._choose_permission(p, cur_perm)
685 685 self.permissions_repository_groups[rg_k] = p, o
686 686
687 687 def _calculate_user_group_permissions(self):
688 688 """
689 689 User group permissions for the current user.
690 690 """
691 691 # user group for user group permissions
692 692 user_group_from_user_group = Permission\
693 693 .get_default_user_group_perms_from_user_group(
694 694 self.user_id, self.scope_user_group_id)
695 695
696 696 multiple_counter = collections.defaultdict(int)
697 697 for perm in user_group_from_user_group:
698 698 g_k = perm.UserGroupUserGroupToPerm\
699 699 .target_user_group.users_group_name
700 700 u_k = perm.UserGroupUserGroupToPerm\
701 701 .user_group.users_group_name
702 702 o = PermOrigin.USERGROUP_USERGROUP % u_k
703 703 multiple_counter[g_k] += 1
704 704 p = perm.Permission.permission_name
705 705
706 706 if perm.UserGroup.user_id == self.user_id:
707 707 # set admin if owner, even for member of other user group
708 708 p = 'usergroup.admin'
709 709 o = PermOrigin.USERGROUP_OWNER
710 710 else:
711 711 if multiple_counter[g_k] > 1:
712 712 cur_perm = self.permissions_user_groups[g_k]
713 713 p = self._choose_permission(p, cur_perm)
714 714 self.permissions_user_groups[g_k] = p, o
715 715
716 716 # user explicit permission for user groups
717 717 user_user_groups_perms = Permission.get_default_user_group_perms(
718 718 self.user_id, self.scope_user_group_id)
719 719 for perm in user_user_groups_perms:
720 720 ug_k = perm.UserUserGroupToPerm.user_group.users_group_name
721 721 u_k = perm.UserUserGroupToPerm.user.username
722 722 o = PermOrigin.USERGROUP_USER % u_k
723 723
724 724 if perm.UserGroup.user_id == self.user_id:
725 725 # set admin if owner
726 726 p = 'usergroup.admin'
727 727 o = PermOrigin.USERGROUP_OWNER
728 728 else:
729 729 p = perm.Permission.permission_name
730 730 if not self.explicit:
731 731 cur_perm = self.permissions_user_groups.get(
732 732 ug_k, 'usergroup.none')
733 733 p = self._choose_permission(p, cur_perm)
734 734 self.permissions_user_groups[ug_k] = p, o
735 735
736 736 def _choose_permission(self, new_perm, cur_perm):
737 737 new_perm_val = Permission.PERM_WEIGHTS[new_perm]
738 738 cur_perm_val = Permission.PERM_WEIGHTS[cur_perm]
739 739 if self.algo == 'higherwin':
740 740 if new_perm_val > cur_perm_val:
741 741 return new_perm
742 742 return cur_perm
743 743 elif self.algo == 'lowerwin':
744 744 if new_perm_val < cur_perm_val:
745 745 return new_perm
746 746 return cur_perm
747 747
748 748 def _permission_structure(self):
749 749 return {
750 750 'global': self.permissions_global,
751 751 'repositories': self.permissions_repositories,
752 752 'repositories_groups': self.permissions_repository_groups,
753 753 'user_groups': self.permissions_user_groups,
754 754 }
755 755
756 756
757 def allowed_auth_token_access(view_name, whitelist=None, auth_token=None):
757 def allowed_auth_token_access(view_name, auth_token, whitelist=None):
758 758 """
759 759 Check if given controller_name is in whitelist of auth token access
760 760 """
761 761 if not whitelist:
762 762 from rhodecode import CONFIG
763 763 whitelist = aslist(
764 764 CONFIG.get('api_access_controllers_whitelist'), sep=',')
765 log.debug(
766 'Allowed controllers for AUTH TOKEN access: %s' % (whitelist,))
765
766 log.debug(
767 'Allowed views for AUTH TOKEN access: %s' % (whitelist,))
768 auth_token_access_valid = False
767 769
768 auth_token_access_valid = False
769 770 for entry in whitelist:
770 if fnmatch.fnmatch(view_name, entry):
771 token_match = True
772 if '@' in entry:
773 # specific AuthToken
774 entry, allowed_token = entry.split('@', 1)
775 token_match = auth_token == allowed_token
776
777 if fnmatch.fnmatch(view_name, entry) and token_match:
771 778 auth_token_access_valid = True
772 779 break
773 780
774 781 if auth_token_access_valid:
775 782 log.debug('view: `%s` matches entry in whitelist: %s'
776 783 % (view_name, whitelist))
777 784 else:
778 785 msg = ('view: `%s` does *NOT* match any entry in whitelist: %s'
779 786 % (view_name, whitelist))
780 787 if auth_token:
781 788 # if we use auth token key and don't have access it's a warning
782 789 log.warning(msg)
783 790 else:
784 791 log.debug(msg)
785 792
786 793 return auth_token_access_valid
787 794
788 795
789 796 class AuthUser(object):
790 797 """
791 798 A simple object that handles all attributes of user in RhodeCode
792 799
793 800 It does lookup based on API key,given user, or user present in session
794 801 Then it fills all required information for such user. It also checks if
795 802 anonymous access is enabled and if so, it returns default user as logged in
796 803 """
797 804 GLOBAL_PERMS = [x[0] for x in Permission.PERMS]
798 805
799 806 def __init__(self, user_id=None, api_key=None, username=None, ip_addr=None):
800 807
801 808 self.user_id = user_id
802 809 self._api_key = api_key
803 810
804 811 self.api_key = None
805 812 self.feed_token = ''
806 813 self.username = username
807 814 self.ip_addr = ip_addr
808 815 self.name = ''
809 816 self.lastname = ''
810 817 self.first_name = ''
811 818 self.last_name = ''
812 819 self.email = ''
813 820 self.is_authenticated = False
814 821 self.admin = False
815 822 self.inherit_default_permissions = False
816 823 self.password = ''
817 824
818 825 self.anonymous_user = None # propagated on propagate_data
819 826 self.propagate_data()
820 827 self._instance = None
821 828 self._permissions_scoped_cache = {} # used to bind scoped calculation
822 829
823 830 @LazyProperty
824 831 def permissions(self):
825 832 return self.get_perms(user=self, cache=False)
826 833
827 834 def permissions_with_scope(self, scope):
828 835 """
829 836 Call the get_perms function with scoped data. The scope in that function
830 837 narrows the SQL calls to the given ID of objects resulting in fetching
831 838 Just particular permission we want to obtain. If scope is an empty dict
832 839 then it basically narrows the scope to GLOBAL permissions only.
833 840
834 841 :param scope: dict
835 842 """
836 843 if 'repo_name' in scope:
837 844 obj = Repository.get_by_repo_name(scope['repo_name'])
838 845 if obj:
839 846 scope['repo_id'] = obj.repo_id
840 847 _scope = {
841 848 'repo_id': -1,
842 849 'user_group_id': -1,
843 850 'repo_group_id': -1,
844 851 }
845 852 _scope.update(scope)
846 853 cache_key = "_".join(map(safe_str, reduce(lambda a, b: a+b,
847 854 _scope.items())))
848 855 if cache_key not in self._permissions_scoped_cache:
849 856 # store in cache to mimic how the @LazyProperty works,
850 857 # the difference here is that we use the unique key calculated
851 858 # from params and values
852 859 res = self.get_perms(user=self, cache=False, scope=_scope)
853 860 self._permissions_scoped_cache[cache_key] = res
854 861 return self._permissions_scoped_cache[cache_key]
855 862
856 863 def get_instance(self):
857 864 return User.get(self.user_id)
858 865
859 866 def update_lastactivity(self):
860 867 if self.user_id:
861 868 User.get(self.user_id).update_lastactivity()
862 869
863 870 def propagate_data(self):
864 871 """
865 872 Fills in user data and propagates values to this instance. Maps fetched
866 873 user attributes to this class instance attributes
867 874 """
868 875 log.debug('AuthUser: starting data propagation for new potential user')
869 876 user_model = UserModel()
870 877 anon_user = self.anonymous_user = User.get_default_user(cache=True)
871 878 is_user_loaded = False
872 879
873 880 # lookup by userid
874 881 if self.user_id is not None and self.user_id != anon_user.user_id:
875 882 log.debug('Trying Auth User lookup by USER ID: `%s`', self.user_id)
876 883 is_user_loaded = user_model.fill_data(self, user_id=self.user_id)
877 884
878 885 # try go get user by api key
879 886 elif self._api_key and self._api_key != anon_user.api_key:
880 887 log.debug('Trying Auth User lookup by API KEY: `%s`', self._api_key)
881 888 is_user_loaded = user_model.fill_data(self, api_key=self._api_key)
882 889
883 890 # lookup by username
884 891 elif self.username:
885 892 log.debug('Trying Auth User lookup by USER NAME: `%s`', self.username)
886 893 is_user_loaded = user_model.fill_data(self, username=self.username)
887 894 else:
888 895 log.debug('No data in %s that could been used to log in', self)
889 896
890 897 if not is_user_loaded:
891 898 log.debug('Failed to load user. Fallback to default user')
892 899 # if we cannot authenticate user try anonymous
893 900 if anon_user.active:
894 901 user_model.fill_data(self, user_id=anon_user.user_id)
895 902 # then we set this user is logged in
896 903 self.is_authenticated = True
897 904 else:
898 905 # in case of disabled anonymous user we reset some of the
899 906 # parameters so such user is "corrupted", skipping the fill_data
900 907 for attr in ['user_id', 'username', 'admin', 'active']:
901 908 setattr(self, attr, None)
902 909 self.is_authenticated = False
903 910
904 911 if not self.username:
905 912 self.username = 'None'
906 913
907 914 log.debug('AuthUser: propagated user is now %s', self)
908 915
909 916 def get_perms(self, user, scope=None, explicit=True, algo='higherwin',
910 917 cache=False):
911 918 """
912 919 Fills user permission attribute with permissions taken from database
913 920 works for permissions given for repositories, and for permissions that
914 921 are granted to groups
915 922
916 923 :param user: instance of User object from database
917 924 :param explicit: In case there are permissions both for user and a group
918 925 that user is part of, explicit flag will defiine if user will
919 926 explicitly override permissions from group, if it's False it will
920 927 make decision based on the algo
921 928 :param algo: algorithm to decide what permission should be choose if
922 929 it's multiple defined, eg user in two different groups. It also
923 930 decides if explicit flag is turned off how to specify the permission
924 931 for case when user is in a group + have defined separate permission
925 932 """
926 933 user_id = user.user_id
927 934 user_is_admin = user.is_admin
928 935
929 936 # inheritance of global permissions like create repo/fork repo etc
930 937 user_inherit_default_permissions = user.inherit_default_permissions
931 938
932 939 log.debug('Computing PERMISSION tree for scope %s' % (scope, ))
933 940 compute = caches.conditional_cache(
934 941 'short_term', 'cache_desc',
935 942 condition=cache, func=_cached_perms_data)
936 943 result = compute(user_id, scope, user_is_admin,
937 944 user_inherit_default_permissions, explicit, algo)
938 945
939 946 result_repr = []
940 947 for k in result:
941 948 result_repr.append((k, len(result[k])))
942 949
943 950 log.debug('PERMISSION tree computed %s' % (result_repr,))
944 951 return result
945 952
946 953 @property
947 954 def is_default(self):
948 955 return self.username == User.DEFAULT_USER
949 956
950 957 @property
951 958 def is_admin(self):
952 959 return self.admin
953 960
954 961 @property
955 962 def is_user_object(self):
956 963 return self.user_id is not None
957 964
958 965 @property
959 966 def repositories_admin(self):
960 967 """
961 968 Returns list of repositories you're an admin of
962 969 """
963 970 return [
964 971 x[0] for x in self.permissions['repositories'].iteritems()
965 972 if x[1] == 'repository.admin']
966 973
967 974 @property
968 975 def repository_groups_admin(self):
969 976 """
970 977 Returns list of repository groups you're an admin of
971 978 """
972 979 return [
973 980 x[0] for x in self.permissions['repositories_groups'].iteritems()
974 981 if x[1] == 'group.admin']
975 982
976 983 @property
977 984 def user_groups_admin(self):
978 985 """
979 986 Returns list of user groups you're an admin of
980 987 """
981 988 return [
982 989 x[0] for x in self.permissions['user_groups'].iteritems()
983 990 if x[1] == 'usergroup.admin']
984 991
985 992 @property
986 993 def ip_allowed(self):
987 994 """
988 995 Checks if ip_addr used in constructor is allowed from defined list of
989 996 allowed ip_addresses for user
990 997
991 998 :returns: boolean, True if ip is in allowed ip range
992 999 """
993 1000 # check IP
994 1001 inherit = self.inherit_default_permissions
995 1002 return AuthUser.check_ip_allowed(self.user_id, self.ip_addr,
996 1003 inherit_from_default=inherit)
997 1004 @property
998 1005 def personal_repo_group(self):
999 1006 return RepoGroup.get_user_personal_repo_group(self.user_id)
1000 1007
1001 1008 @classmethod
1002 1009 def check_ip_allowed(cls, user_id, ip_addr, inherit_from_default):
1003 1010 allowed_ips = AuthUser.get_allowed_ips(
1004 1011 user_id, cache=True, inherit_from_default=inherit_from_default)
1005 1012 if check_ip_access(source_ip=ip_addr, allowed_ips=allowed_ips):
1006 1013 log.debug('IP:%s is in range of %s' % (ip_addr, allowed_ips))
1007 1014 return True
1008 1015 else:
1009 1016 log.info('Access for IP:%s forbidden, '
1010 1017 'not in %s' % (ip_addr, allowed_ips))
1011 1018 return False
1012 1019
1013 1020 def __repr__(self):
1014 1021 return "<AuthUser('id:%s[%s] ip:%s auth:%s')>"\
1015 1022 % (self.user_id, self.username, self.ip_addr, self.is_authenticated)
1016 1023
1017 1024 def set_authenticated(self, authenticated=True):
1018 1025 if self.user_id != self.anonymous_user.user_id:
1019 1026 self.is_authenticated = authenticated
1020 1027
1021 1028 def get_cookie_store(self):
1022 1029 return {
1023 1030 'username': self.username,
1024 1031 'password': md5(self.password),
1025 1032 'user_id': self.user_id,
1026 1033 'is_authenticated': self.is_authenticated
1027 1034 }
1028 1035
1029 1036 @classmethod
1030 1037 def from_cookie_store(cls, cookie_store):
1031 1038 """
1032 1039 Creates AuthUser from a cookie store
1033 1040
1034 1041 :param cls:
1035 1042 :param cookie_store:
1036 1043 """
1037 1044 user_id = cookie_store.get('user_id')
1038 1045 username = cookie_store.get('username')
1039 1046 api_key = cookie_store.get('api_key')
1040 1047 return AuthUser(user_id, api_key, username)
1041 1048
1042 1049 @classmethod
1043 1050 def get_allowed_ips(cls, user_id, cache=False, inherit_from_default=False):
1044 1051 _set = set()
1045 1052
1046 1053 if inherit_from_default:
1047 1054 default_ips = UserIpMap.query().filter(
1048 1055 UserIpMap.user == User.get_default_user(cache=True))
1049 1056 if cache:
1050 1057 default_ips = default_ips.options(
1051 1058 FromCache("sql_cache_short", "get_user_ips_default"))
1052 1059
1053 1060 # populate from default user
1054 1061 for ip in default_ips:
1055 1062 try:
1056 1063 _set.add(ip.ip_addr)
1057 1064 except ObjectDeletedError:
1058 1065 # since we use heavy caching sometimes it happens that
1059 1066 # we get deleted objects here, we just skip them
1060 1067 pass
1061 1068
1062 1069 user_ips = UserIpMap.query().filter(UserIpMap.user_id == user_id)
1063 1070 if cache:
1064 1071 user_ips = user_ips.options(
1065 1072 FromCache("sql_cache_short", "get_user_ips_%s" % user_id))
1066 1073
1067 1074 for ip in user_ips:
1068 1075 try:
1069 1076 _set.add(ip.ip_addr)
1070 1077 except ObjectDeletedError:
1071 1078 # since we use heavy caching sometimes it happens that we get
1072 1079 # deleted objects here, we just skip them
1073 1080 pass
1074 1081 return _set or set(['0.0.0.0/0', '::/0'])
1075 1082
1076 1083
1077 1084 def set_available_permissions(config):
1078 1085 """
1079 1086 This function will propagate pylons globals with all available defined
1080 1087 permission given in db. We don't want to check each time from db for new
1081 1088 permissions since adding a new permission also requires application restart
1082 1089 ie. to decorate new views with the newly created permission
1083 1090
1084 1091 :param config: current pylons config instance
1085 1092
1086 1093 """
1087 1094 log.info('getting information about all available permissions')
1088 1095 try:
1089 1096 sa = meta.Session
1090 1097 all_perms = sa.query(Permission).all()
1091 1098 config['available_permissions'] = [x.permission_name for x in all_perms]
1092 1099 except Exception:
1093 1100 log.error(traceback.format_exc())
1094 1101 finally:
1095 1102 meta.Session.remove()
1096 1103
1097 1104
1098 1105 def get_csrf_token(session=None, force_new=False, save_if_missing=True):
1099 1106 """
1100 1107 Return the current authentication token, creating one if one doesn't
1101 1108 already exist and the save_if_missing flag is present.
1102 1109
1103 1110 :param session: pass in the pylons session, else we use the global ones
1104 1111 :param force_new: force to re-generate the token and store it in session
1105 1112 :param save_if_missing: save the newly generated token if it's missing in
1106 1113 session
1107 1114 """
1108 1115 # NOTE(marcink): probably should be replaced with below one from pyramid 1.9
1109 1116 # from pyramid.csrf import get_csrf_token
1110 1117
1111 1118 if not session:
1112 1119 from pylons import session
1113 1120
1114 1121 if (csrf_token_key not in session and save_if_missing) or force_new:
1115 1122 token = hashlib.sha1(str(random.getrandbits(128))).hexdigest()
1116 1123 session[csrf_token_key] = token
1117 1124 if hasattr(session, 'save'):
1118 1125 session.save()
1119 1126 return session.get(csrf_token_key)
1120 1127
1121 1128
1122 1129 def get_request(perm_class):
1123 1130 from pyramid.threadlocal import get_current_request
1124 1131 pyramid_request = get_current_request()
1125 1132 if not pyramid_request:
1126 1133 # return global request of pylons in case pyramid isn't available
1127 1134 # NOTE(marcink): this should be removed after migration to pyramid
1128 1135 from pylons import request
1129 1136 return request
1130 1137 return pyramid_request
1131 1138
1132 1139
1133 1140 # CHECK DECORATORS
1134 1141 class CSRFRequired(object):
1135 1142 """
1136 1143 Decorator for authenticating a form
1137 1144
1138 1145 This decorator uses an authorization token stored in the client's
1139 1146 session for prevention of certain Cross-site request forgery (CSRF)
1140 1147 attacks (See
1141 1148 http://en.wikipedia.org/wiki/Cross-site_request_forgery for more
1142 1149 information).
1143 1150
1144 1151 For use with the ``webhelpers.secure_form`` helper functions.
1145 1152
1146 1153 """
1147 1154 def __init__(self, token=csrf_token_key, header='X-CSRF-Token',
1148 1155 except_methods=None):
1149 1156 self.token = token
1150 1157 self.header = header
1151 1158 self.except_methods = except_methods or []
1152 1159
1153 1160 def __call__(self, func):
1154 1161 return get_cython_compat_decorator(self.__wrapper, func)
1155 1162
1156 1163 def _get_csrf(self, _request):
1157 1164 return _request.POST.get(self.token, _request.headers.get(self.header))
1158 1165
1159 1166 def check_csrf(self, _request, cur_token):
1160 1167 supplied_token = self._get_csrf(_request)
1161 1168 return supplied_token and supplied_token == cur_token
1162 1169
1163 1170 def _get_request(self):
1164 1171 return get_request(self)
1165 1172
1166 1173 def __wrapper(self, func, *fargs, **fkwargs):
1167 1174 request = self._get_request()
1168 1175
1169 1176 if request.method in self.except_methods:
1170 1177 return func(*fargs, **fkwargs)
1171 1178
1172 1179 cur_token = get_csrf_token(save_if_missing=False)
1173 1180 if self.check_csrf(request, cur_token):
1174 1181 if request.POST.get(self.token):
1175 1182 del request.POST[self.token]
1176 1183 return func(*fargs, **fkwargs)
1177 1184 else:
1178 1185 reason = 'token-missing'
1179 1186 supplied_token = self._get_csrf(request)
1180 1187 if supplied_token and cur_token != supplied_token:
1181 1188 reason = 'token-mismatch [%s:%s]' % (
1182 1189 cur_token or ''[:6], supplied_token or ''[:6])
1183 1190
1184 1191 csrf_message = \
1185 1192 ("Cross-site request forgery detected, request denied. See "
1186 1193 "http://en.wikipedia.org/wiki/Cross-site_request_forgery for "
1187 1194 "more information.")
1188 1195 log.warn('Cross-site request forgery detected, request %r DENIED: %s '
1189 1196 'REMOTE_ADDR:%s, HEADERS:%s' % (
1190 1197 request, reason, request.remote_addr, request.headers))
1191 1198
1192 1199 raise HTTPForbidden(explanation=csrf_message)
1193 1200
1194 1201
1195 1202 class LoginRequired(object):
1196 1203 """
1197 1204 Must be logged in to execute this function else
1198 1205 redirect to login page
1199 1206
1200 1207 :param api_access: if enabled this checks only for valid auth token
1201 1208 and grants access based on valid token
1202 1209 """
1203 1210 def __init__(self, auth_token_access=None):
1204 1211 self.auth_token_access = auth_token_access
1205 1212
1206 1213 def __call__(self, func):
1207 1214 return get_cython_compat_decorator(self.__wrapper, func)
1208 1215
1209 1216 def _get_request(self):
1210 1217 return get_request(self)
1211 1218
1212 1219 def __wrapper(self, func, *fargs, **fkwargs):
1213 1220 from rhodecode.lib import helpers as h
1214 1221 cls = fargs[0]
1215 1222 user = cls._rhodecode_user
1216 1223 request = self._get_request()
1217 1224
1218 1225 loc = "%s:%s" % (cls.__class__.__name__, func.__name__)
1219 1226 log.debug('Starting login restriction checks for user: %s' % (user,))
1220 1227 # check if our IP is allowed
1221 1228 ip_access_valid = True
1222 1229 if not user.ip_allowed:
1223 1230 h.flash(h.literal(_('IP %s not allowed' % (user.ip_addr,))),
1224 1231 category='warning')
1225 1232 ip_access_valid = False
1226 1233
1227 1234 # check if we used an APIKEY and it's a valid one
1228 1235 # defined white-list of controllers which API access will be enabled
1229 1236 _auth_token = request.GET.get(
1230 1237 'auth_token', '') or request.GET.get('api_key', '')
1231 1238 auth_token_access_valid = allowed_auth_token_access(
1232 1239 loc, auth_token=_auth_token)
1233 1240
1234 1241 # explicit controller is enabled or API is in our whitelist
1235 1242 if self.auth_token_access or auth_token_access_valid:
1236 1243 log.debug('Checking AUTH TOKEN access for %s' % (cls,))
1237 1244 db_user = user.get_instance()
1238 1245
1239 1246 if db_user:
1240 1247 if self.auth_token_access:
1241 1248 roles = self.auth_token_access
1242 1249 else:
1243 1250 roles = [UserApiKeys.ROLE_HTTP]
1244 1251 token_match = db_user.authenticate_by_token(
1245 1252 _auth_token, roles=roles)
1246 1253 else:
1247 1254 log.debug('Unable to fetch db instance for auth user: %s', user)
1248 1255 token_match = False
1249 1256
1250 1257 if _auth_token and token_match:
1251 1258 auth_token_access_valid = True
1252 1259 log.debug('AUTH TOKEN ****%s is VALID' % (_auth_token[-4:],))
1253 1260 else:
1254 1261 auth_token_access_valid = False
1255 1262 if not _auth_token:
1256 1263 log.debug("AUTH TOKEN *NOT* present in request")
1257 1264 else:
1258 1265 log.warning(
1259 1266 "AUTH TOKEN ****%s *NOT* valid" % _auth_token[-4:])
1260 1267
1261 1268 log.debug('Checking if %s is authenticated @ %s' % (user.username, loc))
1262 1269 reason = 'RHODECODE_AUTH' if user.is_authenticated \
1263 1270 else 'AUTH_TOKEN_AUTH'
1264 1271
1265 1272 if ip_access_valid and (
1266 1273 user.is_authenticated or auth_token_access_valid):
1267 1274 log.info(
1268 1275 'user %s authenticating with:%s IS authenticated on func %s'
1269 1276 % (user, reason, loc))
1270 1277
1271 1278 # update user data to check last activity
1272 1279 user.update_lastactivity()
1273 1280 Session().commit()
1274 1281 return func(*fargs, **fkwargs)
1275 1282 else:
1276 1283 log.warning(
1277 1284 'user %s authenticating with:%s NOT authenticated on '
1278 1285 'func: %s: IP_ACCESS:%s AUTH_TOKEN_ACCESS:%s'
1279 1286 % (user, reason, loc, ip_access_valid,
1280 1287 auth_token_access_valid))
1281 1288 # we preserve the get PARAM
1282 1289 came_from = request.path_qs
1283 1290 log.debug('redirecting to login page with %s' % (came_from,))
1284 1291 raise HTTPFound(
1285 1292 h.route_path('login', _query={'came_from': came_from}))
1286 1293
1287 1294
1288 1295 class NotAnonymous(object):
1289 1296 """
1290 1297 Must be logged in to execute this function else
1291 1298 redirect to login page
1292 1299 """
1293 1300
1294 1301 def __call__(self, func):
1295 1302 return get_cython_compat_decorator(self.__wrapper, func)
1296 1303
1297 1304 def _get_request(self):
1298 1305 return get_request(self)
1299 1306
1300 1307 def __wrapper(self, func, *fargs, **fkwargs):
1301 1308 import rhodecode.lib.helpers as h
1302 1309 cls = fargs[0]
1303 1310 self.user = cls._rhodecode_user
1304 1311 request = self._get_request()
1305 1312
1306 1313 log.debug('Checking if user is not anonymous @%s' % cls)
1307 1314
1308 1315 anonymous = self.user.username == User.DEFAULT_USER
1309 1316
1310 1317 if anonymous:
1311 1318 came_from = request.path_qs
1312 1319 h.flash(_('You need to be a registered user to '
1313 1320 'perform this action'),
1314 1321 category='warning')
1315 1322 raise HTTPFound(
1316 1323 h.route_path('login', _query={'came_from': came_from}))
1317 1324 else:
1318 1325 return func(*fargs, **fkwargs)
1319 1326
1320 1327
1321 1328 class XHRRequired(object):
1322 1329 # TODO(marcink): remove this in favor of the predicates in pyramid routes
1323 1330
1324 1331 def __call__(self, func):
1325 1332 return get_cython_compat_decorator(self.__wrapper, func)
1326 1333
1327 1334 def _get_request(self):
1328 1335 return get_request(self)
1329 1336
1330 1337 def __wrapper(self, func, *fargs, **fkwargs):
1331 1338 from pylons.controllers.util import abort
1332 1339 request = self._get_request()
1333 1340
1334 1341 log.debug('Checking if request is XMLHttpRequest (XHR)')
1335 1342 xhr_message = 'This is not a valid XMLHttpRequest (XHR) request'
1336 1343
1337 1344 if not request.is_xhr:
1338 1345 abort(400, detail=xhr_message)
1339 1346
1340 1347 return func(*fargs, **fkwargs)
1341 1348
1342 1349
1343 1350 class PermsDecorator(object):
1344 1351 """
1345 1352 Base class for controller decorators, we extract the current user from
1346 1353 the class itself, which has it stored in base controllers
1347 1354 """
1348 1355
1349 1356 def __init__(self, *required_perms):
1350 1357 self.required_perms = set(required_perms)
1351 1358
1352 1359 def __call__(self, func):
1353 1360 return get_cython_compat_decorator(self.__wrapper, func)
1354 1361
1355 1362 def _get_request(self):
1356 1363 return get_request(self)
1357 1364
1358 1365 def _get_came_from(self):
1359 1366 _request = self._get_request()
1360 1367
1361 1368 # both pylons/pyramid has this attribute
1362 1369 return _request.path_qs
1363 1370
1364 1371 def __wrapper(self, func, *fargs, **fkwargs):
1365 1372 import rhodecode.lib.helpers as h
1366 1373 cls = fargs[0]
1367 1374 _user = cls._rhodecode_user
1368 1375
1369 1376 log.debug('checking %s permissions %s for %s %s',
1370 1377 self.__class__.__name__, self.required_perms, cls, _user)
1371 1378
1372 1379 if self.check_permissions(_user):
1373 1380 log.debug('Permission granted for %s %s', cls, _user)
1374 1381 return func(*fargs, **fkwargs)
1375 1382
1376 1383 else:
1377 1384 log.debug('Permission denied for %s %s', cls, _user)
1378 1385 anonymous = _user.username == User.DEFAULT_USER
1379 1386
1380 1387 if anonymous:
1381 1388 came_from = self._get_came_from()
1382 1389 h.flash(_('You need to be signed in to view this page'),
1383 1390 category='warning')
1384 1391 raise HTTPFound(
1385 1392 h.route_path('login', _query={'came_from': came_from}))
1386 1393
1387 1394 else:
1388 1395 # redirect with 404 to prevent resource discovery
1389 1396 raise HTTPNotFound()
1390 1397
1391 1398 def check_permissions(self, user):
1392 1399 """Dummy function for overriding"""
1393 1400 raise NotImplementedError(
1394 1401 'You have to write this function in child class')
1395 1402
1396 1403
1397 1404 class HasPermissionAllDecorator(PermsDecorator):
1398 1405 """
1399 1406 Checks for access permission for all given predicates. All of them
1400 1407 have to be meet in order to fulfill the request
1401 1408 """
1402 1409
1403 1410 def check_permissions(self, user):
1404 1411 perms = user.permissions_with_scope({})
1405 1412 if self.required_perms.issubset(perms['global']):
1406 1413 return True
1407 1414 return False
1408 1415
1409 1416
1410 1417 class HasPermissionAnyDecorator(PermsDecorator):
1411 1418 """
1412 1419 Checks for access permission for any of given predicates. In order to
1413 1420 fulfill the request any of predicates must be meet
1414 1421 """
1415 1422
1416 1423 def check_permissions(self, user):
1417 1424 perms = user.permissions_with_scope({})
1418 1425 if self.required_perms.intersection(perms['global']):
1419 1426 return True
1420 1427 return False
1421 1428
1422 1429
1423 1430 class HasRepoPermissionAllDecorator(PermsDecorator):
1424 1431 """
1425 1432 Checks for access permission for all given predicates for specific
1426 1433 repository. All of them have to be meet in order to fulfill the request
1427 1434 """
1428 1435 def _get_repo_name(self):
1429 1436 _request = self._get_request()
1430 1437 return get_repo_slug(_request)
1431 1438
1432 1439 def check_permissions(self, user):
1433 1440 perms = user.permissions
1434 1441 repo_name = self._get_repo_name()
1435 1442
1436 1443 try:
1437 1444 user_perms = set([perms['repositories'][repo_name]])
1438 1445 except KeyError:
1439 1446 log.debug('cannot locate repo with name: `%s` in permissions defs',
1440 1447 repo_name)
1441 1448 return False
1442 1449
1443 1450 log.debug('checking `%s` permissions for repo `%s`',
1444 1451 user_perms, repo_name)
1445 1452 if self.required_perms.issubset(user_perms):
1446 1453 return True
1447 1454 return False
1448 1455
1449 1456
1450 1457 class HasRepoPermissionAnyDecorator(PermsDecorator):
1451 1458 """
1452 1459 Checks for access permission for any of given predicates for specific
1453 1460 repository. In order to fulfill the request any of predicates must be meet
1454 1461 """
1455 1462 def _get_repo_name(self):
1456 1463 _request = self._get_request()
1457 1464 return get_repo_slug(_request)
1458 1465
1459 1466 def check_permissions(self, user):
1460 1467 perms = user.permissions
1461 1468 repo_name = self._get_repo_name()
1462 1469
1463 1470 try:
1464 1471 user_perms = set([perms['repositories'][repo_name]])
1465 1472 except KeyError:
1466 1473 log.debug(
1467 1474 'cannot locate repo with name: `%s` in permissions defs',
1468 1475 repo_name)
1469 1476 return False
1470 1477
1471 1478 log.debug('checking `%s` permissions for repo `%s`',
1472 1479 user_perms, repo_name)
1473 1480 if self.required_perms.intersection(user_perms):
1474 1481 return True
1475 1482 return False
1476 1483
1477 1484
1478 1485 class HasRepoGroupPermissionAllDecorator(PermsDecorator):
1479 1486 """
1480 1487 Checks for access permission for all given predicates for specific
1481 1488 repository group. All of them have to be meet in order to
1482 1489 fulfill the request
1483 1490 """
1484 1491 def _get_repo_group_name(self):
1485 1492 _request = self._get_request()
1486 1493 return get_repo_group_slug(_request)
1487 1494
1488 1495 def check_permissions(self, user):
1489 1496 perms = user.permissions
1490 1497 group_name = self._get_repo_group_name()
1491 1498 try:
1492 1499 user_perms = set([perms['repositories_groups'][group_name]])
1493 1500 except KeyError:
1494 1501 log.debug(
1495 1502 'cannot locate repo group with name: `%s` in permissions defs',
1496 1503 group_name)
1497 1504 return False
1498 1505
1499 1506 log.debug('checking `%s` permissions for repo group `%s`',
1500 1507 user_perms, group_name)
1501 1508 if self.required_perms.issubset(user_perms):
1502 1509 return True
1503 1510 return False
1504 1511
1505 1512
1506 1513 class HasRepoGroupPermissionAnyDecorator(PermsDecorator):
1507 1514 """
1508 1515 Checks for access permission for any of given predicates for specific
1509 1516 repository group. In order to fulfill the request any
1510 1517 of predicates must be met
1511 1518 """
1512 1519 def _get_repo_group_name(self):
1513 1520 _request = self._get_request()
1514 1521 return get_repo_group_slug(_request)
1515 1522
1516 1523 def check_permissions(self, user):
1517 1524 perms = user.permissions
1518 1525 group_name = self._get_repo_group_name()
1519 1526
1520 1527 try:
1521 1528 user_perms = set([perms['repositories_groups'][group_name]])
1522 1529 except KeyError:
1523 1530 log.debug(
1524 1531 'cannot locate repo group with name: `%s` in permissions defs',
1525 1532 group_name)
1526 1533 return False
1527 1534
1528 1535 log.debug('checking `%s` permissions for repo group `%s`',
1529 1536 user_perms, group_name)
1530 1537 if self.required_perms.intersection(user_perms):
1531 1538 return True
1532 1539 return False
1533 1540
1534 1541
1535 1542 class HasUserGroupPermissionAllDecorator(PermsDecorator):
1536 1543 """
1537 1544 Checks for access permission for all given predicates for specific
1538 1545 user group. All of them have to be meet in order to fulfill the request
1539 1546 """
1540 1547 def _get_user_group_name(self):
1541 1548 _request = self._get_request()
1542 1549 return get_user_group_slug(_request)
1543 1550
1544 1551 def check_permissions(self, user):
1545 1552 perms = user.permissions
1546 1553 group_name = self._get_user_group_name()
1547 1554 try:
1548 1555 user_perms = set([perms['user_groups'][group_name]])
1549 1556 except KeyError:
1550 1557 return False
1551 1558
1552 1559 if self.required_perms.issubset(user_perms):
1553 1560 return True
1554 1561 return False
1555 1562
1556 1563
1557 1564 class HasUserGroupPermissionAnyDecorator(PermsDecorator):
1558 1565 """
1559 1566 Checks for access permission for any of given predicates for specific
1560 1567 user group. In order to fulfill the request any of predicates must be meet
1561 1568 """
1562 1569 def _get_user_group_name(self):
1563 1570 _request = self._get_request()
1564 1571 return get_user_group_slug(_request)
1565 1572
1566 1573 def check_permissions(self, user):
1567 1574 perms = user.permissions
1568 1575 group_name = self._get_user_group_name()
1569 1576 try:
1570 1577 user_perms = set([perms['user_groups'][group_name]])
1571 1578 except KeyError:
1572 1579 return False
1573 1580
1574 1581 if self.required_perms.intersection(user_perms):
1575 1582 return True
1576 1583 return False
1577 1584
1578 1585
1579 1586 # CHECK FUNCTIONS
1580 1587 class PermsFunction(object):
1581 1588 """Base function for other check functions"""
1582 1589
1583 1590 def __init__(self, *perms):
1584 1591 self.required_perms = set(perms)
1585 1592 self.repo_name = None
1586 1593 self.repo_group_name = None
1587 1594 self.user_group_name = None
1588 1595
1589 1596 def __bool__(self):
1590 1597 frame = inspect.currentframe()
1591 1598 stack_trace = traceback.format_stack(frame)
1592 1599 log.error('Checking bool value on a class instance of perm '
1593 1600 'function is not allowed: %s' % ''.join(stack_trace))
1594 1601 # rather than throwing errors, here we always return False so if by
1595 1602 # accident someone checks truth for just an instance it will always end
1596 1603 # up in returning False
1597 1604 return False
1598 1605 __nonzero__ = __bool__
1599 1606
1600 1607 def __call__(self, check_location='', user=None):
1601 1608 if not user:
1602 1609 log.debug('Using user attribute from global request')
1603 1610 # TODO: remove this someday,put as user as attribute here
1604 1611 request = self._get_request()
1605 1612 user = request.user
1606 1613
1607 1614 # init auth user if not already given
1608 1615 if not isinstance(user, AuthUser):
1609 1616 log.debug('Wrapping user %s into AuthUser', user)
1610 1617 user = AuthUser(user.user_id)
1611 1618
1612 1619 cls_name = self.__class__.__name__
1613 1620 check_scope = self._get_check_scope(cls_name)
1614 1621 check_location = check_location or 'unspecified location'
1615 1622
1616 1623 log.debug('checking cls:%s %s usr:%s %s @ %s', cls_name,
1617 1624 self.required_perms, user, check_scope, check_location)
1618 1625 if not user:
1619 1626 log.warning('Empty user given for permission check')
1620 1627 return False
1621 1628
1622 1629 if self.check_permissions(user):
1623 1630 log.debug('Permission to repo:`%s` GRANTED for user:`%s` @ %s',
1624 1631 check_scope, user, check_location)
1625 1632 return True
1626 1633
1627 1634 else:
1628 1635 log.debug('Permission to repo:`%s` DENIED for user:`%s` @ %s',
1629 1636 check_scope, user, check_location)
1630 1637 return False
1631 1638
1632 1639 def _get_request(self):
1633 1640 return get_request(self)
1634 1641
1635 1642 def _get_check_scope(self, cls_name):
1636 1643 return {
1637 1644 'HasPermissionAll': 'GLOBAL',
1638 1645 'HasPermissionAny': 'GLOBAL',
1639 1646 'HasRepoPermissionAll': 'repo:%s' % self.repo_name,
1640 1647 'HasRepoPermissionAny': 'repo:%s' % self.repo_name,
1641 1648 'HasRepoGroupPermissionAll': 'repo_group:%s' % self.repo_group_name,
1642 1649 'HasRepoGroupPermissionAny': 'repo_group:%s' % self.repo_group_name,
1643 1650 'HasUserGroupPermissionAll': 'user_group:%s' % self.user_group_name,
1644 1651 'HasUserGroupPermissionAny': 'user_group:%s' % self.user_group_name,
1645 1652 }.get(cls_name, '?:%s' % cls_name)
1646 1653
1647 1654 def check_permissions(self, user):
1648 1655 """Dummy function for overriding"""
1649 1656 raise Exception('You have to write this function in child class')
1650 1657
1651 1658
1652 1659 class HasPermissionAll(PermsFunction):
1653 1660 def check_permissions(self, user):
1654 1661 perms = user.permissions_with_scope({})
1655 1662 if self.required_perms.issubset(perms.get('global')):
1656 1663 return True
1657 1664 return False
1658 1665
1659 1666
1660 1667 class HasPermissionAny(PermsFunction):
1661 1668 def check_permissions(self, user):
1662 1669 perms = user.permissions_with_scope({})
1663 1670 if self.required_perms.intersection(perms.get('global')):
1664 1671 return True
1665 1672 return False
1666 1673
1667 1674
1668 1675 class HasRepoPermissionAll(PermsFunction):
1669 1676 def __call__(self, repo_name=None, check_location='', user=None):
1670 1677 self.repo_name = repo_name
1671 1678 return super(HasRepoPermissionAll, self).__call__(check_location, user)
1672 1679
1673 1680 def _get_repo_name(self):
1674 1681 if not self.repo_name:
1675 1682 _request = self._get_request()
1676 1683 self.repo_name = get_repo_slug(_request)
1677 1684 return self.repo_name
1678 1685
1679 1686 def check_permissions(self, user):
1680 1687 self.repo_name = self._get_repo_name()
1681 1688 perms = user.permissions
1682 1689 try:
1683 1690 user_perms = set([perms['repositories'][self.repo_name]])
1684 1691 except KeyError:
1685 1692 return False
1686 1693 if self.required_perms.issubset(user_perms):
1687 1694 return True
1688 1695 return False
1689 1696
1690 1697
1691 1698 class HasRepoPermissionAny(PermsFunction):
1692 1699 def __call__(self, repo_name=None, check_location='', user=None):
1693 1700 self.repo_name = repo_name
1694 1701 return super(HasRepoPermissionAny, self).__call__(check_location, user)
1695 1702
1696 1703 def _get_repo_name(self):
1697 1704 if not self.repo_name:
1698 1705 _request = self._get_request()
1699 1706 self.repo_name = get_repo_slug(_request)
1700 1707 return self.repo_name
1701 1708
1702 1709 def check_permissions(self, user):
1703 1710 self.repo_name = self._get_repo_name()
1704 1711 perms = user.permissions
1705 1712 try:
1706 1713 user_perms = set([perms['repositories'][self.repo_name]])
1707 1714 except KeyError:
1708 1715 return False
1709 1716 if self.required_perms.intersection(user_perms):
1710 1717 return True
1711 1718 return False
1712 1719
1713 1720
1714 1721 class HasRepoGroupPermissionAny(PermsFunction):
1715 1722 def __call__(self, group_name=None, check_location='', user=None):
1716 1723 self.repo_group_name = group_name
1717 1724 return super(HasRepoGroupPermissionAny, self).__call__(
1718 1725 check_location, user)
1719 1726
1720 1727 def check_permissions(self, user):
1721 1728 perms = user.permissions
1722 1729 try:
1723 1730 user_perms = set(
1724 1731 [perms['repositories_groups'][self.repo_group_name]])
1725 1732 except KeyError:
1726 1733 return False
1727 1734 if self.required_perms.intersection(user_perms):
1728 1735 return True
1729 1736 return False
1730 1737
1731 1738
1732 1739 class HasRepoGroupPermissionAll(PermsFunction):
1733 1740 def __call__(self, group_name=None, check_location='', user=None):
1734 1741 self.repo_group_name = group_name
1735 1742 return super(HasRepoGroupPermissionAll, self).__call__(
1736 1743 check_location, user)
1737 1744
1738 1745 def check_permissions(self, user):
1739 1746 perms = user.permissions
1740 1747 try:
1741 1748 user_perms = set(
1742 1749 [perms['repositories_groups'][self.repo_group_name]])
1743 1750 except KeyError:
1744 1751 return False
1745 1752 if self.required_perms.issubset(user_perms):
1746 1753 return True
1747 1754 return False
1748 1755
1749 1756
1750 1757 class HasUserGroupPermissionAny(PermsFunction):
1751 1758 def __call__(self, user_group_name=None, check_location='', user=None):
1752 1759 self.user_group_name = user_group_name
1753 1760 return super(HasUserGroupPermissionAny, self).__call__(
1754 1761 check_location, user)
1755 1762
1756 1763 def check_permissions(self, user):
1757 1764 perms = user.permissions
1758 1765 try:
1759 1766 user_perms = set([perms['user_groups'][self.user_group_name]])
1760 1767 except KeyError:
1761 1768 return False
1762 1769 if self.required_perms.intersection(user_perms):
1763 1770 return True
1764 1771 return False
1765 1772
1766 1773
1767 1774 class HasUserGroupPermissionAll(PermsFunction):
1768 1775 def __call__(self, user_group_name=None, check_location='', user=None):
1769 1776 self.user_group_name = user_group_name
1770 1777 return super(HasUserGroupPermissionAll, self).__call__(
1771 1778 check_location, user)
1772 1779
1773 1780 def check_permissions(self, user):
1774 1781 perms = user.permissions
1775 1782 try:
1776 1783 user_perms = set([perms['user_groups'][self.user_group_name]])
1777 1784 except KeyError:
1778 1785 return False
1779 1786 if self.required_perms.issubset(user_perms):
1780 1787 return True
1781 1788 return False
1782 1789
1783 1790
1784 1791 # SPECIAL VERSION TO HANDLE MIDDLEWARE AUTH
1785 1792 class HasPermissionAnyMiddleware(object):
1786 1793 def __init__(self, *perms):
1787 1794 self.required_perms = set(perms)
1788 1795
1789 1796 def __call__(self, user, repo_name):
1790 1797 # repo_name MUST be unicode, since we handle keys in permission
1791 1798 # dict by unicode
1792 1799 repo_name = safe_unicode(repo_name)
1793 1800 user = AuthUser(user.user_id)
1794 1801 log.debug(
1795 1802 'Checking VCS protocol permissions %s for user:%s repo:`%s`',
1796 1803 self.required_perms, user, repo_name)
1797 1804
1798 1805 if self.check_permissions(user, repo_name):
1799 1806 log.debug('Permission to repo:`%s` GRANTED for user:%s @ %s',
1800 1807 repo_name, user, 'PermissionMiddleware')
1801 1808 return True
1802 1809
1803 1810 else:
1804 1811 log.debug('Permission to repo:`%s` DENIED for user:%s @ %s',
1805 1812 repo_name, user, 'PermissionMiddleware')
1806 1813 return False
1807 1814
1808 1815 def check_permissions(self, user, repo_name):
1809 1816 perms = user.permissions_with_scope({'repo_name': repo_name})
1810 1817
1811 1818 try:
1812 1819 user_perms = set([perms['repositories'][repo_name]])
1813 1820 except Exception:
1814 1821 log.exception('Error while accessing user permissions')
1815 1822 return False
1816 1823
1817 1824 if self.required_perms.intersection(user_perms):
1818 1825 return True
1819 1826 return False
1820 1827
1821 1828
1822 1829 # SPECIAL VERSION TO HANDLE API AUTH
1823 1830 class _BaseApiPerm(object):
1824 1831 def __init__(self, *perms):
1825 1832 self.required_perms = set(perms)
1826 1833
1827 1834 def __call__(self, check_location=None, user=None, repo_name=None,
1828 1835 group_name=None, user_group_name=None):
1829 1836 cls_name = self.__class__.__name__
1830 1837 check_scope = 'global:%s' % (self.required_perms,)
1831 1838 if repo_name:
1832 1839 check_scope += ', repo_name:%s' % (repo_name,)
1833 1840
1834 1841 if group_name:
1835 1842 check_scope += ', repo_group_name:%s' % (group_name,)
1836 1843
1837 1844 if user_group_name:
1838 1845 check_scope += ', user_group_name:%s' % (user_group_name,)
1839 1846
1840 1847 log.debug(
1841 1848 'checking cls:%s %s %s @ %s'
1842 1849 % (cls_name, self.required_perms, check_scope, check_location))
1843 1850 if not user:
1844 1851 log.debug('Empty User passed into arguments')
1845 1852 return False
1846 1853
1847 1854 # process user
1848 1855 if not isinstance(user, AuthUser):
1849 1856 user = AuthUser(user.user_id)
1850 1857 if not check_location:
1851 1858 check_location = 'unspecified'
1852 1859 if self.check_permissions(user.permissions, repo_name, group_name,
1853 1860 user_group_name):
1854 1861 log.debug('Permission to repo:`%s` GRANTED for user:`%s` @ %s',
1855 1862 check_scope, user, check_location)
1856 1863 return True
1857 1864
1858 1865 else:
1859 1866 log.debug('Permission to repo:`%s` DENIED for user:`%s` @ %s',
1860 1867 check_scope, user, check_location)
1861 1868 return False
1862 1869
1863 1870 def check_permissions(self, perm_defs, repo_name=None, group_name=None,
1864 1871 user_group_name=None):
1865 1872 """
1866 1873 implement in child class should return True if permissions are ok,
1867 1874 False otherwise
1868 1875
1869 1876 :param perm_defs: dict with permission definitions
1870 1877 :param repo_name: repo name
1871 1878 """
1872 1879 raise NotImplementedError()
1873 1880
1874 1881
1875 1882 class HasPermissionAllApi(_BaseApiPerm):
1876 1883 def check_permissions(self, perm_defs, repo_name=None, group_name=None,
1877 1884 user_group_name=None):
1878 1885 if self.required_perms.issubset(perm_defs.get('global')):
1879 1886 return True
1880 1887 return False
1881 1888
1882 1889
1883 1890 class HasPermissionAnyApi(_BaseApiPerm):
1884 1891 def check_permissions(self, perm_defs, repo_name=None, group_name=None,
1885 1892 user_group_name=None):
1886 1893 if self.required_perms.intersection(perm_defs.get('global')):
1887 1894 return True
1888 1895 return False
1889 1896
1890 1897
1891 1898 class HasRepoPermissionAllApi(_BaseApiPerm):
1892 1899 def check_permissions(self, perm_defs, repo_name=None, group_name=None,
1893 1900 user_group_name=None):
1894 1901 try:
1895 1902 _user_perms = set([perm_defs['repositories'][repo_name]])
1896 1903 except KeyError:
1897 1904 log.warning(traceback.format_exc())
1898 1905 return False
1899 1906 if self.required_perms.issubset(_user_perms):
1900 1907 return True
1901 1908 return False
1902 1909
1903 1910
1904 1911 class HasRepoPermissionAnyApi(_BaseApiPerm):
1905 1912 def check_permissions(self, perm_defs, repo_name=None, group_name=None,
1906 1913 user_group_name=None):
1907 1914 try:
1908 1915 _user_perms = set([perm_defs['repositories'][repo_name]])
1909 1916 except KeyError:
1910 1917 log.warning(traceback.format_exc())
1911 1918 return False
1912 1919 if self.required_perms.intersection(_user_perms):
1913 1920 return True
1914 1921 return False
1915 1922
1916 1923
1917 1924 class HasRepoGroupPermissionAnyApi(_BaseApiPerm):
1918 1925 def check_permissions(self, perm_defs, repo_name=None, group_name=None,
1919 1926 user_group_name=None):
1920 1927 try:
1921 1928 _user_perms = set([perm_defs['repositories_groups'][group_name]])
1922 1929 except KeyError:
1923 1930 log.warning(traceback.format_exc())
1924 1931 return False
1925 1932 if self.required_perms.intersection(_user_perms):
1926 1933 return True
1927 1934 return False
1928 1935
1929 1936
1930 1937 class HasRepoGroupPermissionAllApi(_BaseApiPerm):
1931 1938 def check_permissions(self, perm_defs, repo_name=None, group_name=None,
1932 1939 user_group_name=None):
1933 1940 try:
1934 1941 _user_perms = set([perm_defs['repositories_groups'][group_name]])
1935 1942 except KeyError:
1936 1943 log.warning(traceback.format_exc())
1937 1944 return False
1938 1945 if self.required_perms.issubset(_user_perms):
1939 1946 return True
1940 1947 return False
1941 1948
1942 1949
1943 1950 class HasUserGroupPermissionAnyApi(_BaseApiPerm):
1944 1951 def check_permissions(self, perm_defs, repo_name=None, group_name=None,
1945 1952 user_group_name=None):
1946 1953 try:
1947 1954 _user_perms = set([perm_defs['user_groups'][user_group_name]])
1948 1955 except KeyError:
1949 1956 log.warning(traceback.format_exc())
1950 1957 return False
1951 1958 if self.required_perms.intersection(_user_perms):
1952 1959 return True
1953 1960 return False
1954 1961
1955 1962
1956 1963 def check_ip_access(source_ip, allowed_ips=None):
1957 1964 """
1958 1965 Checks if source_ip is a subnet of any of allowed_ips.
1959 1966
1960 1967 :param source_ip:
1961 1968 :param allowed_ips: list of allowed ips together with mask
1962 1969 """
1963 1970 log.debug('checking if ip:%s is subnet of %s' % (source_ip, allowed_ips))
1964 1971 source_ip_address = ipaddress.ip_address(safe_unicode(source_ip))
1965 1972 if isinstance(allowed_ips, (tuple, list, set)):
1966 1973 for ip in allowed_ips:
1967 1974 ip = safe_unicode(ip)
1968 1975 try:
1969 1976 network_address = ipaddress.ip_network(ip, strict=False)
1970 1977 if source_ip_address in network_address:
1971 1978 log.debug('IP %s is network %s' %
1972 1979 (source_ip_address, network_address))
1973 1980 return True
1974 1981 # for any case we cannot determine the IP, don't crash just
1975 1982 # skip it and log as error, we want to say forbidden still when
1976 1983 # sending bad IP
1977 1984 except Exception:
1978 1985 log.error(traceback.format_exc())
1979 1986 continue
1980 1987 return False
1981 1988
1982 1989
1983 1990 def get_cython_compat_decorator(wrapper, func):
1984 1991 """
1985 1992 Creates a cython compatible decorator. The previously used
1986 1993 decorator.decorator() function seems to be incompatible with cython.
1987 1994
1988 1995 :param wrapper: __wrapper method of the decorator class
1989 1996 :param func: decorated function
1990 1997 """
1991 1998 @wraps(func)
1992 1999 def local_wrapper(*args, **kwds):
1993 2000 return wrapper(func, *args, **kwds)
1994 2001 local_wrapper.__wrapped__ = func
1995 2002 return local_wrapper
1996 2003
1997 2004
General Comments 0
You need to be logged in to leave comments. Login now