##// END OF EJS Templates
admin-users: add view for user groups managment...
Bartłomiej Wołyńczyk -
r1556:9ac012a6 default
parent child Browse files
Show More
@@ -1,108 +1,110 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import mock
21 import mock
22 import pytest
22 import pytest
23
23
24 from rhodecode.model.user import UserModel
24 from rhodecode.model.user import UserModel
25 from rhodecode.model.user_group import UserGroupModel
25 from rhodecode.model.user_group import UserGroupModel
26 from rhodecode.tests import TEST_USER_REGULAR_LOGIN
26 from rhodecode.tests import TEST_USER_ADMIN_EMAIL
27 from rhodecode.api.tests.utils import (
27 from rhodecode.api.tests.utils import (
28 build_data, api_call, assert_error, assert_ok, crash, jsonify)
28 build_data, api_call, assert_error, assert_ok, crash, jsonify)
29
29
30
30
31 @pytest.mark.usefixtures("testuser_api", "app")
31 @pytest.mark.usefixtures("testuser_api", "app")
32 class TestUpdateUserGroup(object):
32 class TestUpdateUserGroup(object):
33 @pytest.mark.parametrize("changing_attr, updates", [
33 @pytest.mark.parametrize("changing_attr, updates", [
34 ('group_name', {'group_name': 'new_group_name'}),
34 ('group_name', {'group_name': 'new_group_name'}),
35 ('group_name', {'group_name': 'test_group_for_update'}),
35 ('group_name', {'group_name': 'test_group_for_update'}),
36 ('owner', {'owner': TEST_USER_REGULAR_LOGIN}),
36 # ('owner', {'owner': TEST_USER_REGULAR_LOGIN}),
37 ('owner_email', {'owner_email': TEST_USER_ADMIN_EMAIL}),
37 ('active', {'active': False}),
38 ('active', {'active': False}),
38 ('active', {'active': True})
39 ('active', {'active': True})
39 ])
40 ])
40 def test_api_update_user_group(self, changing_attr, updates, user_util):
41 def test_api_update_user_group(self, changing_attr, updates, user_util):
41 user_group = user_util.create_user_group()
42 user_group = user_util.create_user_group()
42 group_name = user_group.users_group_name
43 group_name = user_group.users_group_name
43 expected_api_data = user_group.get_api_data()
44 expected_api_data = user_group.get_api_data()
44 expected_api_data.update(updates)
45 expected_api_data.update(updates)
45
46
46 id_, params = build_data(
47 id_, params = build_data(
47 self.apikey, 'update_user_group', usergroupid=group_name,
48 self.apikey, 'update_user_group', usergroupid=group_name,
48 **updates)
49 **updates)
49 response = api_call(self.app, params)
50 response = api_call(self.app, params)
50
51
51 expected = {
52 expected = {
52 'msg': 'updated user group ID:%s %s' % (
53 'msg': 'updated user group ID:%s %s' % (
53 user_group.users_group_id, user_group.users_group_name),
54 user_group.users_group_id, user_group.users_group_name),
54 'user_group': jsonify(expected_api_data)
55 'user_group': jsonify(expected_api_data)
55 }
56 }
56 assert_ok(id_, expected, given=response.body)
57 assert_ok(id_, expected, given=response.body)
57
58
58 @pytest.mark.parametrize("changing_attr, updates", [
59 @pytest.mark.parametrize("changing_attr, updates", [
59 # TODO: mikhail: decide if we need to test against the commented params
60 # TODO: mikhail: decide if we need to test against the commented params
60 # ('group_name', {'group_name': 'new_group_name'}),
61 # ('group_name', {'group_name': 'new_group_name'}),
61 # ('group_name', {'group_name': 'test_group_for_update'}),
62 # ('group_name', {'group_name': 'test_group_for_update'}),
62 ('owner', {'owner': TEST_USER_REGULAR_LOGIN}),
63 # ('owner', {'owner': TEST_USER_REGULAR_LOGIN}),
64 ('owner_email', {'owner_email': TEST_USER_ADMIN_EMAIL}),
63 ('active', {'active': False}),
65 ('active', {'active': False}),
64 ('active', {'active': True})
66 ('active', {'active': True})
65 ])
67 ])
66 def test_api_update_user_group_regular_user(
68 def test_api_update_user_group_regular_user(
67 self, changing_attr, updates, user_util):
69 self, changing_attr, updates, user_util):
68 user_group = user_util.create_user_group()
70 user_group = user_util.create_user_group()
69 group_name = user_group.users_group_name
71 group_name = user_group.users_group_name
70 expected_api_data = user_group.get_api_data()
72 expected_api_data = user_group.get_api_data()
71 expected_api_data.update(updates)
73 expected_api_data.update(updates)
72
74
73
75
74 # grant permission to this user
76 # grant permission to this user
75 user = UserModel().get_by_username(self.TEST_USER_LOGIN)
77 user = UserModel().get_by_username(self.TEST_USER_LOGIN)
76
78
77 user_util.grant_user_permission_to_user_group(
79 user_util.grant_user_permission_to_user_group(
78 user_group, user, 'usergroup.admin')
80 user_group, user, 'usergroup.admin')
79 id_, params = build_data(
81 id_, params = build_data(
80 self.apikey_regular, 'update_user_group',
82 self.apikey_regular, 'update_user_group',
81 usergroupid=group_name, **updates)
83 usergroupid=group_name, **updates)
82 response = api_call(self.app, params)
84 response = api_call(self.app, params)
83 expected = {
85 expected = {
84 'msg': 'updated user group ID:%s %s' % (
86 'msg': 'updated user group ID:%s %s' % (
85 user_group.users_group_id, user_group.users_group_name),
87 user_group.users_group_id, user_group.users_group_name),
86 'user_group': jsonify(expected_api_data)
88 'user_group': jsonify(expected_api_data)
87 }
89 }
88 assert_ok(id_, expected, given=response.body)
90 assert_ok(id_, expected, given=response.body)
89
91
90 def test_api_update_user_group_regular_user_no_permission(self, user_util):
92 def test_api_update_user_group_regular_user_no_permission(self, user_util):
91 user_group = user_util.create_user_group()
93 user_group = user_util.create_user_group()
92 group_name = user_group.users_group_name
94 group_name = user_group.users_group_name
93 id_, params = build_data(
95 id_, params = build_data(
94 self.apikey_regular, 'update_user_group', usergroupid=group_name)
96 self.apikey_regular, 'update_user_group', usergroupid=group_name)
95 response = api_call(self.app, params)
97 response = api_call(self.app, params)
96
98
97 expected = 'user group `%s` does not exist' % (group_name)
99 expected = 'user group `%s` does not exist' % (group_name)
98 assert_error(id_, expected, given=response.body)
100 assert_error(id_, expected, given=response.body)
99
101
100 @mock.patch.object(UserGroupModel, 'update', crash)
102 @mock.patch.object(UserGroupModel, 'update', crash)
101 def test_api_update_user_group_exception_occurred(self, user_util):
103 def test_api_update_user_group_exception_occurred(self, user_util):
102 user_group = user_util.create_user_group()
104 user_group = user_util.create_user_group()
103 group_name = user_group.users_group_name
105 group_name = user_group.users_group_name
104 id_, params = build_data(
106 id_, params = build_data(
105 self.apikey, 'update_user_group', usergroupid=group_name)
107 self.apikey, 'update_user_group', usergroupid=group_name)
106 response = api_call(self.app, params)
108 response = api_call(self.app, params)
107 expected = 'failed to update user group `%s`' % (group_name,)
109 expected = 'failed to update user group `%s`' % (group_name,)
108 assert_error(id_, expected, given=response.body)
110 assert_error(id_, expected, given=response.body)
@@ -1,85 +1,94 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2016-2017 RhodeCode GmbH
3 # Copyright (C) 2016-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21
21
22 from rhodecode.apps.admin.navigation import NavigationRegistry
22 from rhodecode.apps.admin.navigation import NavigationRegistry
23 from rhodecode.config.routing import ADMIN_PREFIX
23 from rhodecode.config.routing import ADMIN_PREFIX
24 from rhodecode.lib.utils2 import str2bool
24 from rhodecode.lib.utils2 import str2bool
25
25
26
26
27 def admin_routes(config):
27 def admin_routes(config):
28 """
28 """
29 Admin prefixed routes
29 Admin prefixed routes
30 """
30 """
31
31
32 config.add_route(
32 config.add_route(
33 name='admin_settings_open_source',
33 name='admin_settings_open_source',
34 pattern='/settings/open_source')
34 pattern='/settings/open_source')
35 config.add_route(
35 config.add_route(
36 name='admin_settings_vcs_svn_generate_cfg',
36 name='admin_settings_vcs_svn_generate_cfg',
37 pattern='/settings/vcs/svn_generate_cfg')
37 pattern='/settings/vcs/svn_generate_cfg')
38
38
39 config.add_route(
39 config.add_route(
40 name='admin_settings_system',
40 name='admin_settings_system',
41 pattern='/settings/system')
41 pattern='/settings/system')
42 config.add_route(
42 config.add_route(
43 name='admin_settings_system_update',
43 name='admin_settings_system_update',
44 pattern='/settings/system/updates')
44 pattern='/settings/system/updates')
45
45
46 config.add_route(
46 config.add_route(
47 name='admin_settings_sessions',
47 name='admin_settings_sessions',
48 pattern='/settings/sessions')
48 pattern='/settings/sessions')
49 config.add_route(
49 config.add_route(
50 name='admin_settings_sessions_cleanup',
50 name='admin_settings_sessions_cleanup',
51 pattern='/settings/sessions/cleanup')
51 pattern='/settings/sessions/cleanup')
52
52
53 # users admin
53 # users admin
54 config.add_route(
54 config.add_route(
55 name='users',
55 name='users',
56 pattern='/users')
56 pattern='/users')
57
57
58 config.add_route(
58 config.add_route(
59 name='users_data',
59 name='users_data',
60 pattern='/users_data')
60 pattern='/users_data')
61
61
62 # user auth tokens
62 # user auth tokens
63 config.add_route(
63 config.add_route(
64 name='edit_user_auth_tokens',
64 name='edit_user_auth_tokens',
65 pattern='/users/{user_id:\d+}/edit/auth_tokens')
65 pattern='/users/{user_id:\d+}/edit/auth_tokens')
66 config.add_route(
66 config.add_route(
67 name='edit_user_auth_tokens_add',
67 name='edit_user_auth_tokens_add',
68 pattern='/users/{user_id:\d+}/edit/auth_tokens/new')
68 pattern='/users/{user_id:\d+}/edit/auth_tokens/new')
69 config.add_route(
69 config.add_route(
70 name='edit_user_auth_tokens_delete',
70 name='edit_user_auth_tokens_delete',
71 pattern='/users/{user_id:\d+}/edit/auth_tokens/delete')
71 pattern='/users/{user_id:\d+}/edit/auth_tokens/delete')
72
72
73 # user groups management
74 config.add_route(
75 name='edit_user_groups_management',
76 pattern='/users/{user_id:\d+}/edit/groups_management')
77
78 config.add_route(
79 name='edit_user_groups_management_updates',
80 pattern='/users/{user_id:\d+}/edit/edit_user_groups_management/updates')
81
73
82
74 def includeme(config):
83 def includeme(config):
75 settings = config.get_settings()
84 settings = config.get_settings()
76
85
77 # Create admin navigation registry and add it to the pyramid registry.
86 # Create admin navigation registry and add it to the pyramid registry.
78 labs_active = str2bool(settings.get('labs_settings_active', False))
87 labs_active = str2bool(settings.get('labs_settings_active', False))
79 navigation_registry = NavigationRegistry(labs_active=labs_active)
88 navigation_registry = NavigationRegistry(labs_active=labs_active)
80 config.registry.registerUtility(navigation_registry)
89 config.registry.registerUtility(navigation_registry)
81
90
82 config.include(admin_routes, route_prefix=ADMIN_PREFIX)
91 config.include(admin_routes, route_prefix=ADMIN_PREFIX)
83
92
84 # Scan module for configuration decorators.
93 # Scan module for configuration decorators.
85 config.scan()
94 config.scan()
@@ -1,237 +1,285 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2016-2017 RhodeCode GmbH
3 # Copyright (C) 2016-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import logging
21 import logging
22
22
23 from pyramid.httpexceptions import HTTPFound
23 from pyramid.httpexceptions import HTTPFound
24 from pyramid.view import view_config
24 from pyramid.view import view_config
25 from rhodecode_tools.lib.ext_json import json
25
26
26 from rhodecode.apps._base import BaseAppView
27 from rhodecode.apps._base import BaseAppView
27 from rhodecode.lib.auth import (
28 from rhodecode.lib.auth import (
28 LoginRequired, HasPermissionAllDecorator, CSRFRequired)
29 LoginRequired, HasPermissionAllDecorator, CSRFRequired)
29 from rhodecode.lib import helpers as h
30 from rhodecode.lib import helpers as h
30 from rhodecode.lib.utils import PartialRenderer
31 from rhodecode.lib.utils import PartialRenderer
31 from rhodecode.lib.utils2 import safe_int, safe_unicode
32 from rhodecode.lib.utils2 import safe_int, safe_unicode
32 from rhodecode.model.auth_token import AuthTokenModel
33 from rhodecode.model.auth_token import AuthTokenModel
34 from rhodecode.model.user_group import UserGroupModel
33 from rhodecode.model.db import User, or_
35 from rhodecode.model.db import User, or_
34 from rhodecode.model.meta import Session
36 from rhodecode.model.meta import Session
35
37
36 log = logging.getLogger(__name__)
38 log = logging.getLogger(__name__)
37
39
38
40
39 class AdminUsersView(BaseAppView):
41 class AdminUsersView(BaseAppView):
40 ALLOW_SCOPED_TOKENS = False
42 ALLOW_SCOPED_TOKENS = False
41 """
43 """
42 This view has alternative version inside EE, if modified please take a look
44 This view has alternative version inside EE, if modified please take a look
43 in there as well.
45 in there as well.
44 """
46 """
45
47
46 def load_default_context(self):
48 def load_default_context(self):
47 c = self._get_local_tmpl_context()
49 c = self._get_local_tmpl_context()
48 c.allow_scoped_tokens = self.ALLOW_SCOPED_TOKENS
50 c.allow_scoped_tokens = self.ALLOW_SCOPED_TOKENS
49 self._register_global_c(c)
51 self._register_global_c(c)
50 return c
52 return c
51
53
52 def _redirect_for_default_user(self, username):
54 def _redirect_for_default_user(self, username):
53 _ = self.request.translate
55 _ = self.request.translate
54 if username == User.DEFAULT_USER:
56 if username == User.DEFAULT_USER:
55 h.flash(_("You can't edit this user"), category='warning')
57 h.flash(_("You can't edit this user"), category='warning')
56 # TODO(marcink): redirect to 'users' admin panel once this
58 # TODO(marcink): redirect to 'users' admin panel once this
57 # is a pyramid view
59 # is a pyramid view
58 raise HTTPFound('/')
60 raise HTTPFound('/')
59
61
60 def _extract_ordering(self, request):
62 def _extract_ordering(self, request):
61 column_index = safe_int(request.GET.get('order[0][column]'))
63 column_index = safe_int(request.GET.get('order[0][column]'))
62 order_dir = request.GET.get(
64 order_dir = request.GET.get(
63 'order[0][dir]', 'desc')
65 'order[0][dir]', 'desc')
64 order_by = request.GET.get(
66 order_by = request.GET.get(
65 'columns[%s][data][sort]' % column_index, 'name_raw')
67 'columns[%s][data][sort]' % column_index, 'name_raw')
66
68
67 # translate datatable to DB columns
69 # translate datatable to DB columns
68 order_by = {
70 order_by = {
69 'first_name': 'name',
71 'first_name': 'name',
70 'last_name': 'lastname',
72 'last_name': 'lastname',
71 }.get(order_by) or order_by
73 }.get(order_by) or order_by
72
74
73 search_q = request.GET.get('search[value]')
75 search_q = request.GET.get('search[value]')
74 return search_q, order_by, order_dir
76 return search_q, order_by, order_dir
75
77
76 def _extract_chunk(self, request):
78 def _extract_chunk(self, request):
77 start = safe_int(request.GET.get('start'), 0)
79 start = safe_int(request.GET.get('start'), 0)
78 length = safe_int(request.GET.get('length'), 25)
80 length = safe_int(request.GET.get('length'), 25)
79 draw = safe_int(request.GET.get('draw'))
81 draw = safe_int(request.GET.get('draw'))
80 return draw, start, length
82 return draw, start, length
81
83
82 @HasPermissionAllDecorator('hg.admin')
84 @HasPermissionAllDecorator('hg.admin')
83 @view_config(
85 @view_config(
84 route_name='users', request_method='GET',
86 route_name='users', request_method='GET',
85 renderer='rhodecode:templates/admin/users/users.mako')
87 renderer='rhodecode:templates/admin/users/users.mako')
86 def users_list(self):
88 def users_list(self):
87 c = self.load_default_context()
89 c = self.load_default_context()
88 return self._get_template_context(c)
90 return self._get_template_context(c)
89
91
90 @HasPermissionAllDecorator('hg.admin')
92 @HasPermissionAllDecorator('hg.admin')
91 @view_config(
93 @view_config(
92 # renderer defined below
94 # renderer defined below
93 route_name='users_data', request_method='GET', renderer='json',
95 route_name='users_data', request_method='GET', renderer='json',
94 xhr=True)
96 xhr=True)
95 def users_list_data(self):
97 def users_list_data(self):
96 draw, start, limit = self._extract_chunk(self.request)
98 draw, start, limit = self._extract_chunk(self.request)
97 search_q, order_by, order_dir = self._extract_ordering(self.request)
99 search_q, order_by, order_dir = self._extract_ordering(self.request)
98
100
99 _render = PartialRenderer('data_table/_dt_elements.mako')
101 _render = PartialRenderer('data_table/_dt_elements.mako')
100
102
101 def user_actions(user_id, username):
103 def user_actions(user_id, username):
102 return _render("user_actions", user_id, username)
104 return _render("user_actions", user_id, username)
103
105
104 users_data_total_count = User.query()\
106 users_data_total_count = User.query()\
105 .filter(User.username != User.DEFAULT_USER) \
107 .filter(User.username != User.DEFAULT_USER) \
106 .count()
108 .count()
107
109
108 # json generate
110 # json generate
109 base_q = User.query().filter(User.username != User.DEFAULT_USER)
111 base_q = User.query().filter(User.username != User.DEFAULT_USER)
110
112
111 if search_q:
113 if search_q:
112 like_expression = u'%{}%'.format(safe_unicode(search_q))
114 like_expression = u'%{}%'.format(safe_unicode(search_q))
113 base_q = base_q.filter(or_(
115 base_q = base_q.filter(or_(
114 User.username.ilike(like_expression),
116 User.username.ilike(like_expression),
115 User._email.ilike(like_expression),
117 User._email.ilike(like_expression),
116 User.name.ilike(like_expression),
118 User.name.ilike(like_expression),
117 User.lastname.ilike(like_expression),
119 User.lastname.ilike(like_expression),
118 ))
120 ))
119
121
120 users_data_total_filtered_count = base_q.count()
122 users_data_total_filtered_count = base_q.count()
121
123
122 sort_col = getattr(User, order_by, None)
124 sort_col = getattr(User, order_by, None)
123 if sort_col and order_dir == 'asc':
125 if sort_col and order_dir == 'asc':
124 base_q = base_q.order_by(sort_col.asc().nullslast())
126 base_q = base_q.order_by(sort_col.asc().nullslast())
125 elif sort_col:
127 elif sort_col:
126 base_q = base_q.order_by(sort_col.desc().nullslast())
128 base_q = base_q.order_by(sort_col.desc().nullslast())
127
129
128 base_q = base_q.offset(start).limit(limit)
130 base_q = base_q.offset(start).limit(limit)
129 users_list = base_q.all()
131 users_list = base_q.all()
130
132
131 users_data = []
133 users_data = []
132 for user in users_list:
134 for user in users_list:
133 users_data.append({
135 users_data.append({
134 "username": h.gravatar_with_user(user.username),
136 "username": h.gravatar_with_user(user.username),
135 "email": user.email,
137 "email": user.email,
136 "first_name": h.escape(user.name),
138 "first_name": h.escape(user.name),
137 "last_name": h.escape(user.lastname),
139 "last_name": h.escape(user.lastname),
138 "last_login": h.format_date(user.last_login),
140 "last_login": h.format_date(user.last_login),
139 "last_activity": h.format_date(user.last_activity),
141 "last_activity": h.format_date(user.last_activity),
140 "active": h.bool2icon(user.active),
142 "active": h.bool2icon(user.active),
141 "active_raw": user.active,
143 "active_raw": user.active,
142 "admin": h.bool2icon(user.admin),
144 "admin": h.bool2icon(user.admin),
143 "extern_type": user.extern_type,
145 "extern_type": user.extern_type,
144 "extern_name": user.extern_name,
146 "extern_name": user.extern_name,
145 "action": user_actions(user.user_id, user.username),
147 "action": user_actions(user.user_id, user.username),
146 })
148 })
147
149
148 data = ({
150 data = ({
149 'draw': draw,
151 'draw': draw,
150 'data': users_data,
152 'data': users_data,
151 'recordsTotal': users_data_total_count,
153 'recordsTotal': users_data_total_count,
152 'recordsFiltered': users_data_total_filtered_count,
154 'recordsFiltered': users_data_total_filtered_count,
153 })
155 })
154
156
155 return data
157 return data
156
158
157 @LoginRequired()
159 @LoginRequired()
158 @HasPermissionAllDecorator('hg.admin')
160 @HasPermissionAllDecorator('hg.admin')
159 @view_config(
161 @view_config(
160 route_name='edit_user_auth_tokens', request_method='GET',
162 route_name='edit_user_auth_tokens', request_method='GET',
161 renderer='rhodecode:templates/admin/users/user_edit.mako')
163 renderer='rhodecode:templates/admin/users/user_edit.mako')
162 def auth_tokens(self):
164 def auth_tokens(self):
163 _ = self.request.translate
165 _ = self.request.translate
164 c = self.load_default_context()
166 c = self.load_default_context()
165
167
166 user_id = self.request.matchdict.get('user_id')
168 user_id = self.request.matchdict.get('user_id')
167 c.user = User.get_or_404(user_id, pyramid_exc=True)
169 c.user = User.get_or_404(user_id, pyramid_exc=True)
168 self._redirect_for_default_user(c.user.username)
170 self._redirect_for_default_user(c.user.username)
169
171
170 c.active = 'auth_tokens'
172 c.active = 'auth_tokens'
171
173
172 c.lifetime_values = [
174 c.lifetime_values = [
173 (str(-1), _('forever')),
175 (str(-1), _('forever')),
174 (str(5), _('5 minutes')),
176 (str(5), _('5 minutes')),
175 (str(60), _('1 hour')),
177 (str(60), _('1 hour')),
176 (str(60 * 24), _('1 day')),
178 (str(60 * 24), _('1 day')),
177 (str(60 * 24 * 30), _('1 month')),
179 (str(60 * 24 * 30), _('1 month')),
178 ]
180 ]
179 c.lifetime_options = [(c.lifetime_values, _("Lifetime"))]
181 c.lifetime_options = [(c.lifetime_values, _("Lifetime"))]
180 c.role_values = [
182 c.role_values = [
181 (x, AuthTokenModel.cls._get_role_name(x))
183 (x, AuthTokenModel.cls._get_role_name(x))
182 for x in AuthTokenModel.cls.ROLES]
184 for x in AuthTokenModel.cls.ROLES]
183 c.role_options = [(c.role_values, _("Role"))]
185 c.role_options = [(c.role_values, _("Role"))]
184 c.user_auth_tokens = AuthTokenModel().get_auth_tokens(
186 c.user_auth_tokens = AuthTokenModel().get_auth_tokens(
185 c.user.user_id, show_expired=True)
187 c.user.user_id, show_expired=True)
186 return self._get_template_context(c)
188 return self._get_template_context(c)
187
189
188 def maybe_attach_token_scope(self, token):
190 def maybe_attach_token_scope(self, token):
189 # implemented in EE edition
191 # implemented in EE edition
190 pass
192 pass
191
193
192 @LoginRequired()
194 @LoginRequired()
193 @HasPermissionAllDecorator('hg.admin')
195 @HasPermissionAllDecorator('hg.admin')
194 @CSRFRequired()
196 @CSRFRequired()
195 @view_config(
197 @view_config(
196 route_name='edit_user_auth_tokens_add', request_method='POST')
198 route_name='edit_user_auth_tokens_add', request_method='POST')
197 def auth_tokens_add(self):
199 def auth_tokens_add(self):
198 _ = self.request.translate
200 _ = self.request.translate
199 c = self.load_default_context()
201 c = self.load_default_context()
200
202
201 user_id = self.request.matchdict.get('user_id')
203 user_id = self.request.matchdict.get('user_id')
202 c.user = User.get_or_404(user_id, pyramid_exc=True)
204 c.user = User.get_or_404(user_id, pyramid_exc=True)
203 self._redirect_for_default_user(c.user.username)
205 self._redirect_for_default_user(c.user.username)
204
206
205 lifetime = safe_int(self.request.POST.get('lifetime'), -1)
207 lifetime = safe_int(self.request.POST.get('lifetime'), -1)
206 description = self.request.POST.get('description')
208 description = self.request.POST.get('description')
207 role = self.request.POST.get('role')
209 role = self.request.POST.get('role')
208
210
209 token = AuthTokenModel().create(
211 token = AuthTokenModel().create(
210 c.user.user_id, description, lifetime, role)
212 c.user.user_id, description, lifetime, role)
211 self.maybe_attach_token_scope(token)
213 self.maybe_attach_token_scope(token)
212 Session().commit()
214 Session().commit()
213
215
214 h.flash(_("Auth token successfully created"), category='success')
216 h.flash(_("Auth token successfully created"), category='success')
215 return HTTPFound(h.route_path('edit_user_auth_tokens', user_id=user_id))
217 return HTTPFound(h.route_path('edit_user_auth_tokens', user_id=user_id))
216
218
217 @LoginRequired()
219 @LoginRequired()
218 @HasPermissionAllDecorator('hg.admin')
220 @HasPermissionAllDecorator('hg.admin')
219 @CSRFRequired()
221 @CSRFRequired()
220 @view_config(
222 @view_config(
221 route_name='edit_user_auth_tokens_delete', request_method='POST')
223 route_name='edit_user_auth_tokens_delete', request_method='POST')
222 def auth_tokens_delete(self):
224 def auth_tokens_delete(self):
223 _ = self.request.translate
225 _ = self.request.translate
224 c = self.load_default_context()
226 c = self.load_default_context()
225
227
226 user_id = self.request.matchdict.get('user_id')
228 user_id = self.request.matchdict.get('user_id')
227 c.user = User.get_or_404(user_id, pyramid_exc=True)
229 c.user = User.get_or_404(user_id, pyramid_exc=True)
228 self._redirect_for_default_user(c.user.username)
230 self._redirect_for_default_user(c.user.username)
229
231
230 del_auth_token = self.request.POST.get('del_auth_token')
232 del_auth_token = self.request.POST.get('del_auth_token')
231
233
232 if del_auth_token:
234 if del_auth_token:
233 AuthTokenModel().delete(del_auth_token, c.user.user_id)
235 AuthTokenModel().delete(del_auth_token, c.user.user_id)
234 Session().commit()
236 Session().commit()
235 h.flash(_("Auth token successfully deleted"), category='success')
237 h.flash(_("Auth token successfully deleted"), category='success')
236
238
237 return HTTPFound(h.route_path('edit_user_auth_tokens', user_id=user_id))
239 return HTTPFound(h.route_path('edit_user_auth_tokens', user_id=user_id))
240
241
242 @LoginRequired()
243 @HasPermissionAllDecorator('hg.admin')
244 @view_config(
245 route_name='edit_user_groups_management', request_method='GET',
246 renderer='rhodecode:templates/admin/users/user_edit.mako')
247 def groups_management(self):
248 c = self.load_default_context()
249
250 user_id = self.request.matchdict.get('user_id')
251 c.user = User.get_or_404(user_id, pyramid_exc=True)
252 c.data = c.user.group_member
253 self._redirect_for_default_user(c.user.username)
254 groups = [UserGroupModel.get_user_groups_as_dict(group.users_group) for group in c.user.group_member]
255 c.groups = json.dumps(groups)
256 c.active = 'groups'
257
258 return self._get_template_context(c)
259
260
261 @LoginRequired()
262 @HasPermissionAllDecorator('hg.admin')
263 @view_config(
264 route_name='edit_user_groups_management_updates', request_method='POST')
265 def groups_management_updates(self):
266 _ = self.request.translate
267 c = self.load_default_context()
268
269 user_id = self.request.matchdict.get('user_id')
270 c.user = User.get_or_404(user_id, pyramid_exc=True)
271 self._redirect_for_default_user(c.user.username)
272
273 users_groups = set(self.request.POST.getall('users_group_id'))
274 users_groups_model = []
275
276 for ugid in users_groups:
277 users_groups_model.append(UserGroupModel().get_group(safe_int(ugid)))
278 user_group_model = UserGroupModel()
279 user_group_model.change_groups(c.user, users_groups_model)
280
281 Session().commit()
282 c.active = 'user_groups_management'
283 h.flash(_("Groups successfully changed"), category='success')
284
285 return HTTPFound(h.route_path('edit_user_groups_management', user_id=user_id))
@@ -1,3967 +1,3968 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 """
21 """
22 Database Models for RhodeCode Enterprise
22 Database Models for RhodeCode Enterprise
23 """
23 """
24
24
25 import re
25 import re
26 import os
26 import os
27 import time
27 import time
28 import hashlib
28 import hashlib
29 import logging
29 import logging
30 import datetime
30 import datetime
31 import warnings
31 import warnings
32 import ipaddress
32 import ipaddress
33 import functools
33 import functools
34 import traceback
34 import traceback
35 import collections
35 import collections
36
36
37
37
38 from sqlalchemy import *
38 from sqlalchemy import *
39 from sqlalchemy.ext.declarative import declared_attr
39 from sqlalchemy.ext.declarative import declared_attr
40 from sqlalchemy.ext.hybrid import hybrid_property
40 from sqlalchemy.ext.hybrid import hybrid_property
41 from sqlalchemy.orm import (
41 from sqlalchemy.orm import (
42 relationship, joinedload, class_mapper, validates, aliased)
42 relationship, joinedload, class_mapper, validates, aliased)
43 from sqlalchemy.sql.expression import true
43 from sqlalchemy.sql.expression import true
44 from beaker.cache import cache_region
44 from beaker.cache import cache_region
45 from zope.cachedescriptors.property import Lazy as LazyProperty
45 from zope.cachedescriptors.property import Lazy as LazyProperty
46
46
47 from pylons import url
47 from pylons import url
48 from pylons.i18n.translation import lazy_ugettext as _
48 from pylons.i18n.translation import lazy_ugettext as _
49
49
50 from rhodecode.lib.vcs import get_vcs_instance
50 from rhodecode.lib.vcs import get_vcs_instance
51 from rhodecode.lib.vcs.backends.base import EmptyCommit, Reference
51 from rhodecode.lib.vcs.backends.base import EmptyCommit, Reference
52 from rhodecode.lib.utils2 import (
52 from rhodecode.lib.utils2 import (
53 str2bool, safe_str, get_commit_safe, safe_unicode, md5_safe,
53 str2bool, safe_str, get_commit_safe, safe_unicode, md5_safe,
54 time_to_datetime, aslist, Optional, safe_int, get_clone_url, AttributeDict,
54 time_to_datetime, aslist, Optional, safe_int, get_clone_url, AttributeDict,
55 glob2re, StrictAttributeDict, cleaned_uri)
55 glob2re, StrictAttributeDict, cleaned_uri)
56 from rhodecode.lib.jsonalchemy import MutationObj, MutationList, JsonType
56 from rhodecode.lib.jsonalchemy import MutationObj, MutationList, JsonType
57 from rhodecode.lib.ext_json import json
57 from rhodecode.lib.ext_json import json
58 from rhodecode.lib.caching_query import FromCache
58 from rhodecode.lib.caching_query import FromCache
59 from rhodecode.lib.encrypt import AESCipher
59 from rhodecode.lib.encrypt import AESCipher
60
60
61 from rhodecode.model.meta import Base, Session
61 from rhodecode.model.meta import Base, Session
62
62
63 URL_SEP = '/'
63 URL_SEP = '/'
64 log = logging.getLogger(__name__)
64 log = logging.getLogger(__name__)
65
65
66 # =============================================================================
66 # =============================================================================
67 # BASE CLASSES
67 # BASE CLASSES
68 # =============================================================================
68 # =============================================================================
69
69
70 # this is propagated from .ini file rhodecode.encrypted_values.secret or
70 # this is propagated from .ini file rhodecode.encrypted_values.secret or
71 # beaker.session.secret if first is not set.
71 # beaker.session.secret if first is not set.
72 # and initialized at environment.py
72 # and initialized at environment.py
73 ENCRYPTION_KEY = None
73 ENCRYPTION_KEY = None
74
74
75 # used to sort permissions by types, '#' used here is not allowed to be in
75 # used to sort permissions by types, '#' used here is not allowed to be in
76 # usernames, and it's very early in sorted string.printable table.
76 # usernames, and it's very early in sorted string.printable table.
77 PERMISSION_TYPE_SORT = {
77 PERMISSION_TYPE_SORT = {
78 'admin': '####',
78 'admin': '####',
79 'write': '###',
79 'write': '###',
80 'read': '##',
80 'read': '##',
81 'none': '#',
81 'none': '#',
82 }
82 }
83
83
84
84
85 def display_sort(obj):
85 def display_sort(obj):
86 """
86 """
87 Sort function used to sort permissions in .permissions() function of
87 Sort function used to sort permissions in .permissions() function of
88 Repository, RepoGroup, UserGroup. Also it put the default user in front
88 Repository, RepoGroup, UserGroup. Also it put the default user in front
89 of all other resources
89 of all other resources
90 """
90 """
91
91
92 if obj.username == User.DEFAULT_USER:
92 if obj.username == User.DEFAULT_USER:
93 return '#####'
93 return '#####'
94 prefix = PERMISSION_TYPE_SORT.get(obj.permission.split('.')[-1], '')
94 prefix = PERMISSION_TYPE_SORT.get(obj.permission.split('.')[-1], '')
95 return prefix + obj.username
95 return prefix + obj.username
96
96
97
97
98 def _hash_key(k):
98 def _hash_key(k):
99 return md5_safe(k)
99 return md5_safe(k)
100
100
101
101
102 class EncryptedTextValue(TypeDecorator):
102 class EncryptedTextValue(TypeDecorator):
103 """
103 """
104 Special column for encrypted long text data, use like::
104 Special column for encrypted long text data, use like::
105
105
106 value = Column("encrypted_value", EncryptedValue(), nullable=False)
106 value = Column("encrypted_value", EncryptedValue(), nullable=False)
107
107
108 This column is intelligent so if value is in unencrypted form it return
108 This column is intelligent so if value is in unencrypted form it return
109 unencrypted form, but on save it always encrypts
109 unencrypted form, but on save it always encrypts
110 """
110 """
111 impl = Text
111 impl = Text
112
112
113 def process_bind_param(self, value, dialect):
113 def process_bind_param(self, value, dialect):
114 if not value:
114 if not value:
115 return value
115 return value
116 if value.startswith('enc$aes$') or value.startswith('enc$aes_hmac$'):
116 if value.startswith('enc$aes$') or value.startswith('enc$aes_hmac$'):
117 # protect against double encrypting if someone manually starts
117 # protect against double encrypting if someone manually starts
118 # doing
118 # doing
119 raise ValueError('value needs to be in unencrypted format, ie. '
119 raise ValueError('value needs to be in unencrypted format, ie. '
120 'not starting with enc$aes')
120 'not starting with enc$aes')
121 return 'enc$aes_hmac$%s' % AESCipher(
121 return 'enc$aes_hmac$%s' % AESCipher(
122 ENCRYPTION_KEY, hmac=True).encrypt(value)
122 ENCRYPTION_KEY, hmac=True).encrypt(value)
123
123
124 def process_result_value(self, value, dialect):
124 def process_result_value(self, value, dialect):
125 import rhodecode
125 import rhodecode
126
126
127 if not value:
127 if not value:
128 return value
128 return value
129
129
130 parts = value.split('$', 3)
130 parts = value.split('$', 3)
131 if not len(parts) == 3:
131 if not len(parts) == 3:
132 # probably not encrypted values
132 # probably not encrypted values
133 return value
133 return value
134 else:
134 else:
135 if parts[0] != 'enc':
135 if parts[0] != 'enc':
136 # parts ok but without our header ?
136 # parts ok but without our header ?
137 return value
137 return value
138 enc_strict_mode = str2bool(rhodecode.CONFIG.get(
138 enc_strict_mode = str2bool(rhodecode.CONFIG.get(
139 'rhodecode.encrypted_values.strict') or True)
139 'rhodecode.encrypted_values.strict') or True)
140 # at that stage we know it's our encryption
140 # at that stage we know it's our encryption
141 if parts[1] == 'aes':
141 if parts[1] == 'aes':
142 decrypted_data = AESCipher(ENCRYPTION_KEY).decrypt(parts[2])
142 decrypted_data = AESCipher(ENCRYPTION_KEY).decrypt(parts[2])
143 elif parts[1] == 'aes_hmac':
143 elif parts[1] == 'aes_hmac':
144 decrypted_data = AESCipher(
144 decrypted_data = AESCipher(
145 ENCRYPTION_KEY, hmac=True,
145 ENCRYPTION_KEY, hmac=True,
146 strict_verification=enc_strict_mode).decrypt(parts[2])
146 strict_verification=enc_strict_mode).decrypt(parts[2])
147 else:
147 else:
148 raise ValueError(
148 raise ValueError(
149 'Encryption type part is wrong, must be `aes` '
149 'Encryption type part is wrong, must be `aes` '
150 'or `aes_hmac`, got `%s` instead' % (parts[1]))
150 'or `aes_hmac`, got `%s` instead' % (parts[1]))
151 return decrypted_data
151 return decrypted_data
152
152
153
153
154 class BaseModel(object):
154 class BaseModel(object):
155 """
155 """
156 Base Model for all classes
156 Base Model for all classes
157 """
157 """
158
158
159 @classmethod
159 @classmethod
160 def _get_keys(cls):
160 def _get_keys(cls):
161 """return column names for this model """
161 """return column names for this model """
162 return class_mapper(cls).c.keys()
162 return class_mapper(cls).c.keys()
163
163
164 def get_dict(self):
164 def get_dict(self):
165 """
165 """
166 return dict with keys and values corresponding
166 return dict with keys and values corresponding
167 to this model data """
167 to this model data """
168
168
169 d = {}
169 d = {}
170 for k in self._get_keys():
170 for k in self._get_keys():
171 d[k] = getattr(self, k)
171 d[k] = getattr(self, k)
172
172
173 # also use __json__() if present to get additional fields
173 # also use __json__() if present to get additional fields
174 _json_attr = getattr(self, '__json__', None)
174 _json_attr = getattr(self, '__json__', None)
175 if _json_attr:
175 if _json_attr:
176 # update with attributes from __json__
176 # update with attributes from __json__
177 if callable(_json_attr):
177 if callable(_json_attr):
178 _json_attr = _json_attr()
178 _json_attr = _json_attr()
179 for k, val in _json_attr.iteritems():
179 for k, val in _json_attr.iteritems():
180 d[k] = val
180 d[k] = val
181 return d
181 return d
182
182
183 def get_appstruct(self):
183 def get_appstruct(self):
184 """return list with keys and values tuples corresponding
184 """return list with keys and values tuples corresponding
185 to this model data """
185 to this model data """
186
186
187 l = []
187 l = []
188 for k in self._get_keys():
188 for k in self._get_keys():
189 l.append((k, getattr(self, k),))
189 l.append((k, getattr(self, k),))
190 return l
190 return l
191
191
192 def populate_obj(self, populate_dict):
192 def populate_obj(self, populate_dict):
193 """populate model with data from given populate_dict"""
193 """populate model with data from given populate_dict"""
194
194
195 for k in self._get_keys():
195 for k in self._get_keys():
196 if k in populate_dict:
196 if k in populate_dict:
197 setattr(self, k, populate_dict[k])
197 setattr(self, k, populate_dict[k])
198
198
199 @classmethod
199 @classmethod
200 def query(cls):
200 def query(cls):
201 return Session().query(cls)
201 return Session().query(cls)
202
202
203 @classmethod
203 @classmethod
204 def get(cls, id_):
204 def get(cls, id_):
205 if id_:
205 if id_:
206 return cls.query().get(id_)
206 return cls.query().get(id_)
207
207
208 @classmethod
208 @classmethod
209 def get_or_404(cls, id_, pyramid_exc=False):
209 def get_or_404(cls, id_, pyramid_exc=False):
210 if pyramid_exc:
210 if pyramid_exc:
211 # NOTE(marcink): backward compat, once migration to pyramid
211 # NOTE(marcink): backward compat, once migration to pyramid
212 # this should only use pyramid exceptions
212 # this should only use pyramid exceptions
213 from pyramid.httpexceptions import HTTPNotFound
213 from pyramid.httpexceptions import HTTPNotFound
214 else:
214 else:
215 from webob.exc import HTTPNotFound
215 from webob.exc import HTTPNotFound
216
216
217 try:
217 try:
218 id_ = int(id_)
218 id_ = int(id_)
219 except (TypeError, ValueError):
219 except (TypeError, ValueError):
220 raise HTTPNotFound
220 raise HTTPNotFound
221
221
222 res = cls.query().get(id_)
222 res = cls.query().get(id_)
223 if not res:
223 if not res:
224 raise HTTPNotFound
224 raise HTTPNotFound
225 return res
225 return res
226
226
227 @classmethod
227 @classmethod
228 def getAll(cls):
228 def getAll(cls):
229 # deprecated and left for backward compatibility
229 # deprecated and left for backward compatibility
230 return cls.get_all()
230 return cls.get_all()
231
231
232 @classmethod
232 @classmethod
233 def get_all(cls):
233 def get_all(cls):
234 return cls.query().all()
234 return cls.query().all()
235
235
236 @classmethod
236 @classmethod
237 def delete(cls, id_):
237 def delete(cls, id_):
238 obj = cls.query().get(id_)
238 obj = cls.query().get(id_)
239 Session().delete(obj)
239 Session().delete(obj)
240
240
241 @classmethod
241 @classmethod
242 def identity_cache(cls, session, attr_name, value):
242 def identity_cache(cls, session, attr_name, value):
243 exist_in_session = []
243 exist_in_session = []
244 for (item_cls, pkey), instance in session.identity_map.items():
244 for (item_cls, pkey), instance in session.identity_map.items():
245 if cls == item_cls and getattr(instance, attr_name) == value:
245 if cls == item_cls and getattr(instance, attr_name) == value:
246 exist_in_session.append(instance)
246 exist_in_session.append(instance)
247 if exist_in_session:
247 if exist_in_session:
248 if len(exist_in_session) == 1:
248 if len(exist_in_session) == 1:
249 return exist_in_session[0]
249 return exist_in_session[0]
250 log.exception(
250 log.exception(
251 'multiple objects with attr %s and '
251 'multiple objects with attr %s and '
252 'value %s found with same name: %r',
252 'value %s found with same name: %r',
253 attr_name, value, exist_in_session)
253 attr_name, value, exist_in_session)
254
254
255 def __repr__(self):
255 def __repr__(self):
256 if hasattr(self, '__unicode__'):
256 if hasattr(self, '__unicode__'):
257 # python repr needs to return str
257 # python repr needs to return str
258 try:
258 try:
259 return safe_str(self.__unicode__())
259 return safe_str(self.__unicode__())
260 except UnicodeDecodeError:
260 except UnicodeDecodeError:
261 pass
261 pass
262 return '<DB:%s>' % (self.__class__.__name__)
262 return '<DB:%s>' % (self.__class__.__name__)
263
263
264
264
265 class RhodeCodeSetting(Base, BaseModel):
265 class RhodeCodeSetting(Base, BaseModel):
266 __tablename__ = 'rhodecode_settings'
266 __tablename__ = 'rhodecode_settings'
267 __table_args__ = (
267 __table_args__ = (
268 UniqueConstraint('app_settings_name'),
268 UniqueConstraint('app_settings_name'),
269 {'extend_existing': True, 'mysql_engine': 'InnoDB',
269 {'extend_existing': True, 'mysql_engine': 'InnoDB',
270 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
270 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
271 )
271 )
272
272
273 SETTINGS_TYPES = {
273 SETTINGS_TYPES = {
274 'str': safe_str,
274 'str': safe_str,
275 'int': safe_int,
275 'int': safe_int,
276 'unicode': safe_unicode,
276 'unicode': safe_unicode,
277 'bool': str2bool,
277 'bool': str2bool,
278 'list': functools.partial(aslist, sep=',')
278 'list': functools.partial(aslist, sep=',')
279 }
279 }
280 DEFAULT_UPDATE_URL = 'https://rhodecode.com/api/v1/info/versions'
280 DEFAULT_UPDATE_URL = 'https://rhodecode.com/api/v1/info/versions'
281 GLOBAL_CONF_KEY = 'app_settings'
281 GLOBAL_CONF_KEY = 'app_settings'
282
282
283 app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
283 app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
284 app_settings_name = Column("app_settings_name", String(255), nullable=True, unique=None, default=None)
284 app_settings_name = Column("app_settings_name", String(255), nullable=True, unique=None, default=None)
285 _app_settings_value = Column("app_settings_value", String(4096), nullable=True, unique=None, default=None)
285 _app_settings_value = Column("app_settings_value", String(4096), nullable=True, unique=None, default=None)
286 _app_settings_type = Column("app_settings_type", String(255), nullable=True, unique=None, default=None)
286 _app_settings_type = Column("app_settings_type", String(255), nullable=True, unique=None, default=None)
287
287
288 def __init__(self, key='', val='', type='unicode'):
288 def __init__(self, key='', val='', type='unicode'):
289 self.app_settings_name = key
289 self.app_settings_name = key
290 self.app_settings_type = type
290 self.app_settings_type = type
291 self.app_settings_value = val
291 self.app_settings_value = val
292
292
293 @validates('_app_settings_value')
293 @validates('_app_settings_value')
294 def validate_settings_value(self, key, val):
294 def validate_settings_value(self, key, val):
295 assert type(val) == unicode
295 assert type(val) == unicode
296 return val
296 return val
297
297
298 @hybrid_property
298 @hybrid_property
299 def app_settings_value(self):
299 def app_settings_value(self):
300 v = self._app_settings_value
300 v = self._app_settings_value
301 _type = self.app_settings_type
301 _type = self.app_settings_type
302 if _type:
302 if _type:
303 _type = self.app_settings_type.split('.')[0]
303 _type = self.app_settings_type.split('.')[0]
304 # decode the encrypted value
304 # decode the encrypted value
305 if 'encrypted' in self.app_settings_type:
305 if 'encrypted' in self.app_settings_type:
306 cipher = EncryptedTextValue()
306 cipher = EncryptedTextValue()
307 v = safe_unicode(cipher.process_result_value(v, None))
307 v = safe_unicode(cipher.process_result_value(v, None))
308
308
309 converter = self.SETTINGS_TYPES.get(_type) or \
309 converter = self.SETTINGS_TYPES.get(_type) or \
310 self.SETTINGS_TYPES['unicode']
310 self.SETTINGS_TYPES['unicode']
311 return converter(v)
311 return converter(v)
312
312
313 @app_settings_value.setter
313 @app_settings_value.setter
314 def app_settings_value(self, val):
314 def app_settings_value(self, val):
315 """
315 """
316 Setter that will always make sure we use unicode in app_settings_value
316 Setter that will always make sure we use unicode in app_settings_value
317
317
318 :param val:
318 :param val:
319 """
319 """
320 val = safe_unicode(val)
320 val = safe_unicode(val)
321 # encode the encrypted value
321 # encode the encrypted value
322 if 'encrypted' in self.app_settings_type:
322 if 'encrypted' in self.app_settings_type:
323 cipher = EncryptedTextValue()
323 cipher = EncryptedTextValue()
324 val = safe_unicode(cipher.process_bind_param(val, None))
324 val = safe_unicode(cipher.process_bind_param(val, None))
325 self._app_settings_value = val
325 self._app_settings_value = val
326
326
327 @hybrid_property
327 @hybrid_property
328 def app_settings_type(self):
328 def app_settings_type(self):
329 return self._app_settings_type
329 return self._app_settings_type
330
330
331 @app_settings_type.setter
331 @app_settings_type.setter
332 def app_settings_type(self, val):
332 def app_settings_type(self, val):
333 if val.split('.')[0] not in self.SETTINGS_TYPES:
333 if val.split('.')[0] not in self.SETTINGS_TYPES:
334 raise Exception('type must be one of %s got %s'
334 raise Exception('type must be one of %s got %s'
335 % (self.SETTINGS_TYPES.keys(), val))
335 % (self.SETTINGS_TYPES.keys(), val))
336 self._app_settings_type = val
336 self._app_settings_type = val
337
337
338 def __unicode__(self):
338 def __unicode__(self):
339 return u"<%s('%s:%s[%s]')>" % (
339 return u"<%s('%s:%s[%s]')>" % (
340 self.__class__.__name__,
340 self.__class__.__name__,
341 self.app_settings_name, self.app_settings_value,
341 self.app_settings_name, self.app_settings_value,
342 self.app_settings_type
342 self.app_settings_type
343 )
343 )
344
344
345
345
346 class RhodeCodeUi(Base, BaseModel):
346 class RhodeCodeUi(Base, BaseModel):
347 __tablename__ = 'rhodecode_ui'
347 __tablename__ = 'rhodecode_ui'
348 __table_args__ = (
348 __table_args__ = (
349 UniqueConstraint('ui_key'),
349 UniqueConstraint('ui_key'),
350 {'extend_existing': True, 'mysql_engine': 'InnoDB',
350 {'extend_existing': True, 'mysql_engine': 'InnoDB',
351 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
351 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
352 )
352 )
353
353
354 HOOK_REPO_SIZE = 'changegroup.repo_size'
354 HOOK_REPO_SIZE = 'changegroup.repo_size'
355 # HG
355 # HG
356 HOOK_PRE_PULL = 'preoutgoing.pre_pull'
356 HOOK_PRE_PULL = 'preoutgoing.pre_pull'
357 HOOK_PULL = 'outgoing.pull_logger'
357 HOOK_PULL = 'outgoing.pull_logger'
358 HOOK_PRE_PUSH = 'prechangegroup.pre_push'
358 HOOK_PRE_PUSH = 'prechangegroup.pre_push'
359 HOOK_PRETX_PUSH = 'pretxnchangegroup.pre_push'
359 HOOK_PRETX_PUSH = 'pretxnchangegroup.pre_push'
360 HOOK_PUSH = 'changegroup.push_logger'
360 HOOK_PUSH = 'changegroup.push_logger'
361
361
362 # TODO: johbo: Unify way how hooks are configured for git and hg,
362 # TODO: johbo: Unify way how hooks are configured for git and hg,
363 # git part is currently hardcoded.
363 # git part is currently hardcoded.
364
364
365 # SVN PATTERNS
365 # SVN PATTERNS
366 SVN_BRANCH_ID = 'vcs_svn_branch'
366 SVN_BRANCH_ID = 'vcs_svn_branch'
367 SVN_TAG_ID = 'vcs_svn_tag'
367 SVN_TAG_ID = 'vcs_svn_tag'
368
368
369 ui_id = Column(
369 ui_id = Column(
370 "ui_id", Integer(), nullable=False, unique=True, default=None,
370 "ui_id", Integer(), nullable=False, unique=True, default=None,
371 primary_key=True)
371 primary_key=True)
372 ui_section = Column(
372 ui_section = Column(
373 "ui_section", String(255), nullable=True, unique=None, default=None)
373 "ui_section", String(255), nullable=True, unique=None, default=None)
374 ui_key = Column(
374 ui_key = Column(
375 "ui_key", String(255), nullable=True, unique=None, default=None)
375 "ui_key", String(255), nullable=True, unique=None, default=None)
376 ui_value = Column(
376 ui_value = Column(
377 "ui_value", String(255), nullable=True, unique=None, default=None)
377 "ui_value", String(255), nullable=True, unique=None, default=None)
378 ui_active = Column(
378 ui_active = Column(
379 "ui_active", Boolean(), nullable=True, unique=None, default=True)
379 "ui_active", Boolean(), nullable=True, unique=None, default=True)
380
380
381 def __repr__(self):
381 def __repr__(self):
382 return '<%s[%s]%s=>%s]>' % (self.__class__.__name__, self.ui_section,
382 return '<%s[%s]%s=>%s]>' % (self.__class__.__name__, self.ui_section,
383 self.ui_key, self.ui_value)
383 self.ui_key, self.ui_value)
384
384
385
385
386 class RepoRhodeCodeSetting(Base, BaseModel):
386 class RepoRhodeCodeSetting(Base, BaseModel):
387 __tablename__ = 'repo_rhodecode_settings'
387 __tablename__ = 'repo_rhodecode_settings'
388 __table_args__ = (
388 __table_args__ = (
389 UniqueConstraint(
389 UniqueConstraint(
390 'app_settings_name', 'repository_id',
390 'app_settings_name', 'repository_id',
391 name='uq_repo_rhodecode_setting_name_repo_id'),
391 name='uq_repo_rhodecode_setting_name_repo_id'),
392 {'extend_existing': True, 'mysql_engine': 'InnoDB',
392 {'extend_existing': True, 'mysql_engine': 'InnoDB',
393 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
393 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
394 )
394 )
395
395
396 repository_id = Column(
396 repository_id = Column(
397 "repository_id", Integer(), ForeignKey('repositories.repo_id'),
397 "repository_id", Integer(), ForeignKey('repositories.repo_id'),
398 nullable=False)
398 nullable=False)
399 app_settings_id = Column(
399 app_settings_id = Column(
400 "app_settings_id", Integer(), nullable=False, unique=True,
400 "app_settings_id", Integer(), nullable=False, unique=True,
401 default=None, primary_key=True)
401 default=None, primary_key=True)
402 app_settings_name = Column(
402 app_settings_name = Column(
403 "app_settings_name", String(255), nullable=True, unique=None,
403 "app_settings_name", String(255), nullable=True, unique=None,
404 default=None)
404 default=None)
405 _app_settings_value = Column(
405 _app_settings_value = Column(
406 "app_settings_value", String(4096), nullable=True, unique=None,
406 "app_settings_value", String(4096), nullable=True, unique=None,
407 default=None)
407 default=None)
408 _app_settings_type = Column(
408 _app_settings_type = Column(
409 "app_settings_type", String(255), nullable=True, unique=None,
409 "app_settings_type", String(255), nullable=True, unique=None,
410 default=None)
410 default=None)
411
411
412 repository = relationship('Repository')
412 repository = relationship('Repository')
413
413
414 def __init__(self, repository_id, key='', val='', type='unicode'):
414 def __init__(self, repository_id, key='', val='', type='unicode'):
415 self.repository_id = repository_id
415 self.repository_id = repository_id
416 self.app_settings_name = key
416 self.app_settings_name = key
417 self.app_settings_type = type
417 self.app_settings_type = type
418 self.app_settings_value = val
418 self.app_settings_value = val
419
419
420 @validates('_app_settings_value')
420 @validates('_app_settings_value')
421 def validate_settings_value(self, key, val):
421 def validate_settings_value(self, key, val):
422 assert type(val) == unicode
422 assert type(val) == unicode
423 return val
423 return val
424
424
425 @hybrid_property
425 @hybrid_property
426 def app_settings_value(self):
426 def app_settings_value(self):
427 v = self._app_settings_value
427 v = self._app_settings_value
428 type_ = self.app_settings_type
428 type_ = self.app_settings_type
429 SETTINGS_TYPES = RhodeCodeSetting.SETTINGS_TYPES
429 SETTINGS_TYPES = RhodeCodeSetting.SETTINGS_TYPES
430 converter = SETTINGS_TYPES.get(type_) or SETTINGS_TYPES['unicode']
430 converter = SETTINGS_TYPES.get(type_) or SETTINGS_TYPES['unicode']
431 return converter(v)
431 return converter(v)
432
432
433 @app_settings_value.setter
433 @app_settings_value.setter
434 def app_settings_value(self, val):
434 def app_settings_value(self, val):
435 """
435 """
436 Setter that will always make sure we use unicode in app_settings_value
436 Setter that will always make sure we use unicode in app_settings_value
437
437
438 :param val:
438 :param val:
439 """
439 """
440 self._app_settings_value = safe_unicode(val)
440 self._app_settings_value = safe_unicode(val)
441
441
442 @hybrid_property
442 @hybrid_property
443 def app_settings_type(self):
443 def app_settings_type(self):
444 return self._app_settings_type
444 return self._app_settings_type
445
445
446 @app_settings_type.setter
446 @app_settings_type.setter
447 def app_settings_type(self, val):
447 def app_settings_type(self, val):
448 SETTINGS_TYPES = RhodeCodeSetting.SETTINGS_TYPES
448 SETTINGS_TYPES = RhodeCodeSetting.SETTINGS_TYPES
449 if val not in SETTINGS_TYPES:
449 if val not in SETTINGS_TYPES:
450 raise Exception('type must be one of %s got %s'
450 raise Exception('type must be one of %s got %s'
451 % (SETTINGS_TYPES.keys(), val))
451 % (SETTINGS_TYPES.keys(), val))
452 self._app_settings_type = val
452 self._app_settings_type = val
453
453
454 def __unicode__(self):
454 def __unicode__(self):
455 return u"<%s('%s:%s:%s[%s]')>" % (
455 return u"<%s('%s:%s:%s[%s]')>" % (
456 self.__class__.__name__, self.repository.repo_name,
456 self.__class__.__name__, self.repository.repo_name,
457 self.app_settings_name, self.app_settings_value,
457 self.app_settings_name, self.app_settings_value,
458 self.app_settings_type
458 self.app_settings_type
459 )
459 )
460
460
461
461
462 class RepoRhodeCodeUi(Base, BaseModel):
462 class RepoRhodeCodeUi(Base, BaseModel):
463 __tablename__ = 'repo_rhodecode_ui'
463 __tablename__ = 'repo_rhodecode_ui'
464 __table_args__ = (
464 __table_args__ = (
465 UniqueConstraint(
465 UniqueConstraint(
466 'repository_id', 'ui_section', 'ui_key',
466 'repository_id', 'ui_section', 'ui_key',
467 name='uq_repo_rhodecode_ui_repository_id_section_key'),
467 name='uq_repo_rhodecode_ui_repository_id_section_key'),
468 {'extend_existing': True, 'mysql_engine': 'InnoDB',
468 {'extend_existing': True, 'mysql_engine': 'InnoDB',
469 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
469 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
470 )
470 )
471
471
472 repository_id = Column(
472 repository_id = Column(
473 "repository_id", Integer(), ForeignKey('repositories.repo_id'),
473 "repository_id", Integer(), ForeignKey('repositories.repo_id'),
474 nullable=False)
474 nullable=False)
475 ui_id = Column(
475 ui_id = Column(
476 "ui_id", Integer(), nullable=False, unique=True, default=None,
476 "ui_id", Integer(), nullable=False, unique=True, default=None,
477 primary_key=True)
477 primary_key=True)
478 ui_section = Column(
478 ui_section = Column(
479 "ui_section", String(255), nullable=True, unique=None, default=None)
479 "ui_section", String(255), nullable=True, unique=None, default=None)
480 ui_key = Column(
480 ui_key = Column(
481 "ui_key", String(255), nullable=True, unique=None, default=None)
481 "ui_key", String(255), nullable=True, unique=None, default=None)
482 ui_value = Column(
482 ui_value = Column(
483 "ui_value", String(255), nullable=True, unique=None, default=None)
483 "ui_value", String(255), nullable=True, unique=None, default=None)
484 ui_active = Column(
484 ui_active = Column(
485 "ui_active", Boolean(), nullable=True, unique=None, default=True)
485 "ui_active", Boolean(), nullable=True, unique=None, default=True)
486
486
487 repository = relationship('Repository')
487 repository = relationship('Repository')
488
488
489 def __repr__(self):
489 def __repr__(self):
490 return '<%s[%s:%s]%s=>%s]>' % (
490 return '<%s[%s:%s]%s=>%s]>' % (
491 self.__class__.__name__, self.repository.repo_name,
491 self.__class__.__name__, self.repository.repo_name,
492 self.ui_section, self.ui_key, self.ui_value)
492 self.ui_section, self.ui_key, self.ui_value)
493
493
494
494
495 class User(Base, BaseModel):
495 class User(Base, BaseModel):
496 __tablename__ = 'users'
496 __tablename__ = 'users'
497 __table_args__ = (
497 __table_args__ = (
498 UniqueConstraint('username'), UniqueConstraint('email'),
498 UniqueConstraint('username'), UniqueConstraint('email'),
499 Index('u_username_idx', 'username'),
499 Index('u_username_idx', 'username'),
500 Index('u_email_idx', 'email'),
500 Index('u_email_idx', 'email'),
501 {'extend_existing': True, 'mysql_engine': 'InnoDB',
501 {'extend_existing': True, 'mysql_engine': 'InnoDB',
502 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
502 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
503 )
503 )
504 DEFAULT_USER = 'default'
504 DEFAULT_USER = 'default'
505 DEFAULT_USER_EMAIL = 'anonymous@rhodecode.org'
505 DEFAULT_USER_EMAIL = 'anonymous@rhodecode.org'
506 DEFAULT_GRAVATAR_URL = 'https://secure.gravatar.com/avatar/{md5email}?d=identicon&s={size}'
506 DEFAULT_GRAVATAR_URL = 'https://secure.gravatar.com/avatar/{md5email}?d=identicon&s={size}'
507
507
508 user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
508 user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
509 username = Column("username", String(255), nullable=True, unique=None, default=None)
509 username = Column("username", String(255), nullable=True, unique=None, default=None)
510 password = Column("password", String(255), nullable=True, unique=None, default=None)
510 password = Column("password", String(255), nullable=True, unique=None, default=None)
511 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
511 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
512 admin = Column("admin", Boolean(), nullable=True, unique=None, default=False)
512 admin = Column("admin", Boolean(), nullable=True, unique=None, default=False)
513 name = Column("firstname", String(255), nullable=True, unique=None, default=None)
513 name = Column("firstname", String(255), nullable=True, unique=None, default=None)
514 lastname = Column("lastname", String(255), nullable=True, unique=None, default=None)
514 lastname = Column("lastname", String(255), nullable=True, unique=None, default=None)
515 _email = Column("email", String(255), nullable=True, unique=None, default=None)
515 _email = Column("email", String(255), nullable=True, unique=None, default=None)
516 last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None)
516 last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None)
517 last_activity = Column('last_activity', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
517 last_activity = Column('last_activity', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
518
518
519 extern_type = Column("extern_type", String(255), nullable=True, unique=None, default=None)
519 extern_type = Column("extern_type", String(255), nullable=True, unique=None, default=None)
520 extern_name = Column("extern_name", String(255), nullable=True, unique=None, default=None)
520 extern_name = Column("extern_name", String(255), nullable=True, unique=None, default=None)
521 _api_key = Column("api_key", String(255), nullable=True, unique=None, default=None)
521 _api_key = Column("api_key", String(255), nullable=True, unique=None, default=None)
522 inherit_default_permissions = Column("inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
522 inherit_default_permissions = Column("inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
523 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
523 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
524 _user_data = Column("user_data", LargeBinary(), nullable=True) # JSON data
524 _user_data = Column("user_data", LargeBinary(), nullable=True) # JSON data
525
525
526 user_log = relationship('UserLog')
526 user_log = relationship('UserLog')
527 user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all')
527 user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all')
528
528
529 repositories = relationship('Repository')
529 repositories = relationship('Repository')
530 repository_groups = relationship('RepoGroup')
530 repository_groups = relationship('RepoGroup')
531 user_groups = relationship('UserGroup')
531 user_groups = relationship('UserGroup')
532
532
533 user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all')
533 user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all')
534 followings = relationship('UserFollowing', primaryjoin='UserFollowing.user_id==User.user_id', cascade='all')
534 followings = relationship('UserFollowing', primaryjoin='UserFollowing.user_id==User.user_id', cascade='all')
535
535
536 repo_to_perm = relationship('UserRepoToPerm', primaryjoin='UserRepoToPerm.user_id==User.user_id', cascade='all')
536 repo_to_perm = relationship('UserRepoToPerm', primaryjoin='UserRepoToPerm.user_id==User.user_id', cascade='all')
537 repo_group_to_perm = relationship('UserRepoGroupToPerm', primaryjoin='UserRepoGroupToPerm.user_id==User.user_id', cascade='all')
537 repo_group_to_perm = relationship('UserRepoGroupToPerm', primaryjoin='UserRepoGroupToPerm.user_id==User.user_id', cascade='all')
538 user_group_to_perm = relationship('UserUserGroupToPerm', primaryjoin='UserUserGroupToPerm.user_id==User.user_id', cascade='all')
538 user_group_to_perm = relationship('UserUserGroupToPerm', primaryjoin='UserUserGroupToPerm.user_id==User.user_id', cascade='all')
539
539
540 group_member = relationship('UserGroupMember', cascade='all')
540 group_member = relationship('UserGroupMember', cascade='all')
541
541
542 notifications = relationship('UserNotification', cascade='all')
542 notifications = relationship('UserNotification', cascade='all')
543 # notifications assigned to this user
543 # notifications assigned to this user
544 user_created_notifications = relationship('Notification', cascade='all')
544 user_created_notifications = relationship('Notification', cascade='all')
545 # comments created by this user
545 # comments created by this user
546 user_comments = relationship('ChangesetComment', cascade='all')
546 user_comments = relationship('ChangesetComment', cascade='all')
547 # user profile extra info
547 # user profile extra info
548 user_emails = relationship('UserEmailMap', cascade='all')
548 user_emails = relationship('UserEmailMap', cascade='all')
549 user_ip_map = relationship('UserIpMap', cascade='all')
549 user_ip_map = relationship('UserIpMap', cascade='all')
550 user_auth_tokens = relationship('UserApiKeys', cascade='all')
550 user_auth_tokens = relationship('UserApiKeys', cascade='all')
551 # gists
551 # gists
552 user_gists = relationship('Gist', cascade='all')
552 user_gists = relationship('Gist', cascade='all')
553 # user pull requests
553 # user pull requests
554 user_pull_requests = relationship('PullRequest', cascade='all')
554 user_pull_requests = relationship('PullRequest', cascade='all')
555 # external identities
555 # external identities
556 extenal_identities = relationship(
556 extenal_identities = relationship(
557 'ExternalIdentity',
557 'ExternalIdentity',
558 primaryjoin="User.user_id==ExternalIdentity.local_user_id",
558 primaryjoin="User.user_id==ExternalIdentity.local_user_id",
559 cascade='all')
559 cascade='all')
560
560
561 def __unicode__(self):
561 def __unicode__(self):
562 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
562 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
563 self.user_id, self.username)
563 self.user_id, self.username)
564
564
565 @hybrid_property
565 @hybrid_property
566 def email(self):
566 def email(self):
567 return self._email
567 return self._email
568
568
569 @email.setter
569 @email.setter
570 def email(self, val):
570 def email(self, val):
571 self._email = val.lower() if val else None
571 self._email = val.lower() if val else None
572
572
573 @hybrid_property
573 @hybrid_property
574 def api_key(self):
574 def api_key(self):
575 """
575 """
576 Fetch if exist an auth-token with role ALL connected to this user
576 Fetch if exist an auth-token with role ALL connected to this user
577 """
577 """
578 user_auth_token = UserApiKeys.query()\
578 user_auth_token = UserApiKeys.query()\
579 .filter(UserApiKeys.user_id == self.user_id)\
579 .filter(UserApiKeys.user_id == self.user_id)\
580 .filter(or_(UserApiKeys.expires == -1,
580 .filter(or_(UserApiKeys.expires == -1,
581 UserApiKeys.expires >= time.time()))\
581 UserApiKeys.expires >= time.time()))\
582 .filter(UserApiKeys.role == UserApiKeys.ROLE_ALL).first()
582 .filter(UserApiKeys.role == UserApiKeys.ROLE_ALL).first()
583 if user_auth_token:
583 if user_auth_token:
584 user_auth_token = user_auth_token.api_key
584 user_auth_token = user_auth_token.api_key
585
585
586 return user_auth_token
586 return user_auth_token
587
587
588 @api_key.setter
588 @api_key.setter
589 def api_key(self, val):
589 def api_key(self, val):
590 # don't allow to set API key this is deprecated for now
590 # don't allow to set API key this is deprecated for now
591 self._api_key = None
591 self._api_key = None
592
592
593 @property
593 @property
594 def firstname(self):
594 def firstname(self):
595 # alias for future
595 # alias for future
596 return self.name
596 return self.name
597
597
598 @property
598 @property
599 def emails(self):
599 def emails(self):
600 other = UserEmailMap.query().filter(UserEmailMap.user==self).all()
600 other = UserEmailMap.query().filter(UserEmailMap.user==self).all()
601 return [self.email] + [x.email for x in other]
601 return [self.email] + [x.email for x in other]
602
602
603 @property
603 @property
604 def auth_tokens(self):
604 def auth_tokens(self):
605 return [x.api_key for x in self.extra_auth_tokens]
605 return [x.api_key for x in self.extra_auth_tokens]
606
606
607 @property
607 @property
608 def extra_auth_tokens(self):
608 def extra_auth_tokens(self):
609 return UserApiKeys.query().filter(UserApiKeys.user == self).all()
609 return UserApiKeys.query().filter(UserApiKeys.user == self).all()
610
610
611 @property
611 @property
612 def feed_token(self):
612 def feed_token(self):
613 return self.get_feed_token()
613 return self.get_feed_token()
614
614
615 def get_feed_token(self):
615 def get_feed_token(self):
616 feed_tokens = UserApiKeys.query()\
616 feed_tokens = UserApiKeys.query()\
617 .filter(UserApiKeys.user == self)\
617 .filter(UserApiKeys.user == self)\
618 .filter(UserApiKeys.role == UserApiKeys.ROLE_FEED)\
618 .filter(UserApiKeys.role == UserApiKeys.ROLE_FEED)\
619 .all()
619 .all()
620 if feed_tokens:
620 if feed_tokens:
621 return feed_tokens[0].api_key
621 return feed_tokens[0].api_key
622 return 'NO_FEED_TOKEN_AVAILABLE'
622 return 'NO_FEED_TOKEN_AVAILABLE'
623
623
624 @classmethod
624 @classmethod
625 def extra_valid_auth_tokens(cls, user, role=None):
625 def extra_valid_auth_tokens(cls, user, role=None):
626 tokens = UserApiKeys.query().filter(UserApiKeys.user == user)\
626 tokens = UserApiKeys.query().filter(UserApiKeys.user == user)\
627 .filter(or_(UserApiKeys.expires == -1,
627 .filter(or_(UserApiKeys.expires == -1,
628 UserApiKeys.expires >= time.time()))
628 UserApiKeys.expires >= time.time()))
629 if role:
629 if role:
630 tokens = tokens.filter(or_(UserApiKeys.role == role,
630 tokens = tokens.filter(or_(UserApiKeys.role == role,
631 UserApiKeys.role == UserApiKeys.ROLE_ALL))
631 UserApiKeys.role == UserApiKeys.ROLE_ALL))
632 return tokens.all()
632 return tokens.all()
633
633
634 def authenticate_by_token(self, auth_token, roles=None, scope_repo_id=None):
634 def authenticate_by_token(self, auth_token, roles=None, scope_repo_id=None):
635 from rhodecode.lib import auth
635 from rhodecode.lib import auth
636
636
637 log.debug('Trying to authenticate user: %s via auth-token, '
637 log.debug('Trying to authenticate user: %s via auth-token, '
638 'and roles: %s', self, roles)
638 'and roles: %s', self, roles)
639
639
640 if not auth_token:
640 if not auth_token:
641 return False
641 return False
642
642
643 crypto_backend = auth.crypto_backend()
643 crypto_backend = auth.crypto_backend()
644
644
645 roles = (roles or []) + [UserApiKeys.ROLE_ALL]
645 roles = (roles or []) + [UserApiKeys.ROLE_ALL]
646 tokens_q = UserApiKeys.query()\
646 tokens_q = UserApiKeys.query()\
647 .filter(UserApiKeys.user_id == self.user_id)\
647 .filter(UserApiKeys.user_id == self.user_id)\
648 .filter(or_(UserApiKeys.expires == -1,
648 .filter(or_(UserApiKeys.expires == -1,
649 UserApiKeys.expires >= time.time()))
649 UserApiKeys.expires >= time.time()))
650
650
651 tokens_q = tokens_q.filter(UserApiKeys.role.in_(roles))
651 tokens_q = tokens_q.filter(UserApiKeys.role.in_(roles))
652
652
653 plain_tokens = []
653 plain_tokens = []
654 hash_tokens = []
654 hash_tokens = []
655
655
656 for token in tokens_q.all():
656 for token in tokens_q.all():
657 # verify scope first
657 # verify scope first
658 if token.repo_id:
658 if token.repo_id:
659 # token has a scope, we need to verify it
659 # token has a scope, we need to verify it
660 if scope_repo_id != token.repo_id:
660 if scope_repo_id != token.repo_id:
661 log.debug(
661 log.debug(
662 'Scope mismatch: token has a set repo scope: %s, '
662 'Scope mismatch: token has a set repo scope: %s, '
663 'and calling scope is:%s, skipping further checks',
663 'and calling scope is:%s, skipping further checks',
664 token.repo, scope_repo_id)
664 token.repo, scope_repo_id)
665 # token has a scope, and it doesn't match, skip token
665 # token has a scope, and it doesn't match, skip token
666 continue
666 continue
667
667
668 if token.api_key.startswith(crypto_backend.ENC_PREF):
668 if token.api_key.startswith(crypto_backend.ENC_PREF):
669 hash_tokens.append(token.api_key)
669 hash_tokens.append(token.api_key)
670 else:
670 else:
671 plain_tokens.append(token.api_key)
671 plain_tokens.append(token.api_key)
672
672
673 is_plain_match = auth_token in plain_tokens
673 is_plain_match = auth_token in plain_tokens
674 if is_plain_match:
674 if is_plain_match:
675 return True
675 return True
676
676
677 for hashed in hash_tokens:
677 for hashed in hash_tokens:
678 # TODO(marcink): this is expensive to calculate, but most secure
678 # TODO(marcink): this is expensive to calculate, but most secure
679 match = crypto_backend.hash_check(auth_token, hashed)
679 match = crypto_backend.hash_check(auth_token, hashed)
680 if match:
680 if match:
681 return True
681 return True
682
682
683 return False
683 return False
684
684
685 @property
685 @property
686 def ip_addresses(self):
686 def ip_addresses(self):
687 ret = UserIpMap.query().filter(UserIpMap.user == self).all()
687 ret = UserIpMap.query().filter(UserIpMap.user == self).all()
688 return [x.ip_addr for x in ret]
688 return [x.ip_addr for x in ret]
689
689
690 @property
690 @property
691 def username_and_name(self):
691 def username_and_name(self):
692 return '%s (%s %s)' % (self.username, self.firstname, self.lastname)
692 return '%s (%s %s)' % (self.username, self.firstname, self.lastname)
693
693
694 @property
694 @property
695 def username_or_name_or_email(self):
695 def username_or_name_or_email(self):
696 full_name = self.full_name if self.full_name is not ' ' else None
696 full_name = self.full_name if self.full_name is not ' ' else None
697 return self.username or full_name or self.email
697 return self.username or full_name or self.email
698
698
699 @property
699 @property
700 def full_name(self):
700 def full_name(self):
701 return '%s %s' % (self.firstname, self.lastname)
701 return '%s %s' % (self.firstname, self.lastname)
702
702
703 @property
703 @property
704 def full_name_or_username(self):
704 def full_name_or_username(self):
705 return ('%s %s' % (self.firstname, self.lastname)
705 return ('%s %s' % (self.firstname, self.lastname)
706 if (self.firstname and self.lastname) else self.username)
706 if (self.firstname and self.lastname) else self.username)
707
707
708 @property
708 @property
709 def full_contact(self):
709 def full_contact(self):
710 return '%s %s <%s>' % (self.firstname, self.lastname, self.email)
710 return '%s %s <%s>' % (self.firstname, self.lastname, self.email)
711
711
712 @property
712 @property
713 def short_contact(self):
713 def short_contact(self):
714 return '%s %s' % (self.firstname, self.lastname)
714 return '%s %s' % (self.firstname, self.lastname)
715
715
716 @property
716 @property
717 def is_admin(self):
717 def is_admin(self):
718 return self.admin
718 return self.admin
719
719
720 @property
720 @property
721 def AuthUser(self):
721 def AuthUser(self):
722 """
722 """
723 Returns instance of AuthUser for this user
723 Returns instance of AuthUser for this user
724 """
724 """
725 from rhodecode.lib.auth import AuthUser
725 from rhodecode.lib.auth import AuthUser
726 return AuthUser(user_id=self.user_id, username=self.username)
726 return AuthUser(user_id=self.user_id, username=self.username)
727
727
728 @hybrid_property
728 @hybrid_property
729 def user_data(self):
729 def user_data(self):
730 if not self._user_data:
730 if not self._user_data:
731 return {}
731 return {}
732
732
733 try:
733 try:
734 return json.loads(self._user_data)
734 return json.loads(self._user_data)
735 except TypeError:
735 except TypeError:
736 return {}
736 return {}
737
737
738 @user_data.setter
738 @user_data.setter
739 def user_data(self, val):
739 def user_data(self, val):
740 if not isinstance(val, dict):
740 if not isinstance(val, dict):
741 raise Exception('user_data must be dict, got %s' % type(val))
741 raise Exception('user_data must be dict, got %s' % type(val))
742 try:
742 try:
743 self._user_data = json.dumps(val)
743 self._user_data = json.dumps(val)
744 except Exception:
744 except Exception:
745 log.error(traceback.format_exc())
745 log.error(traceback.format_exc())
746
746
747 @classmethod
747 @classmethod
748 def get_by_username(cls, username, case_insensitive=False,
748 def get_by_username(cls, username, case_insensitive=False,
749 cache=False, identity_cache=False):
749 cache=False, identity_cache=False):
750 session = Session()
750 session = Session()
751
751
752 if case_insensitive:
752 if case_insensitive:
753 q = cls.query().filter(
753 q = cls.query().filter(
754 func.lower(cls.username) == func.lower(username))
754 func.lower(cls.username) == func.lower(username))
755 else:
755 else:
756 q = cls.query().filter(cls.username == username)
756 q = cls.query().filter(cls.username == username)
757
757
758 if cache:
758 if cache:
759 if identity_cache:
759 if identity_cache:
760 val = cls.identity_cache(session, 'username', username)
760 val = cls.identity_cache(session, 'username', username)
761 if val:
761 if val:
762 return val
762 return val
763 else:
763 else:
764 q = q.options(
764 q = q.options(
765 FromCache("sql_cache_short",
765 FromCache("sql_cache_short",
766 "get_user_by_name_%s" % _hash_key(username)))
766 "get_user_by_name_%s" % _hash_key(username)))
767
767
768 return q.scalar()
768 return q.scalar()
769
769
770 @classmethod
770 @classmethod
771 def get_by_auth_token(cls, auth_token, cache=False):
771 def get_by_auth_token(cls, auth_token, cache=False):
772 q = UserApiKeys.query()\
772 q = UserApiKeys.query()\
773 .filter(UserApiKeys.api_key == auth_token)\
773 .filter(UserApiKeys.api_key == auth_token)\
774 .filter(or_(UserApiKeys.expires == -1,
774 .filter(or_(UserApiKeys.expires == -1,
775 UserApiKeys.expires >= time.time()))
775 UserApiKeys.expires >= time.time()))
776 if cache:
776 if cache:
777 q = q.options(FromCache("sql_cache_short",
777 q = q.options(FromCache("sql_cache_short",
778 "get_auth_token_%s" % auth_token))
778 "get_auth_token_%s" % auth_token))
779
779
780 match = q.first()
780 match = q.first()
781 if match:
781 if match:
782 return match.user
782 return match.user
783
783
784 @classmethod
784 @classmethod
785 def get_by_email(cls, email, case_insensitive=False, cache=False):
785 def get_by_email(cls, email, case_insensitive=False, cache=False):
786
786
787 if case_insensitive:
787 if case_insensitive:
788 q = cls.query().filter(func.lower(cls.email) == func.lower(email))
788 q = cls.query().filter(func.lower(cls.email) == func.lower(email))
789
789
790 else:
790 else:
791 q = cls.query().filter(cls.email == email)
791 q = cls.query().filter(cls.email == email)
792
792
793 if cache:
793 if cache:
794 q = q.options(FromCache("sql_cache_short",
794 q = q.options(FromCache("sql_cache_short",
795 "get_email_key_%s" % _hash_key(email)))
795 "get_email_key_%s" % _hash_key(email)))
796
796
797 ret = q.scalar()
797 ret = q.scalar()
798 if ret is None:
798 if ret is None:
799 q = UserEmailMap.query()
799 q = UserEmailMap.query()
800 # try fetching in alternate email map
800 # try fetching in alternate email map
801 if case_insensitive:
801 if case_insensitive:
802 q = q.filter(func.lower(UserEmailMap.email) == func.lower(email))
802 q = q.filter(func.lower(UserEmailMap.email) == func.lower(email))
803 else:
803 else:
804 q = q.filter(UserEmailMap.email == email)
804 q = q.filter(UserEmailMap.email == email)
805 q = q.options(joinedload(UserEmailMap.user))
805 q = q.options(joinedload(UserEmailMap.user))
806 if cache:
806 if cache:
807 q = q.options(FromCache("sql_cache_short",
807 q = q.options(FromCache("sql_cache_short",
808 "get_email_map_key_%s" % email))
808 "get_email_map_key_%s" % email))
809 ret = getattr(q.scalar(), 'user', None)
809 ret = getattr(q.scalar(), 'user', None)
810
810
811 return ret
811 return ret
812
812
813 @classmethod
813 @classmethod
814 def get_from_cs_author(cls, author):
814 def get_from_cs_author(cls, author):
815 """
815 """
816 Tries to get User objects out of commit author string
816 Tries to get User objects out of commit author string
817
817
818 :param author:
818 :param author:
819 """
819 """
820 from rhodecode.lib.helpers import email, author_name
820 from rhodecode.lib.helpers import email, author_name
821 # Valid email in the attribute passed, see if they're in the system
821 # Valid email in the attribute passed, see if they're in the system
822 _email = email(author)
822 _email = email(author)
823 if _email:
823 if _email:
824 user = cls.get_by_email(_email, case_insensitive=True)
824 user = cls.get_by_email(_email, case_insensitive=True)
825 if user:
825 if user:
826 return user
826 return user
827 # Maybe we can match by username?
827 # Maybe we can match by username?
828 _author = author_name(author)
828 _author = author_name(author)
829 user = cls.get_by_username(_author, case_insensitive=True)
829 user = cls.get_by_username(_author, case_insensitive=True)
830 if user:
830 if user:
831 return user
831 return user
832
832
833 def update_userdata(self, **kwargs):
833 def update_userdata(self, **kwargs):
834 usr = self
834 usr = self
835 old = usr.user_data
835 old = usr.user_data
836 old.update(**kwargs)
836 old.update(**kwargs)
837 usr.user_data = old
837 usr.user_data = old
838 Session().add(usr)
838 Session().add(usr)
839 log.debug('updated userdata with ', kwargs)
839 log.debug('updated userdata with ', kwargs)
840
840
841 def update_lastlogin(self):
841 def update_lastlogin(self):
842 """Update user lastlogin"""
842 """Update user lastlogin"""
843 self.last_login = datetime.datetime.now()
843 self.last_login = datetime.datetime.now()
844 Session().add(self)
844 Session().add(self)
845 log.debug('updated user %s lastlogin', self.username)
845 log.debug('updated user %s lastlogin', self.username)
846
846
847 def update_lastactivity(self):
847 def update_lastactivity(self):
848 """Update user lastactivity"""
848 """Update user lastactivity"""
849 self.last_activity = datetime.datetime.now()
849 self.last_activity = datetime.datetime.now()
850 Session().add(self)
850 Session().add(self)
851 log.debug('updated user %s lastactivity', self.username)
851 log.debug('updated user %s lastactivity', self.username)
852
852
853 def update_password(self, new_password):
853 def update_password(self, new_password):
854 from rhodecode.lib.auth import get_crypt_password
854 from rhodecode.lib.auth import get_crypt_password
855
855
856 self.password = get_crypt_password(new_password)
856 self.password = get_crypt_password(new_password)
857 Session().add(self)
857 Session().add(self)
858
858
859 @classmethod
859 @classmethod
860 def get_first_super_admin(cls):
860 def get_first_super_admin(cls):
861 user = User.query().filter(User.admin == true()).first()
861 user = User.query().filter(User.admin == true()).first()
862 if user is None:
862 if user is None:
863 raise Exception('FATAL: Missing administrative account!')
863 raise Exception('FATAL: Missing administrative account!')
864 return user
864 return user
865
865
866 @classmethod
866 @classmethod
867 def get_all_super_admins(cls):
867 def get_all_super_admins(cls):
868 """
868 """
869 Returns all admin accounts sorted by username
869 Returns all admin accounts sorted by username
870 """
870 """
871 return User.query().filter(User.admin == true())\
871 return User.query().filter(User.admin == true())\
872 .order_by(User.username.asc()).all()
872 .order_by(User.username.asc()).all()
873
873
874 @classmethod
874 @classmethod
875 def get_default_user(cls, cache=False):
875 def get_default_user(cls, cache=False):
876 user = User.get_by_username(User.DEFAULT_USER, cache=cache)
876 user = User.get_by_username(User.DEFAULT_USER, cache=cache)
877 if user is None:
877 if user is None:
878 raise Exception('FATAL: Missing default account!')
878 raise Exception('FATAL: Missing default account!')
879 return user
879 return user
880
880
881 def _get_default_perms(self, user, suffix=''):
881 def _get_default_perms(self, user, suffix=''):
882 from rhodecode.model.permission import PermissionModel
882 from rhodecode.model.permission import PermissionModel
883 return PermissionModel().get_default_perms(user.user_perms, suffix)
883 return PermissionModel().get_default_perms(user.user_perms, suffix)
884
884
885 def get_default_perms(self, suffix=''):
885 def get_default_perms(self, suffix=''):
886 return self._get_default_perms(self, suffix)
886 return self._get_default_perms(self, suffix)
887
887
888 def get_api_data(self, include_secrets=False, details='full'):
888 def get_api_data(self, include_secrets=False, details='full'):
889 """
889 """
890 Common function for generating user related data for API
890 Common function for generating user related data for API
891
891
892 :param include_secrets: By default secrets in the API data will be replaced
892 :param include_secrets: By default secrets in the API data will be replaced
893 by a placeholder value to prevent exposing this data by accident. In case
893 by a placeholder value to prevent exposing this data by accident. In case
894 this data shall be exposed, set this flag to ``True``.
894 this data shall be exposed, set this flag to ``True``.
895
895
896 :param details: details can be 'basic|full' basic gives only a subset of
896 :param details: details can be 'basic|full' basic gives only a subset of
897 the available user information that includes user_id, name and emails.
897 the available user information that includes user_id, name and emails.
898 """
898 """
899 user = self
899 user = self
900 user_data = self.user_data
900 user_data = self.user_data
901 data = {
901 data = {
902 'user_id': user.user_id,
902 'user_id': user.user_id,
903 'username': user.username,
903 'username': user.username,
904 'firstname': user.name,
904 'firstname': user.name,
905 'lastname': user.lastname,
905 'lastname': user.lastname,
906 'email': user.email,
906 'email': user.email,
907 'emails': user.emails,
907 'emails': user.emails,
908 }
908 }
909 if details == 'basic':
909 if details == 'basic':
910 return data
910 return data
911
911
912 api_key_length = 40
912 api_key_length = 40
913 api_key_replacement = '*' * api_key_length
913 api_key_replacement = '*' * api_key_length
914
914
915 extras = {
915 extras = {
916 'api_keys': [api_key_replacement],
916 'api_keys': [api_key_replacement],
917 'auth_tokens': [api_key_replacement],
917 'auth_tokens': [api_key_replacement],
918 'active': user.active,
918 'active': user.active,
919 'admin': user.admin,
919 'admin': user.admin,
920 'extern_type': user.extern_type,
920 'extern_type': user.extern_type,
921 'extern_name': user.extern_name,
921 'extern_name': user.extern_name,
922 'last_login': user.last_login,
922 'last_login': user.last_login,
923 'ip_addresses': user.ip_addresses,
923 'ip_addresses': user.ip_addresses,
924 'language': user_data.get('language')
924 'language': user_data.get('language')
925 }
925 }
926 data.update(extras)
926 data.update(extras)
927
927
928 if include_secrets:
928 if include_secrets:
929 data['api_keys'] = user.auth_tokens
929 data['api_keys'] = user.auth_tokens
930 data['auth_tokens'] = user.extra_auth_tokens
930 data['auth_tokens'] = user.extra_auth_tokens
931 return data
931 return data
932
932
933 def __json__(self):
933 def __json__(self):
934 data = {
934 data = {
935 'full_name': self.full_name,
935 'full_name': self.full_name,
936 'full_name_or_username': self.full_name_or_username,
936 'full_name_or_username': self.full_name_or_username,
937 'short_contact': self.short_contact,
937 'short_contact': self.short_contact,
938 'full_contact': self.full_contact,
938 'full_contact': self.full_contact,
939 }
939 }
940 data.update(self.get_api_data())
940 data.update(self.get_api_data())
941 return data
941 return data
942
942
943
943
944 class UserApiKeys(Base, BaseModel):
944 class UserApiKeys(Base, BaseModel):
945 __tablename__ = 'user_api_keys'
945 __tablename__ = 'user_api_keys'
946 __table_args__ = (
946 __table_args__ = (
947 Index('uak_api_key_idx', 'api_key'),
947 Index('uak_api_key_idx', 'api_key'),
948 Index('uak_api_key_expires_idx', 'api_key', 'expires'),
948 Index('uak_api_key_expires_idx', 'api_key', 'expires'),
949 UniqueConstraint('api_key'),
949 UniqueConstraint('api_key'),
950 {'extend_existing': True, 'mysql_engine': 'InnoDB',
950 {'extend_existing': True, 'mysql_engine': 'InnoDB',
951 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
951 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
952 )
952 )
953 __mapper_args__ = {}
953 __mapper_args__ = {}
954
954
955 # ApiKey role
955 # ApiKey role
956 ROLE_ALL = 'token_role_all'
956 ROLE_ALL = 'token_role_all'
957 ROLE_HTTP = 'token_role_http'
957 ROLE_HTTP = 'token_role_http'
958 ROLE_VCS = 'token_role_vcs'
958 ROLE_VCS = 'token_role_vcs'
959 ROLE_API = 'token_role_api'
959 ROLE_API = 'token_role_api'
960 ROLE_FEED = 'token_role_feed'
960 ROLE_FEED = 'token_role_feed'
961 ROLE_PASSWORD_RESET = 'token_password_reset'
961 ROLE_PASSWORD_RESET = 'token_password_reset'
962
962
963 ROLES = [ROLE_ALL, ROLE_HTTP, ROLE_VCS, ROLE_API, ROLE_FEED]
963 ROLES = [ROLE_ALL, ROLE_HTTP, ROLE_VCS, ROLE_API, ROLE_FEED]
964
964
965 user_api_key_id = Column("user_api_key_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
965 user_api_key_id = Column("user_api_key_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
966 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
966 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
967 api_key = Column("api_key", String(255), nullable=False, unique=True)
967 api_key = Column("api_key", String(255), nullable=False, unique=True)
968 description = Column('description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
968 description = Column('description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
969 expires = Column('expires', Float(53), nullable=False)
969 expires = Column('expires', Float(53), nullable=False)
970 role = Column('role', String(255), nullable=True)
970 role = Column('role', String(255), nullable=True)
971 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
971 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
972
972
973 # scope columns
973 # scope columns
974 repo_id = Column(
974 repo_id = Column(
975 'repo_id', Integer(), ForeignKey('repositories.repo_id'),
975 'repo_id', Integer(), ForeignKey('repositories.repo_id'),
976 nullable=True, unique=None, default=None)
976 nullable=True, unique=None, default=None)
977 repo = relationship('Repository', lazy='joined')
977 repo = relationship('Repository', lazy='joined')
978
978
979 repo_group_id = Column(
979 repo_group_id = Column(
980 'repo_group_id', Integer(), ForeignKey('groups.group_id'),
980 'repo_group_id', Integer(), ForeignKey('groups.group_id'),
981 nullable=True, unique=None, default=None)
981 nullable=True, unique=None, default=None)
982 repo_group = relationship('RepoGroup', lazy='joined')
982 repo_group = relationship('RepoGroup', lazy='joined')
983
983
984 user = relationship('User', lazy='joined')
984 user = relationship('User', lazy='joined')
985
985
986 def __unicode__(self):
986 def __unicode__(self):
987 return u"<%s('%s')>" % (self.__class__.__name__, self.role)
987 return u"<%s('%s')>" % (self.__class__.__name__, self.role)
988
988
989 def __json__(self):
989 def __json__(self):
990 data = {
990 data = {
991 'auth_token': self.api_key,
991 'auth_token': self.api_key,
992 'role': self.role,
992 'role': self.role,
993 'scope': self.scope_humanized,
993 'scope': self.scope_humanized,
994 'expired': self.expired
994 'expired': self.expired
995 }
995 }
996 return data
996 return data
997
997
998 @property
998 @property
999 def expired(self):
999 def expired(self):
1000 if self.expires == -1:
1000 if self.expires == -1:
1001 return False
1001 return False
1002 return time.time() > self.expires
1002 return time.time() > self.expires
1003
1003
1004 @classmethod
1004 @classmethod
1005 def _get_role_name(cls, role):
1005 def _get_role_name(cls, role):
1006 return {
1006 return {
1007 cls.ROLE_ALL: _('all'),
1007 cls.ROLE_ALL: _('all'),
1008 cls.ROLE_HTTP: _('http/web interface'),
1008 cls.ROLE_HTTP: _('http/web interface'),
1009 cls.ROLE_VCS: _('vcs (git/hg/svn protocol)'),
1009 cls.ROLE_VCS: _('vcs (git/hg/svn protocol)'),
1010 cls.ROLE_API: _('api calls'),
1010 cls.ROLE_API: _('api calls'),
1011 cls.ROLE_FEED: _('feed access'),
1011 cls.ROLE_FEED: _('feed access'),
1012 }.get(role, role)
1012 }.get(role, role)
1013
1013
1014 @property
1014 @property
1015 def role_humanized(self):
1015 def role_humanized(self):
1016 return self._get_role_name(self.role)
1016 return self._get_role_name(self.role)
1017
1017
1018 def _get_scope(self):
1018 def _get_scope(self):
1019 if self.repo:
1019 if self.repo:
1020 return repr(self.repo)
1020 return repr(self.repo)
1021 if self.repo_group:
1021 if self.repo_group:
1022 return repr(self.repo_group) + ' (recursive)'
1022 return repr(self.repo_group) + ' (recursive)'
1023 return 'global'
1023 return 'global'
1024
1024
1025 @property
1025 @property
1026 def scope_humanized(self):
1026 def scope_humanized(self):
1027 return self._get_scope()
1027 return self._get_scope()
1028
1028
1029
1029
1030 class UserEmailMap(Base, BaseModel):
1030 class UserEmailMap(Base, BaseModel):
1031 __tablename__ = 'user_email_map'
1031 __tablename__ = 'user_email_map'
1032 __table_args__ = (
1032 __table_args__ = (
1033 Index('uem_email_idx', 'email'),
1033 Index('uem_email_idx', 'email'),
1034 UniqueConstraint('email'),
1034 UniqueConstraint('email'),
1035 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1035 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1036 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
1036 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
1037 )
1037 )
1038 __mapper_args__ = {}
1038 __mapper_args__ = {}
1039
1039
1040 email_id = Column("email_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1040 email_id = Column("email_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1041 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1041 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1042 _email = Column("email", String(255), nullable=True, unique=False, default=None)
1042 _email = Column("email", String(255), nullable=True, unique=False, default=None)
1043 user = relationship('User', lazy='joined')
1043 user = relationship('User', lazy='joined')
1044
1044
1045 @validates('_email')
1045 @validates('_email')
1046 def validate_email(self, key, email):
1046 def validate_email(self, key, email):
1047 # check if this email is not main one
1047 # check if this email is not main one
1048 main_email = Session().query(User).filter(User.email == email).scalar()
1048 main_email = Session().query(User).filter(User.email == email).scalar()
1049 if main_email is not None:
1049 if main_email is not None:
1050 raise AttributeError('email %s is present is user table' % email)
1050 raise AttributeError('email %s is present is user table' % email)
1051 return email
1051 return email
1052
1052
1053 @hybrid_property
1053 @hybrid_property
1054 def email(self):
1054 def email(self):
1055 return self._email
1055 return self._email
1056
1056
1057 @email.setter
1057 @email.setter
1058 def email(self, val):
1058 def email(self, val):
1059 self._email = val.lower() if val else None
1059 self._email = val.lower() if val else None
1060
1060
1061
1061
1062 class UserIpMap(Base, BaseModel):
1062 class UserIpMap(Base, BaseModel):
1063 __tablename__ = 'user_ip_map'
1063 __tablename__ = 'user_ip_map'
1064 __table_args__ = (
1064 __table_args__ = (
1065 UniqueConstraint('user_id', 'ip_addr'),
1065 UniqueConstraint('user_id', 'ip_addr'),
1066 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1066 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1067 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
1067 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
1068 )
1068 )
1069 __mapper_args__ = {}
1069 __mapper_args__ = {}
1070
1070
1071 ip_id = Column("ip_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1071 ip_id = Column("ip_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1072 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1072 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1073 ip_addr = Column("ip_addr", String(255), nullable=True, unique=False, default=None)
1073 ip_addr = Column("ip_addr", String(255), nullable=True, unique=False, default=None)
1074 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
1074 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
1075 description = Column("description", String(10000), nullable=True, unique=None, default=None)
1075 description = Column("description", String(10000), nullable=True, unique=None, default=None)
1076 user = relationship('User', lazy='joined')
1076 user = relationship('User', lazy='joined')
1077
1077
1078 @classmethod
1078 @classmethod
1079 def _get_ip_range(cls, ip_addr):
1079 def _get_ip_range(cls, ip_addr):
1080 net = ipaddress.ip_network(ip_addr, strict=False)
1080 net = ipaddress.ip_network(ip_addr, strict=False)
1081 return [str(net.network_address), str(net.broadcast_address)]
1081 return [str(net.network_address), str(net.broadcast_address)]
1082
1082
1083 def __json__(self):
1083 def __json__(self):
1084 return {
1084 return {
1085 'ip_addr': self.ip_addr,
1085 'ip_addr': self.ip_addr,
1086 'ip_range': self._get_ip_range(self.ip_addr),
1086 'ip_range': self._get_ip_range(self.ip_addr),
1087 }
1087 }
1088
1088
1089 def __unicode__(self):
1089 def __unicode__(self):
1090 return u"<%s('user_id:%s=>%s')>" % (self.__class__.__name__,
1090 return u"<%s('user_id:%s=>%s')>" % (self.__class__.__name__,
1091 self.user_id, self.ip_addr)
1091 self.user_id, self.ip_addr)
1092
1092
1093
1093
1094 class UserLog(Base, BaseModel):
1094 class UserLog(Base, BaseModel):
1095 __tablename__ = 'user_logs'
1095 __tablename__ = 'user_logs'
1096 __table_args__ = (
1096 __table_args__ = (
1097 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1097 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1098 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1098 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1099 )
1099 )
1100 user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1100 user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1101 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1101 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1102 username = Column("username", String(255), nullable=True, unique=None, default=None)
1102 username = Column("username", String(255), nullable=True, unique=None, default=None)
1103 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True)
1103 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True)
1104 repository_name = Column("repository_name", String(255), nullable=True, unique=None, default=None)
1104 repository_name = Column("repository_name", String(255), nullable=True, unique=None, default=None)
1105 user_ip = Column("user_ip", String(255), nullable=True, unique=None, default=None)
1105 user_ip = Column("user_ip", String(255), nullable=True, unique=None, default=None)
1106 action = Column("action", Text().with_variant(Text(1200000), 'mysql'), nullable=True, unique=None, default=None)
1106 action = Column("action", Text().with_variant(Text(1200000), 'mysql'), nullable=True, unique=None, default=None)
1107 action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None)
1107 action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None)
1108
1108
1109 def __unicode__(self):
1109 def __unicode__(self):
1110 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
1110 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
1111 self.repository_name,
1111 self.repository_name,
1112 self.action)
1112 self.action)
1113
1113
1114 @property
1114 @property
1115 def action_as_day(self):
1115 def action_as_day(self):
1116 return datetime.date(*self.action_date.timetuple()[:3])
1116 return datetime.date(*self.action_date.timetuple()[:3])
1117
1117
1118 user = relationship('User')
1118 user = relationship('User')
1119 repository = relationship('Repository', cascade='')
1119 repository = relationship('Repository', cascade='')
1120
1120
1121
1121
1122 class UserGroup(Base, BaseModel):
1122 class UserGroup(Base, BaseModel):
1123 __tablename__ = 'users_groups'
1123 __tablename__ = 'users_groups'
1124 __table_args__ = (
1124 __table_args__ = (
1125 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1125 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1126 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1126 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1127 )
1127 )
1128
1128
1129 users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1129 users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1130 users_group_name = Column("users_group_name", String(255), nullable=False, unique=True, default=None)
1130 users_group_name = Column("users_group_name", String(255), nullable=False, unique=True, default=None)
1131 user_group_description = Column("user_group_description", String(10000), nullable=True, unique=None, default=None)
1131 user_group_description = Column("user_group_description", String(10000), nullable=True, unique=None, default=None)
1132 users_group_active = Column("users_group_active", Boolean(), nullable=True, unique=None, default=None)
1132 users_group_active = Column("users_group_active", Boolean(), nullable=True, unique=None, default=None)
1133 inherit_default_permissions = Column("users_group_inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
1133 inherit_default_permissions = Column("users_group_inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
1134 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
1134 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
1135 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1135 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1136 _group_data = Column("group_data", LargeBinary(), nullable=True) # JSON data
1136 _group_data = Column("group_data", LargeBinary(), nullable=True) # JSON data
1137
1137
1138 members = relationship('UserGroupMember', cascade="all, delete, delete-orphan", lazy="joined")
1138 members = relationship('UserGroupMember', cascade="all, delete, delete-orphan", lazy="joined")
1139 users_group_to_perm = relationship('UserGroupToPerm', cascade='all')
1139 users_group_to_perm = relationship('UserGroupToPerm', cascade='all')
1140 users_group_repo_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
1140 users_group_repo_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
1141 users_group_repo_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
1141 users_group_repo_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
1142 user_user_group_to_perm = relationship('UserUserGroupToPerm', cascade='all')
1142 user_user_group_to_perm = relationship('UserUserGroupToPerm', cascade='all')
1143 user_group_user_group_to_perm = relationship('UserGroupUserGroupToPerm ', primaryjoin="UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id", cascade='all')
1143 user_group_user_group_to_perm = relationship('UserGroupUserGroupToPerm ', primaryjoin="UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id", cascade='all')
1144
1144
1145 user = relationship('User')
1145 user = relationship('User')
1146
1146
1147 @hybrid_property
1147 @hybrid_property
1148 def group_data(self):
1148 def group_data(self):
1149 if not self._group_data:
1149 if not self._group_data:
1150 return {}
1150 return {}
1151
1151
1152 try:
1152 try:
1153 return json.loads(self._group_data)
1153 return json.loads(self._group_data)
1154 except TypeError:
1154 except TypeError:
1155 return {}
1155 return {}
1156
1156
1157 @group_data.setter
1157 @group_data.setter
1158 def group_data(self, val):
1158 def group_data(self, val):
1159 try:
1159 try:
1160 self._group_data = json.dumps(val)
1160 self._group_data = json.dumps(val)
1161 except Exception:
1161 except Exception:
1162 log.error(traceback.format_exc())
1162 log.error(traceback.format_exc())
1163
1163
1164 def __unicode__(self):
1164 def __unicode__(self):
1165 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
1165 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
1166 self.users_group_id,
1166 self.users_group_id,
1167 self.users_group_name)
1167 self.users_group_name)
1168
1168
1169 @classmethod
1169 @classmethod
1170 def get_by_group_name(cls, group_name, cache=False,
1170 def get_by_group_name(cls, group_name, cache=False,
1171 case_insensitive=False):
1171 case_insensitive=False):
1172 if case_insensitive:
1172 if case_insensitive:
1173 q = cls.query().filter(func.lower(cls.users_group_name) ==
1173 q = cls.query().filter(func.lower(cls.users_group_name) ==
1174 func.lower(group_name))
1174 func.lower(group_name))
1175
1175
1176 else:
1176 else:
1177 q = cls.query().filter(cls.users_group_name == group_name)
1177 q = cls.query().filter(cls.users_group_name == group_name)
1178 if cache:
1178 if cache:
1179 q = q.options(FromCache(
1179 q = q.options(FromCache(
1180 "sql_cache_short",
1180 "sql_cache_short",
1181 "get_group_%s" % _hash_key(group_name)))
1181 "get_group_%s" % _hash_key(group_name)))
1182 return q.scalar()
1182 return q.scalar()
1183
1183
1184 @classmethod
1184 @classmethod
1185 def get(cls, user_group_id, cache=False):
1185 def get(cls, user_group_id, cache=False):
1186 user_group = cls.query()
1186 user_group = cls.query()
1187 if cache:
1187 if cache:
1188 user_group = user_group.options(FromCache("sql_cache_short",
1188 user_group = user_group.options(FromCache("sql_cache_short",
1189 "get_users_group_%s" % user_group_id))
1189 "get_users_group_%s" % user_group_id))
1190 return user_group.get(user_group_id)
1190 return user_group.get(user_group_id)
1191
1191
1192 def permissions(self, with_admins=True, with_owner=True):
1192 def permissions(self, with_admins=True, with_owner=True):
1193 q = UserUserGroupToPerm.query().filter(UserUserGroupToPerm.user_group == self)
1193 q = UserUserGroupToPerm.query().filter(UserUserGroupToPerm.user_group == self)
1194 q = q.options(joinedload(UserUserGroupToPerm.user_group),
1194 q = q.options(joinedload(UserUserGroupToPerm.user_group),
1195 joinedload(UserUserGroupToPerm.user),
1195 joinedload(UserUserGroupToPerm.user),
1196 joinedload(UserUserGroupToPerm.permission),)
1196 joinedload(UserUserGroupToPerm.permission),)
1197
1197
1198 # get owners and admins and permissions. We do a trick of re-writing
1198 # get owners and admins and permissions. We do a trick of re-writing
1199 # objects from sqlalchemy to named-tuples due to sqlalchemy session
1199 # objects from sqlalchemy to named-tuples due to sqlalchemy session
1200 # has a global reference and changing one object propagates to all
1200 # has a global reference and changing one object propagates to all
1201 # others. This means if admin is also an owner admin_row that change
1201 # others. This means if admin is also an owner admin_row that change
1202 # would propagate to both objects
1202 # would propagate to both objects
1203 perm_rows = []
1203 perm_rows = []
1204 for _usr in q.all():
1204 for _usr in q.all():
1205 usr = AttributeDict(_usr.user.get_dict())
1205 usr = AttributeDict(_usr.user.get_dict())
1206 usr.permission = _usr.permission.permission_name
1206 usr.permission = _usr.permission.permission_name
1207 perm_rows.append(usr)
1207 perm_rows.append(usr)
1208
1208
1209 # filter the perm rows by 'default' first and then sort them by
1209 # filter the perm rows by 'default' first and then sort them by
1210 # admin,write,read,none permissions sorted again alphabetically in
1210 # admin,write,read,none permissions sorted again alphabetically in
1211 # each group
1211 # each group
1212 perm_rows = sorted(perm_rows, key=display_sort)
1212 perm_rows = sorted(perm_rows, key=display_sort)
1213
1213
1214 _admin_perm = 'usergroup.admin'
1214 _admin_perm = 'usergroup.admin'
1215 owner_row = []
1215 owner_row = []
1216 if with_owner:
1216 if with_owner:
1217 usr = AttributeDict(self.user.get_dict())
1217 usr = AttributeDict(self.user.get_dict())
1218 usr.owner_row = True
1218 usr.owner_row = True
1219 usr.permission = _admin_perm
1219 usr.permission = _admin_perm
1220 owner_row.append(usr)
1220 owner_row.append(usr)
1221
1221
1222 super_admin_rows = []
1222 super_admin_rows = []
1223 if with_admins:
1223 if with_admins:
1224 for usr in User.get_all_super_admins():
1224 for usr in User.get_all_super_admins():
1225 # if this admin is also owner, don't double the record
1225 # if this admin is also owner, don't double the record
1226 if usr.user_id == owner_row[0].user_id:
1226 if usr.user_id == owner_row[0].user_id:
1227 owner_row[0].admin_row = True
1227 owner_row[0].admin_row = True
1228 else:
1228 else:
1229 usr = AttributeDict(usr.get_dict())
1229 usr = AttributeDict(usr.get_dict())
1230 usr.admin_row = True
1230 usr.admin_row = True
1231 usr.permission = _admin_perm
1231 usr.permission = _admin_perm
1232 super_admin_rows.append(usr)
1232 super_admin_rows.append(usr)
1233
1233
1234 return super_admin_rows + owner_row + perm_rows
1234 return super_admin_rows + owner_row + perm_rows
1235
1235
1236 def permission_user_groups(self):
1236 def permission_user_groups(self):
1237 q = UserGroupUserGroupToPerm.query().filter(UserGroupUserGroupToPerm.target_user_group == self)
1237 q = UserGroupUserGroupToPerm.query().filter(UserGroupUserGroupToPerm.target_user_group == self)
1238 q = q.options(joinedload(UserGroupUserGroupToPerm.user_group),
1238 q = q.options(joinedload(UserGroupUserGroupToPerm.user_group),
1239 joinedload(UserGroupUserGroupToPerm.target_user_group),
1239 joinedload(UserGroupUserGroupToPerm.target_user_group),
1240 joinedload(UserGroupUserGroupToPerm.permission),)
1240 joinedload(UserGroupUserGroupToPerm.permission),)
1241
1241
1242 perm_rows = []
1242 perm_rows = []
1243 for _user_group in q.all():
1243 for _user_group in q.all():
1244 usr = AttributeDict(_user_group.user_group.get_dict())
1244 usr = AttributeDict(_user_group.user_group.get_dict())
1245 usr.permission = _user_group.permission.permission_name
1245 usr.permission = _user_group.permission.permission_name
1246 perm_rows.append(usr)
1246 perm_rows.append(usr)
1247
1247
1248 return perm_rows
1248 return perm_rows
1249
1249
1250 def _get_default_perms(self, user_group, suffix=''):
1250 def _get_default_perms(self, user_group, suffix=''):
1251 from rhodecode.model.permission import PermissionModel
1251 from rhodecode.model.permission import PermissionModel
1252 return PermissionModel().get_default_perms(user_group.users_group_to_perm, suffix)
1252 return PermissionModel().get_default_perms(user_group.users_group_to_perm, suffix)
1253
1253
1254 def get_default_perms(self, suffix=''):
1254 def get_default_perms(self, suffix=''):
1255 return self._get_default_perms(self, suffix)
1255 return self._get_default_perms(self, suffix)
1256
1256
1257 def get_api_data(self, with_group_members=True, include_secrets=False):
1257 def get_api_data(self, with_group_members=True, include_secrets=False):
1258 """
1258 """
1259 :param include_secrets: See :meth:`User.get_api_data`, this parameter is
1259 :param include_secrets: See :meth:`User.get_api_data`, this parameter is
1260 basically forwarded.
1260 basically forwarded.
1261
1261
1262 """
1262 """
1263 user_group = self
1263 user_group = self
1264
1265 data = {
1264 data = {
1266 'users_group_id': user_group.users_group_id,
1265 'users_group_id': user_group.users_group_id,
1267 'group_name': user_group.users_group_name,
1266 'group_name': user_group.users_group_name,
1268 'group_description': user_group.user_group_description,
1267 'group_description': user_group.user_group_description,
1269 'active': user_group.users_group_active,
1268 'active': user_group.users_group_active,
1270 'owner': user_group.user.username,
1269 'owner': user_group.user.username,
1270 'owner_email': user_group.user.email,
1271 }
1271 }
1272
1272 if with_group_members:
1273 if with_group_members:
1273 users = []
1274 users = []
1274 for user in user_group.members:
1275 for user in user_group.members:
1275 user = user.user
1276 user = user.user
1276 users.append(user.get_api_data(include_secrets=include_secrets))
1277 users.append(user.get_api_data(include_secrets=include_secrets))
1277 data['users'] = users
1278 data['users'] = users
1278
1279
1279 return data
1280 return data
1280
1281
1281
1282
1282 class UserGroupMember(Base, BaseModel):
1283 class UserGroupMember(Base, BaseModel):
1283 __tablename__ = 'users_groups_members'
1284 __tablename__ = 'users_groups_members'
1284 __table_args__ = (
1285 __table_args__ = (
1285 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1286 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1286 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1287 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1287 )
1288 )
1288
1289
1289 users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1290 users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1290 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1291 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1291 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1292 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1292
1293
1293 user = relationship('User', lazy='joined')
1294 user = relationship('User', lazy='joined')
1294 users_group = relationship('UserGroup')
1295 users_group = relationship('UserGroup')
1295
1296
1296 def __init__(self, gr_id='', u_id=''):
1297 def __init__(self, gr_id='', u_id=''):
1297 self.users_group_id = gr_id
1298 self.users_group_id = gr_id
1298 self.user_id = u_id
1299 self.user_id = u_id
1299
1300
1300
1301
1301 class RepositoryField(Base, BaseModel):
1302 class RepositoryField(Base, BaseModel):
1302 __tablename__ = 'repositories_fields'
1303 __tablename__ = 'repositories_fields'
1303 __table_args__ = (
1304 __table_args__ = (
1304 UniqueConstraint('repository_id', 'field_key'), # no-multi field
1305 UniqueConstraint('repository_id', 'field_key'), # no-multi field
1305 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1306 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1306 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1307 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1307 )
1308 )
1308 PREFIX = 'ex_' # prefix used in form to not conflict with already existing fields
1309 PREFIX = 'ex_' # prefix used in form to not conflict with already existing fields
1309
1310
1310 repo_field_id = Column("repo_field_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1311 repo_field_id = Column("repo_field_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1311 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
1312 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
1312 field_key = Column("field_key", String(250))
1313 field_key = Column("field_key", String(250))
1313 field_label = Column("field_label", String(1024), nullable=False)
1314 field_label = Column("field_label", String(1024), nullable=False)
1314 field_value = Column("field_value", String(10000), nullable=False)
1315 field_value = Column("field_value", String(10000), nullable=False)
1315 field_desc = Column("field_desc", String(1024), nullable=False)
1316 field_desc = Column("field_desc", String(1024), nullable=False)
1316 field_type = Column("field_type", String(255), nullable=False, unique=None)
1317 field_type = Column("field_type", String(255), nullable=False, unique=None)
1317 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1318 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1318
1319
1319 repository = relationship('Repository')
1320 repository = relationship('Repository')
1320
1321
1321 @property
1322 @property
1322 def field_key_prefixed(self):
1323 def field_key_prefixed(self):
1323 return 'ex_%s' % self.field_key
1324 return 'ex_%s' % self.field_key
1324
1325
1325 @classmethod
1326 @classmethod
1326 def un_prefix_key(cls, key):
1327 def un_prefix_key(cls, key):
1327 if key.startswith(cls.PREFIX):
1328 if key.startswith(cls.PREFIX):
1328 return key[len(cls.PREFIX):]
1329 return key[len(cls.PREFIX):]
1329 return key
1330 return key
1330
1331
1331 @classmethod
1332 @classmethod
1332 def get_by_key_name(cls, key, repo):
1333 def get_by_key_name(cls, key, repo):
1333 row = cls.query()\
1334 row = cls.query()\
1334 .filter(cls.repository == repo)\
1335 .filter(cls.repository == repo)\
1335 .filter(cls.field_key == key).scalar()
1336 .filter(cls.field_key == key).scalar()
1336 return row
1337 return row
1337
1338
1338
1339
1339 class Repository(Base, BaseModel):
1340 class Repository(Base, BaseModel):
1340 __tablename__ = 'repositories'
1341 __tablename__ = 'repositories'
1341 __table_args__ = (
1342 __table_args__ = (
1342 Index('r_repo_name_idx', 'repo_name', mysql_length=255),
1343 Index('r_repo_name_idx', 'repo_name', mysql_length=255),
1343 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1344 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1344 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1345 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1345 )
1346 )
1346 DEFAULT_CLONE_URI = '{scheme}://{user}@{netloc}/{repo}'
1347 DEFAULT_CLONE_URI = '{scheme}://{user}@{netloc}/{repo}'
1347 DEFAULT_CLONE_URI_ID = '{scheme}://{user}@{netloc}/_{repoid}'
1348 DEFAULT_CLONE_URI_ID = '{scheme}://{user}@{netloc}/_{repoid}'
1348
1349
1349 STATE_CREATED = 'repo_state_created'
1350 STATE_CREATED = 'repo_state_created'
1350 STATE_PENDING = 'repo_state_pending'
1351 STATE_PENDING = 'repo_state_pending'
1351 STATE_ERROR = 'repo_state_error'
1352 STATE_ERROR = 'repo_state_error'
1352
1353
1353 LOCK_AUTOMATIC = 'lock_auto'
1354 LOCK_AUTOMATIC = 'lock_auto'
1354 LOCK_API = 'lock_api'
1355 LOCK_API = 'lock_api'
1355 LOCK_WEB = 'lock_web'
1356 LOCK_WEB = 'lock_web'
1356 LOCK_PULL = 'lock_pull'
1357 LOCK_PULL = 'lock_pull'
1357
1358
1358 NAME_SEP = URL_SEP
1359 NAME_SEP = URL_SEP
1359
1360
1360 repo_id = Column(
1361 repo_id = Column(
1361 "repo_id", Integer(), nullable=False, unique=True, default=None,
1362 "repo_id", Integer(), nullable=False, unique=True, default=None,
1362 primary_key=True)
1363 primary_key=True)
1363 _repo_name = Column(
1364 _repo_name = Column(
1364 "repo_name", Text(), nullable=False, default=None)
1365 "repo_name", Text(), nullable=False, default=None)
1365 _repo_name_hash = Column(
1366 _repo_name_hash = Column(
1366 "repo_name_hash", String(255), nullable=False, unique=True)
1367 "repo_name_hash", String(255), nullable=False, unique=True)
1367 repo_state = Column("repo_state", String(255), nullable=True)
1368 repo_state = Column("repo_state", String(255), nullable=True)
1368
1369
1369 clone_uri = Column(
1370 clone_uri = Column(
1370 "clone_uri", EncryptedTextValue(), nullable=True, unique=False,
1371 "clone_uri", EncryptedTextValue(), nullable=True, unique=False,
1371 default=None)
1372 default=None)
1372 repo_type = Column(
1373 repo_type = Column(
1373 "repo_type", String(255), nullable=False, unique=False, default=None)
1374 "repo_type", String(255), nullable=False, unique=False, default=None)
1374 user_id = Column(
1375 user_id = Column(
1375 "user_id", Integer(), ForeignKey('users.user_id'), nullable=False,
1376 "user_id", Integer(), ForeignKey('users.user_id'), nullable=False,
1376 unique=False, default=None)
1377 unique=False, default=None)
1377 private = Column(
1378 private = Column(
1378 "private", Boolean(), nullable=True, unique=None, default=None)
1379 "private", Boolean(), nullable=True, unique=None, default=None)
1379 enable_statistics = Column(
1380 enable_statistics = Column(
1380 "statistics", Boolean(), nullable=True, unique=None, default=True)
1381 "statistics", Boolean(), nullable=True, unique=None, default=True)
1381 enable_downloads = Column(
1382 enable_downloads = Column(
1382 "downloads", Boolean(), nullable=True, unique=None, default=True)
1383 "downloads", Boolean(), nullable=True, unique=None, default=True)
1383 description = Column(
1384 description = Column(
1384 "description", String(10000), nullable=True, unique=None, default=None)
1385 "description", String(10000), nullable=True, unique=None, default=None)
1385 created_on = Column(
1386 created_on = Column(
1386 'created_on', DateTime(timezone=False), nullable=True, unique=None,
1387 'created_on', DateTime(timezone=False), nullable=True, unique=None,
1387 default=datetime.datetime.now)
1388 default=datetime.datetime.now)
1388 updated_on = Column(
1389 updated_on = Column(
1389 'updated_on', DateTime(timezone=False), nullable=True, unique=None,
1390 'updated_on', DateTime(timezone=False), nullable=True, unique=None,
1390 default=datetime.datetime.now)
1391 default=datetime.datetime.now)
1391 _landing_revision = Column(
1392 _landing_revision = Column(
1392 "landing_revision", String(255), nullable=False, unique=False,
1393 "landing_revision", String(255), nullable=False, unique=False,
1393 default=None)
1394 default=None)
1394 enable_locking = Column(
1395 enable_locking = Column(
1395 "enable_locking", Boolean(), nullable=False, unique=None,
1396 "enable_locking", Boolean(), nullable=False, unique=None,
1396 default=False)
1397 default=False)
1397 _locked = Column(
1398 _locked = Column(
1398 "locked", String(255), nullable=True, unique=False, default=None)
1399 "locked", String(255), nullable=True, unique=False, default=None)
1399 _changeset_cache = Column(
1400 _changeset_cache = Column(
1400 "changeset_cache", LargeBinary(), nullable=True) # JSON data
1401 "changeset_cache", LargeBinary(), nullable=True) # JSON data
1401
1402
1402 fork_id = Column(
1403 fork_id = Column(
1403 "fork_id", Integer(), ForeignKey('repositories.repo_id'),
1404 "fork_id", Integer(), ForeignKey('repositories.repo_id'),
1404 nullable=True, unique=False, default=None)
1405 nullable=True, unique=False, default=None)
1405 group_id = Column(
1406 group_id = Column(
1406 "group_id", Integer(), ForeignKey('groups.group_id'), nullable=True,
1407 "group_id", Integer(), ForeignKey('groups.group_id'), nullable=True,
1407 unique=False, default=None)
1408 unique=False, default=None)
1408
1409
1409 user = relationship('User', lazy='joined')
1410 user = relationship('User', lazy='joined')
1410 fork = relationship('Repository', remote_side=repo_id, lazy='joined')
1411 fork = relationship('Repository', remote_side=repo_id, lazy='joined')
1411 group = relationship('RepoGroup', lazy='joined')
1412 group = relationship('RepoGroup', lazy='joined')
1412 repo_to_perm = relationship(
1413 repo_to_perm = relationship(
1413 'UserRepoToPerm', cascade='all',
1414 'UserRepoToPerm', cascade='all',
1414 order_by='UserRepoToPerm.repo_to_perm_id')
1415 order_by='UserRepoToPerm.repo_to_perm_id')
1415 users_group_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
1416 users_group_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
1416 stats = relationship('Statistics', cascade='all', uselist=False)
1417 stats = relationship('Statistics', cascade='all', uselist=False)
1417
1418
1418 followers = relationship(
1419 followers = relationship(
1419 'UserFollowing',
1420 'UserFollowing',
1420 primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id',
1421 primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id',
1421 cascade='all')
1422 cascade='all')
1422 extra_fields = relationship(
1423 extra_fields = relationship(
1423 'RepositoryField', cascade="all, delete, delete-orphan")
1424 'RepositoryField', cascade="all, delete, delete-orphan")
1424 logs = relationship('UserLog')
1425 logs = relationship('UserLog')
1425 comments = relationship(
1426 comments = relationship(
1426 'ChangesetComment', cascade="all, delete, delete-orphan")
1427 'ChangesetComment', cascade="all, delete, delete-orphan")
1427 pull_requests_source = relationship(
1428 pull_requests_source = relationship(
1428 'PullRequest',
1429 'PullRequest',
1429 primaryjoin='PullRequest.source_repo_id==Repository.repo_id',
1430 primaryjoin='PullRequest.source_repo_id==Repository.repo_id',
1430 cascade="all, delete, delete-orphan")
1431 cascade="all, delete, delete-orphan")
1431 pull_requests_target = relationship(
1432 pull_requests_target = relationship(
1432 'PullRequest',
1433 'PullRequest',
1433 primaryjoin='PullRequest.target_repo_id==Repository.repo_id',
1434 primaryjoin='PullRequest.target_repo_id==Repository.repo_id',
1434 cascade="all, delete, delete-orphan")
1435 cascade="all, delete, delete-orphan")
1435 ui = relationship('RepoRhodeCodeUi', cascade="all")
1436 ui = relationship('RepoRhodeCodeUi', cascade="all")
1436 settings = relationship('RepoRhodeCodeSetting', cascade="all")
1437 settings = relationship('RepoRhodeCodeSetting', cascade="all")
1437 integrations = relationship('Integration',
1438 integrations = relationship('Integration',
1438 cascade="all, delete, delete-orphan")
1439 cascade="all, delete, delete-orphan")
1439
1440
1440 def __unicode__(self):
1441 def __unicode__(self):
1441 return u"<%s('%s:%s')>" % (self.__class__.__name__, self.repo_id,
1442 return u"<%s('%s:%s')>" % (self.__class__.__name__, self.repo_id,
1442 safe_unicode(self.repo_name))
1443 safe_unicode(self.repo_name))
1443
1444
1444 @hybrid_property
1445 @hybrid_property
1445 def landing_rev(self):
1446 def landing_rev(self):
1446 # always should return [rev_type, rev]
1447 # always should return [rev_type, rev]
1447 if self._landing_revision:
1448 if self._landing_revision:
1448 _rev_info = self._landing_revision.split(':')
1449 _rev_info = self._landing_revision.split(':')
1449 if len(_rev_info) < 2:
1450 if len(_rev_info) < 2:
1450 _rev_info.insert(0, 'rev')
1451 _rev_info.insert(0, 'rev')
1451 return [_rev_info[0], _rev_info[1]]
1452 return [_rev_info[0], _rev_info[1]]
1452 return [None, None]
1453 return [None, None]
1453
1454
1454 @landing_rev.setter
1455 @landing_rev.setter
1455 def landing_rev(self, val):
1456 def landing_rev(self, val):
1456 if ':' not in val:
1457 if ':' not in val:
1457 raise ValueError('value must be delimited with `:` and consist '
1458 raise ValueError('value must be delimited with `:` and consist '
1458 'of <rev_type>:<rev>, got %s instead' % val)
1459 'of <rev_type>:<rev>, got %s instead' % val)
1459 self._landing_revision = val
1460 self._landing_revision = val
1460
1461
1461 @hybrid_property
1462 @hybrid_property
1462 def locked(self):
1463 def locked(self):
1463 if self._locked:
1464 if self._locked:
1464 user_id, timelocked, reason = self._locked.split(':')
1465 user_id, timelocked, reason = self._locked.split(':')
1465 lock_values = int(user_id), timelocked, reason
1466 lock_values = int(user_id), timelocked, reason
1466 else:
1467 else:
1467 lock_values = [None, None, None]
1468 lock_values = [None, None, None]
1468 return lock_values
1469 return lock_values
1469
1470
1470 @locked.setter
1471 @locked.setter
1471 def locked(self, val):
1472 def locked(self, val):
1472 if val and isinstance(val, (list, tuple)):
1473 if val and isinstance(val, (list, tuple)):
1473 self._locked = ':'.join(map(str, val))
1474 self._locked = ':'.join(map(str, val))
1474 else:
1475 else:
1475 self._locked = None
1476 self._locked = None
1476
1477
1477 @hybrid_property
1478 @hybrid_property
1478 def changeset_cache(self):
1479 def changeset_cache(self):
1479 from rhodecode.lib.vcs.backends.base import EmptyCommit
1480 from rhodecode.lib.vcs.backends.base import EmptyCommit
1480 dummy = EmptyCommit().__json__()
1481 dummy = EmptyCommit().__json__()
1481 if not self._changeset_cache:
1482 if not self._changeset_cache:
1482 return dummy
1483 return dummy
1483 try:
1484 try:
1484 return json.loads(self._changeset_cache)
1485 return json.loads(self._changeset_cache)
1485 except TypeError:
1486 except TypeError:
1486 return dummy
1487 return dummy
1487 except Exception:
1488 except Exception:
1488 log.error(traceback.format_exc())
1489 log.error(traceback.format_exc())
1489 return dummy
1490 return dummy
1490
1491
1491 @changeset_cache.setter
1492 @changeset_cache.setter
1492 def changeset_cache(self, val):
1493 def changeset_cache(self, val):
1493 try:
1494 try:
1494 self._changeset_cache = json.dumps(val)
1495 self._changeset_cache = json.dumps(val)
1495 except Exception:
1496 except Exception:
1496 log.error(traceback.format_exc())
1497 log.error(traceback.format_exc())
1497
1498
1498 @hybrid_property
1499 @hybrid_property
1499 def repo_name(self):
1500 def repo_name(self):
1500 return self._repo_name
1501 return self._repo_name
1501
1502
1502 @repo_name.setter
1503 @repo_name.setter
1503 def repo_name(self, value):
1504 def repo_name(self, value):
1504 self._repo_name = value
1505 self._repo_name = value
1505 self._repo_name_hash = hashlib.sha1(safe_str(value)).hexdigest()
1506 self._repo_name_hash = hashlib.sha1(safe_str(value)).hexdigest()
1506
1507
1507 @classmethod
1508 @classmethod
1508 def normalize_repo_name(cls, repo_name):
1509 def normalize_repo_name(cls, repo_name):
1509 """
1510 """
1510 Normalizes os specific repo_name to the format internally stored inside
1511 Normalizes os specific repo_name to the format internally stored inside
1511 database using URL_SEP
1512 database using URL_SEP
1512
1513
1513 :param cls:
1514 :param cls:
1514 :param repo_name:
1515 :param repo_name:
1515 """
1516 """
1516 return cls.NAME_SEP.join(repo_name.split(os.sep))
1517 return cls.NAME_SEP.join(repo_name.split(os.sep))
1517
1518
1518 @classmethod
1519 @classmethod
1519 def get_by_repo_name(cls, repo_name, cache=False, identity_cache=False):
1520 def get_by_repo_name(cls, repo_name, cache=False, identity_cache=False):
1520 session = Session()
1521 session = Session()
1521 q = session.query(cls).filter(cls.repo_name == repo_name)
1522 q = session.query(cls).filter(cls.repo_name == repo_name)
1522
1523
1523 if cache:
1524 if cache:
1524 if identity_cache:
1525 if identity_cache:
1525 val = cls.identity_cache(session, 'repo_name', repo_name)
1526 val = cls.identity_cache(session, 'repo_name', repo_name)
1526 if val:
1527 if val:
1527 return val
1528 return val
1528 else:
1529 else:
1529 q = q.options(
1530 q = q.options(
1530 FromCache("sql_cache_short",
1531 FromCache("sql_cache_short",
1531 "get_repo_by_name_%s" % _hash_key(repo_name)))
1532 "get_repo_by_name_%s" % _hash_key(repo_name)))
1532
1533
1533 return q.scalar()
1534 return q.scalar()
1534
1535
1535 @classmethod
1536 @classmethod
1536 def get_by_full_path(cls, repo_full_path):
1537 def get_by_full_path(cls, repo_full_path):
1537 repo_name = repo_full_path.split(cls.base_path(), 1)[-1]
1538 repo_name = repo_full_path.split(cls.base_path(), 1)[-1]
1538 repo_name = cls.normalize_repo_name(repo_name)
1539 repo_name = cls.normalize_repo_name(repo_name)
1539 return cls.get_by_repo_name(repo_name.strip(URL_SEP))
1540 return cls.get_by_repo_name(repo_name.strip(URL_SEP))
1540
1541
1541 @classmethod
1542 @classmethod
1542 def get_repo_forks(cls, repo_id):
1543 def get_repo_forks(cls, repo_id):
1543 return cls.query().filter(Repository.fork_id == repo_id)
1544 return cls.query().filter(Repository.fork_id == repo_id)
1544
1545
1545 @classmethod
1546 @classmethod
1546 def base_path(cls):
1547 def base_path(cls):
1547 """
1548 """
1548 Returns base path when all repos are stored
1549 Returns base path when all repos are stored
1549
1550
1550 :param cls:
1551 :param cls:
1551 """
1552 """
1552 q = Session().query(RhodeCodeUi)\
1553 q = Session().query(RhodeCodeUi)\
1553 .filter(RhodeCodeUi.ui_key == cls.NAME_SEP)
1554 .filter(RhodeCodeUi.ui_key == cls.NAME_SEP)
1554 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
1555 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
1555 return q.one().ui_value
1556 return q.one().ui_value
1556
1557
1557 @classmethod
1558 @classmethod
1558 def is_valid(cls, repo_name):
1559 def is_valid(cls, repo_name):
1559 """
1560 """
1560 returns True if given repo name is a valid filesystem repository
1561 returns True if given repo name is a valid filesystem repository
1561
1562
1562 :param cls:
1563 :param cls:
1563 :param repo_name:
1564 :param repo_name:
1564 """
1565 """
1565 from rhodecode.lib.utils import is_valid_repo
1566 from rhodecode.lib.utils import is_valid_repo
1566
1567
1567 return is_valid_repo(repo_name, cls.base_path())
1568 return is_valid_repo(repo_name, cls.base_path())
1568
1569
1569 @classmethod
1570 @classmethod
1570 def get_all_repos(cls, user_id=Optional(None), group_id=Optional(None),
1571 def get_all_repos(cls, user_id=Optional(None), group_id=Optional(None),
1571 case_insensitive=True):
1572 case_insensitive=True):
1572 q = Repository.query()
1573 q = Repository.query()
1573
1574
1574 if not isinstance(user_id, Optional):
1575 if not isinstance(user_id, Optional):
1575 q = q.filter(Repository.user_id == user_id)
1576 q = q.filter(Repository.user_id == user_id)
1576
1577
1577 if not isinstance(group_id, Optional):
1578 if not isinstance(group_id, Optional):
1578 q = q.filter(Repository.group_id == group_id)
1579 q = q.filter(Repository.group_id == group_id)
1579
1580
1580 if case_insensitive:
1581 if case_insensitive:
1581 q = q.order_by(func.lower(Repository.repo_name))
1582 q = q.order_by(func.lower(Repository.repo_name))
1582 else:
1583 else:
1583 q = q.order_by(Repository.repo_name)
1584 q = q.order_by(Repository.repo_name)
1584 return q.all()
1585 return q.all()
1585
1586
1586 @property
1587 @property
1587 def forks(self):
1588 def forks(self):
1588 """
1589 """
1589 Return forks of this repo
1590 Return forks of this repo
1590 """
1591 """
1591 return Repository.get_repo_forks(self.repo_id)
1592 return Repository.get_repo_forks(self.repo_id)
1592
1593
1593 @property
1594 @property
1594 def parent(self):
1595 def parent(self):
1595 """
1596 """
1596 Returns fork parent
1597 Returns fork parent
1597 """
1598 """
1598 return self.fork
1599 return self.fork
1599
1600
1600 @property
1601 @property
1601 def just_name(self):
1602 def just_name(self):
1602 return self.repo_name.split(self.NAME_SEP)[-1]
1603 return self.repo_name.split(self.NAME_SEP)[-1]
1603
1604
1604 @property
1605 @property
1605 def groups_with_parents(self):
1606 def groups_with_parents(self):
1606 groups = []
1607 groups = []
1607 if self.group is None:
1608 if self.group is None:
1608 return groups
1609 return groups
1609
1610
1610 cur_gr = self.group
1611 cur_gr = self.group
1611 groups.insert(0, cur_gr)
1612 groups.insert(0, cur_gr)
1612 while 1:
1613 while 1:
1613 gr = getattr(cur_gr, 'parent_group', None)
1614 gr = getattr(cur_gr, 'parent_group', None)
1614 cur_gr = cur_gr.parent_group
1615 cur_gr = cur_gr.parent_group
1615 if gr is None:
1616 if gr is None:
1616 break
1617 break
1617 groups.insert(0, gr)
1618 groups.insert(0, gr)
1618
1619
1619 return groups
1620 return groups
1620
1621
1621 @property
1622 @property
1622 def groups_and_repo(self):
1623 def groups_and_repo(self):
1623 return self.groups_with_parents, self
1624 return self.groups_with_parents, self
1624
1625
1625 @LazyProperty
1626 @LazyProperty
1626 def repo_path(self):
1627 def repo_path(self):
1627 """
1628 """
1628 Returns base full path for that repository means where it actually
1629 Returns base full path for that repository means where it actually
1629 exists on a filesystem
1630 exists on a filesystem
1630 """
1631 """
1631 q = Session().query(RhodeCodeUi).filter(
1632 q = Session().query(RhodeCodeUi).filter(
1632 RhodeCodeUi.ui_key == self.NAME_SEP)
1633 RhodeCodeUi.ui_key == self.NAME_SEP)
1633 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
1634 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
1634 return q.one().ui_value
1635 return q.one().ui_value
1635
1636
1636 @property
1637 @property
1637 def repo_full_path(self):
1638 def repo_full_path(self):
1638 p = [self.repo_path]
1639 p = [self.repo_path]
1639 # we need to split the name by / since this is how we store the
1640 # we need to split the name by / since this is how we store the
1640 # names in the database, but that eventually needs to be converted
1641 # names in the database, but that eventually needs to be converted
1641 # into a valid system path
1642 # into a valid system path
1642 p += self.repo_name.split(self.NAME_SEP)
1643 p += self.repo_name.split(self.NAME_SEP)
1643 return os.path.join(*map(safe_unicode, p))
1644 return os.path.join(*map(safe_unicode, p))
1644
1645
1645 @property
1646 @property
1646 def cache_keys(self):
1647 def cache_keys(self):
1647 """
1648 """
1648 Returns associated cache keys for that repo
1649 Returns associated cache keys for that repo
1649 """
1650 """
1650 return CacheKey.query()\
1651 return CacheKey.query()\
1651 .filter(CacheKey.cache_args == self.repo_name)\
1652 .filter(CacheKey.cache_args == self.repo_name)\
1652 .order_by(CacheKey.cache_key)\
1653 .order_by(CacheKey.cache_key)\
1653 .all()
1654 .all()
1654
1655
1655 def get_new_name(self, repo_name):
1656 def get_new_name(self, repo_name):
1656 """
1657 """
1657 returns new full repository name based on assigned group and new new
1658 returns new full repository name based on assigned group and new new
1658
1659
1659 :param group_name:
1660 :param group_name:
1660 """
1661 """
1661 path_prefix = self.group.full_path_splitted if self.group else []
1662 path_prefix = self.group.full_path_splitted if self.group else []
1662 return self.NAME_SEP.join(path_prefix + [repo_name])
1663 return self.NAME_SEP.join(path_prefix + [repo_name])
1663
1664
1664 @property
1665 @property
1665 def _config(self):
1666 def _config(self):
1666 """
1667 """
1667 Returns db based config object.
1668 Returns db based config object.
1668 """
1669 """
1669 from rhodecode.lib.utils import make_db_config
1670 from rhodecode.lib.utils import make_db_config
1670 return make_db_config(clear_session=False, repo=self)
1671 return make_db_config(clear_session=False, repo=self)
1671
1672
1672 def permissions(self, with_admins=True, with_owner=True):
1673 def permissions(self, with_admins=True, with_owner=True):
1673 q = UserRepoToPerm.query().filter(UserRepoToPerm.repository == self)
1674 q = UserRepoToPerm.query().filter(UserRepoToPerm.repository == self)
1674 q = q.options(joinedload(UserRepoToPerm.repository),
1675 q = q.options(joinedload(UserRepoToPerm.repository),
1675 joinedload(UserRepoToPerm.user),
1676 joinedload(UserRepoToPerm.user),
1676 joinedload(UserRepoToPerm.permission),)
1677 joinedload(UserRepoToPerm.permission),)
1677
1678
1678 # get owners and admins and permissions. We do a trick of re-writing
1679 # get owners and admins and permissions. We do a trick of re-writing
1679 # objects from sqlalchemy to named-tuples due to sqlalchemy session
1680 # objects from sqlalchemy to named-tuples due to sqlalchemy session
1680 # has a global reference and changing one object propagates to all
1681 # has a global reference and changing one object propagates to all
1681 # others. This means if admin is also an owner admin_row that change
1682 # others. This means if admin is also an owner admin_row that change
1682 # would propagate to both objects
1683 # would propagate to both objects
1683 perm_rows = []
1684 perm_rows = []
1684 for _usr in q.all():
1685 for _usr in q.all():
1685 usr = AttributeDict(_usr.user.get_dict())
1686 usr = AttributeDict(_usr.user.get_dict())
1686 usr.permission = _usr.permission.permission_name
1687 usr.permission = _usr.permission.permission_name
1687 perm_rows.append(usr)
1688 perm_rows.append(usr)
1688
1689
1689 # filter the perm rows by 'default' first and then sort them by
1690 # filter the perm rows by 'default' first and then sort them by
1690 # admin,write,read,none permissions sorted again alphabetically in
1691 # admin,write,read,none permissions sorted again alphabetically in
1691 # each group
1692 # each group
1692 perm_rows = sorted(perm_rows, key=display_sort)
1693 perm_rows = sorted(perm_rows, key=display_sort)
1693
1694
1694 _admin_perm = 'repository.admin'
1695 _admin_perm = 'repository.admin'
1695 owner_row = []
1696 owner_row = []
1696 if with_owner:
1697 if with_owner:
1697 usr = AttributeDict(self.user.get_dict())
1698 usr = AttributeDict(self.user.get_dict())
1698 usr.owner_row = True
1699 usr.owner_row = True
1699 usr.permission = _admin_perm
1700 usr.permission = _admin_perm
1700 owner_row.append(usr)
1701 owner_row.append(usr)
1701
1702
1702 super_admin_rows = []
1703 super_admin_rows = []
1703 if with_admins:
1704 if with_admins:
1704 for usr in User.get_all_super_admins():
1705 for usr in User.get_all_super_admins():
1705 # if this admin is also owner, don't double the record
1706 # if this admin is also owner, don't double the record
1706 if usr.user_id == owner_row[0].user_id:
1707 if usr.user_id == owner_row[0].user_id:
1707 owner_row[0].admin_row = True
1708 owner_row[0].admin_row = True
1708 else:
1709 else:
1709 usr = AttributeDict(usr.get_dict())
1710 usr = AttributeDict(usr.get_dict())
1710 usr.admin_row = True
1711 usr.admin_row = True
1711 usr.permission = _admin_perm
1712 usr.permission = _admin_perm
1712 super_admin_rows.append(usr)
1713 super_admin_rows.append(usr)
1713
1714
1714 return super_admin_rows + owner_row + perm_rows
1715 return super_admin_rows + owner_row + perm_rows
1715
1716
1716 def permission_user_groups(self):
1717 def permission_user_groups(self):
1717 q = UserGroupRepoToPerm.query().filter(
1718 q = UserGroupRepoToPerm.query().filter(
1718 UserGroupRepoToPerm.repository == self)
1719 UserGroupRepoToPerm.repository == self)
1719 q = q.options(joinedload(UserGroupRepoToPerm.repository),
1720 q = q.options(joinedload(UserGroupRepoToPerm.repository),
1720 joinedload(UserGroupRepoToPerm.users_group),
1721 joinedload(UserGroupRepoToPerm.users_group),
1721 joinedload(UserGroupRepoToPerm.permission),)
1722 joinedload(UserGroupRepoToPerm.permission),)
1722
1723
1723 perm_rows = []
1724 perm_rows = []
1724 for _user_group in q.all():
1725 for _user_group in q.all():
1725 usr = AttributeDict(_user_group.users_group.get_dict())
1726 usr = AttributeDict(_user_group.users_group.get_dict())
1726 usr.permission = _user_group.permission.permission_name
1727 usr.permission = _user_group.permission.permission_name
1727 perm_rows.append(usr)
1728 perm_rows.append(usr)
1728
1729
1729 return perm_rows
1730 return perm_rows
1730
1731
1731 def get_api_data(self, include_secrets=False):
1732 def get_api_data(self, include_secrets=False):
1732 """
1733 """
1733 Common function for generating repo api data
1734 Common function for generating repo api data
1734
1735
1735 :param include_secrets: See :meth:`User.get_api_data`.
1736 :param include_secrets: See :meth:`User.get_api_data`.
1736
1737
1737 """
1738 """
1738 # TODO: mikhail: Here there is an anti-pattern, we probably need to
1739 # TODO: mikhail: Here there is an anti-pattern, we probably need to
1739 # move this methods on models level.
1740 # move this methods on models level.
1740 from rhodecode.model.settings import SettingsModel
1741 from rhodecode.model.settings import SettingsModel
1741
1742
1742 repo = self
1743 repo = self
1743 _user_id, _time, _reason = self.locked
1744 _user_id, _time, _reason = self.locked
1744
1745
1745 data = {
1746 data = {
1746 'repo_id': repo.repo_id,
1747 'repo_id': repo.repo_id,
1747 'repo_name': repo.repo_name,
1748 'repo_name': repo.repo_name,
1748 'repo_type': repo.repo_type,
1749 'repo_type': repo.repo_type,
1749 'clone_uri': repo.clone_uri or '',
1750 'clone_uri': repo.clone_uri or '',
1750 'url': url('summary_home', repo_name=self.repo_name, qualified=True),
1751 'url': url('summary_home', repo_name=self.repo_name, qualified=True),
1751 'private': repo.private,
1752 'private': repo.private,
1752 'created_on': repo.created_on,
1753 'created_on': repo.created_on,
1753 'description': repo.description,
1754 'description': repo.description,
1754 'landing_rev': repo.landing_rev,
1755 'landing_rev': repo.landing_rev,
1755 'owner': repo.user.username,
1756 'owner': repo.user.username,
1756 'fork_of': repo.fork.repo_name if repo.fork else None,
1757 'fork_of': repo.fork.repo_name if repo.fork else None,
1757 'enable_statistics': repo.enable_statistics,
1758 'enable_statistics': repo.enable_statistics,
1758 'enable_locking': repo.enable_locking,
1759 'enable_locking': repo.enable_locking,
1759 'enable_downloads': repo.enable_downloads,
1760 'enable_downloads': repo.enable_downloads,
1760 'last_changeset': repo.changeset_cache,
1761 'last_changeset': repo.changeset_cache,
1761 'locked_by': User.get(_user_id).get_api_data(
1762 'locked_by': User.get(_user_id).get_api_data(
1762 include_secrets=include_secrets) if _user_id else None,
1763 include_secrets=include_secrets) if _user_id else None,
1763 'locked_date': time_to_datetime(_time) if _time else None,
1764 'locked_date': time_to_datetime(_time) if _time else None,
1764 'lock_reason': _reason if _reason else None,
1765 'lock_reason': _reason if _reason else None,
1765 }
1766 }
1766
1767
1767 # TODO: mikhail: should be per-repo settings here
1768 # TODO: mikhail: should be per-repo settings here
1768 rc_config = SettingsModel().get_all_settings()
1769 rc_config = SettingsModel().get_all_settings()
1769 repository_fields = str2bool(
1770 repository_fields = str2bool(
1770 rc_config.get('rhodecode_repository_fields'))
1771 rc_config.get('rhodecode_repository_fields'))
1771 if repository_fields:
1772 if repository_fields:
1772 for f in self.extra_fields:
1773 for f in self.extra_fields:
1773 data[f.field_key_prefixed] = f.field_value
1774 data[f.field_key_prefixed] = f.field_value
1774
1775
1775 return data
1776 return data
1776
1777
1777 @classmethod
1778 @classmethod
1778 def lock(cls, repo, user_id, lock_time=None, lock_reason=None):
1779 def lock(cls, repo, user_id, lock_time=None, lock_reason=None):
1779 if not lock_time:
1780 if not lock_time:
1780 lock_time = time.time()
1781 lock_time = time.time()
1781 if not lock_reason:
1782 if not lock_reason:
1782 lock_reason = cls.LOCK_AUTOMATIC
1783 lock_reason = cls.LOCK_AUTOMATIC
1783 repo.locked = [user_id, lock_time, lock_reason]
1784 repo.locked = [user_id, lock_time, lock_reason]
1784 Session().add(repo)
1785 Session().add(repo)
1785 Session().commit()
1786 Session().commit()
1786
1787
1787 @classmethod
1788 @classmethod
1788 def unlock(cls, repo):
1789 def unlock(cls, repo):
1789 repo.locked = None
1790 repo.locked = None
1790 Session().add(repo)
1791 Session().add(repo)
1791 Session().commit()
1792 Session().commit()
1792
1793
1793 @classmethod
1794 @classmethod
1794 def getlock(cls, repo):
1795 def getlock(cls, repo):
1795 return repo.locked
1796 return repo.locked
1796
1797
1797 def is_user_lock(self, user_id):
1798 def is_user_lock(self, user_id):
1798 if self.lock[0]:
1799 if self.lock[0]:
1799 lock_user_id = safe_int(self.lock[0])
1800 lock_user_id = safe_int(self.lock[0])
1800 user_id = safe_int(user_id)
1801 user_id = safe_int(user_id)
1801 # both are ints, and they are equal
1802 # both are ints, and they are equal
1802 return all([lock_user_id, user_id]) and lock_user_id == user_id
1803 return all([lock_user_id, user_id]) and lock_user_id == user_id
1803
1804
1804 return False
1805 return False
1805
1806
1806 def get_locking_state(self, action, user_id, only_when_enabled=True):
1807 def get_locking_state(self, action, user_id, only_when_enabled=True):
1807 """
1808 """
1808 Checks locking on this repository, if locking is enabled and lock is
1809 Checks locking on this repository, if locking is enabled and lock is
1809 present returns a tuple of make_lock, locked, locked_by.
1810 present returns a tuple of make_lock, locked, locked_by.
1810 make_lock can have 3 states None (do nothing) True, make lock
1811 make_lock can have 3 states None (do nothing) True, make lock
1811 False release lock, This value is later propagated to hooks, which
1812 False release lock, This value is later propagated to hooks, which
1812 do the locking. Think about this as signals passed to hooks what to do.
1813 do the locking. Think about this as signals passed to hooks what to do.
1813
1814
1814 """
1815 """
1815 # TODO: johbo: This is part of the business logic and should be moved
1816 # TODO: johbo: This is part of the business logic and should be moved
1816 # into the RepositoryModel.
1817 # into the RepositoryModel.
1817
1818
1818 if action not in ('push', 'pull'):
1819 if action not in ('push', 'pull'):
1819 raise ValueError("Invalid action value: %s" % repr(action))
1820 raise ValueError("Invalid action value: %s" % repr(action))
1820
1821
1821 # defines if locked error should be thrown to user
1822 # defines if locked error should be thrown to user
1822 currently_locked = False
1823 currently_locked = False
1823 # defines if new lock should be made, tri-state
1824 # defines if new lock should be made, tri-state
1824 make_lock = None
1825 make_lock = None
1825 repo = self
1826 repo = self
1826 user = User.get(user_id)
1827 user = User.get(user_id)
1827
1828
1828 lock_info = repo.locked
1829 lock_info = repo.locked
1829
1830
1830 if repo and (repo.enable_locking or not only_when_enabled):
1831 if repo and (repo.enable_locking or not only_when_enabled):
1831 if action == 'push':
1832 if action == 'push':
1832 # check if it's already locked !, if it is compare users
1833 # check if it's already locked !, if it is compare users
1833 locked_by_user_id = lock_info[0]
1834 locked_by_user_id = lock_info[0]
1834 if user.user_id == locked_by_user_id:
1835 if user.user_id == locked_by_user_id:
1835 log.debug(
1836 log.debug(
1836 'Got `push` action from user %s, now unlocking', user)
1837 'Got `push` action from user %s, now unlocking', user)
1837 # unlock if we have push from user who locked
1838 # unlock if we have push from user who locked
1838 make_lock = False
1839 make_lock = False
1839 else:
1840 else:
1840 # we're not the same user who locked, ban with
1841 # we're not the same user who locked, ban with
1841 # code defined in settings (default is 423 HTTP Locked) !
1842 # code defined in settings (default is 423 HTTP Locked) !
1842 log.debug('Repo %s is currently locked by %s', repo, user)
1843 log.debug('Repo %s is currently locked by %s', repo, user)
1843 currently_locked = True
1844 currently_locked = True
1844 elif action == 'pull':
1845 elif action == 'pull':
1845 # [0] user [1] date
1846 # [0] user [1] date
1846 if lock_info[0] and lock_info[1]:
1847 if lock_info[0] and lock_info[1]:
1847 log.debug('Repo %s is currently locked by %s', repo, user)
1848 log.debug('Repo %s is currently locked by %s', repo, user)
1848 currently_locked = True
1849 currently_locked = True
1849 else:
1850 else:
1850 log.debug('Setting lock on repo %s by %s', repo, user)
1851 log.debug('Setting lock on repo %s by %s', repo, user)
1851 make_lock = True
1852 make_lock = True
1852
1853
1853 else:
1854 else:
1854 log.debug('Repository %s do not have locking enabled', repo)
1855 log.debug('Repository %s do not have locking enabled', repo)
1855
1856
1856 log.debug('FINAL locking values make_lock:%s,locked:%s,locked_by:%s',
1857 log.debug('FINAL locking values make_lock:%s,locked:%s,locked_by:%s',
1857 make_lock, currently_locked, lock_info)
1858 make_lock, currently_locked, lock_info)
1858
1859
1859 from rhodecode.lib.auth import HasRepoPermissionAny
1860 from rhodecode.lib.auth import HasRepoPermissionAny
1860 perm_check = HasRepoPermissionAny('repository.write', 'repository.admin')
1861 perm_check = HasRepoPermissionAny('repository.write', 'repository.admin')
1861 if make_lock and not perm_check(repo_name=repo.repo_name, user=user):
1862 if make_lock and not perm_check(repo_name=repo.repo_name, user=user):
1862 # if we don't have at least write permission we cannot make a lock
1863 # if we don't have at least write permission we cannot make a lock
1863 log.debug('lock state reset back to FALSE due to lack '
1864 log.debug('lock state reset back to FALSE due to lack '
1864 'of at least read permission')
1865 'of at least read permission')
1865 make_lock = False
1866 make_lock = False
1866
1867
1867 return make_lock, currently_locked, lock_info
1868 return make_lock, currently_locked, lock_info
1868
1869
1869 @property
1870 @property
1870 def last_db_change(self):
1871 def last_db_change(self):
1871 return self.updated_on
1872 return self.updated_on
1872
1873
1873 @property
1874 @property
1874 def clone_uri_hidden(self):
1875 def clone_uri_hidden(self):
1875 clone_uri = self.clone_uri
1876 clone_uri = self.clone_uri
1876 if clone_uri:
1877 if clone_uri:
1877 import urlobject
1878 import urlobject
1878 url_obj = urlobject.URLObject(cleaned_uri(clone_uri))
1879 url_obj = urlobject.URLObject(cleaned_uri(clone_uri))
1879 if url_obj.password:
1880 if url_obj.password:
1880 clone_uri = url_obj.with_password('*****')
1881 clone_uri = url_obj.with_password('*****')
1881 return clone_uri
1882 return clone_uri
1882
1883
1883 def clone_url(self, **override):
1884 def clone_url(self, **override):
1884 qualified_home_url = url('home', qualified=True)
1885 qualified_home_url = url('home', qualified=True)
1885
1886
1886 uri_tmpl = None
1887 uri_tmpl = None
1887 if 'with_id' in override:
1888 if 'with_id' in override:
1888 uri_tmpl = self.DEFAULT_CLONE_URI_ID
1889 uri_tmpl = self.DEFAULT_CLONE_URI_ID
1889 del override['with_id']
1890 del override['with_id']
1890
1891
1891 if 'uri_tmpl' in override:
1892 if 'uri_tmpl' in override:
1892 uri_tmpl = override['uri_tmpl']
1893 uri_tmpl = override['uri_tmpl']
1893 del override['uri_tmpl']
1894 del override['uri_tmpl']
1894
1895
1895 # we didn't override our tmpl from **overrides
1896 # we didn't override our tmpl from **overrides
1896 if not uri_tmpl:
1897 if not uri_tmpl:
1897 uri_tmpl = self.DEFAULT_CLONE_URI
1898 uri_tmpl = self.DEFAULT_CLONE_URI
1898 try:
1899 try:
1899 from pylons import tmpl_context as c
1900 from pylons import tmpl_context as c
1900 uri_tmpl = c.clone_uri_tmpl
1901 uri_tmpl = c.clone_uri_tmpl
1901 except Exception:
1902 except Exception:
1902 # in any case if we call this outside of request context,
1903 # in any case if we call this outside of request context,
1903 # ie, not having tmpl_context set up
1904 # ie, not having tmpl_context set up
1904 pass
1905 pass
1905
1906
1906 return get_clone_url(uri_tmpl=uri_tmpl,
1907 return get_clone_url(uri_tmpl=uri_tmpl,
1907 qualifed_home_url=qualified_home_url,
1908 qualifed_home_url=qualified_home_url,
1908 repo_name=self.repo_name,
1909 repo_name=self.repo_name,
1909 repo_id=self.repo_id, **override)
1910 repo_id=self.repo_id, **override)
1910
1911
1911 def set_state(self, state):
1912 def set_state(self, state):
1912 self.repo_state = state
1913 self.repo_state = state
1913 Session().add(self)
1914 Session().add(self)
1914 #==========================================================================
1915 #==========================================================================
1915 # SCM PROPERTIES
1916 # SCM PROPERTIES
1916 #==========================================================================
1917 #==========================================================================
1917
1918
1918 def get_commit(self, commit_id=None, commit_idx=None, pre_load=None):
1919 def get_commit(self, commit_id=None, commit_idx=None, pre_load=None):
1919 return get_commit_safe(
1920 return get_commit_safe(
1920 self.scm_instance(), commit_id, commit_idx, pre_load=pre_load)
1921 self.scm_instance(), commit_id, commit_idx, pre_load=pre_load)
1921
1922
1922 def get_changeset(self, rev=None, pre_load=None):
1923 def get_changeset(self, rev=None, pre_load=None):
1923 warnings.warn("Use get_commit", DeprecationWarning)
1924 warnings.warn("Use get_commit", DeprecationWarning)
1924 commit_id = None
1925 commit_id = None
1925 commit_idx = None
1926 commit_idx = None
1926 if isinstance(rev, basestring):
1927 if isinstance(rev, basestring):
1927 commit_id = rev
1928 commit_id = rev
1928 else:
1929 else:
1929 commit_idx = rev
1930 commit_idx = rev
1930 return self.get_commit(commit_id=commit_id, commit_idx=commit_idx,
1931 return self.get_commit(commit_id=commit_id, commit_idx=commit_idx,
1931 pre_load=pre_load)
1932 pre_load=pre_load)
1932
1933
1933 def get_landing_commit(self):
1934 def get_landing_commit(self):
1934 """
1935 """
1935 Returns landing commit, or if that doesn't exist returns the tip
1936 Returns landing commit, or if that doesn't exist returns the tip
1936 """
1937 """
1937 _rev_type, _rev = self.landing_rev
1938 _rev_type, _rev = self.landing_rev
1938 commit = self.get_commit(_rev)
1939 commit = self.get_commit(_rev)
1939 if isinstance(commit, EmptyCommit):
1940 if isinstance(commit, EmptyCommit):
1940 return self.get_commit()
1941 return self.get_commit()
1941 return commit
1942 return commit
1942
1943
1943 def update_commit_cache(self, cs_cache=None, config=None):
1944 def update_commit_cache(self, cs_cache=None, config=None):
1944 """
1945 """
1945 Update cache of last changeset for repository, keys should be::
1946 Update cache of last changeset for repository, keys should be::
1946
1947
1947 short_id
1948 short_id
1948 raw_id
1949 raw_id
1949 revision
1950 revision
1950 parents
1951 parents
1951 message
1952 message
1952 date
1953 date
1953 author
1954 author
1954
1955
1955 :param cs_cache:
1956 :param cs_cache:
1956 """
1957 """
1957 from rhodecode.lib.vcs.backends.base import BaseChangeset
1958 from rhodecode.lib.vcs.backends.base import BaseChangeset
1958 if cs_cache is None:
1959 if cs_cache is None:
1959 # use no-cache version here
1960 # use no-cache version here
1960 scm_repo = self.scm_instance(cache=False, config=config)
1961 scm_repo = self.scm_instance(cache=False, config=config)
1961 if scm_repo:
1962 if scm_repo:
1962 cs_cache = scm_repo.get_commit(
1963 cs_cache = scm_repo.get_commit(
1963 pre_load=["author", "date", "message", "parents"])
1964 pre_load=["author", "date", "message", "parents"])
1964 else:
1965 else:
1965 cs_cache = EmptyCommit()
1966 cs_cache = EmptyCommit()
1966
1967
1967 if isinstance(cs_cache, BaseChangeset):
1968 if isinstance(cs_cache, BaseChangeset):
1968 cs_cache = cs_cache.__json__()
1969 cs_cache = cs_cache.__json__()
1969
1970
1970 def is_outdated(new_cs_cache):
1971 def is_outdated(new_cs_cache):
1971 if (new_cs_cache['raw_id'] != self.changeset_cache['raw_id'] or
1972 if (new_cs_cache['raw_id'] != self.changeset_cache['raw_id'] or
1972 new_cs_cache['revision'] != self.changeset_cache['revision']):
1973 new_cs_cache['revision'] != self.changeset_cache['revision']):
1973 return True
1974 return True
1974 return False
1975 return False
1975
1976
1976 # check if we have maybe already latest cached revision
1977 # check if we have maybe already latest cached revision
1977 if is_outdated(cs_cache) or not self.changeset_cache:
1978 if is_outdated(cs_cache) or not self.changeset_cache:
1978 _default = datetime.datetime.fromtimestamp(0)
1979 _default = datetime.datetime.fromtimestamp(0)
1979 last_change = cs_cache.get('date') or _default
1980 last_change = cs_cache.get('date') or _default
1980 log.debug('updated repo %s with new cs cache %s',
1981 log.debug('updated repo %s with new cs cache %s',
1981 self.repo_name, cs_cache)
1982 self.repo_name, cs_cache)
1982 self.updated_on = last_change
1983 self.updated_on = last_change
1983 self.changeset_cache = cs_cache
1984 self.changeset_cache = cs_cache
1984 Session().add(self)
1985 Session().add(self)
1985 Session().commit()
1986 Session().commit()
1986 else:
1987 else:
1987 log.debug('Skipping update_commit_cache for repo:`%s` '
1988 log.debug('Skipping update_commit_cache for repo:`%s` '
1988 'commit already with latest changes', self.repo_name)
1989 'commit already with latest changes', self.repo_name)
1989
1990
1990 @property
1991 @property
1991 def tip(self):
1992 def tip(self):
1992 return self.get_commit('tip')
1993 return self.get_commit('tip')
1993
1994
1994 @property
1995 @property
1995 def author(self):
1996 def author(self):
1996 return self.tip.author
1997 return self.tip.author
1997
1998
1998 @property
1999 @property
1999 def last_change(self):
2000 def last_change(self):
2000 return self.scm_instance().last_change
2001 return self.scm_instance().last_change
2001
2002
2002 def get_comments(self, revisions=None):
2003 def get_comments(self, revisions=None):
2003 """
2004 """
2004 Returns comments for this repository grouped by revisions
2005 Returns comments for this repository grouped by revisions
2005
2006
2006 :param revisions: filter query by revisions only
2007 :param revisions: filter query by revisions only
2007 """
2008 """
2008 cmts = ChangesetComment.query()\
2009 cmts = ChangesetComment.query()\
2009 .filter(ChangesetComment.repo == self)
2010 .filter(ChangesetComment.repo == self)
2010 if revisions:
2011 if revisions:
2011 cmts = cmts.filter(ChangesetComment.revision.in_(revisions))
2012 cmts = cmts.filter(ChangesetComment.revision.in_(revisions))
2012 grouped = collections.defaultdict(list)
2013 grouped = collections.defaultdict(list)
2013 for cmt in cmts.all():
2014 for cmt in cmts.all():
2014 grouped[cmt.revision].append(cmt)
2015 grouped[cmt.revision].append(cmt)
2015 return grouped
2016 return grouped
2016
2017
2017 def statuses(self, revisions=None):
2018 def statuses(self, revisions=None):
2018 """
2019 """
2019 Returns statuses for this repository
2020 Returns statuses for this repository
2020
2021
2021 :param revisions: list of revisions to get statuses for
2022 :param revisions: list of revisions to get statuses for
2022 """
2023 """
2023 statuses = ChangesetStatus.query()\
2024 statuses = ChangesetStatus.query()\
2024 .filter(ChangesetStatus.repo == self)\
2025 .filter(ChangesetStatus.repo == self)\
2025 .filter(ChangesetStatus.version == 0)
2026 .filter(ChangesetStatus.version == 0)
2026
2027
2027 if revisions:
2028 if revisions:
2028 # Try doing the filtering in chunks to avoid hitting limits
2029 # Try doing the filtering in chunks to avoid hitting limits
2029 size = 500
2030 size = 500
2030 status_results = []
2031 status_results = []
2031 for chunk in xrange(0, len(revisions), size):
2032 for chunk in xrange(0, len(revisions), size):
2032 status_results += statuses.filter(
2033 status_results += statuses.filter(
2033 ChangesetStatus.revision.in_(
2034 ChangesetStatus.revision.in_(
2034 revisions[chunk: chunk+size])
2035 revisions[chunk: chunk+size])
2035 ).all()
2036 ).all()
2036 else:
2037 else:
2037 status_results = statuses.all()
2038 status_results = statuses.all()
2038
2039
2039 grouped = {}
2040 grouped = {}
2040
2041
2041 # maybe we have open new pullrequest without a status?
2042 # maybe we have open new pullrequest without a status?
2042 stat = ChangesetStatus.STATUS_UNDER_REVIEW
2043 stat = ChangesetStatus.STATUS_UNDER_REVIEW
2043 status_lbl = ChangesetStatus.get_status_lbl(stat)
2044 status_lbl = ChangesetStatus.get_status_lbl(stat)
2044 for pr in PullRequest.query().filter(PullRequest.source_repo == self).all():
2045 for pr in PullRequest.query().filter(PullRequest.source_repo == self).all():
2045 for rev in pr.revisions:
2046 for rev in pr.revisions:
2046 pr_id = pr.pull_request_id
2047 pr_id = pr.pull_request_id
2047 pr_repo = pr.target_repo.repo_name
2048 pr_repo = pr.target_repo.repo_name
2048 grouped[rev] = [stat, status_lbl, pr_id, pr_repo]
2049 grouped[rev] = [stat, status_lbl, pr_id, pr_repo]
2049
2050
2050 for stat in status_results:
2051 for stat in status_results:
2051 pr_id = pr_repo = None
2052 pr_id = pr_repo = None
2052 if stat.pull_request:
2053 if stat.pull_request:
2053 pr_id = stat.pull_request.pull_request_id
2054 pr_id = stat.pull_request.pull_request_id
2054 pr_repo = stat.pull_request.target_repo.repo_name
2055 pr_repo = stat.pull_request.target_repo.repo_name
2055 grouped[stat.revision] = [str(stat.status), stat.status_lbl,
2056 grouped[stat.revision] = [str(stat.status), stat.status_lbl,
2056 pr_id, pr_repo]
2057 pr_id, pr_repo]
2057 return grouped
2058 return grouped
2058
2059
2059 # ==========================================================================
2060 # ==========================================================================
2060 # SCM CACHE INSTANCE
2061 # SCM CACHE INSTANCE
2061 # ==========================================================================
2062 # ==========================================================================
2062
2063
2063 def scm_instance(self, **kwargs):
2064 def scm_instance(self, **kwargs):
2064 import rhodecode
2065 import rhodecode
2065
2066
2066 # Passing a config will not hit the cache currently only used
2067 # Passing a config will not hit the cache currently only used
2067 # for repo2dbmapper
2068 # for repo2dbmapper
2068 config = kwargs.pop('config', None)
2069 config = kwargs.pop('config', None)
2069 cache = kwargs.pop('cache', None)
2070 cache = kwargs.pop('cache', None)
2070 full_cache = str2bool(rhodecode.CONFIG.get('vcs_full_cache'))
2071 full_cache = str2bool(rhodecode.CONFIG.get('vcs_full_cache'))
2071 # if cache is NOT defined use default global, else we have a full
2072 # if cache is NOT defined use default global, else we have a full
2072 # control over cache behaviour
2073 # control over cache behaviour
2073 if cache is None and full_cache and not config:
2074 if cache is None and full_cache and not config:
2074 return self._get_instance_cached()
2075 return self._get_instance_cached()
2075 return self._get_instance(cache=bool(cache), config=config)
2076 return self._get_instance(cache=bool(cache), config=config)
2076
2077
2077 def _get_instance_cached(self):
2078 def _get_instance_cached(self):
2078 @cache_region('long_term')
2079 @cache_region('long_term')
2079 def _get_repo(cache_key):
2080 def _get_repo(cache_key):
2080 return self._get_instance()
2081 return self._get_instance()
2081
2082
2082 invalidator_context = CacheKey.repo_context_cache(
2083 invalidator_context = CacheKey.repo_context_cache(
2083 _get_repo, self.repo_name, None, thread_scoped=True)
2084 _get_repo, self.repo_name, None, thread_scoped=True)
2084
2085
2085 with invalidator_context as context:
2086 with invalidator_context as context:
2086 context.invalidate()
2087 context.invalidate()
2087 repo = context.compute()
2088 repo = context.compute()
2088
2089
2089 return repo
2090 return repo
2090
2091
2091 def _get_instance(self, cache=True, config=None):
2092 def _get_instance(self, cache=True, config=None):
2092 config = config or self._config
2093 config = config or self._config
2093 custom_wire = {
2094 custom_wire = {
2094 'cache': cache # controls the vcs.remote cache
2095 'cache': cache # controls the vcs.remote cache
2095 }
2096 }
2096 repo = get_vcs_instance(
2097 repo = get_vcs_instance(
2097 repo_path=safe_str(self.repo_full_path),
2098 repo_path=safe_str(self.repo_full_path),
2098 config=config,
2099 config=config,
2099 with_wire=custom_wire,
2100 with_wire=custom_wire,
2100 create=False,
2101 create=False,
2101 _vcs_alias=self.repo_type)
2102 _vcs_alias=self.repo_type)
2102
2103
2103 return repo
2104 return repo
2104
2105
2105 def __json__(self):
2106 def __json__(self):
2106 return {'landing_rev': self.landing_rev}
2107 return {'landing_rev': self.landing_rev}
2107
2108
2108 def get_dict(self):
2109 def get_dict(self):
2109
2110
2110 # Since we transformed `repo_name` to a hybrid property, we need to
2111 # Since we transformed `repo_name` to a hybrid property, we need to
2111 # keep compatibility with the code which uses `repo_name` field.
2112 # keep compatibility with the code which uses `repo_name` field.
2112
2113
2113 result = super(Repository, self).get_dict()
2114 result = super(Repository, self).get_dict()
2114 result['repo_name'] = result.pop('_repo_name', None)
2115 result['repo_name'] = result.pop('_repo_name', None)
2115 return result
2116 return result
2116
2117
2117
2118
2118 class RepoGroup(Base, BaseModel):
2119 class RepoGroup(Base, BaseModel):
2119 __tablename__ = 'groups'
2120 __tablename__ = 'groups'
2120 __table_args__ = (
2121 __table_args__ = (
2121 UniqueConstraint('group_name', 'group_parent_id'),
2122 UniqueConstraint('group_name', 'group_parent_id'),
2122 CheckConstraint('group_id != group_parent_id'),
2123 CheckConstraint('group_id != group_parent_id'),
2123 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2124 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2124 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
2125 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
2125 )
2126 )
2126 __mapper_args__ = {'order_by': 'group_name'}
2127 __mapper_args__ = {'order_by': 'group_name'}
2127
2128
2128 CHOICES_SEPARATOR = '/' # used to generate select2 choices for nested groups
2129 CHOICES_SEPARATOR = '/' # used to generate select2 choices for nested groups
2129
2130
2130 group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2131 group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2131 group_name = Column("group_name", String(255), nullable=False, unique=True, default=None)
2132 group_name = Column("group_name", String(255), nullable=False, unique=True, default=None)
2132 group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
2133 group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
2133 group_description = Column("group_description", String(10000), nullable=True, unique=None, default=None)
2134 group_description = Column("group_description", String(10000), nullable=True, unique=None, default=None)
2134 enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False)
2135 enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False)
2135 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
2136 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
2136 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
2137 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
2137 personal = Column('personal', Boolean(), nullable=True, unique=None, default=None)
2138 personal = Column('personal', Boolean(), nullable=True, unique=None, default=None)
2138
2139
2139 repo_group_to_perm = relationship('UserRepoGroupToPerm', cascade='all', order_by='UserRepoGroupToPerm.group_to_perm_id')
2140 repo_group_to_perm = relationship('UserRepoGroupToPerm', cascade='all', order_by='UserRepoGroupToPerm.group_to_perm_id')
2140 users_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
2141 users_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
2141 parent_group = relationship('RepoGroup', remote_side=group_id)
2142 parent_group = relationship('RepoGroup', remote_side=group_id)
2142 user = relationship('User')
2143 user = relationship('User')
2143 integrations = relationship('Integration',
2144 integrations = relationship('Integration',
2144 cascade="all, delete, delete-orphan")
2145 cascade="all, delete, delete-orphan")
2145
2146
2146 def __init__(self, group_name='', parent_group=None):
2147 def __init__(self, group_name='', parent_group=None):
2147 self.group_name = group_name
2148 self.group_name = group_name
2148 self.parent_group = parent_group
2149 self.parent_group = parent_group
2149
2150
2150 def __unicode__(self):
2151 def __unicode__(self):
2151 return u"<%s('id:%s:%s')>" % (self.__class__.__name__, self.group_id,
2152 return u"<%s('id:%s:%s')>" % (self.__class__.__name__, self.group_id,
2152 self.group_name)
2153 self.group_name)
2153
2154
2154 @classmethod
2155 @classmethod
2155 def _generate_choice(cls, repo_group):
2156 def _generate_choice(cls, repo_group):
2156 from webhelpers.html import literal as _literal
2157 from webhelpers.html import literal as _literal
2157 _name = lambda k: _literal(cls.CHOICES_SEPARATOR.join(k))
2158 _name = lambda k: _literal(cls.CHOICES_SEPARATOR.join(k))
2158 return repo_group.group_id, _name(repo_group.full_path_splitted)
2159 return repo_group.group_id, _name(repo_group.full_path_splitted)
2159
2160
2160 @classmethod
2161 @classmethod
2161 def groups_choices(cls, groups=None, show_empty_group=True):
2162 def groups_choices(cls, groups=None, show_empty_group=True):
2162 if not groups:
2163 if not groups:
2163 groups = cls.query().all()
2164 groups = cls.query().all()
2164
2165
2165 repo_groups = []
2166 repo_groups = []
2166 if show_empty_group:
2167 if show_empty_group:
2167 repo_groups = [('-1', u'-- %s --' % _('No parent'))]
2168 repo_groups = [('-1', u'-- %s --' % _('No parent'))]
2168
2169
2169 repo_groups.extend([cls._generate_choice(x) for x in groups])
2170 repo_groups.extend([cls._generate_choice(x) for x in groups])
2170
2171
2171 repo_groups = sorted(
2172 repo_groups = sorted(
2172 repo_groups, key=lambda t: t[1].split(cls.CHOICES_SEPARATOR)[0])
2173 repo_groups, key=lambda t: t[1].split(cls.CHOICES_SEPARATOR)[0])
2173 return repo_groups
2174 return repo_groups
2174
2175
2175 @classmethod
2176 @classmethod
2176 def url_sep(cls):
2177 def url_sep(cls):
2177 return URL_SEP
2178 return URL_SEP
2178
2179
2179 @classmethod
2180 @classmethod
2180 def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
2181 def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
2181 if case_insensitive:
2182 if case_insensitive:
2182 gr = cls.query().filter(func.lower(cls.group_name)
2183 gr = cls.query().filter(func.lower(cls.group_name)
2183 == func.lower(group_name))
2184 == func.lower(group_name))
2184 else:
2185 else:
2185 gr = cls.query().filter(cls.group_name == group_name)
2186 gr = cls.query().filter(cls.group_name == group_name)
2186 if cache:
2187 if cache:
2187 gr = gr.options(FromCache(
2188 gr = gr.options(FromCache(
2188 "sql_cache_short",
2189 "sql_cache_short",
2189 "get_group_%s" % _hash_key(group_name)))
2190 "get_group_%s" % _hash_key(group_name)))
2190 return gr.scalar()
2191 return gr.scalar()
2191
2192
2192 @classmethod
2193 @classmethod
2193 def get_user_personal_repo_group(cls, user_id):
2194 def get_user_personal_repo_group(cls, user_id):
2194 user = User.get(user_id)
2195 user = User.get(user_id)
2195 return cls.query()\
2196 return cls.query()\
2196 .filter(cls.personal == true())\
2197 .filter(cls.personal == true())\
2197 .filter(cls.user == user).scalar()
2198 .filter(cls.user == user).scalar()
2198
2199
2199 @classmethod
2200 @classmethod
2200 def get_all_repo_groups(cls, user_id=Optional(None), group_id=Optional(None),
2201 def get_all_repo_groups(cls, user_id=Optional(None), group_id=Optional(None),
2201 case_insensitive=True):
2202 case_insensitive=True):
2202 q = RepoGroup.query()
2203 q = RepoGroup.query()
2203
2204
2204 if not isinstance(user_id, Optional):
2205 if not isinstance(user_id, Optional):
2205 q = q.filter(RepoGroup.user_id == user_id)
2206 q = q.filter(RepoGroup.user_id == user_id)
2206
2207
2207 if not isinstance(group_id, Optional):
2208 if not isinstance(group_id, Optional):
2208 q = q.filter(RepoGroup.group_parent_id == group_id)
2209 q = q.filter(RepoGroup.group_parent_id == group_id)
2209
2210
2210 if case_insensitive:
2211 if case_insensitive:
2211 q = q.order_by(func.lower(RepoGroup.group_name))
2212 q = q.order_by(func.lower(RepoGroup.group_name))
2212 else:
2213 else:
2213 q = q.order_by(RepoGroup.group_name)
2214 q = q.order_by(RepoGroup.group_name)
2214 return q.all()
2215 return q.all()
2215
2216
2216 @property
2217 @property
2217 def parents(self):
2218 def parents(self):
2218 parents_recursion_limit = 10
2219 parents_recursion_limit = 10
2219 groups = []
2220 groups = []
2220 if self.parent_group is None:
2221 if self.parent_group is None:
2221 return groups
2222 return groups
2222 cur_gr = self.parent_group
2223 cur_gr = self.parent_group
2223 groups.insert(0, cur_gr)
2224 groups.insert(0, cur_gr)
2224 cnt = 0
2225 cnt = 0
2225 while 1:
2226 while 1:
2226 cnt += 1
2227 cnt += 1
2227 gr = getattr(cur_gr, 'parent_group', None)
2228 gr = getattr(cur_gr, 'parent_group', None)
2228 cur_gr = cur_gr.parent_group
2229 cur_gr = cur_gr.parent_group
2229 if gr is None:
2230 if gr is None:
2230 break
2231 break
2231 if cnt == parents_recursion_limit:
2232 if cnt == parents_recursion_limit:
2232 # this will prevent accidental infinit loops
2233 # this will prevent accidental infinit loops
2233 log.error(('more than %s parents found for group %s, stopping '
2234 log.error(('more than %s parents found for group %s, stopping '
2234 'recursive parent fetching' % (parents_recursion_limit, self)))
2235 'recursive parent fetching' % (parents_recursion_limit, self)))
2235 break
2236 break
2236
2237
2237 groups.insert(0, gr)
2238 groups.insert(0, gr)
2238 return groups
2239 return groups
2239
2240
2240 @property
2241 @property
2241 def children(self):
2242 def children(self):
2242 return RepoGroup.query().filter(RepoGroup.parent_group == self)
2243 return RepoGroup.query().filter(RepoGroup.parent_group == self)
2243
2244
2244 @property
2245 @property
2245 def name(self):
2246 def name(self):
2246 return self.group_name.split(RepoGroup.url_sep())[-1]
2247 return self.group_name.split(RepoGroup.url_sep())[-1]
2247
2248
2248 @property
2249 @property
2249 def full_path(self):
2250 def full_path(self):
2250 return self.group_name
2251 return self.group_name
2251
2252
2252 @property
2253 @property
2253 def full_path_splitted(self):
2254 def full_path_splitted(self):
2254 return self.group_name.split(RepoGroup.url_sep())
2255 return self.group_name.split(RepoGroup.url_sep())
2255
2256
2256 @property
2257 @property
2257 def repositories(self):
2258 def repositories(self):
2258 return Repository.query()\
2259 return Repository.query()\
2259 .filter(Repository.group == self)\
2260 .filter(Repository.group == self)\
2260 .order_by(Repository.repo_name)
2261 .order_by(Repository.repo_name)
2261
2262
2262 @property
2263 @property
2263 def repositories_recursive_count(self):
2264 def repositories_recursive_count(self):
2264 cnt = self.repositories.count()
2265 cnt = self.repositories.count()
2265
2266
2266 def children_count(group):
2267 def children_count(group):
2267 cnt = 0
2268 cnt = 0
2268 for child in group.children:
2269 for child in group.children:
2269 cnt += child.repositories.count()
2270 cnt += child.repositories.count()
2270 cnt += children_count(child)
2271 cnt += children_count(child)
2271 return cnt
2272 return cnt
2272
2273
2273 return cnt + children_count(self)
2274 return cnt + children_count(self)
2274
2275
2275 def _recursive_objects(self, include_repos=True):
2276 def _recursive_objects(self, include_repos=True):
2276 all_ = []
2277 all_ = []
2277
2278
2278 def _get_members(root_gr):
2279 def _get_members(root_gr):
2279 if include_repos:
2280 if include_repos:
2280 for r in root_gr.repositories:
2281 for r in root_gr.repositories:
2281 all_.append(r)
2282 all_.append(r)
2282 childs = root_gr.children.all()
2283 childs = root_gr.children.all()
2283 if childs:
2284 if childs:
2284 for gr in childs:
2285 for gr in childs:
2285 all_.append(gr)
2286 all_.append(gr)
2286 _get_members(gr)
2287 _get_members(gr)
2287
2288
2288 _get_members(self)
2289 _get_members(self)
2289 return [self] + all_
2290 return [self] + all_
2290
2291
2291 def recursive_groups_and_repos(self):
2292 def recursive_groups_and_repos(self):
2292 """
2293 """
2293 Recursive return all groups, with repositories in those groups
2294 Recursive return all groups, with repositories in those groups
2294 """
2295 """
2295 return self._recursive_objects()
2296 return self._recursive_objects()
2296
2297
2297 def recursive_groups(self):
2298 def recursive_groups(self):
2298 """
2299 """
2299 Returns all children groups for this group including children of children
2300 Returns all children groups for this group including children of children
2300 """
2301 """
2301 return self._recursive_objects(include_repos=False)
2302 return self._recursive_objects(include_repos=False)
2302
2303
2303 def get_new_name(self, group_name):
2304 def get_new_name(self, group_name):
2304 """
2305 """
2305 returns new full group name based on parent and new name
2306 returns new full group name based on parent and new name
2306
2307
2307 :param group_name:
2308 :param group_name:
2308 """
2309 """
2309 path_prefix = (self.parent_group.full_path_splitted if
2310 path_prefix = (self.parent_group.full_path_splitted if
2310 self.parent_group else [])
2311 self.parent_group else [])
2311 return RepoGroup.url_sep().join(path_prefix + [group_name])
2312 return RepoGroup.url_sep().join(path_prefix + [group_name])
2312
2313
2313 def permissions(self, with_admins=True, with_owner=True):
2314 def permissions(self, with_admins=True, with_owner=True):
2314 q = UserRepoGroupToPerm.query().filter(UserRepoGroupToPerm.group == self)
2315 q = UserRepoGroupToPerm.query().filter(UserRepoGroupToPerm.group == self)
2315 q = q.options(joinedload(UserRepoGroupToPerm.group),
2316 q = q.options(joinedload(UserRepoGroupToPerm.group),
2316 joinedload(UserRepoGroupToPerm.user),
2317 joinedload(UserRepoGroupToPerm.user),
2317 joinedload(UserRepoGroupToPerm.permission),)
2318 joinedload(UserRepoGroupToPerm.permission),)
2318
2319
2319 # get owners and admins and permissions. We do a trick of re-writing
2320 # get owners and admins and permissions. We do a trick of re-writing
2320 # objects from sqlalchemy to named-tuples due to sqlalchemy session
2321 # objects from sqlalchemy to named-tuples due to sqlalchemy session
2321 # has a global reference and changing one object propagates to all
2322 # has a global reference and changing one object propagates to all
2322 # others. This means if admin is also an owner admin_row that change
2323 # others. This means if admin is also an owner admin_row that change
2323 # would propagate to both objects
2324 # would propagate to both objects
2324 perm_rows = []
2325 perm_rows = []
2325 for _usr in q.all():
2326 for _usr in q.all():
2326 usr = AttributeDict(_usr.user.get_dict())
2327 usr = AttributeDict(_usr.user.get_dict())
2327 usr.permission = _usr.permission.permission_name
2328 usr.permission = _usr.permission.permission_name
2328 perm_rows.append(usr)
2329 perm_rows.append(usr)
2329
2330
2330 # filter the perm rows by 'default' first and then sort them by
2331 # filter the perm rows by 'default' first and then sort them by
2331 # admin,write,read,none permissions sorted again alphabetically in
2332 # admin,write,read,none permissions sorted again alphabetically in
2332 # each group
2333 # each group
2333 perm_rows = sorted(perm_rows, key=display_sort)
2334 perm_rows = sorted(perm_rows, key=display_sort)
2334
2335
2335 _admin_perm = 'group.admin'
2336 _admin_perm = 'group.admin'
2336 owner_row = []
2337 owner_row = []
2337 if with_owner:
2338 if with_owner:
2338 usr = AttributeDict(self.user.get_dict())
2339 usr = AttributeDict(self.user.get_dict())
2339 usr.owner_row = True
2340 usr.owner_row = True
2340 usr.permission = _admin_perm
2341 usr.permission = _admin_perm
2341 owner_row.append(usr)
2342 owner_row.append(usr)
2342
2343
2343 super_admin_rows = []
2344 super_admin_rows = []
2344 if with_admins:
2345 if with_admins:
2345 for usr in User.get_all_super_admins():
2346 for usr in User.get_all_super_admins():
2346 # if this admin is also owner, don't double the record
2347 # if this admin is also owner, don't double the record
2347 if usr.user_id == owner_row[0].user_id:
2348 if usr.user_id == owner_row[0].user_id:
2348 owner_row[0].admin_row = True
2349 owner_row[0].admin_row = True
2349 else:
2350 else:
2350 usr = AttributeDict(usr.get_dict())
2351 usr = AttributeDict(usr.get_dict())
2351 usr.admin_row = True
2352 usr.admin_row = True
2352 usr.permission = _admin_perm
2353 usr.permission = _admin_perm
2353 super_admin_rows.append(usr)
2354 super_admin_rows.append(usr)
2354
2355
2355 return super_admin_rows + owner_row + perm_rows
2356 return super_admin_rows + owner_row + perm_rows
2356
2357
2357 def permission_user_groups(self):
2358 def permission_user_groups(self):
2358 q = UserGroupRepoGroupToPerm.query().filter(UserGroupRepoGroupToPerm.group == self)
2359 q = UserGroupRepoGroupToPerm.query().filter(UserGroupRepoGroupToPerm.group == self)
2359 q = q.options(joinedload(UserGroupRepoGroupToPerm.group),
2360 q = q.options(joinedload(UserGroupRepoGroupToPerm.group),
2360 joinedload(UserGroupRepoGroupToPerm.users_group),
2361 joinedload(UserGroupRepoGroupToPerm.users_group),
2361 joinedload(UserGroupRepoGroupToPerm.permission),)
2362 joinedload(UserGroupRepoGroupToPerm.permission),)
2362
2363
2363 perm_rows = []
2364 perm_rows = []
2364 for _user_group in q.all():
2365 for _user_group in q.all():
2365 usr = AttributeDict(_user_group.users_group.get_dict())
2366 usr = AttributeDict(_user_group.users_group.get_dict())
2366 usr.permission = _user_group.permission.permission_name
2367 usr.permission = _user_group.permission.permission_name
2367 perm_rows.append(usr)
2368 perm_rows.append(usr)
2368
2369
2369 return perm_rows
2370 return perm_rows
2370
2371
2371 def get_api_data(self):
2372 def get_api_data(self):
2372 """
2373 """
2373 Common function for generating api data
2374 Common function for generating api data
2374
2375
2375 """
2376 """
2376 group = self
2377 group = self
2377 data = {
2378 data = {
2378 'group_id': group.group_id,
2379 'group_id': group.group_id,
2379 'group_name': group.group_name,
2380 'group_name': group.group_name,
2380 'group_description': group.group_description,
2381 'group_description': group.group_description,
2381 'parent_group': group.parent_group.group_name if group.parent_group else None,
2382 'parent_group': group.parent_group.group_name if group.parent_group else None,
2382 'repositories': [x.repo_name for x in group.repositories],
2383 'repositories': [x.repo_name for x in group.repositories],
2383 'owner': group.user.username,
2384 'owner': group.user.username,
2384 }
2385 }
2385 return data
2386 return data
2386
2387
2387
2388
2388 class Permission(Base, BaseModel):
2389 class Permission(Base, BaseModel):
2389 __tablename__ = 'permissions'
2390 __tablename__ = 'permissions'
2390 __table_args__ = (
2391 __table_args__ = (
2391 Index('p_perm_name_idx', 'permission_name'),
2392 Index('p_perm_name_idx', 'permission_name'),
2392 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2393 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2393 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
2394 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
2394 )
2395 )
2395 PERMS = [
2396 PERMS = [
2396 ('hg.admin', _('RhodeCode Super Administrator')),
2397 ('hg.admin', _('RhodeCode Super Administrator')),
2397
2398
2398 ('repository.none', _('Repository no access')),
2399 ('repository.none', _('Repository no access')),
2399 ('repository.read', _('Repository read access')),
2400 ('repository.read', _('Repository read access')),
2400 ('repository.write', _('Repository write access')),
2401 ('repository.write', _('Repository write access')),
2401 ('repository.admin', _('Repository admin access')),
2402 ('repository.admin', _('Repository admin access')),
2402
2403
2403 ('group.none', _('Repository group no access')),
2404 ('group.none', _('Repository group no access')),
2404 ('group.read', _('Repository group read access')),
2405 ('group.read', _('Repository group read access')),
2405 ('group.write', _('Repository group write access')),
2406 ('group.write', _('Repository group write access')),
2406 ('group.admin', _('Repository group admin access')),
2407 ('group.admin', _('Repository group admin access')),
2407
2408
2408 ('usergroup.none', _('User group no access')),
2409 ('usergroup.none', _('User group no access')),
2409 ('usergroup.read', _('User group read access')),
2410 ('usergroup.read', _('User group read access')),
2410 ('usergroup.write', _('User group write access')),
2411 ('usergroup.write', _('User group write access')),
2411 ('usergroup.admin', _('User group admin access')),
2412 ('usergroup.admin', _('User group admin access')),
2412
2413
2413 ('hg.repogroup.create.false', _('Repository Group creation disabled')),
2414 ('hg.repogroup.create.false', _('Repository Group creation disabled')),
2414 ('hg.repogroup.create.true', _('Repository Group creation enabled')),
2415 ('hg.repogroup.create.true', _('Repository Group creation enabled')),
2415
2416
2416 ('hg.usergroup.create.false', _('User Group creation disabled')),
2417 ('hg.usergroup.create.false', _('User Group creation disabled')),
2417 ('hg.usergroup.create.true', _('User Group creation enabled')),
2418 ('hg.usergroup.create.true', _('User Group creation enabled')),
2418
2419
2419 ('hg.create.none', _('Repository creation disabled')),
2420 ('hg.create.none', _('Repository creation disabled')),
2420 ('hg.create.repository', _('Repository creation enabled')),
2421 ('hg.create.repository', _('Repository creation enabled')),
2421 ('hg.create.write_on_repogroup.true', _('Repository creation enabled with write permission to a repository group')),
2422 ('hg.create.write_on_repogroup.true', _('Repository creation enabled with write permission to a repository group')),
2422 ('hg.create.write_on_repogroup.false', _('Repository creation disabled with write permission to a repository group')),
2423 ('hg.create.write_on_repogroup.false', _('Repository creation disabled with write permission to a repository group')),
2423
2424
2424 ('hg.fork.none', _('Repository forking disabled')),
2425 ('hg.fork.none', _('Repository forking disabled')),
2425 ('hg.fork.repository', _('Repository forking enabled')),
2426 ('hg.fork.repository', _('Repository forking enabled')),
2426
2427
2427 ('hg.register.none', _('Registration disabled')),
2428 ('hg.register.none', _('Registration disabled')),
2428 ('hg.register.manual_activate', _('User Registration with manual account activation')),
2429 ('hg.register.manual_activate', _('User Registration with manual account activation')),
2429 ('hg.register.auto_activate', _('User Registration with automatic account activation')),
2430 ('hg.register.auto_activate', _('User Registration with automatic account activation')),
2430
2431
2431 ('hg.password_reset.enabled', _('Password reset enabled')),
2432 ('hg.password_reset.enabled', _('Password reset enabled')),
2432 ('hg.password_reset.hidden', _('Password reset hidden')),
2433 ('hg.password_reset.hidden', _('Password reset hidden')),
2433 ('hg.password_reset.disabled', _('Password reset disabled')),
2434 ('hg.password_reset.disabled', _('Password reset disabled')),
2434
2435
2435 ('hg.extern_activate.manual', _('Manual activation of external account')),
2436 ('hg.extern_activate.manual', _('Manual activation of external account')),
2436 ('hg.extern_activate.auto', _('Automatic activation of external account')),
2437 ('hg.extern_activate.auto', _('Automatic activation of external account')),
2437
2438
2438 ('hg.inherit_default_perms.false', _('Inherit object permissions from default user disabled')),
2439 ('hg.inherit_default_perms.false', _('Inherit object permissions from default user disabled')),
2439 ('hg.inherit_default_perms.true', _('Inherit object permissions from default user enabled')),
2440 ('hg.inherit_default_perms.true', _('Inherit object permissions from default user enabled')),
2440 ]
2441 ]
2441
2442
2442 # definition of system default permissions for DEFAULT user
2443 # definition of system default permissions for DEFAULT user
2443 DEFAULT_USER_PERMISSIONS = [
2444 DEFAULT_USER_PERMISSIONS = [
2444 'repository.read',
2445 'repository.read',
2445 'group.read',
2446 'group.read',
2446 'usergroup.read',
2447 'usergroup.read',
2447 'hg.create.repository',
2448 'hg.create.repository',
2448 'hg.repogroup.create.false',
2449 'hg.repogroup.create.false',
2449 'hg.usergroup.create.false',
2450 'hg.usergroup.create.false',
2450 'hg.create.write_on_repogroup.true',
2451 'hg.create.write_on_repogroup.true',
2451 'hg.fork.repository',
2452 'hg.fork.repository',
2452 'hg.register.manual_activate',
2453 'hg.register.manual_activate',
2453 'hg.password_reset.enabled',
2454 'hg.password_reset.enabled',
2454 'hg.extern_activate.auto',
2455 'hg.extern_activate.auto',
2455 'hg.inherit_default_perms.true',
2456 'hg.inherit_default_perms.true',
2456 ]
2457 ]
2457
2458
2458 # defines which permissions are more important higher the more important
2459 # defines which permissions are more important higher the more important
2459 # Weight defines which permissions are more important.
2460 # Weight defines which permissions are more important.
2460 # The higher number the more important.
2461 # The higher number the more important.
2461 PERM_WEIGHTS = {
2462 PERM_WEIGHTS = {
2462 'repository.none': 0,
2463 'repository.none': 0,
2463 'repository.read': 1,
2464 'repository.read': 1,
2464 'repository.write': 3,
2465 'repository.write': 3,
2465 'repository.admin': 4,
2466 'repository.admin': 4,
2466
2467
2467 'group.none': 0,
2468 'group.none': 0,
2468 'group.read': 1,
2469 'group.read': 1,
2469 'group.write': 3,
2470 'group.write': 3,
2470 'group.admin': 4,
2471 'group.admin': 4,
2471
2472
2472 'usergroup.none': 0,
2473 'usergroup.none': 0,
2473 'usergroup.read': 1,
2474 'usergroup.read': 1,
2474 'usergroup.write': 3,
2475 'usergroup.write': 3,
2475 'usergroup.admin': 4,
2476 'usergroup.admin': 4,
2476
2477
2477 'hg.repogroup.create.false': 0,
2478 'hg.repogroup.create.false': 0,
2478 'hg.repogroup.create.true': 1,
2479 'hg.repogroup.create.true': 1,
2479
2480
2480 'hg.usergroup.create.false': 0,
2481 'hg.usergroup.create.false': 0,
2481 'hg.usergroup.create.true': 1,
2482 'hg.usergroup.create.true': 1,
2482
2483
2483 'hg.fork.none': 0,
2484 'hg.fork.none': 0,
2484 'hg.fork.repository': 1,
2485 'hg.fork.repository': 1,
2485 'hg.create.none': 0,
2486 'hg.create.none': 0,
2486 'hg.create.repository': 1
2487 'hg.create.repository': 1
2487 }
2488 }
2488
2489
2489 permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2490 permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2490 permission_name = Column("permission_name", String(255), nullable=True, unique=None, default=None)
2491 permission_name = Column("permission_name", String(255), nullable=True, unique=None, default=None)
2491 permission_longname = Column("permission_longname", String(255), nullable=True, unique=None, default=None)
2492 permission_longname = Column("permission_longname", String(255), nullable=True, unique=None, default=None)
2492
2493
2493 def __unicode__(self):
2494 def __unicode__(self):
2494 return u"<%s('%s:%s')>" % (
2495 return u"<%s('%s:%s')>" % (
2495 self.__class__.__name__, self.permission_id, self.permission_name
2496 self.__class__.__name__, self.permission_id, self.permission_name
2496 )
2497 )
2497
2498
2498 @classmethod
2499 @classmethod
2499 def get_by_key(cls, key):
2500 def get_by_key(cls, key):
2500 return cls.query().filter(cls.permission_name == key).scalar()
2501 return cls.query().filter(cls.permission_name == key).scalar()
2501
2502
2502 @classmethod
2503 @classmethod
2503 def get_default_repo_perms(cls, user_id, repo_id=None):
2504 def get_default_repo_perms(cls, user_id, repo_id=None):
2504 q = Session().query(UserRepoToPerm, Repository, Permission)\
2505 q = Session().query(UserRepoToPerm, Repository, Permission)\
2505 .join((Permission, UserRepoToPerm.permission_id == Permission.permission_id))\
2506 .join((Permission, UserRepoToPerm.permission_id == Permission.permission_id))\
2506 .join((Repository, UserRepoToPerm.repository_id == Repository.repo_id))\
2507 .join((Repository, UserRepoToPerm.repository_id == Repository.repo_id))\
2507 .filter(UserRepoToPerm.user_id == user_id)
2508 .filter(UserRepoToPerm.user_id == user_id)
2508 if repo_id:
2509 if repo_id:
2509 q = q.filter(UserRepoToPerm.repository_id == repo_id)
2510 q = q.filter(UserRepoToPerm.repository_id == repo_id)
2510 return q.all()
2511 return q.all()
2511
2512
2512 @classmethod
2513 @classmethod
2513 def get_default_repo_perms_from_user_group(cls, user_id, repo_id=None):
2514 def get_default_repo_perms_from_user_group(cls, user_id, repo_id=None):
2514 q = Session().query(UserGroupRepoToPerm, Repository, Permission)\
2515 q = Session().query(UserGroupRepoToPerm, Repository, Permission)\
2515 .join(
2516 .join(
2516 Permission,
2517 Permission,
2517 UserGroupRepoToPerm.permission_id == Permission.permission_id)\
2518 UserGroupRepoToPerm.permission_id == Permission.permission_id)\
2518 .join(
2519 .join(
2519 Repository,
2520 Repository,
2520 UserGroupRepoToPerm.repository_id == Repository.repo_id)\
2521 UserGroupRepoToPerm.repository_id == Repository.repo_id)\
2521 .join(
2522 .join(
2522 UserGroup,
2523 UserGroup,
2523 UserGroupRepoToPerm.users_group_id ==
2524 UserGroupRepoToPerm.users_group_id ==
2524 UserGroup.users_group_id)\
2525 UserGroup.users_group_id)\
2525 .join(
2526 .join(
2526 UserGroupMember,
2527 UserGroupMember,
2527 UserGroupRepoToPerm.users_group_id ==
2528 UserGroupRepoToPerm.users_group_id ==
2528 UserGroupMember.users_group_id)\
2529 UserGroupMember.users_group_id)\
2529 .filter(
2530 .filter(
2530 UserGroupMember.user_id == user_id,
2531 UserGroupMember.user_id == user_id,
2531 UserGroup.users_group_active == true())
2532 UserGroup.users_group_active == true())
2532 if repo_id:
2533 if repo_id:
2533 q = q.filter(UserGroupRepoToPerm.repository_id == repo_id)
2534 q = q.filter(UserGroupRepoToPerm.repository_id == repo_id)
2534 return q.all()
2535 return q.all()
2535
2536
2536 @classmethod
2537 @classmethod
2537 def get_default_group_perms(cls, user_id, repo_group_id=None):
2538 def get_default_group_perms(cls, user_id, repo_group_id=None):
2538 q = Session().query(UserRepoGroupToPerm, RepoGroup, Permission)\
2539 q = Session().query(UserRepoGroupToPerm, RepoGroup, Permission)\
2539 .join((Permission, UserRepoGroupToPerm.permission_id == Permission.permission_id))\
2540 .join((Permission, UserRepoGroupToPerm.permission_id == Permission.permission_id))\
2540 .join((RepoGroup, UserRepoGroupToPerm.group_id == RepoGroup.group_id))\
2541 .join((RepoGroup, UserRepoGroupToPerm.group_id == RepoGroup.group_id))\
2541 .filter(UserRepoGroupToPerm.user_id == user_id)
2542 .filter(UserRepoGroupToPerm.user_id == user_id)
2542 if repo_group_id:
2543 if repo_group_id:
2543 q = q.filter(UserRepoGroupToPerm.group_id == repo_group_id)
2544 q = q.filter(UserRepoGroupToPerm.group_id == repo_group_id)
2544 return q.all()
2545 return q.all()
2545
2546
2546 @classmethod
2547 @classmethod
2547 def get_default_group_perms_from_user_group(
2548 def get_default_group_perms_from_user_group(
2548 cls, user_id, repo_group_id=None):
2549 cls, user_id, repo_group_id=None):
2549 q = Session().query(UserGroupRepoGroupToPerm, RepoGroup, Permission)\
2550 q = Session().query(UserGroupRepoGroupToPerm, RepoGroup, Permission)\
2550 .join(
2551 .join(
2551 Permission,
2552 Permission,
2552 UserGroupRepoGroupToPerm.permission_id ==
2553 UserGroupRepoGroupToPerm.permission_id ==
2553 Permission.permission_id)\
2554 Permission.permission_id)\
2554 .join(
2555 .join(
2555 RepoGroup,
2556 RepoGroup,
2556 UserGroupRepoGroupToPerm.group_id == RepoGroup.group_id)\
2557 UserGroupRepoGroupToPerm.group_id == RepoGroup.group_id)\
2557 .join(
2558 .join(
2558 UserGroup,
2559 UserGroup,
2559 UserGroupRepoGroupToPerm.users_group_id ==
2560 UserGroupRepoGroupToPerm.users_group_id ==
2560 UserGroup.users_group_id)\
2561 UserGroup.users_group_id)\
2561 .join(
2562 .join(
2562 UserGroupMember,
2563 UserGroupMember,
2563 UserGroupRepoGroupToPerm.users_group_id ==
2564 UserGroupRepoGroupToPerm.users_group_id ==
2564 UserGroupMember.users_group_id)\
2565 UserGroupMember.users_group_id)\
2565 .filter(
2566 .filter(
2566 UserGroupMember.user_id == user_id,
2567 UserGroupMember.user_id == user_id,
2567 UserGroup.users_group_active == true())
2568 UserGroup.users_group_active == true())
2568 if repo_group_id:
2569 if repo_group_id:
2569 q = q.filter(UserGroupRepoGroupToPerm.group_id == repo_group_id)
2570 q = q.filter(UserGroupRepoGroupToPerm.group_id == repo_group_id)
2570 return q.all()
2571 return q.all()
2571
2572
2572 @classmethod
2573 @classmethod
2573 def get_default_user_group_perms(cls, user_id, user_group_id=None):
2574 def get_default_user_group_perms(cls, user_id, user_group_id=None):
2574 q = Session().query(UserUserGroupToPerm, UserGroup, Permission)\
2575 q = Session().query(UserUserGroupToPerm, UserGroup, Permission)\
2575 .join((Permission, UserUserGroupToPerm.permission_id == Permission.permission_id))\
2576 .join((Permission, UserUserGroupToPerm.permission_id == Permission.permission_id))\
2576 .join((UserGroup, UserUserGroupToPerm.user_group_id == UserGroup.users_group_id))\
2577 .join((UserGroup, UserUserGroupToPerm.user_group_id == UserGroup.users_group_id))\
2577 .filter(UserUserGroupToPerm.user_id == user_id)
2578 .filter(UserUserGroupToPerm.user_id == user_id)
2578 if user_group_id:
2579 if user_group_id:
2579 q = q.filter(UserUserGroupToPerm.user_group_id == user_group_id)
2580 q = q.filter(UserUserGroupToPerm.user_group_id == user_group_id)
2580 return q.all()
2581 return q.all()
2581
2582
2582 @classmethod
2583 @classmethod
2583 def get_default_user_group_perms_from_user_group(
2584 def get_default_user_group_perms_from_user_group(
2584 cls, user_id, user_group_id=None):
2585 cls, user_id, user_group_id=None):
2585 TargetUserGroup = aliased(UserGroup, name='target_user_group')
2586 TargetUserGroup = aliased(UserGroup, name='target_user_group')
2586 q = Session().query(UserGroupUserGroupToPerm, UserGroup, Permission)\
2587 q = Session().query(UserGroupUserGroupToPerm, UserGroup, Permission)\
2587 .join(
2588 .join(
2588 Permission,
2589 Permission,
2589 UserGroupUserGroupToPerm.permission_id ==
2590 UserGroupUserGroupToPerm.permission_id ==
2590 Permission.permission_id)\
2591 Permission.permission_id)\
2591 .join(
2592 .join(
2592 TargetUserGroup,
2593 TargetUserGroup,
2593 UserGroupUserGroupToPerm.target_user_group_id ==
2594 UserGroupUserGroupToPerm.target_user_group_id ==
2594 TargetUserGroup.users_group_id)\
2595 TargetUserGroup.users_group_id)\
2595 .join(
2596 .join(
2596 UserGroup,
2597 UserGroup,
2597 UserGroupUserGroupToPerm.user_group_id ==
2598 UserGroupUserGroupToPerm.user_group_id ==
2598 UserGroup.users_group_id)\
2599 UserGroup.users_group_id)\
2599 .join(
2600 .join(
2600 UserGroupMember,
2601 UserGroupMember,
2601 UserGroupUserGroupToPerm.user_group_id ==
2602 UserGroupUserGroupToPerm.user_group_id ==
2602 UserGroupMember.users_group_id)\
2603 UserGroupMember.users_group_id)\
2603 .filter(
2604 .filter(
2604 UserGroupMember.user_id == user_id,
2605 UserGroupMember.user_id == user_id,
2605 UserGroup.users_group_active == true())
2606 UserGroup.users_group_active == true())
2606 if user_group_id:
2607 if user_group_id:
2607 q = q.filter(
2608 q = q.filter(
2608 UserGroupUserGroupToPerm.user_group_id == user_group_id)
2609 UserGroupUserGroupToPerm.user_group_id == user_group_id)
2609
2610
2610 return q.all()
2611 return q.all()
2611
2612
2612
2613
2613 class UserRepoToPerm(Base, BaseModel):
2614 class UserRepoToPerm(Base, BaseModel):
2614 __tablename__ = 'repo_to_perm'
2615 __tablename__ = 'repo_to_perm'
2615 __table_args__ = (
2616 __table_args__ = (
2616 UniqueConstraint('user_id', 'repository_id', 'permission_id'),
2617 UniqueConstraint('user_id', 'repository_id', 'permission_id'),
2617 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2618 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2618 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2619 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2619 )
2620 )
2620 repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2621 repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2621 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2622 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2622 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2623 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2623 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
2624 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
2624
2625
2625 user = relationship('User')
2626 user = relationship('User')
2626 repository = relationship('Repository')
2627 repository = relationship('Repository')
2627 permission = relationship('Permission')
2628 permission = relationship('Permission')
2628
2629
2629 @classmethod
2630 @classmethod
2630 def create(cls, user, repository, permission):
2631 def create(cls, user, repository, permission):
2631 n = cls()
2632 n = cls()
2632 n.user = user
2633 n.user = user
2633 n.repository = repository
2634 n.repository = repository
2634 n.permission = permission
2635 n.permission = permission
2635 Session().add(n)
2636 Session().add(n)
2636 return n
2637 return n
2637
2638
2638 def __unicode__(self):
2639 def __unicode__(self):
2639 return u'<%s => %s >' % (self.user, self.repository)
2640 return u'<%s => %s >' % (self.user, self.repository)
2640
2641
2641
2642
2642 class UserUserGroupToPerm(Base, BaseModel):
2643 class UserUserGroupToPerm(Base, BaseModel):
2643 __tablename__ = 'user_user_group_to_perm'
2644 __tablename__ = 'user_user_group_to_perm'
2644 __table_args__ = (
2645 __table_args__ = (
2645 UniqueConstraint('user_id', 'user_group_id', 'permission_id'),
2646 UniqueConstraint('user_id', 'user_group_id', 'permission_id'),
2646 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2647 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2647 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2648 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2648 )
2649 )
2649 user_user_group_to_perm_id = Column("user_user_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2650 user_user_group_to_perm_id = Column("user_user_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2650 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2651 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2651 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2652 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2652 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2653 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2653
2654
2654 user = relationship('User')
2655 user = relationship('User')
2655 user_group = relationship('UserGroup')
2656 user_group = relationship('UserGroup')
2656 permission = relationship('Permission')
2657 permission = relationship('Permission')
2657
2658
2658 @classmethod
2659 @classmethod
2659 def create(cls, user, user_group, permission):
2660 def create(cls, user, user_group, permission):
2660 n = cls()
2661 n = cls()
2661 n.user = user
2662 n.user = user
2662 n.user_group = user_group
2663 n.user_group = user_group
2663 n.permission = permission
2664 n.permission = permission
2664 Session().add(n)
2665 Session().add(n)
2665 return n
2666 return n
2666
2667
2667 def __unicode__(self):
2668 def __unicode__(self):
2668 return u'<%s => %s >' % (self.user, self.user_group)
2669 return u'<%s => %s >' % (self.user, self.user_group)
2669
2670
2670
2671
2671 class UserToPerm(Base, BaseModel):
2672 class UserToPerm(Base, BaseModel):
2672 __tablename__ = 'user_to_perm'
2673 __tablename__ = 'user_to_perm'
2673 __table_args__ = (
2674 __table_args__ = (
2674 UniqueConstraint('user_id', 'permission_id'),
2675 UniqueConstraint('user_id', 'permission_id'),
2675 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2676 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2676 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2677 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2677 )
2678 )
2678 user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2679 user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2679 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2680 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2680 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2681 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2681
2682
2682 user = relationship('User')
2683 user = relationship('User')
2683 permission = relationship('Permission', lazy='joined')
2684 permission = relationship('Permission', lazy='joined')
2684
2685
2685 def __unicode__(self):
2686 def __unicode__(self):
2686 return u'<%s => %s >' % (self.user, self.permission)
2687 return u'<%s => %s >' % (self.user, self.permission)
2687
2688
2688
2689
2689 class UserGroupRepoToPerm(Base, BaseModel):
2690 class UserGroupRepoToPerm(Base, BaseModel):
2690 __tablename__ = 'users_group_repo_to_perm'
2691 __tablename__ = 'users_group_repo_to_perm'
2691 __table_args__ = (
2692 __table_args__ = (
2692 UniqueConstraint('repository_id', 'users_group_id', 'permission_id'),
2693 UniqueConstraint('repository_id', 'users_group_id', 'permission_id'),
2693 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2694 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2694 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2695 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2695 )
2696 )
2696 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2697 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2697 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2698 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2698 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2699 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2699 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
2700 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
2700
2701
2701 users_group = relationship('UserGroup')
2702 users_group = relationship('UserGroup')
2702 permission = relationship('Permission')
2703 permission = relationship('Permission')
2703 repository = relationship('Repository')
2704 repository = relationship('Repository')
2704
2705
2705 @classmethod
2706 @classmethod
2706 def create(cls, users_group, repository, permission):
2707 def create(cls, users_group, repository, permission):
2707 n = cls()
2708 n = cls()
2708 n.users_group = users_group
2709 n.users_group = users_group
2709 n.repository = repository
2710 n.repository = repository
2710 n.permission = permission
2711 n.permission = permission
2711 Session().add(n)
2712 Session().add(n)
2712 return n
2713 return n
2713
2714
2714 def __unicode__(self):
2715 def __unicode__(self):
2715 return u'<UserGroupRepoToPerm:%s => %s >' % (self.users_group, self.repository)
2716 return u'<UserGroupRepoToPerm:%s => %s >' % (self.users_group, self.repository)
2716
2717
2717
2718
2718 class UserGroupUserGroupToPerm(Base, BaseModel):
2719 class UserGroupUserGroupToPerm(Base, BaseModel):
2719 __tablename__ = 'user_group_user_group_to_perm'
2720 __tablename__ = 'user_group_user_group_to_perm'
2720 __table_args__ = (
2721 __table_args__ = (
2721 UniqueConstraint('target_user_group_id', 'user_group_id', 'permission_id'),
2722 UniqueConstraint('target_user_group_id', 'user_group_id', 'permission_id'),
2722 CheckConstraint('target_user_group_id != user_group_id'),
2723 CheckConstraint('target_user_group_id != user_group_id'),
2723 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2724 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2724 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2725 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2725 )
2726 )
2726 user_group_user_group_to_perm_id = Column("user_group_user_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2727 user_group_user_group_to_perm_id = Column("user_group_user_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2727 target_user_group_id = Column("target_user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2728 target_user_group_id = Column("target_user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2728 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2729 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2729 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2730 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2730
2731
2731 target_user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id')
2732 target_user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id')
2732 user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.user_group_id==UserGroup.users_group_id')
2733 user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.user_group_id==UserGroup.users_group_id')
2733 permission = relationship('Permission')
2734 permission = relationship('Permission')
2734
2735
2735 @classmethod
2736 @classmethod
2736 def create(cls, target_user_group, user_group, permission):
2737 def create(cls, target_user_group, user_group, permission):
2737 n = cls()
2738 n = cls()
2738 n.target_user_group = target_user_group
2739 n.target_user_group = target_user_group
2739 n.user_group = user_group
2740 n.user_group = user_group
2740 n.permission = permission
2741 n.permission = permission
2741 Session().add(n)
2742 Session().add(n)
2742 return n
2743 return n
2743
2744
2744 def __unicode__(self):
2745 def __unicode__(self):
2745 return u'<UserGroupUserGroup:%s => %s >' % (self.target_user_group, self.user_group)
2746 return u'<UserGroupUserGroup:%s => %s >' % (self.target_user_group, self.user_group)
2746
2747
2747
2748
2748 class UserGroupToPerm(Base, BaseModel):
2749 class UserGroupToPerm(Base, BaseModel):
2749 __tablename__ = 'users_group_to_perm'
2750 __tablename__ = 'users_group_to_perm'
2750 __table_args__ = (
2751 __table_args__ = (
2751 UniqueConstraint('users_group_id', 'permission_id',),
2752 UniqueConstraint('users_group_id', 'permission_id',),
2752 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2753 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2753 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2754 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2754 )
2755 )
2755 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2756 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2756 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2757 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2757 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2758 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2758
2759
2759 users_group = relationship('UserGroup')
2760 users_group = relationship('UserGroup')
2760 permission = relationship('Permission')
2761 permission = relationship('Permission')
2761
2762
2762
2763
2763 class UserRepoGroupToPerm(Base, BaseModel):
2764 class UserRepoGroupToPerm(Base, BaseModel):
2764 __tablename__ = 'user_repo_group_to_perm'
2765 __tablename__ = 'user_repo_group_to_perm'
2765 __table_args__ = (
2766 __table_args__ = (
2766 UniqueConstraint('user_id', 'group_id', 'permission_id'),
2767 UniqueConstraint('user_id', 'group_id', 'permission_id'),
2767 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2768 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2768 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2769 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2769 )
2770 )
2770
2771
2771 group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2772 group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2772 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2773 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2773 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
2774 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
2774 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2775 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2775
2776
2776 user = relationship('User')
2777 user = relationship('User')
2777 group = relationship('RepoGroup')
2778 group = relationship('RepoGroup')
2778 permission = relationship('Permission')
2779 permission = relationship('Permission')
2779
2780
2780 @classmethod
2781 @classmethod
2781 def create(cls, user, repository_group, permission):
2782 def create(cls, user, repository_group, permission):
2782 n = cls()
2783 n = cls()
2783 n.user = user
2784 n.user = user
2784 n.group = repository_group
2785 n.group = repository_group
2785 n.permission = permission
2786 n.permission = permission
2786 Session().add(n)
2787 Session().add(n)
2787 return n
2788 return n
2788
2789
2789
2790
2790 class UserGroupRepoGroupToPerm(Base, BaseModel):
2791 class UserGroupRepoGroupToPerm(Base, BaseModel):
2791 __tablename__ = 'users_group_repo_group_to_perm'
2792 __tablename__ = 'users_group_repo_group_to_perm'
2792 __table_args__ = (
2793 __table_args__ = (
2793 UniqueConstraint('users_group_id', 'group_id'),
2794 UniqueConstraint('users_group_id', 'group_id'),
2794 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2795 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2795 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2796 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2796 )
2797 )
2797
2798
2798 users_group_repo_group_to_perm_id = Column("users_group_repo_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2799 users_group_repo_group_to_perm_id = Column("users_group_repo_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2799 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2800 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2800 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
2801 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
2801 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2802 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2802
2803
2803 users_group = relationship('UserGroup')
2804 users_group = relationship('UserGroup')
2804 permission = relationship('Permission')
2805 permission = relationship('Permission')
2805 group = relationship('RepoGroup')
2806 group = relationship('RepoGroup')
2806
2807
2807 @classmethod
2808 @classmethod
2808 def create(cls, user_group, repository_group, permission):
2809 def create(cls, user_group, repository_group, permission):
2809 n = cls()
2810 n = cls()
2810 n.users_group = user_group
2811 n.users_group = user_group
2811 n.group = repository_group
2812 n.group = repository_group
2812 n.permission = permission
2813 n.permission = permission
2813 Session().add(n)
2814 Session().add(n)
2814 return n
2815 return n
2815
2816
2816 def __unicode__(self):
2817 def __unicode__(self):
2817 return u'<UserGroupRepoGroupToPerm:%s => %s >' % (self.users_group, self.group)
2818 return u'<UserGroupRepoGroupToPerm:%s => %s >' % (self.users_group, self.group)
2818
2819
2819
2820
2820 class Statistics(Base, BaseModel):
2821 class Statistics(Base, BaseModel):
2821 __tablename__ = 'statistics'
2822 __tablename__ = 'statistics'
2822 __table_args__ = (
2823 __table_args__ = (
2823 UniqueConstraint('repository_id'),
2824 UniqueConstraint('repository_id'),
2824 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2825 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2825 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2826 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2826 )
2827 )
2827 stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2828 stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2828 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
2829 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
2829 stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
2830 stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
2830 commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data
2831 commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data
2831 commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data
2832 commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data
2832 languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data
2833 languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data
2833
2834
2834 repository = relationship('Repository', single_parent=True)
2835 repository = relationship('Repository', single_parent=True)
2835
2836
2836
2837
2837 class UserFollowing(Base, BaseModel):
2838 class UserFollowing(Base, BaseModel):
2838 __tablename__ = 'user_followings'
2839 __tablename__ = 'user_followings'
2839 __table_args__ = (
2840 __table_args__ = (
2840 UniqueConstraint('user_id', 'follows_repository_id'),
2841 UniqueConstraint('user_id', 'follows_repository_id'),
2841 UniqueConstraint('user_id', 'follows_user_id'),
2842 UniqueConstraint('user_id', 'follows_user_id'),
2842 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2843 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2843 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2844 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2844 )
2845 )
2845
2846
2846 user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2847 user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2847 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2848 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2848 follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
2849 follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
2849 follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
2850 follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
2850 follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
2851 follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
2851
2852
2852 user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
2853 user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
2853
2854
2854 follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
2855 follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
2855 follows_repository = relationship('Repository', order_by='Repository.repo_name')
2856 follows_repository = relationship('Repository', order_by='Repository.repo_name')
2856
2857
2857 @classmethod
2858 @classmethod
2858 def get_repo_followers(cls, repo_id):
2859 def get_repo_followers(cls, repo_id):
2859 return cls.query().filter(cls.follows_repo_id == repo_id)
2860 return cls.query().filter(cls.follows_repo_id == repo_id)
2860
2861
2861
2862
2862 class CacheKey(Base, BaseModel):
2863 class CacheKey(Base, BaseModel):
2863 __tablename__ = 'cache_invalidation'
2864 __tablename__ = 'cache_invalidation'
2864 __table_args__ = (
2865 __table_args__ = (
2865 UniqueConstraint('cache_key'),
2866 UniqueConstraint('cache_key'),
2866 Index('key_idx', 'cache_key'),
2867 Index('key_idx', 'cache_key'),
2867 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2868 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2868 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
2869 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
2869 )
2870 )
2870 CACHE_TYPE_ATOM = 'ATOM'
2871 CACHE_TYPE_ATOM = 'ATOM'
2871 CACHE_TYPE_RSS = 'RSS'
2872 CACHE_TYPE_RSS = 'RSS'
2872 CACHE_TYPE_README = 'README'
2873 CACHE_TYPE_README = 'README'
2873
2874
2874 cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2875 cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2875 cache_key = Column("cache_key", String(255), nullable=True, unique=None, default=None)
2876 cache_key = Column("cache_key", String(255), nullable=True, unique=None, default=None)
2876 cache_args = Column("cache_args", String(255), nullable=True, unique=None, default=None)
2877 cache_args = Column("cache_args", String(255), nullable=True, unique=None, default=None)
2877 cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
2878 cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
2878
2879
2879 def __init__(self, cache_key, cache_args=''):
2880 def __init__(self, cache_key, cache_args=''):
2880 self.cache_key = cache_key
2881 self.cache_key = cache_key
2881 self.cache_args = cache_args
2882 self.cache_args = cache_args
2882 self.cache_active = False
2883 self.cache_active = False
2883
2884
2884 def __unicode__(self):
2885 def __unicode__(self):
2885 return u"<%s('%s:%s[%s]')>" % (
2886 return u"<%s('%s:%s[%s]')>" % (
2886 self.__class__.__name__,
2887 self.__class__.__name__,
2887 self.cache_id, self.cache_key, self.cache_active)
2888 self.cache_id, self.cache_key, self.cache_active)
2888
2889
2889 def _cache_key_partition(self):
2890 def _cache_key_partition(self):
2890 prefix, repo_name, suffix = self.cache_key.partition(self.cache_args)
2891 prefix, repo_name, suffix = self.cache_key.partition(self.cache_args)
2891 return prefix, repo_name, suffix
2892 return prefix, repo_name, suffix
2892
2893
2893 def get_prefix(self):
2894 def get_prefix(self):
2894 """
2895 """
2895 Try to extract prefix from existing cache key. The key could consist
2896 Try to extract prefix from existing cache key. The key could consist
2896 of prefix, repo_name, suffix
2897 of prefix, repo_name, suffix
2897 """
2898 """
2898 # this returns prefix, repo_name, suffix
2899 # this returns prefix, repo_name, suffix
2899 return self._cache_key_partition()[0]
2900 return self._cache_key_partition()[0]
2900
2901
2901 def get_suffix(self):
2902 def get_suffix(self):
2902 """
2903 """
2903 get suffix that might have been used in _get_cache_key to
2904 get suffix that might have been used in _get_cache_key to
2904 generate self.cache_key. Only used for informational purposes
2905 generate self.cache_key. Only used for informational purposes
2905 in repo_edit.mako.
2906 in repo_edit.mako.
2906 """
2907 """
2907 # prefix, repo_name, suffix
2908 # prefix, repo_name, suffix
2908 return self._cache_key_partition()[2]
2909 return self._cache_key_partition()[2]
2909
2910
2910 @classmethod
2911 @classmethod
2911 def delete_all_cache(cls):
2912 def delete_all_cache(cls):
2912 """
2913 """
2913 Delete all cache keys from database.
2914 Delete all cache keys from database.
2914 Should only be run when all instances are down and all entries
2915 Should only be run when all instances are down and all entries
2915 thus stale.
2916 thus stale.
2916 """
2917 """
2917 cls.query().delete()
2918 cls.query().delete()
2918 Session().commit()
2919 Session().commit()
2919
2920
2920 @classmethod
2921 @classmethod
2921 def get_cache_key(cls, repo_name, cache_type):
2922 def get_cache_key(cls, repo_name, cache_type):
2922 """
2923 """
2923
2924
2924 Generate a cache key for this process of RhodeCode instance.
2925 Generate a cache key for this process of RhodeCode instance.
2925 Prefix most likely will be process id or maybe explicitly set
2926 Prefix most likely will be process id or maybe explicitly set
2926 instance_id from .ini file.
2927 instance_id from .ini file.
2927 """
2928 """
2928 import rhodecode
2929 import rhodecode
2929 prefix = safe_unicode(rhodecode.CONFIG.get('instance_id') or '')
2930 prefix = safe_unicode(rhodecode.CONFIG.get('instance_id') or '')
2930
2931
2931 repo_as_unicode = safe_unicode(repo_name)
2932 repo_as_unicode = safe_unicode(repo_name)
2932 key = u'{}_{}'.format(repo_as_unicode, cache_type) \
2933 key = u'{}_{}'.format(repo_as_unicode, cache_type) \
2933 if cache_type else repo_as_unicode
2934 if cache_type else repo_as_unicode
2934
2935
2935 return u'{}{}'.format(prefix, key)
2936 return u'{}{}'.format(prefix, key)
2936
2937
2937 @classmethod
2938 @classmethod
2938 def set_invalidate(cls, repo_name, delete=False):
2939 def set_invalidate(cls, repo_name, delete=False):
2939 """
2940 """
2940 Mark all caches of a repo as invalid in the database.
2941 Mark all caches of a repo as invalid in the database.
2941 """
2942 """
2942
2943
2943 try:
2944 try:
2944 qry = Session().query(cls).filter(cls.cache_args == repo_name)
2945 qry = Session().query(cls).filter(cls.cache_args == repo_name)
2945 if delete:
2946 if delete:
2946 log.debug('cache objects deleted for repo %s',
2947 log.debug('cache objects deleted for repo %s',
2947 safe_str(repo_name))
2948 safe_str(repo_name))
2948 qry.delete()
2949 qry.delete()
2949 else:
2950 else:
2950 log.debug('cache objects marked as invalid for repo %s',
2951 log.debug('cache objects marked as invalid for repo %s',
2951 safe_str(repo_name))
2952 safe_str(repo_name))
2952 qry.update({"cache_active": False})
2953 qry.update({"cache_active": False})
2953
2954
2954 Session().commit()
2955 Session().commit()
2955 except Exception:
2956 except Exception:
2956 log.exception(
2957 log.exception(
2957 'Cache key invalidation failed for repository %s',
2958 'Cache key invalidation failed for repository %s',
2958 safe_str(repo_name))
2959 safe_str(repo_name))
2959 Session().rollback()
2960 Session().rollback()
2960
2961
2961 @classmethod
2962 @classmethod
2962 def get_active_cache(cls, cache_key):
2963 def get_active_cache(cls, cache_key):
2963 inv_obj = cls.query().filter(cls.cache_key == cache_key).scalar()
2964 inv_obj = cls.query().filter(cls.cache_key == cache_key).scalar()
2964 if inv_obj:
2965 if inv_obj:
2965 return inv_obj
2966 return inv_obj
2966 return None
2967 return None
2967
2968
2968 @classmethod
2969 @classmethod
2969 def repo_context_cache(cls, compute_func, repo_name, cache_type,
2970 def repo_context_cache(cls, compute_func, repo_name, cache_type,
2970 thread_scoped=False):
2971 thread_scoped=False):
2971 """
2972 """
2972 @cache_region('long_term')
2973 @cache_region('long_term')
2973 def _heavy_calculation(cache_key):
2974 def _heavy_calculation(cache_key):
2974 return 'result'
2975 return 'result'
2975
2976
2976 cache_context = CacheKey.repo_context_cache(
2977 cache_context = CacheKey.repo_context_cache(
2977 _heavy_calculation, repo_name, cache_type)
2978 _heavy_calculation, repo_name, cache_type)
2978
2979
2979 with cache_context as context:
2980 with cache_context as context:
2980 context.invalidate()
2981 context.invalidate()
2981 computed = context.compute()
2982 computed = context.compute()
2982
2983
2983 assert computed == 'result'
2984 assert computed == 'result'
2984 """
2985 """
2985 from rhodecode.lib import caches
2986 from rhodecode.lib import caches
2986 return caches.InvalidationContext(
2987 return caches.InvalidationContext(
2987 compute_func, repo_name, cache_type, thread_scoped=thread_scoped)
2988 compute_func, repo_name, cache_type, thread_scoped=thread_scoped)
2988
2989
2989
2990
2990 class ChangesetComment(Base, BaseModel):
2991 class ChangesetComment(Base, BaseModel):
2991 __tablename__ = 'changeset_comments'
2992 __tablename__ = 'changeset_comments'
2992 __table_args__ = (
2993 __table_args__ = (
2993 Index('cc_revision_idx', 'revision'),
2994 Index('cc_revision_idx', 'revision'),
2994 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2995 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2995 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
2996 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
2996 )
2997 )
2997
2998
2998 COMMENT_OUTDATED = u'comment_outdated'
2999 COMMENT_OUTDATED = u'comment_outdated'
2999 COMMENT_TYPE_NOTE = u'note'
3000 COMMENT_TYPE_NOTE = u'note'
3000 COMMENT_TYPE_TODO = u'todo'
3001 COMMENT_TYPE_TODO = u'todo'
3001 COMMENT_TYPES = [COMMENT_TYPE_NOTE, COMMENT_TYPE_TODO]
3002 COMMENT_TYPES = [COMMENT_TYPE_NOTE, COMMENT_TYPE_TODO]
3002
3003
3003 comment_id = Column('comment_id', Integer(), nullable=False, primary_key=True)
3004 comment_id = Column('comment_id', Integer(), nullable=False, primary_key=True)
3004 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
3005 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
3005 revision = Column('revision', String(40), nullable=True)
3006 revision = Column('revision', String(40), nullable=True)
3006 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
3007 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
3007 pull_request_version_id = Column("pull_request_version_id", Integer(), ForeignKey('pull_request_versions.pull_request_version_id'), nullable=True)
3008 pull_request_version_id = Column("pull_request_version_id", Integer(), ForeignKey('pull_request_versions.pull_request_version_id'), nullable=True)
3008 line_no = Column('line_no', Unicode(10), nullable=True)
3009 line_no = Column('line_no', Unicode(10), nullable=True)
3009 hl_lines = Column('hl_lines', Unicode(512), nullable=True)
3010 hl_lines = Column('hl_lines', Unicode(512), nullable=True)
3010 f_path = Column('f_path', Unicode(1000), nullable=True)
3011 f_path = Column('f_path', Unicode(1000), nullable=True)
3011 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
3012 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
3012 text = Column('text', UnicodeText().with_variant(UnicodeText(25000), 'mysql'), nullable=False)
3013 text = Column('text', UnicodeText().with_variant(UnicodeText(25000), 'mysql'), nullable=False)
3013 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3014 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3014 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3015 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3015 renderer = Column('renderer', Unicode(64), nullable=True)
3016 renderer = Column('renderer', Unicode(64), nullable=True)
3016 display_state = Column('display_state', Unicode(128), nullable=True)
3017 display_state = Column('display_state', Unicode(128), nullable=True)
3017
3018
3018 comment_type = Column('comment_type', Unicode(128), nullable=True, default=COMMENT_TYPE_NOTE)
3019 comment_type = Column('comment_type', Unicode(128), nullable=True, default=COMMENT_TYPE_NOTE)
3019 resolved_comment_id = Column('resolved_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'), nullable=True)
3020 resolved_comment_id = Column('resolved_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'), nullable=True)
3020 resolved_comment = relationship('ChangesetComment', remote_side=comment_id, backref='resolved_by')
3021 resolved_comment = relationship('ChangesetComment', remote_side=comment_id, backref='resolved_by')
3021 author = relationship('User', lazy='joined')
3022 author = relationship('User', lazy='joined')
3022 repo = relationship('Repository')
3023 repo = relationship('Repository')
3023 status_change = relationship('ChangesetStatus', cascade="all, delete, delete-orphan", lazy='joined')
3024 status_change = relationship('ChangesetStatus', cascade="all, delete, delete-orphan", lazy='joined')
3024 pull_request = relationship('PullRequest', lazy='joined')
3025 pull_request = relationship('PullRequest', lazy='joined')
3025 pull_request_version = relationship('PullRequestVersion')
3026 pull_request_version = relationship('PullRequestVersion')
3026
3027
3027 @classmethod
3028 @classmethod
3028 def get_users(cls, revision=None, pull_request_id=None):
3029 def get_users(cls, revision=None, pull_request_id=None):
3029 """
3030 """
3030 Returns user associated with this ChangesetComment. ie those
3031 Returns user associated with this ChangesetComment. ie those
3031 who actually commented
3032 who actually commented
3032
3033
3033 :param cls:
3034 :param cls:
3034 :param revision:
3035 :param revision:
3035 """
3036 """
3036 q = Session().query(User)\
3037 q = Session().query(User)\
3037 .join(ChangesetComment.author)
3038 .join(ChangesetComment.author)
3038 if revision:
3039 if revision:
3039 q = q.filter(cls.revision == revision)
3040 q = q.filter(cls.revision == revision)
3040 elif pull_request_id:
3041 elif pull_request_id:
3041 q = q.filter(cls.pull_request_id == pull_request_id)
3042 q = q.filter(cls.pull_request_id == pull_request_id)
3042 return q.all()
3043 return q.all()
3043
3044
3044 @classmethod
3045 @classmethod
3045 def get_index_from_version(cls, pr_version, versions):
3046 def get_index_from_version(cls, pr_version, versions):
3046 num_versions = [x.pull_request_version_id for x in versions]
3047 num_versions = [x.pull_request_version_id for x in versions]
3047 try:
3048 try:
3048 return num_versions.index(pr_version) +1
3049 return num_versions.index(pr_version) +1
3049 except (IndexError, ValueError):
3050 except (IndexError, ValueError):
3050 return
3051 return
3051
3052
3052 @property
3053 @property
3053 def outdated(self):
3054 def outdated(self):
3054 return self.display_state == self.COMMENT_OUTDATED
3055 return self.display_state == self.COMMENT_OUTDATED
3055
3056
3056 def outdated_at_version(self, version):
3057 def outdated_at_version(self, version):
3057 """
3058 """
3058 Checks if comment is outdated for given pull request version
3059 Checks if comment is outdated for given pull request version
3059 """
3060 """
3060 return self.outdated and self.pull_request_version_id != version
3061 return self.outdated and self.pull_request_version_id != version
3061
3062
3062 def older_than_version(self, version):
3063 def older_than_version(self, version):
3063 """
3064 """
3064 Checks if comment is made from previous version than given
3065 Checks if comment is made from previous version than given
3065 """
3066 """
3066 if version is None:
3067 if version is None:
3067 return self.pull_request_version_id is not None
3068 return self.pull_request_version_id is not None
3068
3069
3069 return self.pull_request_version_id < version
3070 return self.pull_request_version_id < version
3070
3071
3071 @property
3072 @property
3072 def resolved(self):
3073 def resolved(self):
3073 return self.resolved_by[0] if self.resolved_by else None
3074 return self.resolved_by[0] if self.resolved_by else None
3074
3075
3075 @property
3076 @property
3076 def is_todo(self):
3077 def is_todo(self):
3077 return self.comment_type == self.COMMENT_TYPE_TODO
3078 return self.comment_type == self.COMMENT_TYPE_TODO
3078
3079
3079 def get_index_version(self, versions):
3080 def get_index_version(self, versions):
3080 return self.get_index_from_version(
3081 return self.get_index_from_version(
3081 self.pull_request_version_id, versions)
3082 self.pull_request_version_id, versions)
3082
3083
3083 def render(self, mentions=False):
3084 def render(self, mentions=False):
3084 from rhodecode.lib import helpers as h
3085 from rhodecode.lib import helpers as h
3085 return h.render(self.text, renderer=self.renderer, mentions=mentions)
3086 return h.render(self.text, renderer=self.renderer, mentions=mentions)
3086
3087
3087 def __repr__(self):
3088 def __repr__(self):
3088 if self.comment_id:
3089 if self.comment_id:
3089 return '<DB:Comment #%s>' % self.comment_id
3090 return '<DB:Comment #%s>' % self.comment_id
3090 else:
3091 else:
3091 return '<DB:Comment at %#x>' % id(self)
3092 return '<DB:Comment at %#x>' % id(self)
3092
3093
3093
3094
3094 class ChangesetStatus(Base, BaseModel):
3095 class ChangesetStatus(Base, BaseModel):
3095 __tablename__ = 'changeset_statuses'
3096 __tablename__ = 'changeset_statuses'
3096 __table_args__ = (
3097 __table_args__ = (
3097 Index('cs_revision_idx', 'revision'),
3098 Index('cs_revision_idx', 'revision'),
3098 Index('cs_version_idx', 'version'),
3099 Index('cs_version_idx', 'version'),
3099 UniqueConstraint('repo_id', 'revision', 'version'),
3100 UniqueConstraint('repo_id', 'revision', 'version'),
3100 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3101 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3101 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3102 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3102 )
3103 )
3103 STATUS_NOT_REVIEWED = DEFAULT = 'not_reviewed'
3104 STATUS_NOT_REVIEWED = DEFAULT = 'not_reviewed'
3104 STATUS_APPROVED = 'approved'
3105 STATUS_APPROVED = 'approved'
3105 STATUS_REJECTED = 'rejected'
3106 STATUS_REJECTED = 'rejected'
3106 STATUS_UNDER_REVIEW = 'under_review'
3107 STATUS_UNDER_REVIEW = 'under_review'
3107
3108
3108 STATUSES = [
3109 STATUSES = [
3109 (STATUS_NOT_REVIEWED, _("Not Reviewed")), # (no icon) and default
3110 (STATUS_NOT_REVIEWED, _("Not Reviewed")), # (no icon) and default
3110 (STATUS_APPROVED, _("Approved")),
3111 (STATUS_APPROVED, _("Approved")),
3111 (STATUS_REJECTED, _("Rejected")),
3112 (STATUS_REJECTED, _("Rejected")),
3112 (STATUS_UNDER_REVIEW, _("Under Review")),
3113 (STATUS_UNDER_REVIEW, _("Under Review")),
3113 ]
3114 ]
3114
3115
3115 changeset_status_id = Column('changeset_status_id', Integer(), nullable=False, primary_key=True)
3116 changeset_status_id = Column('changeset_status_id', Integer(), nullable=False, primary_key=True)
3116 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
3117 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
3117 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
3118 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
3118 revision = Column('revision', String(40), nullable=False)
3119 revision = Column('revision', String(40), nullable=False)
3119 status = Column('status', String(128), nullable=False, default=DEFAULT)
3120 status = Column('status', String(128), nullable=False, default=DEFAULT)
3120 changeset_comment_id = Column('changeset_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'))
3121 changeset_comment_id = Column('changeset_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'))
3121 modified_at = Column('modified_at', DateTime(), nullable=False, default=datetime.datetime.now)
3122 modified_at = Column('modified_at', DateTime(), nullable=False, default=datetime.datetime.now)
3122 version = Column('version', Integer(), nullable=False, default=0)
3123 version = Column('version', Integer(), nullable=False, default=0)
3123 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
3124 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
3124
3125
3125 author = relationship('User', lazy='joined')
3126 author = relationship('User', lazy='joined')
3126 repo = relationship('Repository')
3127 repo = relationship('Repository')
3127 comment = relationship('ChangesetComment', lazy='joined')
3128 comment = relationship('ChangesetComment', lazy='joined')
3128 pull_request = relationship('PullRequest', lazy='joined')
3129 pull_request = relationship('PullRequest', lazy='joined')
3129
3130
3130 def __unicode__(self):
3131 def __unicode__(self):
3131 return u"<%s('%s[v%s]:%s')>" % (
3132 return u"<%s('%s[v%s]:%s')>" % (
3132 self.__class__.__name__,
3133 self.__class__.__name__,
3133 self.status, self.version, self.author
3134 self.status, self.version, self.author
3134 )
3135 )
3135
3136
3136 @classmethod
3137 @classmethod
3137 def get_status_lbl(cls, value):
3138 def get_status_lbl(cls, value):
3138 return dict(cls.STATUSES).get(value)
3139 return dict(cls.STATUSES).get(value)
3139
3140
3140 @property
3141 @property
3141 def status_lbl(self):
3142 def status_lbl(self):
3142 return ChangesetStatus.get_status_lbl(self.status)
3143 return ChangesetStatus.get_status_lbl(self.status)
3143
3144
3144
3145
3145 class _PullRequestBase(BaseModel):
3146 class _PullRequestBase(BaseModel):
3146 """
3147 """
3147 Common attributes of pull request and version entries.
3148 Common attributes of pull request and version entries.
3148 """
3149 """
3149
3150
3150 # .status values
3151 # .status values
3151 STATUS_NEW = u'new'
3152 STATUS_NEW = u'new'
3152 STATUS_OPEN = u'open'
3153 STATUS_OPEN = u'open'
3153 STATUS_CLOSED = u'closed'
3154 STATUS_CLOSED = u'closed'
3154
3155
3155 title = Column('title', Unicode(255), nullable=True)
3156 title = Column('title', Unicode(255), nullable=True)
3156 description = Column(
3157 description = Column(
3157 'description', UnicodeText().with_variant(UnicodeText(10240), 'mysql'),
3158 'description', UnicodeText().with_variant(UnicodeText(10240), 'mysql'),
3158 nullable=True)
3159 nullable=True)
3159 # new/open/closed status of pull request (not approve/reject/etc)
3160 # new/open/closed status of pull request (not approve/reject/etc)
3160 status = Column('status', Unicode(255), nullable=False, default=STATUS_NEW)
3161 status = Column('status', Unicode(255), nullable=False, default=STATUS_NEW)
3161 created_on = Column(
3162 created_on = Column(
3162 'created_on', DateTime(timezone=False), nullable=False,
3163 'created_on', DateTime(timezone=False), nullable=False,
3163 default=datetime.datetime.now)
3164 default=datetime.datetime.now)
3164 updated_on = Column(
3165 updated_on = Column(
3165 'updated_on', DateTime(timezone=False), nullable=False,
3166 'updated_on', DateTime(timezone=False), nullable=False,
3166 default=datetime.datetime.now)
3167 default=datetime.datetime.now)
3167
3168
3168 @declared_attr
3169 @declared_attr
3169 def user_id(cls):
3170 def user_id(cls):
3170 return Column(
3171 return Column(
3171 "user_id", Integer(), ForeignKey('users.user_id'), nullable=False,
3172 "user_id", Integer(), ForeignKey('users.user_id'), nullable=False,
3172 unique=None)
3173 unique=None)
3173
3174
3174 # 500 revisions max
3175 # 500 revisions max
3175 _revisions = Column(
3176 _revisions = Column(
3176 'revisions', UnicodeText().with_variant(UnicodeText(20500), 'mysql'))
3177 'revisions', UnicodeText().with_variant(UnicodeText(20500), 'mysql'))
3177
3178
3178 @declared_attr
3179 @declared_attr
3179 def source_repo_id(cls):
3180 def source_repo_id(cls):
3180 # TODO: dan: rename column to source_repo_id
3181 # TODO: dan: rename column to source_repo_id
3181 return Column(
3182 return Column(
3182 'org_repo_id', Integer(), ForeignKey('repositories.repo_id'),
3183 'org_repo_id', Integer(), ForeignKey('repositories.repo_id'),
3183 nullable=False)
3184 nullable=False)
3184
3185
3185 source_ref = Column('org_ref', Unicode(255), nullable=False)
3186 source_ref = Column('org_ref', Unicode(255), nullable=False)
3186
3187
3187 @declared_attr
3188 @declared_attr
3188 def target_repo_id(cls):
3189 def target_repo_id(cls):
3189 # TODO: dan: rename column to target_repo_id
3190 # TODO: dan: rename column to target_repo_id
3190 return Column(
3191 return Column(
3191 'other_repo_id', Integer(), ForeignKey('repositories.repo_id'),
3192 'other_repo_id', Integer(), ForeignKey('repositories.repo_id'),
3192 nullable=False)
3193 nullable=False)
3193
3194
3194 target_ref = Column('other_ref', Unicode(255), nullable=False)
3195 target_ref = Column('other_ref', Unicode(255), nullable=False)
3195 _shadow_merge_ref = Column('shadow_merge_ref', Unicode(255), nullable=True)
3196 _shadow_merge_ref = Column('shadow_merge_ref', Unicode(255), nullable=True)
3196
3197
3197 # TODO: dan: rename column to last_merge_source_rev
3198 # TODO: dan: rename column to last_merge_source_rev
3198 _last_merge_source_rev = Column(
3199 _last_merge_source_rev = Column(
3199 'last_merge_org_rev', String(40), nullable=True)
3200 'last_merge_org_rev', String(40), nullable=True)
3200 # TODO: dan: rename column to last_merge_target_rev
3201 # TODO: dan: rename column to last_merge_target_rev
3201 _last_merge_target_rev = Column(
3202 _last_merge_target_rev = Column(
3202 'last_merge_other_rev', String(40), nullable=True)
3203 'last_merge_other_rev', String(40), nullable=True)
3203 _last_merge_status = Column('merge_status', Integer(), nullable=True)
3204 _last_merge_status = Column('merge_status', Integer(), nullable=True)
3204 merge_rev = Column('merge_rev', String(40), nullable=True)
3205 merge_rev = Column('merge_rev', String(40), nullable=True)
3205
3206
3206 @hybrid_property
3207 @hybrid_property
3207 def revisions(self):
3208 def revisions(self):
3208 return self._revisions.split(':') if self._revisions else []
3209 return self._revisions.split(':') if self._revisions else []
3209
3210
3210 @revisions.setter
3211 @revisions.setter
3211 def revisions(self, val):
3212 def revisions(self, val):
3212 self._revisions = ':'.join(val)
3213 self._revisions = ':'.join(val)
3213
3214
3214 @declared_attr
3215 @declared_attr
3215 def author(cls):
3216 def author(cls):
3216 return relationship('User', lazy='joined')
3217 return relationship('User', lazy='joined')
3217
3218
3218 @declared_attr
3219 @declared_attr
3219 def source_repo(cls):
3220 def source_repo(cls):
3220 return relationship(
3221 return relationship(
3221 'Repository',
3222 'Repository',
3222 primaryjoin='%s.source_repo_id==Repository.repo_id' % cls.__name__)
3223 primaryjoin='%s.source_repo_id==Repository.repo_id' % cls.__name__)
3223
3224
3224 @property
3225 @property
3225 def source_ref_parts(self):
3226 def source_ref_parts(self):
3226 return self.unicode_to_reference(self.source_ref)
3227 return self.unicode_to_reference(self.source_ref)
3227
3228
3228 @declared_attr
3229 @declared_attr
3229 def target_repo(cls):
3230 def target_repo(cls):
3230 return relationship(
3231 return relationship(
3231 'Repository',
3232 'Repository',
3232 primaryjoin='%s.target_repo_id==Repository.repo_id' % cls.__name__)
3233 primaryjoin='%s.target_repo_id==Repository.repo_id' % cls.__name__)
3233
3234
3234 @property
3235 @property
3235 def target_ref_parts(self):
3236 def target_ref_parts(self):
3236 return self.unicode_to_reference(self.target_ref)
3237 return self.unicode_to_reference(self.target_ref)
3237
3238
3238 @property
3239 @property
3239 def shadow_merge_ref(self):
3240 def shadow_merge_ref(self):
3240 return self.unicode_to_reference(self._shadow_merge_ref)
3241 return self.unicode_to_reference(self._shadow_merge_ref)
3241
3242
3242 @shadow_merge_ref.setter
3243 @shadow_merge_ref.setter
3243 def shadow_merge_ref(self, ref):
3244 def shadow_merge_ref(self, ref):
3244 self._shadow_merge_ref = self.reference_to_unicode(ref)
3245 self._shadow_merge_ref = self.reference_to_unicode(ref)
3245
3246
3246 def unicode_to_reference(self, raw):
3247 def unicode_to_reference(self, raw):
3247 """
3248 """
3248 Convert a unicode (or string) to a reference object.
3249 Convert a unicode (or string) to a reference object.
3249 If unicode evaluates to False it returns None.
3250 If unicode evaluates to False it returns None.
3250 """
3251 """
3251 if raw:
3252 if raw:
3252 refs = raw.split(':')
3253 refs = raw.split(':')
3253 return Reference(*refs)
3254 return Reference(*refs)
3254 else:
3255 else:
3255 return None
3256 return None
3256
3257
3257 def reference_to_unicode(self, ref):
3258 def reference_to_unicode(self, ref):
3258 """
3259 """
3259 Convert a reference object to unicode.
3260 Convert a reference object to unicode.
3260 If reference is None it returns None.
3261 If reference is None it returns None.
3261 """
3262 """
3262 if ref:
3263 if ref:
3263 return u':'.join(ref)
3264 return u':'.join(ref)
3264 else:
3265 else:
3265 return None
3266 return None
3266
3267
3267 def get_api_data(self):
3268 def get_api_data(self):
3268 from rhodecode.model.pull_request import PullRequestModel
3269 from rhodecode.model.pull_request import PullRequestModel
3269 pull_request = self
3270 pull_request = self
3270 merge_status = PullRequestModel().merge_status(pull_request)
3271 merge_status = PullRequestModel().merge_status(pull_request)
3271
3272
3272 pull_request_url = url(
3273 pull_request_url = url(
3273 'pullrequest_show', repo_name=self.target_repo.repo_name,
3274 'pullrequest_show', repo_name=self.target_repo.repo_name,
3274 pull_request_id=self.pull_request_id, qualified=True)
3275 pull_request_id=self.pull_request_id, qualified=True)
3275
3276
3276 merge_data = {
3277 merge_data = {
3277 'clone_url': PullRequestModel().get_shadow_clone_url(pull_request),
3278 'clone_url': PullRequestModel().get_shadow_clone_url(pull_request),
3278 'reference': (
3279 'reference': (
3279 pull_request.shadow_merge_ref._asdict()
3280 pull_request.shadow_merge_ref._asdict()
3280 if pull_request.shadow_merge_ref else None),
3281 if pull_request.shadow_merge_ref else None),
3281 }
3282 }
3282
3283
3283 data = {
3284 data = {
3284 'pull_request_id': pull_request.pull_request_id,
3285 'pull_request_id': pull_request.pull_request_id,
3285 'url': pull_request_url,
3286 'url': pull_request_url,
3286 'title': pull_request.title,
3287 'title': pull_request.title,
3287 'description': pull_request.description,
3288 'description': pull_request.description,
3288 'status': pull_request.status,
3289 'status': pull_request.status,
3289 'created_on': pull_request.created_on,
3290 'created_on': pull_request.created_on,
3290 'updated_on': pull_request.updated_on,
3291 'updated_on': pull_request.updated_on,
3291 'commit_ids': pull_request.revisions,
3292 'commit_ids': pull_request.revisions,
3292 'review_status': pull_request.calculated_review_status(),
3293 'review_status': pull_request.calculated_review_status(),
3293 'mergeable': {
3294 'mergeable': {
3294 'status': merge_status[0],
3295 'status': merge_status[0],
3295 'message': unicode(merge_status[1]),
3296 'message': unicode(merge_status[1]),
3296 },
3297 },
3297 'source': {
3298 'source': {
3298 'clone_url': pull_request.source_repo.clone_url(),
3299 'clone_url': pull_request.source_repo.clone_url(),
3299 'repository': pull_request.source_repo.repo_name,
3300 'repository': pull_request.source_repo.repo_name,
3300 'reference': {
3301 'reference': {
3301 'name': pull_request.source_ref_parts.name,
3302 'name': pull_request.source_ref_parts.name,
3302 'type': pull_request.source_ref_parts.type,
3303 'type': pull_request.source_ref_parts.type,
3303 'commit_id': pull_request.source_ref_parts.commit_id,
3304 'commit_id': pull_request.source_ref_parts.commit_id,
3304 },
3305 },
3305 },
3306 },
3306 'target': {
3307 'target': {
3307 'clone_url': pull_request.target_repo.clone_url(),
3308 'clone_url': pull_request.target_repo.clone_url(),
3308 'repository': pull_request.target_repo.repo_name,
3309 'repository': pull_request.target_repo.repo_name,
3309 'reference': {
3310 'reference': {
3310 'name': pull_request.target_ref_parts.name,
3311 'name': pull_request.target_ref_parts.name,
3311 'type': pull_request.target_ref_parts.type,
3312 'type': pull_request.target_ref_parts.type,
3312 'commit_id': pull_request.target_ref_parts.commit_id,
3313 'commit_id': pull_request.target_ref_parts.commit_id,
3313 },
3314 },
3314 },
3315 },
3315 'merge': merge_data,
3316 'merge': merge_data,
3316 'author': pull_request.author.get_api_data(include_secrets=False,
3317 'author': pull_request.author.get_api_data(include_secrets=False,
3317 details='basic'),
3318 details='basic'),
3318 'reviewers': [
3319 'reviewers': [
3319 {
3320 {
3320 'user': reviewer.get_api_data(include_secrets=False,
3321 'user': reviewer.get_api_data(include_secrets=False,
3321 details='basic'),
3322 details='basic'),
3322 'reasons': reasons,
3323 'reasons': reasons,
3323 'review_status': st[0][1].status if st else 'not_reviewed',
3324 'review_status': st[0][1].status if st else 'not_reviewed',
3324 }
3325 }
3325 for reviewer, reasons, st in pull_request.reviewers_statuses()
3326 for reviewer, reasons, st in pull_request.reviewers_statuses()
3326 ]
3327 ]
3327 }
3328 }
3328
3329
3329 return data
3330 return data
3330
3331
3331
3332
3332 class PullRequest(Base, _PullRequestBase):
3333 class PullRequest(Base, _PullRequestBase):
3333 __tablename__ = 'pull_requests'
3334 __tablename__ = 'pull_requests'
3334 __table_args__ = (
3335 __table_args__ = (
3335 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3336 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3336 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3337 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3337 )
3338 )
3338
3339
3339 pull_request_id = Column(
3340 pull_request_id = Column(
3340 'pull_request_id', Integer(), nullable=False, primary_key=True)
3341 'pull_request_id', Integer(), nullable=False, primary_key=True)
3341
3342
3342 def __repr__(self):
3343 def __repr__(self):
3343 if self.pull_request_id:
3344 if self.pull_request_id:
3344 return '<DB:PullRequest #%s>' % self.pull_request_id
3345 return '<DB:PullRequest #%s>' % self.pull_request_id
3345 else:
3346 else:
3346 return '<DB:PullRequest at %#x>' % id(self)
3347 return '<DB:PullRequest at %#x>' % id(self)
3347
3348
3348 reviewers = relationship('PullRequestReviewers',
3349 reviewers = relationship('PullRequestReviewers',
3349 cascade="all, delete, delete-orphan")
3350 cascade="all, delete, delete-orphan")
3350 statuses = relationship('ChangesetStatus')
3351 statuses = relationship('ChangesetStatus')
3351 comments = relationship('ChangesetComment',
3352 comments = relationship('ChangesetComment',
3352 cascade="all, delete, delete-orphan")
3353 cascade="all, delete, delete-orphan")
3353 versions = relationship('PullRequestVersion',
3354 versions = relationship('PullRequestVersion',
3354 cascade="all, delete, delete-orphan",
3355 cascade="all, delete, delete-orphan",
3355 lazy='dynamic')
3356 lazy='dynamic')
3356
3357
3357 @classmethod
3358 @classmethod
3358 def get_pr_display_object(cls, pull_request_obj, org_pull_request_obj,
3359 def get_pr_display_object(cls, pull_request_obj, org_pull_request_obj,
3359 internal_methods=None):
3360 internal_methods=None):
3360
3361
3361 class PullRequestDisplay(object):
3362 class PullRequestDisplay(object):
3362 """
3363 """
3363 Special object wrapper for showing PullRequest data via Versions
3364 Special object wrapper for showing PullRequest data via Versions
3364 It mimics PR object as close as possible. This is read only object
3365 It mimics PR object as close as possible. This is read only object
3365 just for display
3366 just for display
3366 """
3367 """
3367
3368
3368 def __init__(self, attrs, internal=None):
3369 def __init__(self, attrs, internal=None):
3369 self.attrs = attrs
3370 self.attrs = attrs
3370 # internal have priority over the given ones via attrs
3371 # internal have priority over the given ones via attrs
3371 self.internal = internal or ['versions']
3372 self.internal = internal or ['versions']
3372
3373
3373 def __getattr__(self, item):
3374 def __getattr__(self, item):
3374 if item in self.internal:
3375 if item in self.internal:
3375 return getattr(self, item)
3376 return getattr(self, item)
3376 try:
3377 try:
3377 return self.attrs[item]
3378 return self.attrs[item]
3378 except KeyError:
3379 except KeyError:
3379 raise AttributeError(
3380 raise AttributeError(
3380 '%s object has no attribute %s' % (self, item))
3381 '%s object has no attribute %s' % (self, item))
3381
3382
3382 def __repr__(self):
3383 def __repr__(self):
3383 return '<DB:PullRequestDisplay #%s>' % self.attrs.get('pull_request_id')
3384 return '<DB:PullRequestDisplay #%s>' % self.attrs.get('pull_request_id')
3384
3385
3385 def versions(self):
3386 def versions(self):
3386 return pull_request_obj.versions.order_by(
3387 return pull_request_obj.versions.order_by(
3387 PullRequestVersion.pull_request_version_id).all()
3388 PullRequestVersion.pull_request_version_id).all()
3388
3389
3389 def is_closed(self):
3390 def is_closed(self):
3390 return pull_request_obj.is_closed()
3391 return pull_request_obj.is_closed()
3391
3392
3392 @property
3393 @property
3393 def pull_request_version_id(self):
3394 def pull_request_version_id(self):
3394 return getattr(pull_request_obj, 'pull_request_version_id', None)
3395 return getattr(pull_request_obj, 'pull_request_version_id', None)
3395
3396
3396 attrs = StrictAttributeDict(pull_request_obj.get_api_data())
3397 attrs = StrictAttributeDict(pull_request_obj.get_api_data())
3397
3398
3398 attrs.author = StrictAttributeDict(
3399 attrs.author = StrictAttributeDict(
3399 pull_request_obj.author.get_api_data())
3400 pull_request_obj.author.get_api_data())
3400 if pull_request_obj.target_repo:
3401 if pull_request_obj.target_repo:
3401 attrs.target_repo = StrictAttributeDict(
3402 attrs.target_repo = StrictAttributeDict(
3402 pull_request_obj.target_repo.get_api_data())
3403 pull_request_obj.target_repo.get_api_data())
3403 attrs.target_repo.clone_url = pull_request_obj.target_repo.clone_url
3404 attrs.target_repo.clone_url = pull_request_obj.target_repo.clone_url
3404
3405
3405 if pull_request_obj.source_repo:
3406 if pull_request_obj.source_repo:
3406 attrs.source_repo = StrictAttributeDict(
3407 attrs.source_repo = StrictAttributeDict(
3407 pull_request_obj.source_repo.get_api_data())
3408 pull_request_obj.source_repo.get_api_data())
3408 attrs.source_repo.clone_url = pull_request_obj.source_repo.clone_url
3409 attrs.source_repo.clone_url = pull_request_obj.source_repo.clone_url
3409
3410
3410 attrs.source_ref_parts = pull_request_obj.source_ref_parts
3411 attrs.source_ref_parts = pull_request_obj.source_ref_parts
3411 attrs.target_ref_parts = pull_request_obj.target_ref_parts
3412 attrs.target_ref_parts = pull_request_obj.target_ref_parts
3412 attrs.revisions = pull_request_obj.revisions
3413 attrs.revisions = pull_request_obj.revisions
3413
3414
3414 attrs.shadow_merge_ref = org_pull_request_obj.shadow_merge_ref
3415 attrs.shadow_merge_ref = org_pull_request_obj.shadow_merge_ref
3415
3416
3416 return PullRequestDisplay(attrs, internal=internal_methods)
3417 return PullRequestDisplay(attrs, internal=internal_methods)
3417
3418
3418 def is_closed(self):
3419 def is_closed(self):
3419 return self.status == self.STATUS_CLOSED
3420 return self.status == self.STATUS_CLOSED
3420
3421
3421 def __json__(self):
3422 def __json__(self):
3422 return {
3423 return {
3423 'revisions': self.revisions,
3424 'revisions': self.revisions,
3424 }
3425 }
3425
3426
3426 def calculated_review_status(self):
3427 def calculated_review_status(self):
3427 from rhodecode.model.changeset_status import ChangesetStatusModel
3428 from rhodecode.model.changeset_status import ChangesetStatusModel
3428 return ChangesetStatusModel().calculated_review_status(self)
3429 return ChangesetStatusModel().calculated_review_status(self)
3429
3430
3430 def reviewers_statuses(self):
3431 def reviewers_statuses(self):
3431 from rhodecode.model.changeset_status import ChangesetStatusModel
3432 from rhodecode.model.changeset_status import ChangesetStatusModel
3432 return ChangesetStatusModel().reviewers_statuses(self)
3433 return ChangesetStatusModel().reviewers_statuses(self)
3433
3434
3434 @property
3435 @property
3435 def workspace_id(self):
3436 def workspace_id(self):
3436 from rhodecode.model.pull_request import PullRequestModel
3437 from rhodecode.model.pull_request import PullRequestModel
3437 return PullRequestModel()._workspace_id(self)
3438 return PullRequestModel()._workspace_id(self)
3438
3439
3439 def get_shadow_repo(self):
3440 def get_shadow_repo(self):
3440 workspace_id = self.workspace_id
3441 workspace_id = self.workspace_id
3441 vcs_obj = self.target_repo.scm_instance()
3442 vcs_obj = self.target_repo.scm_instance()
3442 shadow_repository_path = vcs_obj._get_shadow_repository_path(
3443 shadow_repository_path = vcs_obj._get_shadow_repository_path(
3443 workspace_id)
3444 workspace_id)
3444 return vcs_obj._get_shadow_instance(shadow_repository_path)
3445 return vcs_obj._get_shadow_instance(shadow_repository_path)
3445
3446
3446
3447
3447 class PullRequestVersion(Base, _PullRequestBase):
3448 class PullRequestVersion(Base, _PullRequestBase):
3448 __tablename__ = 'pull_request_versions'
3449 __tablename__ = 'pull_request_versions'
3449 __table_args__ = (
3450 __table_args__ = (
3450 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3451 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3451 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3452 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3452 )
3453 )
3453
3454
3454 pull_request_version_id = Column(
3455 pull_request_version_id = Column(
3455 'pull_request_version_id', Integer(), nullable=False, primary_key=True)
3456 'pull_request_version_id', Integer(), nullable=False, primary_key=True)
3456 pull_request_id = Column(
3457 pull_request_id = Column(
3457 'pull_request_id', Integer(),
3458 'pull_request_id', Integer(),
3458 ForeignKey('pull_requests.pull_request_id'), nullable=False)
3459 ForeignKey('pull_requests.pull_request_id'), nullable=False)
3459 pull_request = relationship('PullRequest')
3460 pull_request = relationship('PullRequest')
3460
3461
3461 def __repr__(self):
3462 def __repr__(self):
3462 if self.pull_request_version_id:
3463 if self.pull_request_version_id:
3463 return '<DB:PullRequestVersion #%s>' % self.pull_request_version_id
3464 return '<DB:PullRequestVersion #%s>' % self.pull_request_version_id
3464 else:
3465 else:
3465 return '<DB:PullRequestVersion at %#x>' % id(self)
3466 return '<DB:PullRequestVersion at %#x>' % id(self)
3466
3467
3467 @property
3468 @property
3468 def reviewers(self):
3469 def reviewers(self):
3469 return self.pull_request.reviewers
3470 return self.pull_request.reviewers
3470
3471
3471 @property
3472 @property
3472 def versions(self):
3473 def versions(self):
3473 return self.pull_request.versions
3474 return self.pull_request.versions
3474
3475
3475 def is_closed(self):
3476 def is_closed(self):
3476 # calculate from original
3477 # calculate from original
3477 return self.pull_request.status == self.STATUS_CLOSED
3478 return self.pull_request.status == self.STATUS_CLOSED
3478
3479
3479 def calculated_review_status(self):
3480 def calculated_review_status(self):
3480 return self.pull_request.calculated_review_status()
3481 return self.pull_request.calculated_review_status()
3481
3482
3482 def reviewers_statuses(self):
3483 def reviewers_statuses(self):
3483 return self.pull_request.reviewers_statuses()
3484 return self.pull_request.reviewers_statuses()
3484
3485
3485
3486
3486 class PullRequestReviewers(Base, BaseModel):
3487 class PullRequestReviewers(Base, BaseModel):
3487 __tablename__ = 'pull_request_reviewers'
3488 __tablename__ = 'pull_request_reviewers'
3488 __table_args__ = (
3489 __table_args__ = (
3489 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3490 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3490 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3491 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3491 )
3492 )
3492
3493
3493 def __init__(self, user=None, pull_request=None, reasons=None):
3494 def __init__(self, user=None, pull_request=None, reasons=None):
3494 self.user = user
3495 self.user = user
3495 self.pull_request = pull_request
3496 self.pull_request = pull_request
3496 self.reasons = reasons or []
3497 self.reasons = reasons or []
3497
3498
3498 @hybrid_property
3499 @hybrid_property
3499 def reasons(self):
3500 def reasons(self):
3500 if not self._reasons:
3501 if not self._reasons:
3501 return []
3502 return []
3502 return self._reasons
3503 return self._reasons
3503
3504
3504 @reasons.setter
3505 @reasons.setter
3505 def reasons(self, val):
3506 def reasons(self, val):
3506 val = val or []
3507 val = val or []
3507 if any(not isinstance(x, basestring) for x in val):
3508 if any(not isinstance(x, basestring) for x in val):
3508 raise Exception('invalid reasons type, must be list of strings')
3509 raise Exception('invalid reasons type, must be list of strings')
3509 self._reasons = val
3510 self._reasons = val
3510
3511
3511 pull_requests_reviewers_id = Column(
3512 pull_requests_reviewers_id = Column(
3512 'pull_requests_reviewers_id', Integer(), nullable=False,
3513 'pull_requests_reviewers_id', Integer(), nullable=False,
3513 primary_key=True)
3514 primary_key=True)
3514 pull_request_id = Column(
3515 pull_request_id = Column(
3515 "pull_request_id", Integer(),
3516 "pull_request_id", Integer(),
3516 ForeignKey('pull_requests.pull_request_id'), nullable=False)
3517 ForeignKey('pull_requests.pull_request_id'), nullable=False)
3517 user_id = Column(
3518 user_id = Column(
3518 "user_id", Integer(), ForeignKey('users.user_id'), nullable=True)
3519 "user_id", Integer(), ForeignKey('users.user_id'), nullable=True)
3519 _reasons = Column(
3520 _reasons = Column(
3520 'reason', MutationList.as_mutable(
3521 'reason', MutationList.as_mutable(
3521 JsonType('list', dialect_map=dict(mysql=UnicodeText(16384)))))
3522 JsonType('list', dialect_map=dict(mysql=UnicodeText(16384)))))
3522
3523
3523 user = relationship('User')
3524 user = relationship('User')
3524 pull_request = relationship('PullRequest')
3525 pull_request = relationship('PullRequest')
3525
3526
3526
3527
3527 class Notification(Base, BaseModel):
3528 class Notification(Base, BaseModel):
3528 __tablename__ = 'notifications'
3529 __tablename__ = 'notifications'
3529 __table_args__ = (
3530 __table_args__ = (
3530 Index('notification_type_idx', 'type'),
3531 Index('notification_type_idx', 'type'),
3531 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3532 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3532 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3533 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3533 )
3534 )
3534
3535
3535 TYPE_CHANGESET_COMMENT = u'cs_comment'
3536 TYPE_CHANGESET_COMMENT = u'cs_comment'
3536 TYPE_MESSAGE = u'message'
3537 TYPE_MESSAGE = u'message'
3537 TYPE_MENTION = u'mention'
3538 TYPE_MENTION = u'mention'
3538 TYPE_REGISTRATION = u'registration'
3539 TYPE_REGISTRATION = u'registration'
3539 TYPE_PULL_REQUEST = u'pull_request'
3540 TYPE_PULL_REQUEST = u'pull_request'
3540 TYPE_PULL_REQUEST_COMMENT = u'pull_request_comment'
3541 TYPE_PULL_REQUEST_COMMENT = u'pull_request_comment'
3541
3542
3542 notification_id = Column('notification_id', Integer(), nullable=False, primary_key=True)
3543 notification_id = Column('notification_id', Integer(), nullable=False, primary_key=True)
3543 subject = Column('subject', Unicode(512), nullable=True)
3544 subject = Column('subject', Unicode(512), nullable=True)
3544 body = Column('body', UnicodeText().with_variant(UnicodeText(50000), 'mysql'), nullable=True)
3545 body = Column('body', UnicodeText().with_variant(UnicodeText(50000), 'mysql'), nullable=True)
3545 created_by = Column("created_by", Integer(), ForeignKey('users.user_id'), nullable=True)
3546 created_by = Column("created_by", Integer(), ForeignKey('users.user_id'), nullable=True)
3546 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3547 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3547 type_ = Column('type', Unicode(255))
3548 type_ = Column('type', Unicode(255))
3548
3549
3549 created_by_user = relationship('User')
3550 created_by_user = relationship('User')
3550 notifications_to_users = relationship('UserNotification', lazy='joined',
3551 notifications_to_users = relationship('UserNotification', lazy='joined',
3551 cascade="all, delete, delete-orphan")
3552 cascade="all, delete, delete-orphan")
3552
3553
3553 @property
3554 @property
3554 def recipients(self):
3555 def recipients(self):
3555 return [x.user for x in UserNotification.query()\
3556 return [x.user for x in UserNotification.query()\
3556 .filter(UserNotification.notification == self)\
3557 .filter(UserNotification.notification == self)\
3557 .order_by(UserNotification.user_id.asc()).all()]
3558 .order_by(UserNotification.user_id.asc()).all()]
3558
3559
3559 @classmethod
3560 @classmethod
3560 def create(cls, created_by, subject, body, recipients, type_=None):
3561 def create(cls, created_by, subject, body, recipients, type_=None):
3561 if type_ is None:
3562 if type_ is None:
3562 type_ = Notification.TYPE_MESSAGE
3563 type_ = Notification.TYPE_MESSAGE
3563
3564
3564 notification = cls()
3565 notification = cls()
3565 notification.created_by_user = created_by
3566 notification.created_by_user = created_by
3566 notification.subject = subject
3567 notification.subject = subject
3567 notification.body = body
3568 notification.body = body
3568 notification.type_ = type_
3569 notification.type_ = type_
3569 notification.created_on = datetime.datetime.now()
3570 notification.created_on = datetime.datetime.now()
3570
3571
3571 for u in recipients:
3572 for u in recipients:
3572 assoc = UserNotification()
3573 assoc = UserNotification()
3573 assoc.notification = notification
3574 assoc.notification = notification
3574
3575
3575 # if created_by is inside recipients mark his notification
3576 # if created_by is inside recipients mark his notification
3576 # as read
3577 # as read
3577 if u.user_id == created_by.user_id:
3578 if u.user_id == created_by.user_id:
3578 assoc.read = True
3579 assoc.read = True
3579
3580
3580 u.notifications.append(assoc)
3581 u.notifications.append(assoc)
3581 Session().add(notification)
3582 Session().add(notification)
3582
3583
3583 return notification
3584 return notification
3584
3585
3585 @property
3586 @property
3586 def description(self):
3587 def description(self):
3587 from rhodecode.model.notification import NotificationModel
3588 from rhodecode.model.notification import NotificationModel
3588 return NotificationModel().make_description(self)
3589 return NotificationModel().make_description(self)
3589
3590
3590
3591
3591 class UserNotification(Base, BaseModel):
3592 class UserNotification(Base, BaseModel):
3592 __tablename__ = 'user_to_notification'
3593 __tablename__ = 'user_to_notification'
3593 __table_args__ = (
3594 __table_args__ = (
3594 UniqueConstraint('user_id', 'notification_id'),
3595 UniqueConstraint('user_id', 'notification_id'),
3595 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3596 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3596 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3597 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3597 )
3598 )
3598 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
3599 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
3599 notification_id = Column("notification_id", Integer(), ForeignKey('notifications.notification_id'), primary_key=True)
3600 notification_id = Column("notification_id", Integer(), ForeignKey('notifications.notification_id'), primary_key=True)
3600 read = Column('read', Boolean, default=False)
3601 read = Column('read', Boolean, default=False)
3601 sent_on = Column('sent_on', DateTime(timezone=False), nullable=True, unique=None)
3602 sent_on = Column('sent_on', DateTime(timezone=False), nullable=True, unique=None)
3602
3603
3603 user = relationship('User', lazy="joined")
3604 user = relationship('User', lazy="joined")
3604 notification = relationship('Notification', lazy="joined",
3605 notification = relationship('Notification', lazy="joined",
3605 order_by=lambda: Notification.created_on.desc(),)
3606 order_by=lambda: Notification.created_on.desc(),)
3606
3607
3607 def mark_as_read(self):
3608 def mark_as_read(self):
3608 self.read = True
3609 self.read = True
3609 Session().add(self)
3610 Session().add(self)
3610
3611
3611
3612
3612 class Gist(Base, BaseModel):
3613 class Gist(Base, BaseModel):
3613 __tablename__ = 'gists'
3614 __tablename__ = 'gists'
3614 __table_args__ = (
3615 __table_args__ = (
3615 Index('g_gist_access_id_idx', 'gist_access_id'),
3616 Index('g_gist_access_id_idx', 'gist_access_id'),
3616 Index('g_created_on_idx', 'created_on'),
3617 Index('g_created_on_idx', 'created_on'),
3617 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3618 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3618 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3619 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3619 )
3620 )
3620 GIST_PUBLIC = u'public'
3621 GIST_PUBLIC = u'public'
3621 GIST_PRIVATE = u'private'
3622 GIST_PRIVATE = u'private'
3622 DEFAULT_FILENAME = u'gistfile1.txt'
3623 DEFAULT_FILENAME = u'gistfile1.txt'
3623
3624
3624 ACL_LEVEL_PUBLIC = u'acl_public'
3625 ACL_LEVEL_PUBLIC = u'acl_public'
3625 ACL_LEVEL_PRIVATE = u'acl_private'
3626 ACL_LEVEL_PRIVATE = u'acl_private'
3626
3627
3627 gist_id = Column('gist_id', Integer(), primary_key=True)
3628 gist_id = Column('gist_id', Integer(), primary_key=True)
3628 gist_access_id = Column('gist_access_id', Unicode(250))
3629 gist_access_id = Column('gist_access_id', Unicode(250))
3629 gist_description = Column('gist_description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
3630 gist_description = Column('gist_description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
3630 gist_owner = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=True)
3631 gist_owner = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=True)
3631 gist_expires = Column('gist_expires', Float(53), nullable=False)
3632 gist_expires = Column('gist_expires', Float(53), nullable=False)
3632 gist_type = Column('gist_type', Unicode(128), nullable=False)
3633 gist_type = Column('gist_type', Unicode(128), nullable=False)
3633 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3634 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3634 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3635 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3635 acl_level = Column('acl_level', Unicode(128), nullable=True)
3636 acl_level = Column('acl_level', Unicode(128), nullable=True)
3636
3637
3637 owner = relationship('User')
3638 owner = relationship('User')
3638
3639
3639 def __repr__(self):
3640 def __repr__(self):
3640 return '<Gist:[%s]%s>' % (self.gist_type, self.gist_access_id)
3641 return '<Gist:[%s]%s>' % (self.gist_type, self.gist_access_id)
3641
3642
3642 @classmethod
3643 @classmethod
3643 def get_or_404(cls, id_, pyramid_exc=False):
3644 def get_or_404(cls, id_, pyramid_exc=False):
3644
3645
3645 if pyramid_exc:
3646 if pyramid_exc:
3646 from pyramid.httpexceptions import HTTPNotFound
3647 from pyramid.httpexceptions import HTTPNotFound
3647 else:
3648 else:
3648 from webob.exc import HTTPNotFound
3649 from webob.exc import HTTPNotFound
3649
3650
3650 res = cls.query().filter(cls.gist_access_id == id_).scalar()
3651 res = cls.query().filter(cls.gist_access_id == id_).scalar()
3651 if not res:
3652 if not res:
3652 raise HTTPNotFound
3653 raise HTTPNotFound
3653 return res
3654 return res
3654
3655
3655 @classmethod
3656 @classmethod
3656 def get_by_access_id(cls, gist_access_id):
3657 def get_by_access_id(cls, gist_access_id):
3657 return cls.query().filter(cls.gist_access_id == gist_access_id).scalar()
3658 return cls.query().filter(cls.gist_access_id == gist_access_id).scalar()
3658
3659
3659 def gist_url(self):
3660 def gist_url(self):
3660 import rhodecode
3661 import rhodecode
3661 alias_url = rhodecode.CONFIG.get('gist_alias_url')
3662 alias_url = rhodecode.CONFIG.get('gist_alias_url')
3662 if alias_url:
3663 if alias_url:
3663 return alias_url.replace('{gistid}', self.gist_access_id)
3664 return alias_url.replace('{gistid}', self.gist_access_id)
3664
3665
3665 return url('gist', gist_id=self.gist_access_id, qualified=True)
3666 return url('gist', gist_id=self.gist_access_id, qualified=True)
3666
3667
3667 @classmethod
3668 @classmethod
3668 def base_path(cls):
3669 def base_path(cls):
3669 """
3670 """
3670 Returns base path when all gists are stored
3671 Returns base path when all gists are stored
3671
3672
3672 :param cls:
3673 :param cls:
3673 """
3674 """
3674 from rhodecode.model.gist import GIST_STORE_LOC
3675 from rhodecode.model.gist import GIST_STORE_LOC
3675 q = Session().query(RhodeCodeUi)\
3676 q = Session().query(RhodeCodeUi)\
3676 .filter(RhodeCodeUi.ui_key == URL_SEP)
3677 .filter(RhodeCodeUi.ui_key == URL_SEP)
3677 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
3678 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
3678 return os.path.join(q.one().ui_value, GIST_STORE_LOC)
3679 return os.path.join(q.one().ui_value, GIST_STORE_LOC)
3679
3680
3680 def get_api_data(self):
3681 def get_api_data(self):
3681 """
3682 """
3682 Common function for generating gist related data for API
3683 Common function for generating gist related data for API
3683 """
3684 """
3684 gist = self
3685 gist = self
3685 data = {
3686 data = {
3686 'gist_id': gist.gist_id,
3687 'gist_id': gist.gist_id,
3687 'type': gist.gist_type,
3688 'type': gist.gist_type,
3688 'access_id': gist.gist_access_id,
3689 'access_id': gist.gist_access_id,
3689 'description': gist.gist_description,
3690 'description': gist.gist_description,
3690 'url': gist.gist_url(),
3691 'url': gist.gist_url(),
3691 'expires': gist.gist_expires,
3692 'expires': gist.gist_expires,
3692 'created_on': gist.created_on,
3693 'created_on': gist.created_on,
3693 'modified_at': gist.modified_at,
3694 'modified_at': gist.modified_at,
3694 'content': None,
3695 'content': None,
3695 'acl_level': gist.acl_level,
3696 'acl_level': gist.acl_level,
3696 }
3697 }
3697 return data
3698 return data
3698
3699
3699 def __json__(self):
3700 def __json__(self):
3700 data = dict(
3701 data = dict(
3701 )
3702 )
3702 data.update(self.get_api_data())
3703 data.update(self.get_api_data())
3703 return data
3704 return data
3704 # SCM functions
3705 # SCM functions
3705
3706
3706 def scm_instance(self, **kwargs):
3707 def scm_instance(self, **kwargs):
3707 full_repo_path = os.path.join(self.base_path(), self.gist_access_id)
3708 full_repo_path = os.path.join(self.base_path(), self.gist_access_id)
3708 return get_vcs_instance(
3709 return get_vcs_instance(
3709 repo_path=safe_str(full_repo_path), create=False)
3710 repo_path=safe_str(full_repo_path), create=False)
3710
3711
3711
3712
3712 class ExternalIdentity(Base, BaseModel):
3713 class ExternalIdentity(Base, BaseModel):
3713 __tablename__ = 'external_identities'
3714 __tablename__ = 'external_identities'
3714 __table_args__ = (
3715 __table_args__ = (
3715 Index('local_user_id_idx', 'local_user_id'),
3716 Index('local_user_id_idx', 'local_user_id'),
3716 Index('external_id_idx', 'external_id'),
3717 Index('external_id_idx', 'external_id'),
3717 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3718 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3718 'mysql_charset': 'utf8'})
3719 'mysql_charset': 'utf8'})
3719
3720
3720 external_id = Column('external_id', Unicode(255), default=u'',
3721 external_id = Column('external_id', Unicode(255), default=u'',
3721 primary_key=True)
3722 primary_key=True)
3722 external_username = Column('external_username', Unicode(1024), default=u'')
3723 external_username = Column('external_username', Unicode(1024), default=u'')
3723 local_user_id = Column('local_user_id', Integer(),
3724 local_user_id = Column('local_user_id', Integer(),
3724 ForeignKey('users.user_id'), primary_key=True)
3725 ForeignKey('users.user_id'), primary_key=True)
3725 provider_name = Column('provider_name', Unicode(255), default=u'',
3726 provider_name = Column('provider_name', Unicode(255), default=u'',
3726 primary_key=True)
3727 primary_key=True)
3727 access_token = Column('access_token', String(1024), default=u'')
3728 access_token = Column('access_token', String(1024), default=u'')
3728 alt_token = Column('alt_token', String(1024), default=u'')
3729 alt_token = Column('alt_token', String(1024), default=u'')
3729 token_secret = Column('token_secret', String(1024), default=u'')
3730 token_secret = Column('token_secret', String(1024), default=u'')
3730
3731
3731 @classmethod
3732 @classmethod
3732 def by_external_id_and_provider(cls, external_id, provider_name,
3733 def by_external_id_and_provider(cls, external_id, provider_name,
3733 local_user_id=None):
3734 local_user_id=None):
3734 """
3735 """
3735 Returns ExternalIdentity instance based on search params
3736 Returns ExternalIdentity instance based on search params
3736
3737
3737 :param external_id:
3738 :param external_id:
3738 :param provider_name:
3739 :param provider_name:
3739 :return: ExternalIdentity
3740 :return: ExternalIdentity
3740 """
3741 """
3741 query = cls.query()
3742 query = cls.query()
3742 query = query.filter(cls.external_id == external_id)
3743 query = query.filter(cls.external_id == external_id)
3743 query = query.filter(cls.provider_name == provider_name)
3744 query = query.filter(cls.provider_name == provider_name)
3744 if local_user_id:
3745 if local_user_id:
3745 query = query.filter(cls.local_user_id == local_user_id)
3746 query = query.filter(cls.local_user_id == local_user_id)
3746 return query.first()
3747 return query.first()
3747
3748
3748 @classmethod
3749 @classmethod
3749 def user_by_external_id_and_provider(cls, external_id, provider_name):
3750 def user_by_external_id_and_provider(cls, external_id, provider_name):
3750 """
3751 """
3751 Returns User instance based on search params
3752 Returns User instance based on search params
3752
3753
3753 :param external_id:
3754 :param external_id:
3754 :param provider_name:
3755 :param provider_name:
3755 :return: User
3756 :return: User
3756 """
3757 """
3757 query = User.query()
3758 query = User.query()
3758 query = query.filter(cls.external_id == external_id)
3759 query = query.filter(cls.external_id == external_id)
3759 query = query.filter(cls.provider_name == provider_name)
3760 query = query.filter(cls.provider_name == provider_name)
3760 query = query.filter(User.user_id == cls.local_user_id)
3761 query = query.filter(User.user_id == cls.local_user_id)
3761 return query.first()
3762 return query.first()
3762
3763
3763 @classmethod
3764 @classmethod
3764 def by_local_user_id(cls, local_user_id):
3765 def by_local_user_id(cls, local_user_id):
3765 """
3766 """
3766 Returns all tokens for user
3767 Returns all tokens for user
3767
3768
3768 :param local_user_id:
3769 :param local_user_id:
3769 :return: ExternalIdentity
3770 :return: ExternalIdentity
3770 """
3771 """
3771 query = cls.query()
3772 query = cls.query()
3772 query = query.filter(cls.local_user_id == local_user_id)
3773 query = query.filter(cls.local_user_id == local_user_id)
3773 return query
3774 return query
3774
3775
3775
3776
3776 class Integration(Base, BaseModel):
3777 class Integration(Base, BaseModel):
3777 __tablename__ = 'integrations'
3778 __tablename__ = 'integrations'
3778 __table_args__ = (
3779 __table_args__ = (
3779 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3780 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3780 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3781 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3781 )
3782 )
3782
3783
3783 integration_id = Column('integration_id', Integer(), primary_key=True)
3784 integration_id = Column('integration_id', Integer(), primary_key=True)
3784 integration_type = Column('integration_type', String(255))
3785 integration_type = Column('integration_type', String(255))
3785 enabled = Column('enabled', Boolean(), nullable=False)
3786 enabled = Column('enabled', Boolean(), nullable=False)
3786 name = Column('name', String(255), nullable=False)
3787 name = Column('name', String(255), nullable=False)
3787 child_repos_only = Column('child_repos_only', Boolean(), nullable=False,
3788 child_repos_only = Column('child_repos_only', Boolean(), nullable=False,
3788 default=False)
3789 default=False)
3789
3790
3790 settings = Column(
3791 settings = Column(
3791 'settings_json', MutationObj.as_mutable(
3792 'settings_json', MutationObj.as_mutable(
3792 JsonType(dialect_map=dict(mysql=UnicodeText(16384)))))
3793 JsonType(dialect_map=dict(mysql=UnicodeText(16384)))))
3793 repo_id = Column(
3794 repo_id = Column(
3794 'repo_id', Integer(), ForeignKey('repositories.repo_id'),
3795 'repo_id', Integer(), ForeignKey('repositories.repo_id'),
3795 nullable=True, unique=None, default=None)
3796 nullable=True, unique=None, default=None)
3796 repo = relationship('Repository', lazy='joined')
3797 repo = relationship('Repository', lazy='joined')
3797
3798
3798 repo_group_id = Column(
3799 repo_group_id = Column(
3799 'repo_group_id', Integer(), ForeignKey('groups.group_id'),
3800 'repo_group_id', Integer(), ForeignKey('groups.group_id'),
3800 nullable=True, unique=None, default=None)
3801 nullable=True, unique=None, default=None)
3801 repo_group = relationship('RepoGroup', lazy='joined')
3802 repo_group = relationship('RepoGroup', lazy='joined')
3802
3803
3803 @property
3804 @property
3804 def scope(self):
3805 def scope(self):
3805 if self.repo:
3806 if self.repo:
3806 return repr(self.repo)
3807 return repr(self.repo)
3807 if self.repo_group:
3808 if self.repo_group:
3808 if self.child_repos_only:
3809 if self.child_repos_only:
3809 return repr(self.repo_group) + ' (child repos only)'
3810 return repr(self.repo_group) + ' (child repos only)'
3810 else:
3811 else:
3811 return repr(self.repo_group) + ' (recursive)'
3812 return repr(self.repo_group) + ' (recursive)'
3812 if self.child_repos_only:
3813 if self.child_repos_only:
3813 return 'root_repos'
3814 return 'root_repos'
3814 return 'global'
3815 return 'global'
3815
3816
3816 def __repr__(self):
3817 def __repr__(self):
3817 return '<Integration(%r, %r)>' % (self.integration_type, self.scope)
3818 return '<Integration(%r, %r)>' % (self.integration_type, self.scope)
3818
3819
3819
3820
3820 class RepoReviewRuleUser(Base, BaseModel):
3821 class RepoReviewRuleUser(Base, BaseModel):
3821 __tablename__ = 'repo_review_rules_users'
3822 __tablename__ = 'repo_review_rules_users'
3822 __table_args__ = (
3823 __table_args__ = (
3823 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3824 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3824 'mysql_charset': 'utf8', 'sqlite_autoincrement': True,}
3825 'mysql_charset': 'utf8', 'sqlite_autoincrement': True,}
3825 )
3826 )
3826 repo_review_rule_user_id = Column(
3827 repo_review_rule_user_id = Column(
3827 'repo_review_rule_user_id', Integer(), primary_key=True)
3828 'repo_review_rule_user_id', Integer(), primary_key=True)
3828 repo_review_rule_id = Column("repo_review_rule_id",
3829 repo_review_rule_id = Column("repo_review_rule_id",
3829 Integer(), ForeignKey('repo_review_rules.repo_review_rule_id'))
3830 Integer(), ForeignKey('repo_review_rules.repo_review_rule_id'))
3830 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'),
3831 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'),
3831 nullable=False)
3832 nullable=False)
3832 user = relationship('User')
3833 user = relationship('User')
3833
3834
3834
3835
3835 class RepoReviewRuleUserGroup(Base, BaseModel):
3836 class RepoReviewRuleUserGroup(Base, BaseModel):
3836 __tablename__ = 'repo_review_rules_users_groups'
3837 __tablename__ = 'repo_review_rules_users_groups'
3837 __table_args__ = (
3838 __table_args__ = (
3838 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3839 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3839 'mysql_charset': 'utf8', 'sqlite_autoincrement': True,}
3840 'mysql_charset': 'utf8', 'sqlite_autoincrement': True,}
3840 )
3841 )
3841 repo_review_rule_users_group_id = Column(
3842 repo_review_rule_users_group_id = Column(
3842 'repo_review_rule_users_group_id', Integer(), primary_key=True)
3843 'repo_review_rule_users_group_id', Integer(), primary_key=True)
3843 repo_review_rule_id = Column("repo_review_rule_id",
3844 repo_review_rule_id = Column("repo_review_rule_id",
3844 Integer(), ForeignKey('repo_review_rules.repo_review_rule_id'))
3845 Integer(), ForeignKey('repo_review_rules.repo_review_rule_id'))
3845 users_group_id = Column("users_group_id", Integer(),
3846 users_group_id = Column("users_group_id", Integer(),
3846 ForeignKey('users_groups.users_group_id'), nullable=False)
3847 ForeignKey('users_groups.users_group_id'), nullable=False)
3847 users_group = relationship('UserGroup')
3848 users_group = relationship('UserGroup')
3848
3849
3849
3850
3850 class RepoReviewRule(Base, BaseModel):
3851 class RepoReviewRule(Base, BaseModel):
3851 __tablename__ = 'repo_review_rules'
3852 __tablename__ = 'repo_review_rules'
3852 __table_args__ = (
3853 __table_args__ = (
3853 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3854 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3854 'mysql_charset': 'utf8', 'sqlite_autoincrement': True,}
3855 'mysql_charset': 'utf8', 'sqlite_autoincrement': True,}
3855 )
3856 )
3856
3857
3857 repo_review_rule_id = Column(
3858 repo_review_rule_id = Column(
3858 'repo_review_rule_id', Integer(), primary_key=True)
3859 'repo_review_rule_id', Integer(), primary_key=True)
3859 repo_id = Column(
3860 repo_id = Column(
3860 "repo_id", Integer(), ForeignKey('repositories.repo_id'))
3861 "repo_id", Integer(), ForeignKey('repositories.repo_id'))
3861 repo = relationship('Repository', backref='review_rules')
3862 repo = relationship('Repository', backref='review_rules')
3862
3863
3863 _branch_pattern = Column("branch_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'),
3864 _branch_pattern = Column("branch_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'),
3864 default=u'*') # glob
3865 default=u'*') # glob
3865 _file_pattern = Column("file_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'),
3866 _file_pattern = Column("file_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'),
3866 default=u'*') # glob
3867 default=u'*') # glob
3867
3868
3868 use_authors_for_review = Column("use_authors_for_review", Boolean(),
3869 use_authors_for_review = Column("use_authors_for_review", Boolean(),
3869 nullable=False, default=False)
3870 nullable=False, default=False)
3870 rule_users = relationship('RepoReviewRuleUser')
3871 rule_users = relationship('RepoReviewRuleUser')
3871 rule_user_groups = relationship('RepoReviewRuleUserGroup')
3872 rule_user_groups = relationship('RepoReviewRuleUserGroup')
3872
3873
3873 @hybrid_property
3874 @hybrid_property
3874 def branch_pattern(self):
3875 def branch_pattern(self):
3875 return self._branch_pattern or '*'
3876 return self._branch_pattern or '*'
3876
3877
3877 def _validate_glob(self, value):
3878 def _validate_glob(self, value):
3878 re.compile('^' + glob2re(value) + '$')
3879 re.compile('^' + glob2re(value) + '$')
3879
3880
3880 @branch_pattern.setter
3881 @branch_pattern.setter
3881 def branch_pattern(self, value):
3882 def branch_pattern(self, value):
3882 self._validate_glob(value)
3883 self._validate_glob(value)
3883 self._branch_pattern = value or '*'
3884 self._branch_pattern = value or '*'
3884
3885
3885 @hybrid_property
3886 @hybrid_property
3886 def file_pattern(self):
3887 def file_pattern(self):
3887 return self._file_pattern or '*'
3888 return self._file_pattern or '*'
3888
3889
3889 @file_pattern.setter
3890 @file_pattern.setter
3890 def file_pattern(self, value):
3891 def file_pattern(self, value):
3891 self._validate_glob(value)
3892 self._validate_glob(value)
3892 self._file_pattern = value or '*'
3893 self._file_pattern = value or '*'
3893
3894
3894 def matches(self, branch, files_changed):
3895 def matches(self, branch, files_changed):
3895 """
3896 """
3896 Check if this review rule matches a branch/files in a pull request
3897 Check if this review rule matches a branch/files in a pull request
3897
3898
3898 :param branch: branch name for the commit
3899 :param branch: branch name for the commit
3899 :param files_changed: list of file paths changed in the pull request
3900 :param files_changed: list of file paths changed in the pull request
3900 """
3901 """
3901
3902
3902 branch = branch or ''
3903 branch = branch or ''
3903 files_changed = files_changed or []
3904 files_changed = files_changed or []
3904
3905
3905 branch_matches = True
3906 branch_matches = True
3906 if branch:
3907 if branch:
3907 branch_regex = re.compile('^' + glob2re(self.branch_pattern) + '$')
3908 branch_regex = re.compile('^' + glob2re(self.branch_pattern) + '$')
3908 branch_matches = bool(branch_regex.search(branch))
3909 branch_matches = bool(branch_regex.search(branch))
3909
3910
3910 files_matches = True
3911 files_matches = True
3911 if self.file_pattern != '*':
3912 if self.file_pattern != '*':
3912 files_matches = False
3913 files_matches = False
3913 file_regex = re.compile(glob2re(self.file_pattern))
3914 file_regex = re.compile(glob2re(self.file_pattern))
3914 for filename in files_changed:
3915 for filename in files_changed:
3915 if file_regex.search(filename):
3916 if file_regex.search(filename):
3916 files_matches = True
3917 files_matches = True
3917 break
3918 break
3918
3919
3919 return branch_matches and files_matches
3920 return branch_matches and files_matches
3920
3921
3921 @property
3922 @property
3922 def review_users(self):
3923 def review_users(self):
3923 """ Returns the users which this rule applies to """
3924 """ Returns the users which this rule applies to """
3924
3925
3925 users = set()
3926 users = set()
3926 users |= set([
3927 users |= set([
3927 rule_user.user for rule_user in self.rule_users
3928 rule_user.user for rule_user in self.rule_users
3928 if rule_user.user.active])
3929 if rule_user.user.active])
3929 users |= set(
3930 users |= set(
3930 member.user
3931 member.user
3931 for rule_user_group in self.rule_user_groups
3932 for rule_user_group in self.rule_user_groups
3932 for member in rule_user_group.users_group.members
3933 for member in rule_user_group.users_group.members
3933 if member.user.active
3934 if member.user.active
3934 )
3935 )
3935 return users
3936 return users
3936
3937
3937 def __repr__(self):
3938 def __repr__(self):
3938 return '<RepoReviewerRule(id=%r, repo=%r)>' % (
3939 return '<RepoReviewerRule(id=%r, repo=%r)>' % (
3939 self.repo_review_rule_id, self.repo)
3940 self.repo_review_rule_id, self.repo)
3940
3941
3941
3942
3942 class DbMigrateVersion(Base, BaseModel):
3943 class DbMigrateVersion(Base, BaseModel):
3943 __tablename__ = 'db_migrate_version'
3944 __tablename__ = 'db_migrate_version'
3944 __table_args__ = (
3945 __table_args__ = (
3945 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3946 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3946 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3947 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3947 )
3948 )
3948 repository_id = Column('repository_id', String(250), primary_key=True)
3949 repository_id = Column('repository_id', String(250), primary_key=True)
3949 repository_path = Column('repository_path', Text)
3950 repository_path = Column('repository_path', Text)
3950 version = Column('version', Integer)
3951 version = Column('version', Integer)
3951
3952
3952
3953
3953 class DbSession(Base, BaseModel):
3954 class DbSession(Base, BaseModel):
3954 __tablename__ = 'db_session'
3955 __tablename__ = 'db_session'
3955 __table_args__ = (
3956 __table_args__ = (
3956 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3957 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3957 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3958 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3958 )
3959 )
3959
3960
3960 def __repr__(self):
3961 def __repr__(self):
3961 return '<DB:DbSession({})>'.format(self.id)
3962 return '<DB:DbSession({})>'.format(self.id)
3962
3963
3963 id = Column('id', Integer())
3964 id = Column('id', Integer())
3964 namespace = Column('namespace', String(255), primary_key=True)
3965 namespace = Column('namespace', String(255), primary_key=True)
3965 accessed = Column('accessed', DateTime, nullable=False)
3966 accessed = Column('accessed', DateTime, nullable=False)
3966 created = Column('created', DateTime, nullable=False)
3967 created = Column('created', DateTime, nullable=False)
3967 data = Column('data', PickleType, nullable=False)
3968 data = Column('data', PickleType, nullable=False)
@@ -1,1073 +1,1080 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 """
21 """
22 Repository model for rhodecode
22 Repository model for rhodecode
23 """
23 """
24
24
25 import logging
25 import logging
26 import os
26 import os
27 import re
27 import re
28 import shutil
28 import shutil
29 import time
29 import time
30 import traceback
30 import traceback
31 from datetime import datetime, timedelta
31 from datetime import datetime, timedelta
32
32
33 from sqlalchemy.sql import func
33 from sqlalchemy.sql import func
34 from sqlalchemy.sql.expression import true, or_
34 from sqlalchemy.sql.expression import true, or_
35 from zope.cachedescriptors.property import Lazy as LazyProperty
35 from zope.cachedescriptors.property import Lazy as LazyProperty
36
36
37 from rhodecode import events
37 from rhodecode import events
38 from rhodecode.lib import helpers as h
38 from rhodecode.lib import helpers as h
39 from rhodecode.lib.auth import HasUserGroupPermissionAny
39 from rhodecode.lib.auth import HasUserGroupPermissionAny
40 from rhodecode.lib.caching_query import FromCache
40 from rhodecode.lib.caching_query import FromCache
41 from rhodecode.lib.exceptions import AttachedForksError
41 from rhodecode.lib.exceptions import AttachedForksError
42 from rhodecode.lib.hooks_base import log_delete_repository
42 from rhodecode.lib.hooks_base import log_delete_repository
43 from rhodecode.lib.markup_renderer import MarkupRenderer
43 from rhodecode.lib.markup_renderer import MarkupRenderer
44 from rhodecode.lib.utils import make_db_config
44 from rhodecode.lib.utils import make_db_config
45 from rhodecode.lib.utils2 import (
45 from rhodecode.lib.utils2 import (
46 safe_str, safe_unicode, remove_prefix, obfuscate_url_pw,
46 safe_str, safe_unicode, remove_prefix, obfuscate_url_pw,
47 get_current_rhodecode_user, safe_int, datetime_to_time, action_logger_generic)
47 get_current_rhodecode_user, safe_int, datetime_to_time, action_logger_generic)
48 from rhodecode.lib.vcs.backends import get_backend
48 from rhodecode.lib.vcs.backends import get_backend
49 from rhodecode.lib.vcs.exceptions import NodeDoesNotExistError
49 from rhodecode.lib.vcs.exceptions import NodeDoesNotExistError
50 from rhodecode.model import BaseModel
50 from rhodecode.model import BaseModel
51 from rhodecode.model.db import (
51 from rhodecode.model.db import (
52 Repository, UserRepoToPerm, UserGroupRepoToPerm, UserRepoGroupToPerm,
52 Repository, UserRepoToPerm, UserGroupRepoToPerm, UserRepoGroupToPerm,
53 UserGroupRepoGroupToPerm, User, Permission, Statistics, UserGroup,
53 UserGroupRepoGroupToPerm, User, Permission, Statistics, UserGroup,
54 RepoGroup, RepositoryField)
54 RepoGroup, RepositoryField)
55 from rhodecode.model.scm import UserGroupList
55 from rhodecode.model.scm import UserGroupList
56 from rhodecode.model.settings import VcsSettingsModel
56 from rhodecode.model.settings import VcsSettingsModel
57
57
58
58
59 log = logging.getLogger(__name__)
59 log = logging.getLogger(__name__)
60
60
61
61
62 class RepoModel(BaseModel):
62 class RepoModel(BaseModel):
63
63
64 cls = Repository
64 cls = Repository
65
65
66 def _get_user_group(self, users_group):
66 def _get_user_group(self, users_group):
67 return self._get_instance(UserGroup, users_group,
67 return self._get_instance(UserGroup, users_group,
68 callback=UserGroup.get_by_group_name)
68 callback=UserGroup.get_by_group_name)
69
69
70 def _get_repo_group(self, repo_group):
70 def _get_repo_group(self, repo_group):
71 return self._get_instance(RepoGroup, repo_group,
71 return self._get_instance(RepoGroup, repo_group,
72 callback=RepoGroup.get_by_group_name)
72 callback=RepoGroup.get_by_group_name)
73
73
74 def _create_default_perms(self, repository, private):
74 def _create_default_perms(self, repository, private):
75 # create default permission
75 # create default permission
76 default = 'repository.read'
76 default = 'repository.read'
77 def_user = User.get_default_user()
77 def_user = User.get_default_user()
78 for p in def_user.user_perms:
78 for p in def_user.user_perms:
79 if p.permission.permission_name.startswith('repository.'):
79 if p.permission.permission_name.startswith('repository.'):
80 default = p.permission.permission_name
80 default = p.permission.permission_name
81 break
81 break
82
82
83 default_perm = 'repository.none' if private else default
83 default_perm = 'repository.none' if private else default
84
84
85 repo_to_perm = UserRepoToPerm()
85 repo_to_perm = UserRepoToPerm()
86 repo_to_perm.permission = Permission.get_by_key(default_perm)
86 repo_to_perm.permission = Permission.get_by_key(default_perm)
87
87
88 repo_to_perm.repository = repository
88 repo_to_perm.repository = repository
89 repo_to_perm.user_id = def_user.user_id
89 repo_to_perm.user_id = def_user.user_id
90
90
91 return repo_to_perm
91 return repo_to_perm
92
92
93 @LazyProperty
93 @LazyProperty
94 def repos_path(self):
94 def repos_path(self):
95 """
95 """
96 Gets the repositories root path from database
96 Gets the repositories root path from database
97 """
97 """
98 settings_model = VcsSettingsModel(sa=self.sa)
98 settings_model = VcsSettingsModel(sa=self.sa)
99 return settings_model.get_repos_location()
99 return settings_model.get_repos_location()
100
100
101 def get(self, repo_id, cache=False):
101 def get(self, repo_id, cache=False):
102 repo = self.sa.query(Repository) \
102 repo = self.sa.query(Repository) \
103 .filter(Repository.repo_id == repo_id)
103 .filter(Repository.repo_id == repo_id)
104
104
105 if cache:
105 if cache:
106 repo = repo.options(FromCache("sql_cache_short",
106 repo = repo.options(FromCache("sql_cache_short",
107 "get_repo_%s" % repo_id))
107 "get_repo_%s" % repo_id))
108 return repo.scalar()
108 return repo.scalar()
109
109
110 def get_repo(self, repository):
110 def get_repo(self, repository):
111 return self._get_repo(repository)
111 return self._get_repo(repository)
112
112
113 def get_by_repo_name(self, repo_name, cache=False):
113 def get_by_repo_name(self, repo_name, cache=False):
114 repo = self.sa.query(Repository) \
114 repo = self.sa.query(Repository) \
115 .filter(Repository.repo_name == repo_name)
115 .filter(Repository.repo_name == repo_name)
116
116
117 if cache:
117 if cache:
118 repo = repo.options(FromCache("sql_cache_short",
118 repo = repo.options(FromCache("sql_cache_short",
119 "get_repo_%s" % repo_name))
119 "get_repo_%s" % repo_name))
120 return repo.scalar()
120 return repo.scalar()
121
121
122 def _extract_id_from_repo_name(self, repo_name):
122 def _extract_id_from_repo_name(self, repo_name):
123 if repo_name.startswith('/'):
123 if repo_name.startswith('/'):
124 repo_name = repo_name.lstrip('/')
124 repo_name = repo_name.lstrip('/')
125 by_id_match = re.match(r'^_(\d{1,})', repo_name)
125 by_id_match = re.match(r'^_(\d{1,})', repo_name)
126 if by_id_match:
126 if by_id_match:
127 return by_id_match.groups()[0]
127 return by_id_match.groups()[0]
128
128
129 def get_repo_by_id(self, repo_name):
129 def get_repo_by_id(self, repo_name):
130 """
130 """
131 Extracts repo_name by id from special urls.
131 Extracts repo_name by id from special urls.
132 Example url is _11/repo_name
132 Example url is _11/repo_name
133
133
134 :param repo_name:
134 :param repo_name:
135 :return: repo object if matched else None
135 :return: repo object if matched else None
136 """
136 """
137 try:
137 try:
138 _repo_id = self._extract_id_from_repo_name(repo_name)
138 _repo_id = self._extract_id_from_repo_name(repo_name)
139 if _repo_id:
139 if _repo_id:
140 return self.get(_repo_id)
140 return self.get(_repo_id)
141 except Exception:
141 except Exception:
142 log.exception('Failed to extract repo_name from URL')
142 log.exception('Failed to extract repo_name from URL')
143
143
144 return None
144 return None
145
145
146 def get_repos_for_root(self, root, traverse=False):
146 def get_repos_for_root(self, root, traverse=False):
147 if traverse:
147 if traverse:
148 like_expression = u'{}%'.format(safe_unicode(root))
148 like_expression = u'{}%'.format(safe_unicode(root))
149 repos = Repository.query().filter(
149 repos = Repository.query().filter(
150 Repository.repo_name.like(like_expression)).all()
150 Repository.repo_name.like(like_expression)).all()
151 else:
151 else:
152 if root and not isinstance(root, RepoGroup):
152 if root and not isinstance(root, RepoGroup):
153 raise ValueError(
153 raise ValueError(
154 'Root must be an instance '
154 'Root must be an instance '
155 'of RepoGroup, got:{} instead'.format(type(root)))
155 'of RepoGroup, got:{} instead'.format(type(root)))
156 repos = Repository.query().filter(Repository.group == root).all()
156 repos = Repository.query().filter(Repository.group == root).all()
157 return repos
157 return repos
158
158
159 def get_url(self, repo):
159 def get_url(self, repo):
160 return h.url('summary_home', repo_name=safe_str(repo.repo_name),
160 return h.url('summary_home', repo_name=safe_str(repo.repo_name),
161 qualified=True)
161 qualified=True)
162
162
163 def get_users(self, name_contains=None, limit=20, only_active=True):
163 def get_users(self, name_contains=None, limit=20, only_active=True):
164
164
165 # TODO: mikhail: move this method to the UserModel.
165 # TODO: mikhail: move this method to the UserModel.
166 query = self.sa.query(User)
166 query = self.sa.query(User)
167 if only_active:
167 if only_active:
168 query = query.filter(User.active == true())
168 query = query.filter(User.active == true())
169
169
170 if name_contains:
170 if name_contains:
171 ilike_expression = u'%{}%'.format(safe_unicode(name_contains))
171 ilike_expression = u'%{}%'.format(safe_unicode(name_contains))
172 query = query.filter(
172 query = query.filter(
173 or_(
173 or_(
174 User.name.ilike(ilike_expression),
174 User.name.ilike(ilike_expression),
175 User.lastname.ilike(ilike_expression),
175 User.lastname.ilike(ilike_expression),
176 User.username.ilike(ilike_expression)
176 User.username.ilike(ilike_expression)
177 )
177 )
178 )
178 )
179 query = query.limit(limit)
179 query = query.limit(limit)
180 users = query.all()
180 users = query.all()
181
181
182 _users = [
182 _users = [
183 {
183 {
184 'id': user.user_id,
184 'id': user.user_id,
185 'first_name': user.name,
185 'first_name': user.name,
186 'last_name': user.lastname,
186 'last_name': user.lastname,
187 'username': user.username,
187 'username': user.username,
188 'email': user.email,
188 'email': user.email,
189 'icon_link': h.gravatar_url(user.email, 30),
189 'icon_link': h.gravatar_url(user.email, 30),
190 'value_display': h.person(user),
190 'value_display': h.person(user),
191 'value': user.username,
191 'value': user.username,
192 'value_type': 'user',
192 'value_type': 'user',
193 'active': user.active,
193 'active': user.active,
194 }
194 }
195 for user in users
195 for user in users
196 ]
196 ]
197 return _users
197 return _users
198
198
199 def get_user_groups(self, name_contains=None, limit=20, only_active=True):
199 def get_user_groups(self, name_contains=None, limit=20, only_active=True):
200
200 # TODO: mikhail: move this method to the UserGroupModel.
201 # TODO: mikhail: move this method to the UserGroupModel.
201 query = self.sa.query(UserGroup)
202 query = self.sa.query(UserGroup)
202 if only_active:
203 if only_active:
203 query = query.filter(UserGroup.users_group_active == true())
204 query = query.filter(UserGroup.users_group_active == true())
204
205
205 if name_contains:
206 if name_contains:
206 ilike_expression = u'%{}%'.format(safe_unicode(name_contains))
207 ilike_expression = u'%{}%'.format(safe_unicode(name_contains))
207 query = query.filter(
208 query = query.filter(
208 UserGroup.users_group_name.ilike(ilike_expression))\
209 UserGroup.users_group_name.ilike(ilike_expression))\
209 .order_by(func.length(UserGroup.users_group_name))\
210 .order_by(func.length(UserGroup.users_group_name))\
210 .order_by(UserGroup.users_group_name)
211 .order_by(UserGroup.users_group_name)
211
212
212 query = query.limit(limit)
213 query = query.limit(limit)
213 user_groups = query.all()
214 user_groups = query.all()
214 perm_set = ['usergroup.read', 'usergroup.write', 'usergroup.admin']
215 perm_set = ['usergroup.read', 'usergroup.write', 'usergroup.admin']
215 user_groups = UserGroupList(user_groups, perm_set=perm_set)
216 user_groups = UserGroupList(user_groups, perm_set=perm_set)
216
217
217 _groups = [
218 _groups = [
218 {
219 {
219 'id': group.users_group_id,
220 'id': group.users_group_id,
220 # TODO: marcink figure out a way to generate the url for the
221 # TODO: marcink figure out a way to generate the url for the
221 # icon
222 # icon
222 'icon_link': '',
223 'icon_link': '',
223 'value_display': 'Group: %s (%d members)' % (
224 'value_display': 'Group: %s (%d members)' % (
224 group.users_group_name, len(group.members),),
225 group.users_group_name, len(group.members),),
225 'value': group.users_group_name,
226 'value': group.users_group_name,
227 'description': group.user_group_description,
228 'owner': group.user.username,
229
230 'owner_icon': h.gravatar_url(group.user.email, 30),
231 'value_display_owner': h.person(group.user.email),
232
226 'value_type': 'user_group',
233 'value_type': 'user_group',
227 'active': group.users_group_active,
234 'active': group.users_group_active,
228 }
235 }
229 for group in user_groups
236 for group in user_groups
230 ]
237 ]
231 return _groups
238 return _groups
232
239
233 @classmethod
240 @classmethod
234 def update_repoinfo(cls, repositories=None):
241 def update_repoinfo(cls, repositories=None):
235 if not repositories:
242 if not repositories:
236 repositories = Repository.getAll()
243 repositories = Repository.getAll()
237 for repo in repositories:
244 for repo in repositories:
238 repo.update_commit_cache()
245 repo.update_commit_cache()
239
246
240 def get_repos_as_dict(self, repo_list=None, admin=False,
247 def get_repos_as_dict(self, repo_list=None, admin=False,
241 super_user_actions=False):
248 super_user_actions=False):
242
249
243 from rhodecode.lib.utils import PartialRenderer
250 from rhodecode.lib.utils import PartialRenderer
244 _render = PartialRenderer('data_table/_dt_elements.mako')
251 _render = PartialRenderer('data_table/_dt_elements.mako')
245 c = _render.c
252 c = _render.c
246
253
247 def quick_menu(repo_name):
254 def quick_menu(repo_name):
248 return _render('quick_menu', repo_name)
255 return _render('quick_menu', repo_name)
249
256
250 def repo_lnk(name, rtype, rstate, private, fork_of):
257 def repo_lnk(name, rtype, rstate, private, fork_of):
251 return _render('repo_name', name, rtype, rstate, private, fork_of,
258 return _render('repo_name', name, rtype, rstate, private, fork_of,
252 short_name=not admin, admin=False)
259 short_name=not admin, admin=False)
253
260
254 def last_change(last_change):
261 def last_change(last_change):
255 if admin and isinstance(last_change, datetime) and not last_change.tzinfo:
262 if admin and isinstance(last_change, datetime) and not last_change.tzinfo:
256 last_change = last_change + timedelta(seconds=
263 last_change = last_change + timedelta(seconds=
257 (datetime.now() - datetime.utcnow()).seconds)
264 (datetime.now() - datetime.utcnow()).seconds)
258 return _render("last_change", last_change)
265 return _render("last_change", last_change)
259
266
260 def rss_lnk(repo_name):
267 def rss_lnk(repo_name):
261 return _render("rss", repo_name)
268 return _render("rss", repo_name)
262
269
263 def atom_lnk(repo_name):
270 def atom_lnk(repo_name):
264 return _render("atom", repo_name)
271 return _render("atom", repo_name)
265
272
266 def last_rev(repo_name, cs_cache):
273 def last_rev(repo_name, cs_cache):
267 return _render('revision', repo_name, cs_cache.get('revision'),
274 return _render('revision', repo_name, cs_cache.get('revision'),
268 cs_cache.get('raw_id'), cs_cache.get('author'),
275 cs_cache.get('raw_id'), cs_cache.get('author'),
269 cs_cache.get('message'))
276 cs_cache.get('message'))
270
277
271 def desc(desc):
278 def desc(desc):
272 if c.visual.stylify_metatags:
279 if c.visual.stylify_metatags:
273 desc = h.urlify_text(h.escaped_stylize(desc))
280 desc = h.urlify_text(h.escaped_stylize(desc))
274 else:
281 else:
275 desc = h.urlify_text(h.html_escape(desc))
282 desc = h.urlify_text(h.html_escape(desc))
276
283
277 return _render('repo_desc', desc)
284 return _render('repo_desc', desc)
278
285
279 def state(repo_state):
286 def state(repo_state):
280 return _render("repo_state", repo_state)
287 return _render("repo_state", repo_state)
281
288
282 def repo_actions(repo_name):
289 def repo_actions(repo_name):
283 return _render('repo_actions', repo_name, super_user_actions)
290 return _render('repo_actions', repo_name, super_user_actions)
284
291
285 def user_profile(username):
292 def user_profile(username):
286 return _render('user_profile', username)
293 return _render('user_profile', username)
287
294
288 repos_data = []
295 repos_data = []
289 for repo in repo_list:
296 for repo in repo_list:
290 cs_cache = repo.changeset_cache
297 cs_cache = repo.changeset_cache
291 row = {
298 row = {
292 "menu": quick_menu(repo.repo_name),
299 "menu": quick_menu(repo.repo_name),
293
300
294 "name": repo_lnk(repo.repo_name, repo.repo_type,
301 "name": repo_lnk(repo.repo_name, repo.repo_type,
295 repo.repo_state, repo.private, repo.fork),
302 repo.repo_state, repo.private, repo.fork),
296 "name_raw": repo.repo_name.lower(),
303 "name_raw": repo.repo_name.lower(),
297
304
298 "last_change": last_change(repo.last_db_change),
305 "last_change": last_change(repo.last_db_change),
299 "last_change_raw": datetime_to_time(repo.last_db_change),
306 "last_change_raw": datetime_to_time(repo.last_db_change),
300
307
301 "last_changeset": last_rev(repo.repo_name, cs_cache),
308 "last_changeset": last_rev(repo.repo_name, cs_cache),
302 "last_changeset_raw": cs_cache.get('revision'),
309 "last_changeset_raw": cs_cache.get('revision'),
303
310
304 "desc": desc(repo.description),
311 "desc": desc(repo.description),
305 "owner": user_profile(repo.user.username),
312 "owner": user_profile(repo.user.username),
306
313
307 "state": state(repo.repo_state),
314 "state": state(repo.repo_state),
308 "rss": rss_lnk(repo.repo_name),
315 "rss": rss_lnk(repo.repo_name),
309
316
310 "atom": atom_lnk(repo.repo_name),
317 "atom": atom_lnk(repo.repo_name),
311 }
318 }
312 if admin:
319 if admin:
313 row.update({
320 row.update({
314 "action": repo_actions(repo.repo_name),
321 "action": repo_actions(repo.repo_name),
315 })
322 })
316 repos_data.append(row)
323 repos_data.append(row)
317
324
318 return repos_data
325 return repos_data
319
326
320 def _get_defaults(self, repo_name):
327 def _get_defaults(self, repo_name):
321 """
328 """
322 Gets information about repository, and returns a dict for
329 Gets information about repository, and returns a dict for
323 usage in forms
330 usage in forms
324
331
325 :param repo_name:
332 :param repo_name:
326 """
333 """
327
334
328 repo_info = Repository.get_by_repo_name(repo_name)
335 repo_info = Repository.get_by_repo_name(repo_name)
329
336
330 if repo_info is None:
337 if repo_info is None:
331 return None
338 return None
332
339
333 defaults = repo_info.get_dict()
340 defaults = repo_info.get_dict()
334 defaults['repo_name'] = repo_info.just_name
341 defaults['repo_name'] = repo_info.just_name
335
342
336 groups = repo_info.groups_with_parents
343 groups = repo_info.groups_with_parents
337 parent_group = groups[-1] if groups else None
344 parent_group = groups[-1] if groups else None
338
345
339 # we use -1 as this is how in HTML, we mark an empty group
346 # we use -1 as this is how in HTML, we mark an empty group
340 defaults['repo_group'] = getattr(parent_group, 'group_id', -1)
347 defaults['repo_group'] = getattr(parent_group, 'group_id', -1)
341
348
342 keys_to_process = (
349 keys_to_process = (
343 {'k': 'repo_type', 'strip': False},
350 {'k': 'repo_type', 'strip': False},
344 {'k': 'repo_enable_downloads', 'strip': True},
351 {'k': 'repo_enable_downloads', 'strip': True},
345 {'k': 'repo_description', 'strip': True},
352 {'k': 'repo_description', 'strip': True},
346 {'k': 'repo_enable_locking', 'strip': True},
353 {'k': 'repo_enable_locking', 'strip': True},
347 {'k': 'repo_landing_rev', 'strip': True},
354 {'k': 'repo_landing_rev', 'strip': True},
348 {'k': 'clone_uri', 'strip': False},
355 {'k': 'clone_uri', 'strip': False},
349 {'k': 'repo_private', 'strip': True},
356 {'k': 'repo_private', 'strip': True},
350 {'k': 'repo_enable_statistics', 'strip': True}
357 {'k': 'repo_enable_statistics', 'strip': True}
351 )
358 )
352
359
353 for item in keys_to_process:
360 for item in keys_to_process:
354 attr = item['k']
361 attr = item['k']
355 if item['strip']:
362 if item['strip']:
356 attr = remove_prefix(item['k'], 'repo_')
363 attr = remove_prefix(item['k'], 'repo_')
357
364
358 val = defaults[attr]
365 val = defaults[attr]
359 if item['k'] == 'repo_landing_rev':
366 if item['k'] == 'repo_landing_rev':
360 val = ':'.join(defaults[attr])
367 val = ':'.join(defaults[attr])
361 defaults[item['k']] = val
368 defaults[item['k']] = val
362 if item['k'] == 'clone_uri':
369 if item['k'] == 'clone_uri':
363 defaults['clone_uri_hidden'] = repo_info.clone_uri_hidden
370 defaults['clone_uri_hidden'] = repo_info.clone_uri_hidden
364
371
365 # fill owner
372 # fill owner
366 if repo_info.user:
373 if repo_info.user:
367 defaults.update({'user': repo_info.user.username})
374 defaults.update({'user': repo_info.user.username})
368 else:
375 else:
369 replacement_user = User.get_first_super_admin().username
376 replacement_user = User.get_first_super_admin().username
370 defaults.update({'user': replacement_user})
377 defaults.update({'user': replacement_user})
371
378
372 # fill repository users
379 # fill repository users
373 for p in repo_info.repo_to_perm:
380 for p in repo_info.repo_to_perm:
374 defaults.update({'u_perm_%s' % p.user.user_id:
381 defaults.update({'u_perm_%s' % p.user.user_id:
375 p.permission.permission_name})
382 p.permission.permission_name})
376
383
377 # fill repository groups
384 # fill repository groups
378 for p in repo_info.users_group_to_perm:
385 for p in repo_info.users_group_to_perm:
379 defaults.update({'g_perm_%s' % p.users_group.users_group_id:
386 defaults.update({'g_perm_%s' % p.users_group.users_group_id:
380 p.permission.permission_name})
387 p.permission.permission_name})
381
388
382 return defaults
389 return defaults
383
390
384 def update(self, repo, **kwargs):
391 def update(self, repo, **kwargs):
385 try:
392 try:
386 cur_repo = self._get_repo(repo)
393 cur_repo = self._get_repo(repo)
387 source_repo_name = cur_repo.repo_name
394 source_repo_name = cur_repo.repo_name
388 if 'user' in kwargs:
395 if 'user' in kwargs:
389 cur_repo.user = User.get_by_username(kwargs['user'])
396 cur_repo.user = User.get_by_username(kwargs['user'])
390
397
391 if 'repo_group' in kwargs:
398 if 'repo_group' in kwargs:
392 cur_repo.group = RepoGroup.get(kwargs['repo_group'])
399 cur_repo.group = RepoGroup.get(kwargs['repo_group'])
393 log.debug('Updating repo %s with params:%s', cur_repo, kwargs)
400 log.debug('Updating repo %s with params:%s', cur_repo, kwargs)
394
401
395 update_keys = [
402 update_keys = [
396 (1, 'repo_description'),
403 (1, 'repo_description'),
397 (1, 'repo_landing_rev'),
404 (1, 'repo_landing_rev'),
398 (1, 'repo_private'),
405 (1, 'repo_private'),
399 (1, 'repo_enable_downloads'),
406 (1, 'repo_enable_downloads'),
400 (1, 'repo_enable_locking'),
407 (1, 'repo_enable_locking'),
401 (1, 'repo_enable_statistics'),
408 (1, 'repo_enable_statistics'),
402 (0, 'clone_uri'),
409 (0, 'clone_uri'),
403 (0, 'fork_id')
410 (0, 'fork_id')
404 ]
411 ]
405 for strip, k in update_keys:
412 for strip, k in update_keys:
406 if k in kwargs:
413 if k in kwargs:
407 val = kwargs[k]
414 val = kwargs[k]
408 if strip:
415 if strip:
409 k = remove_prefix(k, 'repo_')
416 k = remove_prefix(k, 'repo_')
410 if k == 'clone_uri':
417 if k == 'clone_uri':
411 from rhodecode.model.validators import Missing
418 from rhodecode.model.validators import Missing
412 _change = kwargs.get('clone_uri_change')
419 _change = kwargs.get('clone_uri_change')
413 if _change in [Missing, 'OLD']:
420 if _change in [Missing, 'OLD']:
414 # we don't change the value, so use original one
421 # we don't change the value, so use original one
415 val = cur_repo.clone_uri
422 val = cur_repo.clone_uri
416
423
417 setattr(cur_repo, k, val)
424 setattr(cur_repo, k, val)
418
425
419 new_name = cur_repo.get_new_name(kwargs['repo_name'])
426 new_name = cur_repo.get_new_name(kwargs['repo_name'])
420 cur_repo.repo_name = new_name
427 cur_repo.repo_name = new_name
421
428
422 # if private flag is set, reset default permission to NONE
429 # if private flag is set, reset default permission to NONE
423 if kwargs.get('repo_private'):
430 if kwargs.get('repo_private'):
424 EMPTY_PERM = 'repository.none'
431 EMPTY_PERM = 'repository.none'
425 RepoModel().grant_user_permission(
432 RepoModel().grant_user_permission(
426 repo=cur_repo, user=User.DEFAULT_USER, perm=EMPTY_PERM
433 repo=cur_repo, user=User.DEFAULT_USER, perm=EMPTY_PERM
427 )
434 )
428
435
429 # handle extra fields
436 # handle extra fields
430 for field in filter(lambda k: k.startswith(RepositoryField.PREFIX),
437 for field in filter(lambda k: k.startswith(RepositoryField.PREFIX),
431 kwargs):
438 kwargs):
432 k = RepositoryField.un_prefix_key(field)
439 k = RepositoryField.un_prefix_key(field)
433 ex_field = RepositoryField.get_by_key_name(
440 ex_field = RepositoryField.get_by_key_name(
434 key=k, repo=cur_repo)
441 key=k, repo=cur_repo)
435 if ex_field:
442 if ex_field:
436 ex_field.field_value = kwargs[field]
443 ex_field.field_value = kwargs[field]
437 self.sa.add(ex_field)
444 self.sa.add(ex_field)
438 self.sa.add(cur_repo)
445 self.sa.add(cur_repo)
439
446
440 if source_repo_name != new_name:
447 if source_repo_name != new_name:
441 # rename repository
448 # rename repository
442 self._rename_filesystem_repo(
449 self._rename_filesystem_repo(
443 old=source_repo_name, new=new_name)
450 old=source_repo_name, new=new_name)
444
451
445 return cur_repo
452 return cur_repo
446 except Exception:
453 except Exception:
447 log.error(traceback.format_exc())
454 log.error(traceback.format_exc())
448 raise
455 raise
449
456
450 def _create_repo(self, repo_name, repo_type, description, owner,
457 def _create_repo(self, repo_name, repo_type, description, owner,
451 private=False, clone_uri=None, repo_group=None,
458 private=False, clone_uri=None, repo_group=None,
452 landing_rev='rev:tip', fork_of=None,
459 landing_rev='rev:tip', fork_of=None,
453 copy_fork_permissions=False, enable_statistics=False,
460 copy_fork_permissions=False, enable_statistics=False,
454 enable_locking=False, enable_downloads=False,
461 enable_locking=False, enable_downloads=False,
455 copy_group_permissions=False,
462 copy_group_permissions=False,
456 state=Repository.STATE_PENDING):
463 state=Repository.STATE_PENDING):
457 """
464 """
458 Create repository inside database with PENDING state, this should be
465 Create repository inside database with PENDING state, this should be
459 only executed by create() repo. With exception of importing existing
466 only executed by create() repo. With exception of importing existing
460 repos
467 repos
461 """
468 """
462 from rhodecode.model.scm import ScmModel
469 from rhodecode.model.scm import ScmModel
463
470
464 owner = self._get_user(owner)
471 owner = self._get_user(owner)
465 fork_of = self._get_repo(fork_of)
472 fork_of = self._get_repo(fork_of)
466 repo_group = self._get_repo_group(safe_int(repo_group))
473 repo_group = self._get_repo_group(safe_int(repo_group))
467
474
468 try:
475 try:
469 repo_name = safe_unicode(repo_name)
476 repo_name = safe_unicode(repo_name)
470 description = safe_unicode(description)
477 description = safe_unicode(description)
471 # repo name is just a name of repository
478 # repo name is just a name of repository
472 # while repo_name_full is a full qualified name that is combined
479 # while repo_name_full is a full qualified name that is combined
473 # with name and path of group
480 # with name and path of group
474 repo_name_full = repo_name
481 repo_name_full = repo_name
475 repo_name = repo_name.split(Repository.NAME_SEP)[-1]
482 repo_name = repo_name.split(Repository.NAME_SEP)[-1]
476
483
477 new_repo = Repository()
484 new_repo = Repository()
478 new_repo.repo_state = state
485 new_repo.repo_state = state
479 new_repo.enable_statistics = False
486 new_repo.enable_statistics = False
480 new_repo.repo_name = repo_name_full
487 new_repo.repo_name = repo_name_full
481 new_repo.repo_type = repo_type
488 new_repo.repo_type = repo_type
482 new_repo.user = owner
489 new_repo.user = owner
483 new_repo.group = repo_group
490 new_repo.group = repo_group
484 new_repo.description = description or repo_name
491 new_repo.description = description or repo_name
485 new_repo.private = private
492 new_repo.private = private
486 new_repo.clone_uri = clone_uri
493 new_repo.clone_uri = clone_uri
487 new_repo.landing_rev = landing_rev
494 new_repo.landing_rev = landing_rev
488
495
489 new_repo.enable_statistics = enable_statistics
496 new_repo.enable_statistics = enable_statistics
490 new_repo.enable_locking = enable_locking
497 new_repo.enable_locking = enable_locking
491 new_repo.enable_downloads = enable_downloads
498 new_repo.enable_downloads = enable_downloads
492
499
493 if repo_group:
500 if repo_group:
494 new_repo.enable_locking = repo_group.enable_locking
501 new_repo.enable_locking = repo_group.enable_locking
495
502
496 if fork_of:
503 if fork_of:
497 parent_repo = fork_of
504 parent_repo = fork_of
498 new_repo.fork = parent_repo
505 new_repo.fork = parent_repo
499
506
500 events.trigger(events.RepoPreCreateEvent(new_repo))
507 events.trigger(events.RepoPreCreateEvent(new_repo))
501
508
502 self.sa.add(new_repo)
509 self.sa.add(new_repo)
503
510
504 EMPTY_PERM = 'repository.none'
511 EMPTY_PERM = 'repository.none'
505 if fork_of and copy_fork_permissions:
512 if fork_of and copy_fork_permissions:
506 repo = fork_of
513 repo = fork_of
507 user_perms = UserRepoToPerm.query() \
514 user_perms = UserRepoToPerm.query() \
508 .filter(UserRepoToPerm.repository == repo).all()
515 .filter(UserRepoToPerm.repository == repo).all()
509 group_perms = UserGroupRepoToPerm.query() \
516 group_perms = UserGroupRepoToPerm.query() \
510 .filter(UserGroupRepoToPerm.repository == repo).all()
517 .filter(UserGroupRepoToPerm.repository == repo).all()
511
518
512 for perm in user_perms:
519 for perm in user_perms:
513 UserRepoToPerm.create(
520 UserRepoToPerm.create(
514 perm.user, new_repo, perm.permission)
521 perm.user, new_repo, perm.permission)
515
522
516 for perm in group_perms:
523 for perm in group_perms:
517 UserGroupRepoToPerm.create(
524 UserGroupRepoToPerm.create(
518 perm.users_group, new_repo, perm.permission)
525 perm.users_group, new_repo, perm.permission)
519 # in case we copy permissions and also set this repo to private
526 # in case we copy permissions and also set this repo to private
520 # override the default user permission to make it a private
527 # override the default user permission to make it a private
521 # repo
528 # repo
522 if private:
529 if private:
523 RepoModel(self.sa).grant_user_permission(
530 RepoModel(self.sa).grant_user_permission(
524 repo=new_repo, user=User.DEFAULT_USER, perm=EMPTY_PERM)
531 repo=new_repo, user=User.DEFAULT_USER, perm=EMPTY_PERM)
525
532
526 elif repo_group and copy_group_permissions:
533 elif repo_group and copy_group_permissions:
527 user_perms = UserRepoGroupToPerm.query() \
534 user_perms = UserRepoGroupToPerm.query() \
528 .filter(UserRepoGroupToPerm.group == repo_group).all()
535 .filter(UserRepoGroupToPerm.group == repo_group).all()
529
536
530 group_perms = UserGroupRepoGroupToPerm.query() \
537 group_perms = UserGroupRepoGroupToPerm.query() \
531 .filter(UserGroupRepoGroupToPerm.group == repo_group).all()
538 .filter(UserGroupRepoGroupToPerm.group == repo_group).all()
532
539
533 for perm in user_perms:
540 for perm in user_perms:
534 perm_name = perm.permission.permission_name.replace(
541 perm_name = perm.permission.permission_name.replace(
535 'group.', 'repository.')
542 'group.', 'repository.')
536 perm_obj = Permission.get_by_key(perm_name)
543 perm_obj = Permission.get_by_key(perm_name)
537 UserRepoToPerm.create(perm.user, new_repo, perm_obj)
544 UserRepoToPerm.create(perm.user, new_repo, perm_obj)
538
545
539 for perm in group_perms:
546 for perm in group_perms:
540 perm_name = perm.permission.permission_name.replace(
547 perm_name = perm.permission.permission_name.replace(
541 'group.', 'repository.')
548 'group.', 'repository.')
542 perm_obj = Permission.get_by_key(perm_name)
549 perm_obj = Permission.get_by_key(perm_name)
543 UserGroupRepoToPerm.create(
550 UserGroupRepoToPerm.create(
544 perm.users_group, new_repo, perm_obj)
551 perm.users_group, new_repo, perm_obj)
545
552
546 if private:
553 if private:
547 RepoModel(self.sa).grant_user_permission(
554 RepoModel(self.sa).grant_user_permission(
548 repo=new_repo, user=User.DEFAULT_USER, perm=EMPTY_PERM)
555 repo=new_repo, user=User.DEFAULT_USER, perm=EMPTY_PERM)
549
556
550 else:
557 else:
551 perm_obj = self._create_default_perms(new_repo, private)
558 perm_obj = self._create_default_perms(new_repo, private)
552 self.sa.add(perm_obj)
559 self.sa.add(perm_obj)
553
560
554 # now automatically start following this repository as owner
561 # now automatically start following this repository as owner
555 ScmModel(self.sa).toggle_following_repo(new_repo.repo_id,
562 ScmModel(self.sa).toggle_following_repo(new_repo.repo_id,
556 owner.user_id)
563 owner.user_id)
557
564
558 # we need to flush here, in order to check if database won't
565 # we need to flush here, in order to check if database won't
559 # throw any exceptions, create filesystem dirs at the very end
566 # throw any exceptions, create filesystem dirs at the very end
560 self.sa.flush()
567 self.sa.flush()
561 events.trigger(events.RepoCreateEvent(new_repo))
568 events.trigger(events.RepoCreateEvent(new_repo))
562 return new_repo
569 return new_repo
563
570
564 except Exception:
571 except Exception:
565 log.error(traceback.format_exc())
572 log.error(traceback.format_exc())
566 raise
573 raise
567
574
568 def create(self, form_data, cur_user):
575 def create(self, form_data, cur_user):
569 """
576 """
570 Create repository using celery tasks
577 Create repository using celery tasks
571
578
572 :param form_data:
579 :param form_data:
573 :param cur_user:
580 :param cur_user:
574 """
581 """
575 from rhodecode.lib.celerylib import tasks, run_task
582 from rhodecode.lib.celerylib import tasks, run_task
576 return run_task(tasks.create_repo, form_data, cur_user)
583 return run_task(tasks.create_repo, form_data, cur_user)
577
584
578 def update_permissions(self, repo, perm_additions=None, perm_updates=None,
585 def update_permissions(self, repo, perm_additions=None, perm_updates=None,
579 perm_deletions=None, check_perms=True,
586 perm_deletions=None, check_perms=True,
580 cur_user=None):
587 cur_user=None):
581 if not perm_additions:
588 if not perm_additions:
582 perm_additions = []
589 perm_additions = []
583 if not perm_updates:
590 if not perm_updates:
584 perm_updates = []
591 perm_updates = []
585 if not perm_deletions:
592 if not perm_deletions:
586 perm_deletions = []
593 perm_deletions = []
587
594
588 req_perms = ('usergroup.read', 'usergroup.write', 'usergroup.admin')
595 req_perms = ('usergroup.read', 'usergroup.write', 'usergroup.admin')
589
596
590 # update permissions
597 # update permissions
591 for member_id, perm, member_type in perm_updates:
598 for member_id, perm, member_type in perm_updates:
592 member_id = int(member_id)
599 member_id = int(member_id)
593 if member_type == 'user':
600 if member_type == 'user':
594 # this updates also current one if found
601 # this updates also current one if found
595 self.grant_user_permission(
602 self.grant_user_permission(
596 repo=repo, user=member_id, perm=perm)
603 repo=repo, user=member_id, perm=perm)
597 else: # set for user group
604 else: # set for user group
598 # check if we have permissions to alter this usergroup
605 # check if we have permissions to alter this usergroup
599 member_name = UserGroup.get(member_id).users_group_name
606 member_name = UserGroup.get(member_id).users_group_name
600 if not check_perms or HasUserGroupPermissionAny(
607 if not check_perms or HasUserGroupPermissionAny(
601 *req_perms)(member_name, user=cur_user):
608 *req_perms)(member_name, user=cur_user):
602 self.grant_user_group_permission(
609 self.grant_user_group_permission(
603 repo=repo, group_name=member_id, perm=perm)
610 repo=repo, group_name=member_id, perm=perm)
604
611
605 # set new permissions
612 # set new permissions
606 for member_id, perm, member_type in perm_additions:
613 for member_id, perm, member_type in perm_additions:
607 member_id = int(member_id)
614 member_id = int(member_id)
608 if member_type == 'user':
615 if member_type == 'user':
609 self.grant_user_permission(
616 self.grant_user_permission(
610 repo=repo, user=member_id, perm=perm)
617 repo=repo, user=member_id, perm=perm)
611 else: # set for user group
618 else: # set for user group
612 # check if we have permissions to alter this usergroup
619 # check if we have permissions to alter this usergroup
613 member_name = UserGroup.get(member_id).users_group_name
620 member_name = UserGroup.get(member_id).users_group_name
614 if not check_perms or HasUserGroupPermissionAny(
621 if not check_perms or HasUserGroupPermissionAny(
615 *req_perms)(member_name, user=cur_user):
622 *req_perms)(member_name, user=cur_user):
616 self.grant_user_group_permission(
623 self.grant_user_group_permission(
617 repo=repo, group_name=member_id, perm=perm)
624 repo=repo, group_name=member_id, perm=perm)
618
625
619 # delete permissions
626 # delete permissions
620 for member_id, perm, member_type in perm_deletions:
627 for member_id, perm, member_type in perm_deletions:
621 member_id = int(member_id)
628 member_id = int(member_id)
622 if member_type == 'user':
629 if member_type == 'user':
623 self.revoke_user_permission(repo=repo, user=member_id)
630 self.revoke_user_permission(repo=repo, user=member_id)
624 else: # set for user group
631 else: # set for user group
625 # check if we have permissions to alter this usergroup
632 # check if we have permissions to alter this usergroup
626 member_name = UserGroup.get(member_id).users_group_name
633 member_name = UserGroup.get(member_id).users_group_name
627 if not check_perms or HasUserGroupPermissionAny(
634 if not check_perms or HasUserGroupPermissionAny(
628 *req_perms)(member_name, user=cur_user):
635 *req_perms)(member_name, user=cur_user):
629 self.revoke_user_group_permission(
636 self.revoke_user_group_permission(
630 repo=repo, group_name=member_id)
637 repo=repo, group_name=member_id)
631
638
632 def create_fork(self, form_data, cur_user):
639 def create_fork(self, form_data, cur_user):
633 """
640 """
634 Simple wrapper into executing celery task for fork creation
641 Simple wrapper into executing celery task for fork creation
635
642
636 :param form_data:
643 :param form_data:
637 :param cur_user:
644 :param cur_user:
638 """
645 """
639 from rhodecode.lib.celerylib import tasks, run_task
646 from rhodecode.lib.celerylib import tasks, run_task
640 return run_task(tasks.create_repo_fork, form_data, cur_user)
647 return run_task(tasks.create_repo_fork, form_data, cur_user)
641
648
642 def delete(self, repo, forks=None, fs_remove=True, cur_user=None):
649 def delete(self, repo, forks=None, fs_remove=True, cur_user=None):
643 """
650 """
644 Delete given repository, forks parameter defines what do do with
651 Delete given repository, forks parameter defines what do do with
645 attached forks. Throws AttachedForksError if deleted repo has attached
652 attached forks. Throws AttachedForksError if deleted repo has attached
646 forks
653 forks
647
654
648 :param repo:
655 :param repo:
649 :param forks: str 'delete' or 'detach'
656 :param forks: str 'delete' or 'detach'
650 :param fs_remove: remove(archive) repo from filesystem
657 :param fs_remove: remove(archive) repo from filesystem
651 """
658 """
652 if not cur_user:
659 if not cur_user:
653 cur_user = getattr(get_current_rhodecode_user(), 'username', None)
660 cur_user = getattr(get_current_rhodecode_user(), 'username', None)
654 repo = self._get_repo(repo)
661 repo = self._get_repo(repo)
655 if repo:
662 if repo:
656 if forks == 'detach':
663 if forks == 'detach':
657 for r in repo.forks:
664 for r in repo.forks:
658 r.fork = None
665 r.fork = None
659 self.sa.add(r)
666 self.sa.add(r)
660 elif forks == 'delete':
667 elif forks == 'delete':
661 for r in repo.forks:
668 for r in repo.forks:
662 self.delete(r, forks='delete')
669 self.delete(r, forks='delete')
663 elif [f for f in repo.forks]:
670 elif [f for f in repo.forks]:
664 raise AttachedForksError()
671 raise AttachedForksError()
665
672
666 old_repo_dict = repo.get_dict()
673 old_repo_dict = repo.get_dict()
667 events.trigger(events.RepoPreDeleteEvent(repo))
674 events.trigger(events.RepoPreDeleteEvent(repo))
668 try:
675 try:
669 self.sa.delete(repo)
676 self.sa.delete(repo)
670 if fs_remove:
677 if fs_remove:
671 self._delete_filesystem_repo(repo)
678 self._delete_filesystem_repo(repo)
672 else:
679 else:
673 log.debug('skipping removal from filesystem')
680 log.debug('skipping removal from filesystem')
674 old_repo_dict.update({
681 old_repo_dict.update({
675 'deleted_by': cur_user,
682 'deleted_by': cur_user,
676 'deleted_on': time.time(),
683 'deleted_on': time.time(),
677 })
684 })
678 log_delete_repository(**old_repo_dict)
685 log_delete_repository(**old_repo_dict)
679 events.trigger(events.RepoDeleteEvent(repo))
686 events.trigger(events.RepoDeleteEvent(repo))
680 except Exception:
687 except Exception:
681 log.error(traceback.format_exc())
688 log.error(traceback.format_exc())
682 raise
689 raise
683
690
684 def grant_user_permission(self, repo, user, perm):
691 def grant_user_permission(self, repo, user, perm):
685 """
692 """
686 Grant permission for user on given repository, or update existing one
693 Grant permission for user on given repository, or update existing one
687 if found
694 if found
688
695
689 :param repo: Instance of Repository, repository_id, or repository name
696 :param repo: Instance of Repository, repository_id, or repository name
690 :param user: Instance of User, user_id or username
697 :param user: Instance of User, user_id or username
691 :param perm: Instance of Permission, or permission_name
698 :param perm: Instance of Permission, or permission_name
692 """
699 """
693 user = self._get_user(user)
700 user = self._get_user(user)
694 repo = self._get_repo(repo)
701 repo = self._get_repo(repo)
695 permission = self._get_perm(perm)
702 permission = self._get_perm(perm)
696
703
697 # check if we have that permission already
704 # check if we have that permission already
698 obj = self.sa.query(UserRepoToPerm) \
705 obj = self.sa.query(UserRepoToPerm) \
699 .filter(UserRepoToPerm.user == user) \
706 .filter(UserRepoToPerm.user == user) \
700 .filter(UserRepoToPerm.repository == repo) \
707 .filter(UserRepoToPerm.repository == repo) \
701 .scalar()
708 .scalar()
702 if obj is None:
709 if obj is None:
703 # create new !
710 # create new !
704 obj = UserRepoToPerm()
711 obj = UserRepoToPerm()
705 obj.repository = repo
712 obj.repository = repo
706 obj.user = user
713 obj.user = user
707 obj.permission = permission
714 obj.permission = permission
708 self.sa.add(obj)
715 self.sa.add(obj)
709 log.debug('Granted perm %s to %s on %s', perm, user, repo)
716 log.debug('Granted perm %s to %s on %s', perm, user, repo)
710 action_logger_generic(
717 action_logger_generic(
711 'granted permission: {} to user: {} on repo: {}'.format(
718 'granted permission: {} to user: {} on repo: {}'.format(
712 perm, user, repo), namespace='security.repo')
719 perm, user, repo), namespace='security.repo')
713 return obj
720 return obj
714
721
715 def revoke_user_permission(self, repo, user):
722 def revoke_user_permission(self, repo, user):
716 """
723 """
717 Revoke permission for user on given repository
724 Revoke permission for user on given repository
718
725
719 :param repo: Instance of Repository, repository_id, or repository name
726 :param repo: Instance of Repository, repository_id, or repository name
720 :param user: Instance of User, user_id or username
727 :param user: Instance of User, user_id or username
721 """
728 """
722
729
723 user = self._get_user(user)
730 user = self._get_user(user)
724 repo = self._get_repo(repo)
731 repo = self._get_repo(repo)
725
732
726 obj = self.sa.query(UserRepoToPerm) \
733 obj = self.sa.query(UserRepoToPerm) \
727 .filter(UserRepoToPerm.repository == repo) \
734 .filter(UserRepoToPerm.repository == repo) \
728 .filter(UserRepoToPerm.user == user) \
735 .filter(UserRepoToPerm.user == user) \
729 .scalar()
736 .scalar()
730 if obj:
737 if obj:
731 self.sa.delete(obj)
738 self.sa.delete(obj)
732 log.debug('Revoked perm on %s on %s', repo, user)
739 log.debug('Revoked perm on %s on %s', repo, user)
733 action_logger_generic(
740 action_logger_generic(
734 'revoked permission from user: {} on repo: {}'.format(
741 'revoked permission from user: {} on repo: {}'.format(
735 user, repo), namespace='security.repo')
742 user, repo), namespace='security.repo')
736
743
737 def grant_user_group_permission(self, repo, group_name, perm):
744 def grant_user_group_permission(self, repo, group_name, perm):
738 """
745 """
739 Grant permission for user group on given repository, or update
746 Grant permission for user group on given repository, or update
740 existing one if found
747 existing one if found
741
748
742 :param repo: Instance of Repository, repository_id, or repository name
749 :param repo: Instance of Repository, repository_id, or repository name
743 :param group_name: Instance of UserGroup, users_group_id,
750 :param group_name: Instance of UserGroup, users_group_id,
744 or user group name
751 or user group name
745 :param perm: Instance of Permission, or permission_name
752 :param perm: Instance of Permission, or permission_name
746 """
753 """
747 repo = self._get_repo(repo)
754 repo = self._get_repo(repo)
748 group_name = self._get_user_group(group_name)
755 group_name = self._get_user_group(group_name)
749 permission = self._get_perm(perm)
756 permission = self._get_perm(perm)
750
757
751 # check if we have that permission already
758 # check if we have that permission already
752 obj = self.sa.query(UserGroupRepoToPerm) \
759 obj = self.sa.query(UserGroupRepoToPerm) \
753 .filter(UserGroupRepoToPerm.users_group == group_name) \
760 .filter(UserGroupRepoToPerm.users_group == group_name) \
754 .filter(UserGroupRepoToPerm.repository == repo) \
761 .filter(UserGroupRepoToPerm.repository == repo) \
755 .scalar()
762 .scalar()
756
763
757 if obj is None:
764 if obj is None:
758 # create new
765 # create new
759 obj = UserGroupRepoToPerm()
766 obj = UserGroupRepoToPerm()
760
767
761 obj.repository = repo
768 obj.repository = repo
762 obj.users_group = group_name
769 obj.users_group = group_name
763 obj.permission = permission
770 obj.permission = permission
764 self.sa.add(obj)
771 self.sa.add(obj)
765 log.debug('Granted perm %s to %s on %s', perm, group_name, repo)
772 log.debug('Granted perm %s to %s on %s', perm, group_name, repo)
766 action_logger_generic(
773 action_logger_generic(
767 'granted permission: {} to usergroup: {} on repo: {}'.format(
774 'granted permission: {} to usergroup: {} on repo: {}'.format(
768 perm, group_name, repo), namespace='security.repo')
775 perm, group_name, repo), namespace='security.repo')
769
776
770 return obj
777 return obj
771
778
772 def revoke_user_group_permission(self, repo, group_name):
779 def revoke_user_group_permission(self, repo, group_name):
773 """
780 """
774 Revoke permission for user group on given repository
781 Revoke permission for user group on given repository
775
782
776 :param repo: Instance of Repository, repository_id, or repository name
783 :param repo: Instance of Repository, repository_id, or repository name
777 :param group_name: Instance of UserGroup, users_group_id,
784 :param group_name: Instance of UserGroup, users_group_id,
778 or user group name
785 or user group name
779 """
786 """
780 repo = self._get_repo(repo)
787 repo = self._get_repo(repo)
781 group_name = self._get_user_group(group_name)
788 group_name = self._get_user_group(group_name)
782
789
783 obj = self.sa.query(UserGroupRepoToPerm) \
790 obj = self.sa.query(UserGroupRepoToPerm) \
784 .filter(UserGroupRepoToPerm.repository == repo) \
791 .filter(UserGroupRepoToPerm.repository == repo) \
785 .filter(UserGroupRepoToPerm.users_group == group_name) \
792 .filter(UserGroupRepoToPerm.users_group == group_name) \
786 .scalar()
793 .scalar()
787 if obj:
794 if obj:
788 self.sa.delete(obj)
795 self.sa.delete(obj)
789 log.debug('Revoked perm to %s on %s', repo, group_name)
796 log.debug('Revoked perm to %s on %s', repo, group_name)
790 action_logger_generic(
797 action_logger_generic(
791 'revoked permission from usergroup: {} on repo: {}'.format(
798 'revoked permission from usergroup: {} on repo: {}'.format(
792 group_name, repo), namespace='security.repo')
799 group_name, repo), namespace='security.repo')
793
800
794 def delete_stats(self, repo_name):
801 def delete_stats(self, repo_name):
795 """
802 """
796 removes stats for given repo
803 removes stats for given repo
797
804
798 :param repo_name:
805 :param repo_name:
799 """
806 """
800 repo = self._get_repo(repo_name)
807 repo = self._get_repo(repo_name)
801 try:
808 try:
802 obj = self.sa.query(Statistics) \
809 obj = self.sa.query(Statistics) \
803 .filter(Statistics.repository == repo).scalar()
810 .filter(Statistics.repository == repo).scalar()
804 if obj:
811 if obj:
805 self.sa.delete(obj)
812 self.sa.delete(obj)
806 except Exception:
813 except Exception:
807 log.error(traceback.format_exc())
814 log.error(traceback.format_exc())
808 raise
815 raise
809
816
810 def add_repo_field(self, repo_name, field_key, field_label, field_value='',
817 def add_repo_field(self, repo_name, field_key, field_label, field_value='',
811 field_type='str', field_desc=''):
818 field_type='str', field_desc=''):
812
819
813 repo = self._get_repo(repo_name)
820 repo = self._get_repo(repo_name)
814
821
815 new_field = RepositoryField()
822 new_field = RepositoryField()
816 new_field.repository = repo
823 new_field.repository = repo
817 new_field.field_key = field_key
824 new_field.field_key = field_key
818 new_field.field_type = field_type # python type
825 new_field.field_type = field_type # python type
819 new_field.field_value = field_value
826 new_field.field_value = field_value
820 new_field.field_desc = field_desc
827 new_field.field_desc = field_desc
821 new_field.field_label = field_label
828 new_field.field_label = field_label
822 self.sa.add(new_field)
829 self.sa.add(new_field)
823 return new_field
830 return new_field
824
831
825 def delete_repo_field(self, repo_name, field_key):
832 def delete_repo_field(self, repo_name, field_key):
826 repo = self._get_repo(repo_name)
833 repo = self._get_repo(repo_name)
827 field = RepositoryField.get_by_key_name(field_key, repo)
834 field = RepositoryField.get_by_key_name(field_key, repo)
828 if field:
835 if field:
829 self.sa.delete(field)
836 self.sa.delete(field)
830
837
831 def _create_filesystem_repo(self, repo_name, repo_type, repo_group,
838 def _create_filesystem_repo(self, repo_name, repo_type, repo_group,
832 clone_uri=None, repo_store_location=None,
839 clone_uri=None, repo_store_location=None,
833 use_global_config=False):
840 use_global_config=False):
834 """
841 """
835 makes repository on filesystem. It's group aware means it'll create
842 makes repository on filesystem. It's group aware means it'll create
836 a repository within a group, and alter the paths accordingly of
843 a repository within a group, and alter the paths accordingly of
837 group location
844 group location
838
845
839 :param repo_name:
846 :param repo_name:
840 :param alias:
847 :param alias:
841 :param parent:
848 :param parent:
842 :param clone_uri:
849 :param clone_uri:
843 :param repo_store_location:
850 :param repo_store_location:
844 """
851 """
845 from rhodecode.lib.utils import is_valid_repo, is_valid_repo_group
852 from rhodecode.lib.utils import is_valid_repo, is_valid_repo_group
846 from rhodecode.model.scm import ScmModel
853 from rhodecode.model.scm import ScmModel
847
854
848 if Repository.NAME_SEP in repo_name:
855 if Repository.NAME_SEP in repo_name:
849 raise ValueError(
856 raise ValueError(
850 'repo_name must not contain groups got `%s`' % repo_name)
857 'repo_name must not contain groups got `%s`' % repo_name)
851
858
852 if isinstance(repo_group, RepoGroup):
859 if isinstance(repo_group, RepoGroup):
853 new_parent_path = os.sep.join(repo_group.full_path_splitted)
860 new_parent_path = os.sep.join(repo_group.full_path_splitted)
854 else:
861 else:
855 new_parent_path = repo_group or ''
862 new_parent_path = repo_group or ''
856
863
857 if repo_store_location:
864 if repo_store_location:
858 _paths = [repo_store_location]
865 _paths = [repo_store_location]
859 else:
866 else:
860 _paths = [self.repos_path, new_parent_path, repo_name]
867 _paths = [self.repos_path, new_parent_path, repo_name]
861 # we need to make it str for mercurial
868 # we need to make it str for mercurial
862 repo_path = os.path.join(*map(lambda x: safe_str(x), _paths))
869 repo_path = os.path.join(*map(lambda x: safe_str(x), _paths))
863
870
864 # check if this path is not a repository
871 # check if this path is not a repository
865 if is_valid_repo(repo_path, self.repos_path):
872 if is_valid_repo(repo_path, self.repos_path):
866 raise Exception('This path %s is a valid repository' % repo_path)
873 raise Exception('This path %s is a valid repository' % repo_path)
867
874
868 # check if this path is a group
875 # check if this path is a group
869 if is_valid_repo_group(repo_path, self.repos_path):
876 if is_valid_repo_group(repo_path, self.repos_path):
870 raise Exception('This path %s is a valid group' % repo_path)
877 raise Exception('This path %s is a valid group' % repo_path)
871
878
872 log.info('creating repo %s in %s from url: `%s`',
879 log.info('creating repo %s in %s from url: `%s`',
873 repo_name, safe_unicode(repo_path),
880 repo_name, safe_unicode(repo_path),
874 obfuscate_url_pw(clone_uri))
881 obfuscate_url_pw(clone_uri))
875
882
876 backend = get_backend(repo_type)
883 backend = get_backend(repo_type)
877
884
878 config_repo = None if use_global_config else repo_name
885 config_repo = None if use_global_config else repo_name
879 if config_repo and new_parent_path:
886 if config_repo and new_parent_path:
880 config_repo = Repository.NAME_SEP.join(
887 config_repo = Repository.NAME_SEP.join(
881 (new_parent_path, config_repo))
888 (new_parent_path, config_repo))
882 config = make_db_config(clear_session=False, repo=config_repo)
889 config = make_db_config(clear_session=False, repo=config_repo)
883 config.set('extensions', 'largefiles', '')
890 config.set('extensions', 'largefiles', '')
884
891
885 # patch and reset hooks section of UI config to not run any
892 # patch and reset hooks section of UI config to not run any
886 # hooks on creating remote repo
893 # hooks on creating remote repo
887 config.clear_section('hooks')
894 config.clear_section('hooks')
888
895
889 # TODO: johbo: Unify this, hardcoded "bare=True" does not look nice
896 # TODO: johbo: Unify this, hardcoded "bare=True" does not look nice
890 if repo_type == 'git':
897 if repo_type == 'git':
891 repo = backend(
898 repo = backend(
892 repo_path, config=config, create=True, src_url=clone_uri,
899 repo_path, config=config, create=True, src_url=clone_uri,
893 bare=True)
900 bare=True)
894 else:
901 else:
895 repo = backend(
902 repo = backend(
896 repo_path, config=config, create=True, src_url=clone_uri)
903 repo_path, config=config, create=True, src_url=clone_uri)
897
904
898 ScmModel().install_hooks(repo, repo_type=repo_type)
905 ScmModel().install_hooks(repo, repo_type=repo_type)
899
906
900 log.debug('Created repo %s with %s backend',
907 log.debug('Created repo %s with %s backend',
901 safe_unicode(repo_name), safe_unicode(repo_type))
908 safe_unicode(repo_name), safe_unicode(repo_type))
902 return repo
909 return repo
903
910
904 def _rename_filesystem_repo(self, old, new):
911 def _rename_filesystem_repo(self, old, new):
905 """
912 """
906 renames repository on filesystem
913 renames repository on filesystem
907
914
908 :param old: old name
915 :param old: old name
909 :param new: new name
916 :param new: new name
910 """
917 """
911 log.info('renaming repo from %s to %s', old, new)
918 log.info('renaming repo from %s to %s', old, new)
912
919
913 old_path = os.path.join(self.repos_path, old)
920 old_path = os.path.join(self.repos_path, old)
914 new_path = os.path.join(self.repos_path, new)
921 new_path = os.path.join(self.repos_path, new)
915 if os.path.isdir(new_path):
922 if os.path.isdir(new_path):
916 raise Exception(
923 raise Exception(
917 'Was trying to rename to already existing dir %s' % new_path
924 'Was trying to rename to already existing dir %s' % new_path
918 )
925 )
919 shutil.move(old_path, new_path)
926 shutil.move(old_path, new_path)
920
927
921 def _delete_filesystem_repo(self, repo):
928 def _delete_filesystem_repo(self, repo):
922 """
929 """
923 removes repo from filesystem, the removal is acctually made by
930 removes repo from filesystem, the removal is acctually made by
924 added rm__ prefix into dir, and rename internat .hg/.git dirs so this
931 added rm__ prefix into dir, and rename internat .hg/.git dirs so this
925 repository is no longer valid for rhodecode, can be undeleted later on
932 repository is no longer valid for rhodecode, can be undeleted later on
926 by reverting the renames on this repository
933 by reverting the renames on this repository
927
934
928 :param repo: repo object
935 :param repo: repo object
929 """
936 """
930 rm_path = os.path.join(self.repos_path, repo.repo_name)
937 rm_path = os.path.join(self.repos_path, repo.repo_name)
931 repo_group = repo.group
938 repo_group = repo.group
932 log.info("Removing repository %s", rm_path)
939 log.info("Removing repository %s", rm_path)
933 # disable hg/git internal that it doesn't get detected as repo
940 # disable hg/git internal that it doesn't get detected as repo
934 alias = repo.repo_type
941 alias = repo.repo_type
935
942
936 config = make_db_config(clear_session=False)
943 config = make_db_config(clear_session=False)
937 config.set('extensions', 'largefiles', '')
944 config.set('extensions', 'largefiles', '')
938 bare = getattr(repo.scm_instance(config=config), 'bare', False)
945 bare = getattr(repo.scm_instance(config=config), 'bare', False)
939
946
940 # skip this for bare git repos
947 # skip this for bare git repos
941 if not bare:
948 if not bare:
942 # disable VCS repo
949 # disable VCS repo
943 vcs_path = os.path.join(rm_path, '.%s' % alias)
950 vcs_path = os.path.join(rm_path, '.%s' % alias)
944 if os.path.exists(vcs_path):
951 if os.path.exists(vcs_path):
945 shutil.move(vcs_path, os.path.join(rm_path, 'rm__.%s' % alias))
952 shutil.move(vcs_path, os.path.join(rm_path, 'rm__.%s' % alias))
946
953
947 _now = datetime.now()
954 _now = datetime.now()
948 _ms = str(_now.microsecond).rjust(6, '0')
955 _ms = str(_now.microsecond).rjust(6, '0')
949 _d = 'rm__%s__%s' % (_now.strftime('%Y%m%d_%H%M%S_' + _ms),
956 _d = 'rm__%s__%s' % (_now.strftime('%Y%m%d_%H%M%S_' + _ms),
950 repo.just_name)
957 repo.just_name)
951 if repo_group:
958 if repo_group:
952 # if repository is in group, prefix the removal path with the group
959 # if repository is in group, prefix the removal path with the group
953 args = repo_group.full_path_splitted + [_d]
960 args = repo_group.full_path_splitted + [_d]
954 _d = os.path.join(*args)
961 _d = os.path.join(*args)
955
962
956 if os.path.isdir(rm_path):
963 if os.path.isdir(rm_path):
957 shutil.move(rm_path, os.path.join(self.repos_path, _d))
964 shutil.move(rm_path, os.path.join(self.repos_path, _d))
958
965
959
966
960 class ReadmeFinder:
967 class ReadmeFinder:
961 """
968 """
962 Utility which knows how to find a readme for a specific commit.
969 Utility which knows how to find a readme for a specific commit.
963
970
964 The main idea is that this is a configurable algorithm. When creating an
971 The main idea is that this is a configurable algorithm. When creating an
965 instance you can define parameters, currently only the `default_renderer`.
972 instance you can define parameters, currently only the `default_renderer`.
966 Based on this configuration the method :meth:`search` behaves slightly
973 Based on this configuration the method :meth:`search` behaves slightly
967 different.
974 different.
968 """
975 """
969
976
970 readme_re = re.compile(r'^readme(\.[^\.]+)?$', re.IGNORECASE)
977 readme_re = re.compile(r'^readme(\.[^\.]+)?$', re.IGNORECASE)
971 path_re = re.compile(r'^docs?', re.IGNORECASE)
978 path_re = re.compile(r'^docs?', re.IGNORECASE)
972
979
973 default_priorities = {
980 default_priorities = {
974 None: 0,
981 None: 0,
975 '.text': 2,
982 '.text': 2,
976 '.txt': 3,
983 '.txt': 3,
977 '.rst': 1,
984 '.rst': 1,
978 '.rest': 2,
985 '.rest': 2,
979 '.md': 1,
986 '.md': 1,
980 '.mkdn': 2,
987 '.mkdn': 2,
981 '.mdown': 3,
988 '.mdown': 3,
982 '.markdown': 4,
989 '.markdown': 4,
983 }
990 }
984
991
985 path_priority = {
992 path_priority = {
986 'doc': 0,
993 'doc': 0,
987 'docs': 1,
994 'docs': 1,
988 }
995 }
989
996
990 FALLBACK_PRIORITY = 99
997 FALLBACK_PRIORITY = 99
991
998
992 RENDERER_TO_EXTENSION = {
999 RENDERER_TO_EXTENSION = {
993 'rst': ['.rst', '.rest'],
1000 'rst': ['.rst', '.rest'],
994 'markdown': ['.md', 'mkdn', '.mdown', '.markdown'],
1001 'markdown': ['.md', 'mkdn', '.mdown', '.markdown'],
995 }
1002 }
996
1003
997 def __init__(self, default_renderer=None):
1004 def __init__(self, default_renderer=None):
998 self._default_renderer = default_renderer
1005 self._default_renderer = default_renderer
999 self._renderer_extensions = self.RENDERER_TO_EXTENSION.get(
1006 self._renderer_extensions = self.RENDERER_TO_EXTENSION.get(
1000 default_renderer, [])
1007 default_renderer, [])
1001
1008
1002 def search(self, commit, path='/'):
1009 def search(self, commit, path='/'):
1003 """
1010 """
1004 Find a readme in the given `commit`.
1011 Find a readme in the given `commit`.
1005 """
1012 """
1006 nodes = commit.get_nodes(path)
1013 nodes = commit.get_nodes(path)
1007 matches = self._match_readmes(nodes)
1014 matches = self._match_readmes(nodes)
1008 matches = self._sort_according_to_priority(matches)
1015 matches = self._sort_according_to_priority(matches)
1009 if matches:
1016 if matches:
1010 return matches[0].node
1017 return matches[0].node
1011
1018
1012 paths = self._match_paths(nodes)
1019 paths = self._match_paths(nodes)
1013 paths = self._sort_paths_according_to_priority(paths)
1020 paths = self._sort_paths_according_to_priority(paths)
1014 for path in paths:
1021 for path in paths:
1015 match = self.search(commit, path=path)
1022 match = self.search(commit, path=path)
1016 if match:
1023 if match:
1017 return match
1024 return match
1018
1025
1019 return None
1026 return None
1020
1027
1021 def _match_readmes(self, nodes):
1028 def _match_readmes(self, nodes):
1022 for node in nodes:
1029 for node in nodes:
1023 if not node.is_file():
1030 if not node.is_file():
1024 continue
1031 continue
1025 path = node.path.rsplit('/', 1)[-1]
1032 path = node.path.rsplit('/', 1)[-1]
1026 match = self.readme_re.match(path)
1033 match = self.readme_re.match(path)
1027 if match:
1034 if match:
1028 extension = match.group(1)
1035 extension = match.group(1)
1029 yield ReadmeMatch(node, match, self._priority(extension))
1036 yield ReadmeMatch(node, match, self._priority(extension))
1030
1037
1031 def _match_paths(self, nodes):
1038 def _match_paths(self, nodes):
1032 for node in nodes:
1039 for node in nodes:
1033 if not node.is_dir():
1040 if not node.is_dir():
1034 continue
1041 continue
1035 match = self.path_re.match(node.path)
1042 match = self.path_re.match(node.path)
1036 if match:
1043 if match:
1037 yield node.path
1044 yield node.path
1038
1045
1039 def _priority(self, extension):
1046 def _priority(self, extension):
1040 renderer_priority = (
1047 renderer_priority = (
1041 0 if extension in self._renderer_extensions else 1)
1048 0 if extension in self._renderer_extensions else 1)
1042 extension_priority = self.default_priorities.get(
1049 extension_priority = self.default_priorities.get(
1043 extension, self.FALLBACK_PRIORITY)
1050 extension, self.FALLBACK_PRIORITY)
1044 return (renderer_priority, extension_priority)
1051 return (renderer_priority, extension_priority)
1045
1052
1046 def _sort_according_to_priority(self, matches):
1053 def _sort_according_to_priority(self, matches):
1047
1054
1048 def priority_and_path(match):
1055 def priority_and_path(match):
1049 return (match.priority, match.path)
1056 return (match.priority, match.path)
1050
1057
1051 return sorted(matches, key=priority_and_path)
1058 return sorted(matches, key=priority_and_path)
1052
1059
1053 def _sort_paths_according_to_priority(self, paths):
1060 def _sort_paths_according_to_priority(self, paths):
1054
1061
1055 def priority_and_path(path):
1062 def priority_and_path(path):
1056 return (self.path_priority.get(path, self.FALLBACK_PRIORITY), path)
1063 return (self.path_priority.get(path, self.FALLBACK_PRIORITY), path)
1057
1064
1058 return sorted(paths, key=priority_and_path)
1065 return sorted(paths, key=priority_and_path)
1059
1066
1060
1067
1061 class ReadmeMatch:
1068 class ReadmeMatch:
1062
1069
1063 def __init__(self, node, match, priority):
1070 def __init__(self, node, match, priority):
1064 self.node = node
1071 self.node = node
1065 self._match = match
1072 self._match = match
1066 self.priority = priority
1073 self.priority = priority
1067
1074
1068 @property
1075 @property
1069 def path(self):
1076 def path(self):
1070 return self.node.path
1077 return self.node.path
1071
1078
1072 def __repr__(self):
1079 def __repr__(self):
1073 return '<ReadmeMatch {} priority={}'.format(self.path, self.priority)
1080 return '<ReadmeMatch {} priority={}'.format(self.path, self.priority)
@@ -1,514 +1,560 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2011-2017 RhodeCode GmbH
3 # Copyright (C) 2011-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21
21
22 """
22 """
23 user group model for RhodeCode
23 user group model for RhodeCode
24 """
24 """
25
25
26
26
27 import logging
27 import logging
28 import traceback
28 import traceback
29
29
30 from rhodecode.lib.utils2 import safe_str
30 from rhodecode.lib.utils2 import safe_str
31 from rhodecode.model import BaseModel
31 from rhodecode.model import BaseModel
32 from rhodecode.model.db import UserGroupMember, UserGroup,\
32 from rhodecode.model.db import UserGroupMember, UserGroup,\
33 UserGroupRepoToPerm, Permission, UserGroupToPerm, User, UserUserGroupToPerm,\
33 UserGroupRepoToPerm, Permission, UserGroupToPerm, User, UserUserGroupToPerm,\
34 UserGroupUserGroupToPerm, UserGroupRepoGroupToPerm
34 UserGroupUserGroupToPerm, UserGroupRepoGroupToPerm
35 from rhodecode.lib.exceptions import UserGroupAssignedException,\
35 from rhodecode.lib.exceptions import UserGroupAssignedException,\
36 RepoGroupAssignmentError
36 RepoGroupAssignmentError
37 from rhodecode.lib.utils2 import get_current_rhodecode_user, action_logger_generic
37 from rhodecode.lib.utils2 import get_current_rhodecode_user, action_logger_generic
38
38
39 log = logging.getLogger(__name__)
39 log = logging.getLogger(__name__)
40
40
41
41
42 class UserGroupModel(BaseModel):
42 class UserGroupModel(BaseModel):
43
43
44 cls = UserGroup
44 cls = UserGroup
45
45
46 def _get_user_group(self, user_group):
46 def _get_user_group(self, user_group):
47 return self._get_instance(UserGroup, user_group,
47 return self._get_instance(UserGroup, user_group,
48 callback=UserGroup.get_by_group_name)
48 callback=UserGroup.get_by_group_name)
49
49
50 def _create_default_perms(self, user_group):
50 def _create_default_perms(self, user_group):
51 # create default permission
51 # create default permission
52 default_perm = 'usergroup.read'
52 default_perm = 'usergroup.read'
53 def_user = User.get_default_user()
53 def_user = User.get_default_user()
54 for p in def_user.user_perms:
54 for p in def_user.user_perms:
55 if p.permission.permission_name.startswith('usergroup.'):
55 if p.permission.permission_name.startswith('usergroup.'):
56 default_perm = p.permission.permission_name
56 default_perm = p.permission.permission_name
57 break
57 break
58
58
59 user_group_to_perm = UserUserGroupToPerm()
59 user_group_to_perm = UserUserGroupToPerm()
60 user_group_to_perm.permission = Permission.get_by_key(default_perm)
60 user_group_to_perm.permission = Permission.get_by_key(default_perm)
61
61
62 user_group_to_perm.user_group = user_group
62 user_group_to_perm.user_group = user_group
63 user_group_to_perm.user_id = def_user.user_id
63 user_group_to_perm.user_id = def_user.user_id
64 return user_group_to_perm
64 return user_group_to_perm
65
65
66 def update_permissions(self, user_group, perm_additions=None, perm_updates=None,
66 def update_permissions(self, user_group, perm_additions=None, perm_updates=None,
67 perm_deletions=None, check_perms=True, cur_user=None):
67 perm_deletions=None, check_perms=True, cur_user=None):
68 from rhodecode.lib.auth import HasUserGroupPermissionAny
68 from rhodecode.lib.auth import HasUserGroupPermissionAny
69 if not perm_additions:
69 if not perm_additions:
70 perm_additions = []
70 perm_additions = []
71 if not perm_updates:
71 if not perm_updates:
72 perm_updates = []
72 perm_updates = []
73 if not perm_deletions:
73 if not perm_deletions:
74 perm_deletions = []
74 perm_deletions = []
75
75
76 req_perms = ('usergroup.read', 'usergroup.write', 'usergroup.admin')
76 req_perms = ('usergroup.read', 'usergroup.write', 'usergroup.admin')
77
77
78 # update permissions
78 # update permissions
79 for member_id, perm, member_type in perm_updates:
79 for member_id, perm, member_type in perm_updates:
80 member_id = int(member_id)
80 member_id = int(member_id)
81 if member_type == 'user':
81 if member_type == 'user':
82 # this updates existing one
82 # this updates existing one
83 self.grant_user_permission(
83 self.grant_user_permission(
84 user_group=user_group, user=member_id, perm=perm
84 user_group=user_group, user=member_id, perm=perm
85 )
85 )
86 else:
86 else:
87 # check if we have permissions to alter this usergroup
87 # check if we have permissions to alter this usergroup
88 member_name = UserGroup.get(member_id).users_group_name
88 member_name = UserGroup.get(member_id).users_group_name
89 if not check_perms or HasUserGroupPermissionAny(*req_perms)(member_name, user=cur_user):
89 if not check_perms or HasUserGroupPermissionAny(*req_perms)(member_name, user=cur_user):
90 self.grant_user_group_permission(
90 self.grant_user_group_permission(
91 target_user_group=user_group, user_group=member_id, perm=perm
91 target_user_group=user_group, user_group=member_id, perm=perm
92 )
92 )
93
93
94 # set new permissions
94 # set new permissions
95 for member_id, perm, member_type in perm_additions:
95 for member_id, perm, member_type in perm_additions:
96 member_id = int(member_id)
96 member_id = int(member_id)
97 if member_type == 'user':
97 if member_type == 'user':
98 self.grant_user_permission(
98 self.grant_user_permission(
99 user_group=user_group, user=member_id, perm=perm
99 user_group=user_group, user=member_id, perm=perm
100 )
100 )
101 else:
101 else:
102 # check if we have permissions to alter this usergroup
102 # check if we have permissions to alter this usergroup
103 member_name = UserGroup.get(member_id).users_group_name
103 member_name = UserGroup.get(member_id).users_group_name
104 if not check_perms or HasUserGroupPermissionAny(*req_perms)(member_name, user=cur_user):
104 if not check_perms or HasUserGroupPermissionAny(*req_perms)(member_name, user=cur_user):
105 self.grant_user_group_permission(
105 self.grant_user_group_permission(
106 target_user_group=user_group, user_group=member_id, perm=perm
106 target_user_group=user_group, user_group=member_id, perm=perm
107 )
107 )
108
108
109 # delete permissions
109 # delete permissions
110 for member_id, perm, member_type in perm_deletions:
110 for member_id, perm, member_type in perm_deletions:
111 member_id = int(member_id)
111 member_id = int(member_id)
112 if member_type == 'user':
112 if member_type == 'user':
113 self.revoke_user_permission(user_group=user_group, user=member_id)
113 self.revoke_user_permission(user_group=user_group, user=member_id)
114 else:
114 else:
115 #check if we have permissions to alter this usergroup
115 #check if we have permissions to alter this usergroup
116 member_name = UserGroup.get(member_id).users_group_name
116 member_name = UserGroup.get(member_id).users_group_name
117 if not check_perms or HasUserGroupPermissionAny(*req_perms)(member_name, user=cur_user):
117 if not check_perms or HasUserGroupPermissionAny(*req_perms)(member_name, user=cur_user):
118 self.revoke_user_group_permission(
118 self.revoke_user_group_permission(
119 target_user_group=user_group, user_group=member_id
119 target_user_group=user_group, user_group=member_id
120 )
120 )
121
121
122 def get(self, user_group_id, cache=False):
122 def get(self, user_group_id, cache=False):
123 return UserGroup.get(user_group_id)
123 return UserGroup.get(user_group_id)
124
124
125 def get_group(self, user_group):
125 def get_group(self, user_group):
126 return self._get_user_group(user_group)
126 return self._get_user_group(user_group)
127
127
128 def get_by_name(self, name, cache=False, case_insensitive=False):
128 def get_by_name(self, name, cache=False, case_insensitive=False):
129 return UserGroup.get_by_group_name(name, cache, case_insensitive)
129 return UserGroup.get_by_group_name(name, cache, case_insensitive)
130
130
131 def create(self, name, description, owner, active=True, group_data=None):
131 def create(self, name, description, owner, active=True, group_data=None):
132 try:
132 try:
133 new_user_group = UserGroup()
133 new_user_group = UserGroup()
134 new_user_group.user = self._get_user(owner)
134 new_user_group.user = self._get_user(owner)
135 new_user_group.users_group_name = name
135 new_user_group.users_group_name = name
136 new_user_group.user_group_description = description
136 new_user_group.user_group_description = description
137 new_user_group.users_group_active = active
137 new_user_group.users_group_active = active
138 if group_data:
138 if group_data:
139 new_user_group.group_data = group_data
139 new_user_group.group_data = group_data
140 self.sa.add(new_user_group)
140 self.sa.add(new_user_group)
141 perm_obj = self._create_default_perms(new_user_group)
141 perm_obj = self._create_default_perms(new_user_group)
142 self.sa.add(perm_obj)
142 self.sa.add(perm_obj)
143
143
144 self.grant_user_permission(user_group=new_user_group,
144 self.grant_user_permission(user_group=new_user_group,
145 user=owner, perm='usergroup.admin')
145 user=owner, perm='usergroup.admin')
146
146
147 return new_user_group
147 return new_user_group
148 except Exception:
148 except Exception:
149 log.error(traceback.format_exc())
149 log.error(traceback.format_exc())
150 raise
150 raise
151
151
152 def _get_memberships_for_user_ids(self, user_group, user_id_list):
152 def _get_memberships_for_user_ids(self, user_group, user_id_list):
153 members = []
153 members = []
154 for user_id in user_id_list:
154 for user_id in user_id_list:
155 member = self._get_membership(user_group.users_group_id, user_id)
155 member = self._get_membership(user_group.users_group_id, user_id)
156 members.append(member)
156 members.append(member)
157 return members
157 return members
158
158
159 def _get_added_and_removed_user_ids(self, user_group, user_id_list):
159 def _get_added_and_removed_user_ids(self, user_group, user_id_list):
160 current_members = user_group.members or []
160 current_members = user_group.members or []
161 current_members_ids = [m.user.user_id for m in current_members]
161 current_members_ids = [m.user.user_id for m in current_members]
162
162
163 added_members = [
163 added_members = [
164 user_id for user_id in user_id_list
164 user_id for user_id in user_id_list
165 if user_id not in current_members_ids]
165 if user_id not in current_members_ids]
166 if user_id_list == []:
166 if user_id_list == []:
167 # all members were deleted
167 # all members were deleted
168 deleted_members = current_members_ids
168 deleted_members = current_members_ids
169 else:
169 else:
170 deleted_members = [
170 deleted_members = [
171 user_id for user_id in current_members_ids
171 user_id for user_id in current_members_ids
172 if user_id not in user_id_list]
172 if user_id not in user_id_list]
173
173
174 return (added_members, deleted_members)
174 return (added_members, deleted_members)
175
175
176 def _set_users_as_members(self, user_group, user_ids):
176 def _set_users_as_members(self, user_group, user_ids):
177 user_group.members = []
177 user_group.members = []
178 self.sa.flush()
178 self.sa.flush()
179 members = self._get_memberships_for_user_ids(
179 members = self._get_memberships_for_user_ids(
180 user_group, user_ids)
180 user_group, user_ids)
181 user_group.members = members
181 user_group.members = members
182 self.sa.add(user_group)
182 self.sa.add(user_group)
183
183
184 def _update_members_from_user_ids(self, user_group, user_ids):
184 def _update_members_from_user_ids(self, user_group, user_ids):
185 added, removed = self._get_added_and_removed_user_ids(
185 added, removed = self._get_added_and_removed_user_ids(
186 user_group, user_ids)
186 user_group, user_ids)
187 self._set_users_as_members(user_group, user_ids)
187 self._set_users_as_members(user_group, user_ids)
188 self._log_user_changes('added to', user_group, added)
188 self._log_user_changes('added to', user_group, added)
189 self._log_user_changes('removed from', user_group, removed)
189 self._log_user_changes('removed from', user_group, removed)
190
190
191 def _clean_members_data(self, members_data):
191 def _clean_members_data(self, members_data):
192 if not members_data:
192 if not members_data:
193 members_data = []
193 members_data = []
194
194
195 members = []
195 members = []
196 for user in members_data:
196 for user in members_data:
197 uid = int(user['member_user_id'])
197 uid = int(user['member_user_id'])
198 if uid not in members and user['type'] in ['new', 'existing']:
198 if uid not in members and user['type'] in ['new', 'existing']:
199 members.append(uid)
199 members.append(uid)
200 return members
200 return members
201
201
202 def update(self, user_group, form_data):
202 def update(self, user_group, form_data):
203 user_group = self._get_user_group(user_group)
203 user_group = self._get_user_group(user_group)
204 if 'users_group_name' in form_data:
204 if 'users_group_name' in form_data:
205 user_group.users_group_name = form_data['users_group_name']
205 user_group.users_group_name = form_data['users_group_name']
206 if 'users_group_active' in form_data:
206 if 'users_group_active' in form_data:
207 user_group.users_group_active = form_data['users_group_active']
207 user_group.users_group_active = form_data['users_group_active']
208 if 'user_group_description' in form_data:
208 if 'user_group_description' in form_data:
209 user_group.user_group_description = form_data[
209 user_group.user_group_description = form_data[
210 'user_group_description']
210 'user_group_description']
211
211
212 # handle owner change
212 # handle owner change
213 if 'user' in form_data:
213 if 'user' in form_data:
214 owner = form_data['user']
214 owner = form_data['user']
215 if isinstance(owner, basestring):
215 if isinstance(owner, basestring):
216 owner = User.get_by_username(form_data['user'])
216 owner = User.get_by_username(form_data['user'])
217
217
218 if not isinstance(owner, User):
218 if not isinstance(owner, User):
219 raise ValueError(
219 raise ValueError(
220 'invalid owner for user group: %s' % form_data['user'])
220 'invalid owner for user group: %s' % form_data['user'])
221
221
222 user_group.user = owner
222 user_group.user = owner
223
223
224 if 'users_group_members' in form_data:
224 if 'users_group_members' in form_data:
225 members_id_list = self._clean_members_data(
225 members_id_list = self._clean_members_data(
226 form_data['users_group_members'])
226 form_data['users_group_members'])
227 self._update_members_from_user_ids(user_group, members_id_list)
227 self._update_members_from_user_ids(user_group, members_id_list)
228
228
229 self.sa.add(user_group)
229 self.sa.add(user_group)
230
230
231 def delete(self, user_group, force=False):
231 def delete(self, user_group, force=False):
232 """
232 """
233 Deletes repository group, unless force flag is used
233 Deletes repository group, unless force flag is used
234 raises exception if there are members in that group, else deletes
234 raises exception if there are members in that group, else deletes
235 group and users
235 group and users
236
236
237 :param user_group:
237 :param user_group:
238 :param force:
238 :param force:
239 """
239 """
240 user_group = self._get_user_group(user_group)
240 user_group = self._get_user_group(user_group)
241 try:
241 try:
242 # check if this group is not assigned to repo
242 # check if this group is not assigned to repo
243 assigned_to_repo = [x.repository for x in UserGroupRepoToPerm.query()\
243 assigned_to_repo = [x.repository for x in UserGroupRepoToPerm.query()\
244 .filter(UserGroupRepoToPerm.users_group == user_group).all()]
244 .filter(UserGroupRepoToPerm.users_group == user_group).all()]
245 # check if this group is not assigned to repo
245 # check if this group is not assigned to repo
246 assigned_to_repo_group = [x.group for x in UserGroupRepoGroupToPerm.query()\
246 assigned_to_repo_group = [x.group for x in UserGroupRepoGroupToPerm.query()\
247 .filter(UserGroupRepoGroupToPerm.users_group == user_group).all()]
247 .filter(UserGroupRepoGroupToPerm.users_group == user_group).all()]
248
248
249 if (assigned_to_repo or assigned_to_repo_group) and not force:
249 if (assigned_to_repo or assigned_to_repo_group) and not force:
250 assigned = ','.join(map(safe_str,
250 assigned = ','.join(map(safe_str,
251 assigned_to_repo+assigned_to_repo_group))
251 assigned_to_repo+assigned_to_repo_group))
252
252
253 raise UserGroupAssignedException(
253 raise UserGroupAssignedException(
254 'UserGroup assigned to %s' % (assigned,))
254 'UserGroup assigned to %s' % (assigned,))
255 self.sa.delete(user_group)
255 self.sa.delete(user_group)
256 except Exception:
256 except Exception:
257 log.error(traceback.format_exc())
257 log.error(traceback.format_exc())
258 raise
258 raise
259
259
260 def _log_user_changes(self, action, user_group, user_or_users):
260 def _log_user_changes(self, action, user_group, user_or_users):
261 users = user_or_users
261 users = user_or_users
262 if not isinstance(users, (list, tuple)):
262 if not isinstance(users, (list, tuple)):
263 users = [users]
263 users = [users]
264 rhodecode_user = get_current_rhodecode_user()
264 rhodecode_user = get_current_rhodecode_user()
265 ipaddr = getattr(rhodecode_user, 'ip_addr', '')
265 ipaddr = getattr(rhodecode_user, 'ip_addr', '')
266 group_name = user_group.users_group_name
266 group_name = user_group.users_group_name
267
267
268 for user_or_user_id in users:
268 for user_or_user_id in users:
269 user = self._get_user(user_or_user_id)
269 user = self._get_user(user_or_user_id)
270 log_text = 'User {user} {action} {group}'.format(
270 log_text = 'User {user} {action} {group}'.format(
271 action=action, user=user.username, group=group_name)
271 action=action, user=user.username, group=group_name)
272 log.info('Logging action: {0} by {1} ip:{2}'.format(
272 log.info('Logging action: {0} by {1} ip:{2}'.format(
273 log_text, rhodecode_user, ipaddr))
273 log_text, rhodecode_user, ipaddr))
274
274
275 def _find_user_in_group(self, user, user_group):
275 def _find_user_in_group(self, user, user_group):
276 user_group_member = None
276 user_group_member = None
277 for m in user_group.members:
277 for m in user_group.members:
278 if m.user_id == user.user_id:
278 if m.user_id == user.user_id:
279 # Found this user's membership row
279 # Found this user's membership row
280 user_group_member = m
280 user_group_member = m
281 break
281 break
282
282
283 return user_group_member
283 return user_group_member
284
284
285 def _get_membership(self, user_group_id, user_id):
285 def _get_membership(self, user_group_id, user_id):
286 user_group_member = UserGroupMember(user_group_id, user_id)
286 user_group_member = UserGroupMember(user_group_id, user_id)
287 return user_group_member
287 return user_group_member
288
288
289 def add_user_to_group(self, user_group, user):
289 def add_user_to_group(self, user_group, user):
290 user_group = self._get_user_group(user_group)
290 user_group = self._get_user_group(user_group)
291 user = self._get_user(user)
291 user = self._get_user(user)
292 user_member = self._find_user_in_group(user, user_group)
292 user_member = self._find_user_in_group(user, user_group)
293 if user_member:
293 if user_member:
294 # user already in the group, skip
294 # user already in the group, skip
295 return True
295 return True
296
296
297 member = self._get_membership(
297 member = self._get_membership(
298 user_group.users_group_id, user.user_id)
298 user_group.users_group_id, user.user_id)
299 user_group.members.append(member)
299 user_group.members.append(member)
300
300
301 try:
301 try:
302 self.sa.add(member)
302 self.sa.add(member)
303 except Exception:
303 except Exception:
304 # what could go wrong here?
304 # what could go wrong here?
305 log.error(traceback.format_exc())
305 log.error(traceback.format_exc())
306 raise
306 raise
307
307
308 self._log_user_changes('added to', user_group, user)
308 self._log_user_changes('added to', user_group, user)
309 return member
309 return member
310
310
311 def remove_user_from_group(self, user_group, user):
311 def remove_user_from_group(self, user_group, user):
312 user_group = self._get_user_group(user_group)
312 user_group = self._get_user_group(user_group)
313 user = self._get_user(user)
313 user = self._get_user(user)
314 user_group_member = self._find_user_in_group(user, user_group)
314 user_group_member = self._find_user_in_group(user, user_group)
315
315
316 if not user_group_member:
316 if not user_group_member:
317 # User isn't in that group
317 # User isn't in that group
318 return False
318 return False
319
319
320 try:
320 try:
321 self.sa.delete(user_group_member)
321 self.sa.delete(user_group_member)
322 except Exception:
322 except Exception:
323 log.error(traceback.format_exc())
323 log.error(traceback.format_exc())
324 raise
324 raise
325
325
326 self._log_user_changes('removed from', user_group, user)
326 self._log_user_changes('removed from', user_group, user)
327 return True
327 return True
328
328
329 def has_perm(self, user_group, perm):
329 def has_perm(self, user_group, perm):
330 user_group = self._get_user_group(user_group)
330 user_group = self._get_user_group(user_group)
331 perm = self._get_perm(perm)
331 perm = self._get_perm(perm)
332
332
333 return UserGroupToPerm.query()\
333 return UserGroupToPerm.query()\
334 .filter(UserGroupToPerm.users_group == user_group)\
334 .filter(UserGroupToPerm.users_group == user_group)\
335 .filter(UserGroupToPerm.permission == perm).scalar() is not None
335 .filter(UserGroupToPerm.permission == perm).scalar() is not None
336
336
337 def grant_perm(self, user_group, perm):
337 def grant_perm(self, user_group, perm):
338 user_group = self._get_user_group(user_group)
338 user_group = self._get_user_group(user_group)
339 perm = self._get_perm(perm)
339 perm = self._get_perm(perm)
340
340
341 # if this permission is already granted skip it
341 # if this permission is already granted skip it
342 _perm = UserGroupToPerm.query()\
342 _perm = UserGroupToPerm.query()\
343 .filter(UserGroupToPerm.users_group == user_group)\
343 .filter(UserGroupToPerm.users_group == user_group)\
344 .filter(UserGroupToPerm.permission == perm)\
344 .filter(UserGroupToPerm.permission == perm)\
345 .scalar()
345 .scalar()
346 if _perm:
346 if _perm:
347 return
347 return
348
348
349 new = UserGroupToPerm()
349 new = UserGroupToPerm()
350 new.users_group = user_group
350 new.users_group = user_group
351 new.permission = perm
351 new.permission = perm
352 self.sa.add(new)
352 self.sa.add(new)
353 return new
353 return new
354
354
355 def revoke_perm(self, user_group, perm):
355 def revoke_perm(self, user_group, perm):
356 user_group = self._get_user_group(user_group)
356 user_group = self._get_user_group(user_group)
357 perm = self._get_perm(perm)
357 perm = self._get_perm(perm)
358
358
359 obj = UserGroupToPerm.query()\
359 obj = UserGroupToPerm.query()\
360 .filter(UserGroupToPerm.users_group == user_group)\
360 .filter(UserGroupToPerm.users_group == user_group)\
361 .filter(UserGroupToPerm.permission == perm).scalar()
361 .filter(UserGroupToPerm.permission == perm).scalar()
362 if obj:
362 if obj:
363 self.sa.delete(obj)
363 self.sa.delete(obj)
364
364
365 def grant_user_permission(self, user_group, user, perm):
365 def grant_user_permission(self, user_group, user, perm):
366 """
366 """
367 Grant permission for user on given user group, or update
367 Grant permission for user on given user group, or update
368 existing one if found
368 existing one if found
369
369
370 :param user_group: Instance of UserGroup, users_group_id,
370 :param user_group: Instance of UserGroup, users_group_id,
371 or users_group_name
371 or users_group_name
372 :param user: Instance of User, user_id or username
372 :param user: Instance of User, user_id or username
373 :param perm: Instance of Permission, or permission_name
373 :param perm: Instance of Permission, or permission_name
374 """
374 """
375
375
376 user_group = self._get_user_group(user_group)
376 user_group = self._get_user_group(user_group)
377 user = self._get_user(user)
377 user = self._get_user(user)
378 permission = self._get_perm(perm)
378 permission = self._get_perm(perm)
379
379
380 # check if we have that permission already
380 # check if we have that permission already
381 obj = self.sa.query(UserUserGroupToPerm)\
381 obj = self.sa.query(UserUserGroupToPerm)\
382 .filter(UserUserGroupToPerm.user == user)\
382 .filter(UserUserGroupToPerm.user == user)\
383 .filter(UserUserGroupToPerm.user_group == user_group)\
383 .filter(UserUserGroupToPerm.user_group == user_group)\
384 .scalar()
384 .scalar()
385 if obj is None:
385 if obj is None:
386 # create new !
386 # create new !
387 obj = UserUserGroupToPerm()
387 obj = UserUserGroupToPerm()
388 obj.user_group = user_group
388 obj.user_group = user_group
389 obj.user = user
389 obj.user = user
390 obj.permission = permission
390 obj.permission = permission
391 self.sa.add(obj)
391 self.sa.add(obj)
392 log.debug('Granted perm %s to %s on %s', perm, user, user_group)
392 log.debug('Granted perm %s to %s on %s', perm, user, user_group)
393 action_logger_generic(
393 action_logger_generic(
394 'granted permission: {} to user: {} on usergroup: {}'.format(
394 'granted permission: {} to user: {} on usergroup: {}'.format(
395 perm, user, user_group), namespace='security.usergroup')
395 perm, user, user_group), namespace='security.usergroup')
396
396
397 return obj
397 return obj
398
398
399 def revoke_user_permission(self, user_group, user):
399 def revoke_user_permission(self, user_group, user):
400 """
400 """
401 Revoke permission for user on given user group
401 Revoke permission for user on given user group
402
402
403 :param user_group: Instance of UserGroup, users_group_id,
403 :param user_group: Instance of UserGroup, users_group_id,
404 or users_group name
404 or users_group name
405 :param user: Instance of User, user_id or username
405 :param user: Instance of User, user_id or username
406 """
406 """
407
407
408 user_group = self._get_user_group(user_group)
408 user_group = self._get_user_group(user_group)
409 user = self._get_user(user)
409 user = self._get_user(user)
410
410
411 obj = self.sa.query(UserUserGroupToPerm)\
411 obj = self.sa.query(UserUserGroupToPerm)\
412 .filter(UserUserGroupToPerm.user == user)\
412 .filter(UserUserGroupToPerm.user == user)\
413 .filter(UserUserGroupToPerm.user_group == user_group)\
413 .filter(UserUserGroupToPerm.user_group == user_group)\
414 .scalar()
414 .scalar()
415 if obj:
415 if obj:
416 self.sa.delete(obj)
416 self.sa.delete(obj)
417 log.debug('Revoked perm on %s on %s', user_group, user)
417 log.debug('Revoked perm on %s on %s', user_group, user)
418 action_logger_generic(
418 action_logger_generic(
419 'revoked permission from user: {} on usergroup: {}'.format(
419 'revoked permission from user: {} on usergroup: {}'.format(
420 user, user_group), namespace='security.usergroup')
420 user, user_group), namespace='security.usergroup')
421
421
422 def grant_user_group_permission(self, target_user_group, user_group, perm):
422 def grant_user_group_permission(self, target_user_group, user_group, perm):
423 """
423 """
424 Grant user group permission for given target_user_group
424 Grant user group permission for given target_user_group
425
425
426 :param target_user_group:
426 :param target_user_group:
427 :param user_group:
427 :param user_group:
428 :param perm:
428 :param perm:
429 """
429 """
430 target_user_group = self._get_user_group(target_user_group)
430 target_user_group = self._get_user_group(target_user_group)
431 user_group = self._get_user_group(user_group)
431 user_group = self._get_user_group(user_group)
432 permission = self._get_perm(perm)
432 permission = self._get_perm(perm)
433 # forbid assigning same user group to itself
433 # forbid assigning same user group to itself
434 if target_user_group == user_group:
434 if target_user_group == user_group:
435 raise RepoGroupAssignmentError('target repo:%s cannot be '
435 raise RepoGroupAssignmentError('target repo:%s cannot be '
436 'assigned to itself' % target_user_group)
436 'assigned to itself' % target_user_group)
437
437
438 # check if we have that permission already
438 # check if we have that permission already
439 obj = self.sa.query(UserGroupUserGroupToPerm)\
439 obj = self.sa.query(UserGroupUserGroupToPerm)\
440 .filter(UserGroupUserGroupToPerm.target_user_group == target_user_group)\
440 .filter(UserGroupUserGroupToPerm.target_user_group == target_user_group)\
441 .filter(UserGroupUserGroupToPerm.user_group == user_group)\
441 .filter(UserGroupUserGroupToPerm.user_group == user_group)\
442 .scalar()
442 .scalar()
443 if obj is None:
443 if obj is None:
444 # create new !
444 # create new !
445 obj = UserGroupUserGroupToPerm()
445 obj = UserGroupUserGroupToPerm()
446 obj.user_group = user_group
446 obj.user_group = user_group
447 obj.target_user_group = target_user_group
447 obj.target_user_group = target_user_group
448 obj.permission = permission
448 obj.permission = permission
449 self.sa.add(obj)
449 self.sa.add(obj)
450 log.debug(
450 log.debug(
451 'Granted perm %s to %s on %s', perm, target_user_group, user_group)
451 'Granted perm %s to %s on %s', perm, target_user_group, user_group)
452 action_logger_generic(
452 action_logger_generic(
453 'granted permission: {} to usergroup: {} on usergroup: {}'.format(
453 'granted permission: {} to usergroup: {} on usergroup: {}'.format(
454 perm, user_group, target_user_group),
454 perm, user_group, target_user_group),
455 namespace='security.usergroup')
455 namespace='security.usergroup')
456
456
457 return obj
457 return obj
458
458
459 def revoke_user_group_permission(self, target_user_group, user_group):
459 def revoke_user_group_permission(self, target_user_group, user_group):
460 """
460 """
461 Revoke user group permission for given target_user_group
461 Revoke user group permission for given target_user_group
462
462
463 :param target_user_group:
463 :param target_user_group:
464 :param user_group:
464 :param user_group:
465 """
465 """
466 target_user_group = self._get_user_group(target_user_group)
466 target_user_group = self._get_user_group(target_user_group)
467 user_group = self._get_user_group(user_group)
467 user_group = self._get_user_group(user_group)
468
468
469 obj = self.sa.query(UserGroupUserGroupToPerm)\
469 obj = self.sa.query(UserGroupUserGroupToPerm)\
470 .filter(UserGroupUserGroupToPerm.target_user_group == target_user_group)\
470 .filter(UserGroupUserGroupToPerm.target_user_group == target_user_group)\
471 .filter(UserGroupUserGroupToPerm.user_group == user_group)\
471 .filter(UserGroupUserGroupToPerm.user_group == user_group)\
472 .scalar()
472 .scalar()
473 if obj:
473 if obj:
474 self.sa.delete(obj)
474 self.sa.delete(obj)
475 log.debug(
475 log.debug(
476 'Revoked perm on %s on %s', target_user_group, user_group)
476 'Revoked perm on %s on %s', target_user_group, user_group)
477 action_logger_generic(
477 action_logger_generic(
478 'revoked permission from usergroup: {} on usergroup: {}'.format(
478 'revoked permission from usergroup: {} on usergroup: {}'.format(
479 user_group, target_user_group),
479 user_group, target_user_group),
480 namespace='security.repogroup')
480 namespace='security.repogroup')
481
481
482 def enforce_groups(self, user, groups, extern_type=None):
482 def enforce_groups(self, user, groups, extern_type=None):
483 user = self._get_user(user)
483 user = self._get_user(user)
484 log.debug('Enforcing groups %s on user %s', groups, user)
484 log.debug('Enforcing groups %s on user %s', groups, user)
485 current_groups = user.group_member
485 current_groups = user.group_member
486 # find the external created groups
486 # find the external created groups
487 externals = [x.users_group for x in current_groups
487 externals = [x.users_group for x in current_groups
488 if 'extern_type' in x.users_group.group_data]
488 if 'extern_type' in x.users_group.group_data]
489
489
490 # calculate from what groups user should be removed
490 # calculate from what groups user should be removed
491 # externals that are not in groups
491 # externals that are not in groups
492 for gr in externals:
492 for gr in externals:
493 if gr.users_group_name not in groups:
493 if gr.users_group_name not in groups:
494 log.debug('Removing user %s from user group %s', user, gr)
494 log.debug('Removing user %s from user group %s', user, gr)
495 self.remove_user_from_group(gr, user)
495 self.remove_user_from_group(gr, user)
496
496
497 # now we calculate in which groups user should be == groups params
497 # now we calculate in which groups user should be == groups params
498 owner = User.get_first_super_admin().username
498 owner = User.get_first_super_admin().username
499 for gr in set(groups):
499 for gr in set(groups):
500 existing_group = UserGroup.get_by_group_name(gr)
500 existing_group = UserGroup.get_by_group_name(gr)
501 if not existing_group:
501 if not existing_group:
502 desc = 'Automatically created from plugin:%s' % extern_type
502 desc = 'Automatically created from plugin:%s' % extern_type
503 # we use first admin account to set the owner of the group
503 # we use first admin account to set the owner of the group
504 existing_group = UserGroupModel().create(gr, desc, owner,
504 existing_group = UserGroupModel().create(gr, desc, owner,
505 group_data={'extern_type': extern_type})
505 group_data={'extern_type': extern_type})
506
506
507 # we can only add users to special groups created via plugins
507 # we can only add users to special groups created via plugins
508 managed = 'extern_type' in existing_group.group_data
508 managed = 'extern_type' in existing_group.group_data
509 if managed:
509 if managed:
510 log.debug('Adding user %s to user group %s', user, gr)
510 log.debug('Adding user %s to user group %s', user, gr)
511 UserGroupModel().add_user_to_group(existing_group, user)
511 UserGroupModel().add_user_to_group(existing_group, user)
512 else:
512 else:
513 log.debug('Skipping addition to group %s since it is '
513 log.debug('Skipping addition to group %s since it is '
514 'not managed by auth plugins' % gr)
514 'not managed by auth plugins' % gr)
515
516
517 def change_groups(self, user, groups):
518 """
519 This method changes user group assignment
520 :param user: User
521 :param groups: array of UserGroupModel
522 :return:
523 """
524 user = self._get_user(user)
525 log.debug('Changing user(%s) assignment to groups(%s)', user, groups)
526 current_groups = user.group_member
527 current_groups = [x.users_group for x in current_groups]
528
529 # calculate from what groups user should be removed/add
530 groups = set(groups)
531 current_groups = set(current_groups)
532
533 groups_to_remove = current_groups - groups
534 groups_to_add = groups - current_groups
535
536 for gr in groups_to_remove:
537 log.debug('Removing user %s from user group %s', user.username, gr.users_group_name)
538 self.remove_user_from_group(gr.users_group_name, user.username)
539 for gr in groups_to_add:
540 log.debug('Adding user %s to user group %s', user.username, gr.users_group_name)
541 UserGroupModel().add_user_to_group(gr.users_group_name, user.username)
542
543 @staticmethod
544 def get_user_groups_as_dict(user_group):
545 import rhodecode.lib.helpers as h
546
547 data = {
548 'users_group_id': user_group.users_group_id,
549 'group_name': user_group.users_group_name,
550 'group_description': user_group.user_group_description,
551 'active': user_group.users_group_active,
552 "owner": user_group.user.username,
553 'owner_icon': h.gravatar_url(user_group.user.email, 30),
554 "owner_data": {'owner': user_group.user.username, 'owner_icon': h.gravatar_url(user_group.user.email, 30)}
555 }
556 return data
557
558
559
560
@@ -1,49 +1,54 b''
1 ## -*- coding: utf-8 -*-
1 ## -*- coding: utf-8 -*-
2 <%inherit file="/base/base.mako"/>
2 <%inherit file="/base/base.mako"/>
3
3
4 <%def name="title()">
4 <%def name="title()">
5 ${_('%s user settings') % c.user.username}
5 ${_('%s user settings') % c.user.username}
6 %if c.rhodecode_name:
6 %if c.rhodecode_name:
7 &middot; ${h.branding(c.rhodecode_name)}
7 &middot; ${h.branding(c.rhodecode_name)}
8 %endif
8 %endif
9 </%def>
9 </%def>
10
10
11 <%def name="breadcrumbs_links()">
11 <%def name="breadcrumbs_links()">
12 ${h.link_to(_('Admin'),h.url('admin_home'))}
12 ${h.link_to(_('Admin'),h.url('admin_home'))}
13 &raquo;
13 &raquo;
14 ${h.link_to(_('Users'),h.route_path('users'))}
14 ${h.link_to(_('Users'),h.route_path('users'))}
15 &raquo;
15 &raquo;
16 ${c.user.username}
16 ${c.user.username}
17 </%def>
17 </%def>
18
18
19 <%def name="menu_bar_nav()">
19 <%def name="menu_bar_nav()">
20 ${self.menu_items(active='admin')}
20 ${self.menu_items(active='admin')}
21 </%def>
21 </%def>
22
22
23 <%def name="main()">
23 <%def name="main()">
24 <div class="box user_settings">
24 <div class="box user_settings">
25 <div class="title">
25 <div class="title">
26 ${self.breadcrumbs()}
26 ${self.breadcrumbs()}
27 </div>
27 </div>
28
28
29 ##main
29 ##main
30 <div class="sidebar-col-wrapper">
30 <div class="sidebar-col-wrapper">
31 <div class="sidebar">
31 <div class="sidebar">
32 <ul class="nav nav-pills nav-stacked">
32 <ul class="nav nav-pills nav-stacked">
33 <li class="${'active' if c.active=='profile' else ''}"><a href="${h.url('edit_user', user_id=c.user.user_id)}">${_('User Profile')}</a></li>
33 <li class="${'active' if c.active=='profile' else ''}"><a href="${h.url('edit_user', user_id=c.user.user_id)}">${_('User Profile')}</a></li>
34 <li class="${'active' if c.active=='auth_tokens' else ''}"><a href="${h.route_path('edit_user_auth_tokens', user_id=c.user.user_id)}">${_('Auth tokens')}</a></li>
34 <li class="${'active' if c.active=='auth_tokens' else ''}"><a href="${h.route_path('edit_user_auth_tokens', user_id=c.user.user_id)}">${_('Auth tokens')}</a></li>
35 <li class="${'active' if c.active=='advanced' else ''}"><a href="${h.url('edit_user_advanced', user_id=c.user.user_id)}">${_('Advanced')}</a></li>
35 <li class="${'active' if c.active=='advanced' else ''}"><a href="${h.url('edit_user_advanced', user_id=c.user.user_id)}">${_('Advanced')}</a></li>
36 <li class="${'active' if c.active=='global_perms' else ''}"><a href="${h.url('edit_user_global_perms', user_id=c.user.user_id)}">${_('Global permissions')}</a></li>
36 <li class="${'active' if c.active=='global_perms' else ''}"><a href="${h.url('edit_user_global_perms', user_id=c.user.user_id)}">${_('Global permissions')}</a></li>
37 <li class="${'active' if c.active=='perms_summary' else ''}"><a href="${h.url('edit_user_perms_summary', user_id=c.user.user_id)}">${_('Permissions summary')}</a></li>
37 <li class="${'active' if c.active=='perms_summary' else ''}"><a href="${h.url('edit_user_perms_summary', user_id=c.user.user_id)}">${_('Permissions summary')}</a></li>
38 <li class="${'active' if c.active=='emails' else ''}"><a href="${h.url('edit_user_emails', user_id=c.user.user_id)}">${_('Emails')}</a></li>
38 <li class="${'active' if c.active=='emails' else ''}"><a href="${h.url('edit_user_emails', user_id=c.user.user_id)}">${_('Emails')}</a></li>
39 <li class="${'active' if c.active=='ips' else ''}"><a href="${h.url('edit_user_ips', user_id=c.user.user_id)}">${_('Ip Whitelist')}</a></li>
39 <li class="${'active' if c.active=='ips' else ''}"><a href="${h.url('edit_user_ips', user_id=c.user.user_id)}">${_('Ip Whitelist')}</a></li>
40
41 <li class="${'active' if c.active=='groups' else ''}">
42 <a href="${h.route_path('edit_user_groups_management', user_id=c.user.user_id)}">${_('User Groups Management')}</a>
43 </li>
44
40 </ul>
45 </ul>
41 </div>
46 </div>
42
47
43 <div class="main-content-full-width">
48 <div class="main-content-full-width">
44 <%include file="/admin/users/user_edit_${c.active}.mako"/>
49 <%include file="/admin/users/user_edit_${c.active}.mako"/>
45 </div>
50 </div>
46 </div>
51 </div>
47 </div>
52 </div>
48
53
49 </%def>
54 </%def>
@@ -1,98 +1,145 b''
1 ## -*- coding: utf-8 -*-
1 ## -*- coding: utf-8 -*-
2 <%inherit file="/base/base.mako"/>
3
2
4 <%def name="title()">
5 ${_('User groups administration')}
6 %if c.rhodecode_name:
7 &middot; ${h.branding(c.rhodecode_name)}
8 %endif
9 </%def>
10
11 <%def name="breadcrumbs_links()">
12 <input class="q_filter_box" id="q_filter" size="15" type="text" name="filter" placeholder="${_('quick filter...')}" value=""/>
13 ${h.link_to(_('Admin'),h.url('admin_home'))} &raquo; <span id="user_group_count">0</span> ${_('user groups')}
14 </%def>
15
3
16 <%def name="menu_bar_nav()">
4 <div class="panel panel-default">
17 ${self.menu_items(active='admin')}
5 <div class="panel-heading">
18 </%def>
6 <h3 class="panel-title">${_('User groups administration')}</h3>
19
7 </div>
20 <%def name="main()">
8 <div class="panel-body">
21 <div class="box">
9 <div class="field">
22
10 <div class="label label-checkbox">
23 <div class="title">
11 <label for="users_group_active">${_('Add user to group')}:</label>
24 ${self.breadcrumbs()}
12 </div>
25 <ul class="links">
13 <div class="input">
26 %if h.HasPermissionAny('hg.admin', 'hg.usergroup.create.true')():
14 ${h.text('add_user_to_group', placeholder="user group name", class_="medium")}
27 <li>
28 <a href="${h.url('new_users_group')}" class="btn btn-small btn-success">${_(u'Add User Group')}</a>
29 </li>
30 %endif
31 </ul>
32 </div>
15 </div>
33
16
17 </div>
18
19 <div class="groups_management">
20 ${h.secure_form(h.route_path('edit_user_groups_management_updates', user_id=c.user.user_id), method='post')}
34 <div id="repos_list_wrap">
21 <div id="repos_list_wrap">
35 <table id="user_group_list_table" class="display"></table>
22 <table id="user_group_list_table" class="display"></table>
36 </div>
23 </div>
37
24 <div class="buttons">
25 ${h.submit('save',_('Save'),class_="btn")}
26 </div>
27 ${h.end_form()}
28 </div>
29 </div>
38 </div>
30 </div>
39 <script>
31 <script>
32 var api;
40 $(document).ready(function() {
33 $(document).ready(function() {
41
34
42 var get_datatable_count = function(){
35 var get_datatable_count = function(){
43 var api = $('#user_group_list_table').dataTable().api();
44 $('#user_group_count').text(api.page.info().recordsDisplay);
36 $('#user_group_count').text(api.page.info().recordsDisplay);
45 };
37 };
46
38
47 // user list
39 $('#user_group_list_table').on('click', 'a.editor_remove', function (e) {
40 e.preventDefault();
41 var row = api.row($(this).closest('tr'));
42 row.remove().draw();
43 } );
44
48 $('#user_group_list_table').DataTable({
45 $('#user_group_list_table').DataTable({
49 data: ${c.data|n},
46 data: ${c.groups|n},
50 dom: 'rtp',
47 dom: 'rtp',
51 pageLength: ${c.visual.admin_grid_items},
48 pageLength: ${c.visual.admin_grid_items},
52 order: [[ 0, "asc" ]],
49 order: [[ 0, "asc" ]],
53 columns: [
50 columns: [
54 { data: {"_": "group_name",
51 { data: {"_": "group_name",
55 "sort": "group_name_raw"}, title: "${_('Name')}", className: "td-componentname" },
52 "sort": "group_name"}, title: "${_('Name')}", className: "td-componentname," ,
56 { data: {"_": "desc",
53 render: function (data,type,full,meta)
57 "sort": "desc"}, title: "${_('Description')}", className: "td-description" },
54 {return '<div><i class="icon-group" title="User group">'+data+'</i></div>'}},
58 { data: {"_": "members",
55
59 "sort": "members",
56 { data: {"_": "group_description",
60 "type": Number}, title: "${_('Members')}", className: "td-number" },
57 "sort": "group_description"}, title: "${_('Description')}", className: "td-description" },
58 { data: {"_": "users_group_id"}, className: "td-user",
59 render: function (data,type,full,meta)
60 {return '<input type="hidden" name="users_group_id" value="'+data+'">'}},
61 { data: {"_": "active",
61 { data: {"_": "active",
62 "sort": "active"}, title: "${_('Active')}", className: "td-active", className: "td-number"},
62 "sort": "active"}, title: "${_('Active')}", className: "td-active", className: "td-number"},
63 { data: {"_": "owner",
63 { data: {"_": "owner_data"}, title: "${_('Owner')}", className: "td-user",
64 "sort": "owner"}, title: "${_('Owner')}", className: "td-user" },
64 render: function (data,type,full,meta)
65 { data: {"_": "action",
65 {return '<div class="rc-user tooltip">'+
66 "sort": "action"}, title: "${_('Action')}", className: "td-action" }
66 '<img class="gravatar" src="'+ data.owner_icon +'" height="16" width="16">'+
67 data.owner +'</div>'
68 }
69 },
70 { data: null,
71 title: "${_('Action')}",
72 className: "td-action",
73 defaultContent: '<a href="" class="btn btn-link btn-danger">Delete</a>'
74 },
67 ],
75 ],
68 language: {
76 language: {
69 paginate: DEFAULT_GRID_PAGINATION,
77 paginate: DEFAULT_GRID_PAGINATION,
70 emptyTable: _gettext("No user groups available yet.")
78 emptyTable: _gettext("No user groups available yet.")
71 },
79 },
72 "initComplete": function( settings, json ) {
80 "initComplete": function( settings, json ) {
81 var data_grid = $('#user_group_list_table').dataTable();
82 api = data_grid.api();
73 get_datatable_count();
83 get_datatable_count();
74 }
84 }
75 });
85 });
76
86
77 // update the counter when doing search
87 // update the counter when doing search
78 $('#user_group_list_table').on( 'search.dt', function (e,settings) {
88 $('#user_group_list_table').on( 'search.dt', function (e,settings) {
79 get_datatable_count();
89 get_datatable_count();
80 });
90 });
81
91
82 // filter, filter both grids
92 // filter, filter both grids
83 $('#q_filter').on( 'keyup', function () {
93 $('#q_filter').on( 'keyup', function () {
84 var user_api = $('#user_group_list_table').dataTable().api();
94 var user_api = $('#user_group_list_table').dataTable().api();
85 user_api
95 user_api
86 .columns(0)
96 .columns(0)
87 .search(this.value)
97 .search(this.value)
88 .draw();
98 .draw();
89 });
99 });
90
100
91 // refilter table if page load via back button
101 // refilter table if page load via back button
92 $("#q_filter").trigger('keyup');
102 $("#q_filter").trigger('keyup');
93
103
94 });
104 });
95
105
106 $('#language').select2({
107 'containerCssClass': "drop-menu",
108 'dropdownCssClass': "drop-menu-dropdown",
109 'dropdownAutoWidth': true
110 });
111
112
113
114 $(document).ready(function(){
115 $("#group_parent_id").select2({
116 'containerCssClass': "drop-menu",
117 'dropdownCssClass': "drop-menu-dropdown",
118 'dropdownAutoWidth': true
119 });
120
121 $('#add_user_to_group').autocomplete({
122 serviceUrl: pyroutes.url('user_group_autocomplete_data'),
123 minChars:2,
124 maxHeight:400,
125 width:300,
126 deferRequestBy: 300, //miliseconds
127 showNoSuggestionNotice: true,
128 params: { user_groups:true },
129 formatResult: autocompleteFormatResult,
130 lookupFilter: autocompleteFilterResult,
131 onSelect: function(element, suggestion){
132 var owner = {owner_icon: suggestion.owner_icon, owner:suggestion.owner};
133 api.row.add(
134 {"active": suggestion.active,
135 "owner_data": owner,
136 "users_group_id": suggestion.id,
137 "group_description": suggestion.description,
138 "group_name": suggestion.value}).draw();
139 }
140 });
141 })
142
96 </script>
143 </script>
97
144
98 </%def>
145
@@ -1,309 +1,310 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import tempfile
21 import tempfile
22
22
23 import mock
23 import mock
24 import pytest
24 import pytest
25
25
26 from rhodecode.lib.exceptions import AttachedForksError
26 from rhodecode.lib.exceptions import AttachedForksError
27 from rhodecode.lib.utils import make_db_config
27 from rhodecode.lib.utils import make_db_config
28 from rhodecode.model.db import Repository
28 from rhodecode.model.db import Repository
29 from rhodecode.model.meta import Session
29 from rhodecode.model.meta import Session
30 from rhodecode.model.repo import RepoModel
30 from rhodecode.model.repo import RepoModel
31 from rhodecode.model.scm import ScmModel
31 from rhodecode.model.scm import ScmModel
32 from rhodecode.lib.utils2 import safe_unicode
32 from rhodecode.lib.utils2 import safe_unicode
33
33
34
34
35 class TestRepoModel:
35 class TestRepoModel:
36
36
37 def test_remove_repo(self, backend):
37 def test_remove_repo(self, backend):
38 repo = backend.create_repo()
38 repo = backend.create_repo()
39 Session().commit()
39 Session().commit()
40 RepoModel().delete(repo=repo)
40 RepoModel().delete(repo=repo)
41 Session().commit()
41 Session().commit()
42
42
43 repos = ScmModel().repo_scan()
43 repos = ScmModel().repo_scan()
44
44
45 assert Repository.get_by_repo_name(repo_name=backend.repo_name) is None
45 assert Repository.get_by_repo_name(repo_name=backend.repo_name) is None
46 assert repo.repo_name not in repos
46 assert repo.repo_name not in repos
47
47
48 def test_remove_repo_raises_exc_when_attached_forks(self, backend):
48 def test_remove_repo_raises_exc_when_attached_forks(self, backend):
49 repo = backend.create_repo()
49 repo = backend.create_repo()
50 Session().commit()
50 Session().commit()
51 backend.create_fork()
51 backend.create_fork()
52 Session().commit()
52 Session().commit()
53
53
54 with pytest.raises(AttachedForksError):
54 with pytest.raises(AttachedForksError):
55 RepoModel().delete(repo=repo)
55 RepoModel().delete(repo=repo)
56
56
57 def test_remove_repo_delete_forks(self, backend):
57 def test_remove_repo_delete_forks(self, backend):
58 repo = backend.create_repo()
58 repo = backend.create_repo()
59 Session().commit()
59 Session().commit()
60
60
61 fork = backend.create_fork()
61 fork = backend.create_fork()
62 Session().commit()
62 Session().commit()
63
63
64 fork_of_fork = backend.create_fork()
64 fork_of_fork = backend.create_fork()
65 Session().commit()
65 Session().commit()
66
66
67 RepoModel().delete(repo=repo, forks='delete')
67 RepoModel().delete(repo=repo, forks='delete')
68 Session().commit()
68 Session().commit()
69
69
70 assert Repository.get_by_repo_name(repo_name=repo.repo_name) is None
70 assert Repository.get_by_repo_name(repo_name=repo.repo_name) is None
71 assert Repository.get_by_repo_name(repo_name=fork.repo_name) is None
71 assert Repository.get_by_repo_name(repo_name=fork.repo_name) is None
72 assert (
72 assert (
73 Repository.get_by_repo_name(repo_name=fork_of_fork.repo_name)
73 Repository.get_by_repo_name(repo_name=fork_of_fork.repo_name)
74 is None)
74 is None)
75
75
76 def test_remove_repo_detach_forks(self, backend):
76 def test_remove_repo_detach_forks(self, backend):
77 repo = backend.create_repo()
77 repo = backend.create_repo()
78 Session().commit()
78 Session().commit()
79
79
80 fork = backend.create_fork()
80 fork = backend.create_fork()
81 Session().commit()
81 Session().commit()
82
82
83 fork_of_fork = backend.create_fork()
83 fork_of_fork = backend.create_fork()
84 Session().commit()
84 Session().commit()
85
85
86 RepoModel().delete(repo=repo, forks='detach')
86 RepoModel().delete(repo=repo, forks='detach')
87 Session().commit()
87 Session().commit()
88
88
89 assert Repository.get_by_repo_name(repo_name=repo.repo_name) is None
89 assert Repository.get_by_repo_name(repo_name=repo.repo_name) is None
90 assert (
90 assert (
91 Repository.get_by_repo_name(repo_name=fork.repo_name) is not None)
91 Repository.get_by_repo_name(repo_name=fork.repo_name) is not None)
92 assert (
92 assert (
93 Repository.get_by_repo_name(repo_name=fork_of_fork.repo_name)
93 Repository.get_by_repo_name(repo_name=fork_of_fork.repo_name)
94 is not None)
94 is not None)
95
95
96 @pytest.mark.parametrize("filename, expected", [
96 @pytest.mark.parametrize("filename, expected", [
97 ("README", True),
97 ("README", True),
98 ("README.rst", False),
98 ("README.rst", False),
99 ])
99 ])
100 def test_filenode_is_link(self, vcsbackend, filename, expected):
100 def test_filenode_is_link(self, vcsbackend, filename, expected):
101 repo = vcsbackend.repo
101 repo = vcsbackend.repo
102 assert repo.get_commit().is_link(filename) is expected
102 assert repo.get_commit().is_link(filename) is expected
103
103
104 def test_get_commit(self, backend):
104 def test_get_commit(self, backend):
105 backend.repo.get_commit()
105 backend.repo.get_commit()
106
106
107 def test_get_changeset_is_deprecated(self, backend):
107 def test_get_changeset_is_deprecated(self, backend):
108 repo = backend.repo
108 repo = backend.repo
109 pytest.deprecated_call(repo.get_changeset)
109 pytest.deprecated_call(repo.get_changeset)
110
110
111 def test_clone_url_encrypted_value(self, backend):
111 def test_clone_url_encrypted_value(self, backend):
112 repo = backend.create_repo()
112 repo = backend.create_repo()
113 Session().commit()
113 Session().commit()
114
114
115 repo.clone_url = 'https://marcink:qweqwe@code.rhodecode.com'
115 repo.clone_url = 'https://marcink:qweqwe@code.rhodecode.com'
116 Session().add(repo)
116 Session().add(repo)
117 Session().commit()
117 Session().commit()
118
118
119 assert repo.clone_url == 'https://marcink:qweqwe@code.rhodecode.com'
119 assert repo.clone_url == 'https://marcink:qweqwe@code.rhodecode.com'
120
120
121 @pytest.mark.backends("git", "svn")
121 @pytest.mark.backends("git", "svn")
122 def test_create_filesystem_repo_installs_hooks(self, tmpdir, backend):
122 def test_create_filesystem_repo_installs_hooks(self, tmpdir, backend):
123 hook_methods = {
123 hook_methods = {
124 'git': 'install_git_hook',
124 'git': 'install_git_hook',
125 'svn': 'install_svn_hooks'
125 'svn': 'install_svn_hooks'
126 }
126 }
127 repo = backend.create_repo()
127 repo = backend.create_repo()
128 repo_name = repo.repo_name
128 repo_name = repo.repo_name
129 model = RepoModel()
129 model = RepoModel()
130 repo_location = tempfile.mkdtemp()
130 repo_location = tempfile.mkdtemp()
131 model.repos_path = repo_location
131 model.repos_path = repo_location
132 method = hook_methods[backend.alias]
132 method = hook_methods[backend.alias]
133 with mock.patch.object(ScmModel, method) as hooks_mock:
133 with mock.patch.object(ScmModel, method) as hooks_mock:
134 model._create_filesystem_repo(
134 model._create_filesystem_repo(
135 repo_name, backend.alias, repo_group='', clone_uri=None)
135 repo_name, backend.alias, repo_group='', clone_uri=None)
136 assert hooks_mock.call_count == 1
136 assert hooks_mock.call_count == 1
137 hook_args, hook_kwargs = hooks_mock.call_args
137 hook_args, hook_kwargs = hooks_mock.call_args
138 assert hook_args[0].name == repo_name
138 assert hook_args[0].name == repo_name
139
139
140 @pytest.mark.parametrize("use_global_config, repo_name_passed", [
140 @pytest.mark.parametrize("use_global_config, repo_name_passed", [
141 (True, False),
141 (True, False),
142 (False, True)
142 (False, True)
143 ])
143 ])
144 def test_per_repo_config_is_generated_during_filesystem_repo_creation(
144 def test_per_repo_config_is_generated_during_filesystem_repo_creation(
145 self, tmpdir, backend, use_global_config, repo_name_passed):
145 self, tmpdir, backend, use_global_config, repo_name_passed):
146 repo_name = 'test-{}-repo-{}'.format(backend.alias, use_global_config)
146 repo_name = 'test-{}-repo-{}'.format(backend.alias, use_global_config)
147 config = make_db_config()
147 config = make_db_config()
148 model = RepoModel()
148 model = RepoModel()
149 with mock.patch('rhodecode.model.repo.make_db_config') as config_mock:
149 with mock.patch('rhodecode.model.repo.make_db_config') as config_mock:
150 config_mock.return_value = config
150 config_mock.return_value = config
151 model._create_filesystem_repo(
151 model._create_filesystem_repo(
152 repo_name, backend.alias, repo_group='', clone_uri=None,
152 repo_name, backend.alias, repo_group='', clone_uri=None,
153 use_global_config=use_global_config)
153 use_global_config=use_global_config)
154 expected_repo_name = repo_name if repo_name_passed else None
154 expected_repo_name = repo_name if repo_name_passed else None
155 expected_call = mock.call(clear_session=False, repo=expected_repo_name)
155 expected_call = mock.call(clear_session=False, repo=expected_repo_name)
156 assert expected_call in config_mock.call_args_list
156 assert expected_call in config_mock.call_args_list
157
157
158 def test_update_commit_cache_with_config(serf, backend):
158 def test_update_commit_cache_with_config(serf, backend):
159 repo = backend.create_repo()
159 repo = backend.create_repo()
160 with mock.patch('rhodecode.model.db.Repository.scm_instance') as scm:
160 with mock.patch('rhodecode.model.db.Repository.scm_instance') as scm:
161 scm_instance = mock.Mock()
161 scm_instance = mock.Mock()
162 scm_instance.get_commit.return_value = {
162 scm_instance.get_commit.return_value = {
163 'raw_id': 40*'0',
163 'raw_id': 40*'0',
164 'revision': 1
164 'revision': 1
165 }
165 }
166 scm.return_value = scm_instance
166 scm.return_value = scm_instance
167 repo.update_commit_cache()
167 repo.update_commit_cache()
168 scm.assert_called_with(cache=False, config=None)
168 scm.assert_called_with(cache=False, config=None)
169 config = {'test': 'config'}
169 config = {'test': 'config'}
170 repo.update_commit_cache(config=config)
170 repo.update_commit_cache(config=config)
171 scm.assert_called_with(
171 scm.assert_called_with(
172 cache=False, config=config)
172 cache=False, config=config)
173
173
174
174
175 class TestGetUsers(object):
175 class TestGetUsers(object):
176 def test_returns_active_users(self, backend, user_util):
176 def test_returns_active_users(self, backend, user_util):
177 for i in range(4):
177 for i in range(4):
178 is_active = i % 2 == 0
178 is_active = i % 2 == 0
179 user_util.create_user(active=is_active, lastname='Fake user')
179 user_util.create_user(active=is_active, lastname='Fake user')
180
180
181 with mock.patch('rhodecode.lib.helpers.gravatar_url'):
181 with mock.patch('rhodecode.lib.helpers.gravatar_url'):
182 users = RepoModel().get_users()
182 users = RepoModel().get_users()
183 fake_users = [u for u in users if u['last_name'] == 'Fake user']
183 fake_users = [u for u in users if u['last_name'] == 'Fake user']
184 assert len(fake_users) == 2
184 assert len(fake_users) == 2
185
185
186 expected_keys = (
186 expected_keys = (
187 'id', 'first_name', 'last_name', 'username', 'icon_link',
187 'id', 'first_name', 'last_name', 'username', 'icon_link',
188 'value_display', 'value', 'value_type')
188 'value_display', 'value', 'value_type')
189 for user in users:
189 for user in users:
190 assert user['value_type'] is 'user'
190 assert user['value_type'] is 'user'
191 for key in expected_keys:
191 for key in expected_keys:
192 assert key in user
192 assert key in user
193
193
194 def test_returns_user_filtered_by_last_name(self, backend, user_util):
194 def test_returns_user_filtered_by_last_name(self, backend, user_util):
195 keywords = ('aBc', u'ünicode')
195 keywords = ('aBc', u'ünicode')
196 for keyword in keywords:
196 for keyword in keywords:
197 for i in range(2):
197 for i in range(2):
198 user_util.create_user(
198 user_util.create_user(
199 active=True, lastname=u'Fake {} user'.format(keyword))
199 active=True, lastname=u'Fake {} user'.format(keyword))
200
200
201 with mock.patch('rhodecode.lib.helpers.gravatar_url'):
201 with mock.patch('rhodecode.lib.helpers.gravatar_url'):
202 keyword = keywords[1].lower()
202 keyword = keywords[1].lower()
203 users = RepoModel().get_users(name_contains=keyword)
203 users = RepoModel().get_users(name_contains=keyword)
204
204
205 fake_users = [u for u in users if u['last_name'].startswith('Fake')]
205 fake_users = [u for u in users if u['last_name'].startswith('Fake')]
206 assert len(fake_users) == 2
206 assert len(fake_users) == 2
207 for user in fake_users:
207 for user in fake_users:
208 assert user['last_name'] == safe_unicode('Fake ünicode user')
208 assert user['last_name'] == safe_unicode('Fake ünicode user')
209
209
210 def test_returns_user_filtered_by_first_name(self, backend, user_util):
210 def test_returns_user_filtered_by_first_name(self, backend, user_util):
211 created_users = []
211 created_users = []
212 keywords = ('aBc', u'ünicode')
212 keywords = ('aBc', u'ünicode')
213 for keyword in keywords:
213 for keyword in keywords:
214 for i in range(2):
214 for i in range(2):
215 created_users.append(user_util.create_user(
215 created_users.append(user_util.create_user(
216 active=True, lastname='Fake user',
216 active=True, lastname='Fake user',
217 firstname=u'Fake {} user'.format(keyword)))
217 firstname=u'Fake {} user'.format(keyword)))
218
218
219 keyword = keywords[1].lower()
219 keyword = keywords[1].lower()
220 with mock.patch('rhodecode.lib.helpers.gravatar_url'):
220 with mock.patch('rhodecode.lib.helpers.gravatar_url'):
221 users = RepoModel().get_users(name_contains=keyword)
221 users = RepoModel().get_users(name_contains=keyword)
222
222
223 fake_users = [u for u in users if u['last_name'].startswith('Fake')]
223 fake_users = [u for u in users if u['last_name'].startswith('Fake')]
224 assert len(fake_users) == 2
224 assert len(fake_users) == 2
225 for user in fake_users:
225 for user in fake_users:
226 assert user['first_name'] == safe_unicode('Fake ünicode user')
226 assert user['first_name'] == safe_unicode('Fake ünicode user')
227
227
228 def test_returns_user_filtered_by_username(self, backend, user_util):
228 def test_returns_user_filtered_by_username(self, backend, user_util):
229 created_users = []
229 created_users = []
230 for i in range(5):
230 for i in range(5):
231 created_users.append(user_util.create_user(
231 created_users.append(user_util.create_user(
232 active=True, lastname='Fake user'))
232 active=True, lastname='Fake user'))
233
233
234 user_filter = created_users[-1].username[-2:]
234 user_filter = created_users[-1].username[-2:]
235 with mock.patch('rhodecode.lib.helpers.gravatar_url'):
235 with mock.patch('rhodecode.lib.helpers.gravatar_url'):
236 users = RepoModel().get_users(name_contains=user_filter)
236 users = RepoModel().get_users(name_contains=user_filter)
237
237
238 fake_users = [u for u in users if u['last_name'].startswith('Fake')]
238 fake_users = [u for u in users if u['last_name'].startswith('Fake')]
239 assert len(fake_users) == 1
239 assert len(fake_users) == 1
240 assert fake_users[0]['username'] == created_users[-1].username
240 assert fake_users[0]['username'] == created_users[-1].username
241
241
242 def test_returns_limited_user_list(self, backend, user_util):
242 def test_returns_limited_user_list(self, backend, user_util):
243 created_users = []
243 created_users = []
244 for i in range(5):
244 for i in range(5):
245 created_users.append(user_util.create_user(
245 created_users.append(user_util.create_user(
246 active=True, lastname='Fake user'))
246 active=True, lastname='Fake user'))
247
247
248 with mock.patch('rhodecode.lib.helpers.gravatar_url'):
248 with mock.patch('rhodecode.lib.helpers.gravatar_url'):
249 users = RepoModel().get_users(name_contains='Fake', limit=3)
249 users = RepoModel().get_users(name_contains='Fake', limit=3)
250
250
251 fake_users = [u for u in users if u['last_name'].startswith('Fake')]
251 fake_users = [u for u in users if u['last_name'].startswith('Fake')]
252 assert len(fake_users) == 3
252 assert len(fake_users) == 3
253
253
254
254
255 class TestGetUserGroups(object):
255 class TestGetUserGroups(object):
256 def test_returns_filtered_list(self, backend, user_util):
256 def test_returns_filtered_list(self, backend, user_util):
257 created_groups = []
257 created_groups = []
258 for i in range(4):
258 for i in range(4):
259 created_groups.append(
259 created_groups.append(
260 user_util.create_user_group(users_group_active=True))
260 user_util.create_user_group(users_group_active=True))
261
261
262 group_filter = created_groups[-1].users_group_name[-2:]
262 group_filter = created_groups[-1].users_group_name[-2:]
263 with mock.patch('rhodecode.lib.helpers.gravatar_url'):
263 with self._patch_user_group_list():
264 with self._patch_user_group_list():
264 groups = RepoModel().get_user_groups(group_filter)
265 groups = RepoModel().get_user_groups(group_filter)
265
266
266 fake_groups = [
267 fake_groups = [
267 u for u in groups if u['value'].startswith('test_returns')]
268 u for u in groups if u['value'].startswith('test_returns')]
268 assert len(fake_groups) == 1
269 assert len(fake_groups) == 1
269 assert fake_groups[0]['value'] == created_groups[-1].users_group_name
270 assert fake_groups[0]['value'] == created_groups[-1].users_group_name
270 assert fake_groups[0]['value_display'].startswith(
271 assert fake_groups[0]['value_display'].startswith(
271 'Group: test_returns')
272 'Group: test_returns')
272
273
273 def test_returns_limited_list(self, backend, user_util):
274 def test_returns_limited_list(self, backend, user_util):
274 created_groups = []
275 created_groups = []
275 for i in range(3):
276 for i in range(3):
276 created_groups.append(
277 created_groups.append(
277 user_util.create_user_group(users_group_active=True))
278 user_util.create_user_group(users_group_active=True))
278
279 with mock.patch('rhodecode.lib.helpers.gravatar_url'):
279 with self._patch_user_group_list():
280 with self._patch_user_group_list():
280 groups = RepoModel().get_user_groups('test_returns')
281 groups = RepoModel().get_user_groups('test_returns')
281
282
282 fake_groups = [
283 fake_groups = [
283 u for u in groups if u['value'].startswith('test_returns')]
284 u for u in groups if u['value'].startswith('test_returns')]
284 assert len(fake_groups) == 3
285 assert len(fake_groups) == 3
285
286
286 def test_returns_active_user_groups(self, backend, user_util):
287 def test_returns_active_user_groups(self, backend, user_util):
287 for i in range(4):
288 for i in range(4):
288 is_active = i % 2 == 0
289 is_active = i % 2 == 0
289 user_util.create_user_group(users_group_active=is_active)
290 user_util.create_user_group(users_group_active=is_active)
290
291 with mock.patch('rhodecode.lib.helpers.gravatar_url'):
291 with self._patch_user_group_list():
292 with self._patch_user_group_list():
292 groups = RepoModel().get_user_groups()
293 groups = RepoModel().get_user_groups()
293 expected = ('id', 'icon_link', 'value_display', 'value', 'value_type')
294 expected = ('id', 'icon_link', 'value_display', 'value', 'value_type')
294 for group in groups:
295 for group in groups:
295 assert group['value_type'] is 'user_group'
296 assert group['value_type'] is 'user_group'
296 for key in expected:
297 for key in expected:
297 assert key in group
298 assert key in group
298
299
299 fake_groups = [
300 fake_groups = [
300 u for u in groups if u['value'].startswith('test_returns')]
301 u for u in groups if u['value'].startswith('test_returns')]
301 assert len(fake_groups) == 2
302 assert len(fake_groups) == 2
302 for user in fake_groups:
303 for user in fake_groups:
303 assert user['value_display'].startswith('Group: test_returns')
304 assert user['value_display'].startswith('Group: test_returns')
304
305
305 def _patch_user_group_list(self):
306 def _patch_user_group_list(self):
306 def side_effect(group_list, perm_set):
307 def side_effect(group_list, perm_set):
307 return group_list
308 return group_list
308 return mock.patch(
309 return mock.patch(
309 'rhodecode.model.repo.UserGroupList', side_effect=side_effect)
310 'rhodecode.model.repo.UserGroupList', side_effect=side_effect)
General Comments 0
You need to be logged in to leave comments. Login now