##// 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

The requested changes are too big and content was truncated. Show full diff

@@ -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 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
@@ -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