##// END OF EJS Templates
permissions: rename write+ to write or higher for more explicit meaning.
marcink -
r4462:91b375f2 stable
parent child Browse files
Show More
@@ -1,140 +1,140 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2011-2020 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 import logging
22 22
23 23 from pyramid.httpexceptions import HTTPFound
24 24 from pyramid.view import view_config
25 25
26 26 from rhodecode.apps._base import RepoAppView
27 27 from rhodecode.lib import helpers as h
28 28 from rhodecode.lib import audit_logger
29 29 from rhodecode.lib.auth import (
30 30 LoginRequired, HasRepoPermissionAnyDecorator, CSRFRequired)
31 31 from rhodecode.lib.utils2 import str2bool
32 32 from rhodecode.model.db import User
33 33 from rhodecode.model.forms import RepoPermsForm
34 34 from rhodecode.model.meta import Session
35 35 from rhodecode.model.permission import PermissionModel
36 36 from rhodecode.model.repo import RepoModel
37 37
38 38 log = logging.getLogger(__name__)
39 39
40 40
41 41 class RepoSettingsPermissionsView(RepoAppView):
42 42
43 43 def load_default_context(self):
44 44 c = self._get_local_tmpl_context()
45 45 return c
46 46
47 47 @LoginRequired()
48 48 @HasRepoPermissionAnyDecorator('repository.admin')
49 49 @view_config(
50 50 route_name='edit_repo_perms', request_method='GET',
51 51 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
52 52 def edit_permissions(self):
53 53 _ = self.request.translate
54 54 c = self.load_default_context()
55 55 c.active = 'permissions'
56 56 if self.request.GET.get('branch_permissions'):
57 h.flash(_('Explicitly add user or user group with write+ '
57 h.flash(_('Explicitly add user or user group with write or higher '
58 58 'permission to modify their branch permissions.'),
59 59 category='notice')
60 60 return self._get_template_context(c)
61 61
62 62 @LoginRequired()
63 63 @HasRepoPermissionAnyDecorator('repository.admin')
64 64 @CSRFRequired()
65 65 @view_config(
66 66 route_name='edit_repo_perms', request_method='POST',
67 67 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
68 68 def edit_permissions_update(self):
69 69 _ = self.request.translate
70 70 c = self.load_default_context()
71 71 c.active = 'permissions'
72 72 data = self.request.POST
73 73 # store private flag outside of HTML to verify if we can modify
74 74 # default user permissions, prevents submission of FAKE post data
75 75 # into the form for private repos
76 76 data['repo_private'] = self.db_repo.private
77 77 form = RepoPermsForm(self.request.translate)().to_python(data)
78 78 changes = RepoModel().update_permissions(
79 79 self.db_repo_name, form['perm_additions'], form['perm_updates'],
80 80 form['perm_deletions'])
81 81
82 82 action_data = {
83 83 'added': changes['added'],
84 84 'updated': changes['updated'],
85 85 'deleted': changes['deleted'],
86 86 }
87 87 audit_logger.store_web(
88 88 'repo.edit.permissions', action_data=action_data,
89 89 user=self._rhodecode_user, repo=self.db_repo)
90 90
91 91 Session().commit()
92 92 h.flash(_('Repository access permissions updated'), category='success')
93 93
94 94 affected_user_ids = None
95 95 if changes.get('default_user_changed', False):
96 96 # if we change the default user, we need to flush everyone permissions
97 97 affected_user_ids = User.get_all_user_ids()
98 98 PermissionModel().flush_user_permission_caches(
99 99 changes, affected_user_ids=affected_user_ids)
100 100
101 101 raise HTTPFound(
102 102 h.route_path('edit_repo_perms', repo_name=self.db_repo_name))
103 103
104 104 @LoginRequired()
105 105 @HasRepoPermissionAnyDecorator('repository.admin')
106 106 @CSRFRequired()
107 107 @view_config(
108 108 route_name='edit_repo_perms_set_private', request_method='POST',
109 109 renderer='json_ext')
110 110 def edit_permissions_set_private_repo(self):
111 111 _ = self.request.translate
112 112 self.load_default_context()
113 113
114 114 private_flag = str2bool(self.request.POST.get('private'))
115 115
116 116 try:
117 117 repo = RepoModel().get(self.db_repo.repo_id)
118 118 repo.private = private_flag
119 119 Session().add(repo)
120 120 RepoModel().grant_user_permission(
121 121 repo=self.db_repo, user=User.DEFAULT_USER, perm='repository.none'
122 122 )
123 123
124 124 Session().commit()
125 125
126 126 h.flash(_('Repository `{}` private mode set successfully').format(self.db_repo_name),
127 127 category='success')
128 128 # NOTE(dan): we change repo private mode we need to notify all USERS
129 129 affected_user_ids = User.get_all_user_ids()
130 130 PermissionModel().trigger_permission_flush(affected_user_ids)
131 131
132 132 except Exception:
133 133 log.exception("Exception during update of repository")
134 134 h.flash(_('Error occurred during update of repository {}').format(
135 135 self.db_repo_name), category='error')
136 136
137 137 return {
138 138 'redirect_url': h.route_path('edit_repo_perms', repo_name=self.db_repo_name),
139 139 'private': private_flag
140 140 }
@@ -1,162 +1,162 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2016-2020 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 import os
22 22 import sys
23 23 import json
24 24 import logging
25 25
26 26 from rhodecode.lib.hooks_daemon import prepare_callback_daemon
27 27 from rhodecode.lib.vcs.conf import settings as vcs_settings
28 28 from rhodecode.model.scm import ScmModel
29 29
30 30 log = logging.getLogger(__name__)
31 31
32 32
33 33 class VcsServer(object):
34 34 _path = None # set executable path for hg/git/svn binary
35 35 backend = None # set in child classes
36 36 tunnel = None # subprocess handling tunnel
37 37 write_perms = ['repository.admin', 'repository.write']
38 38 read_perms = ['repository.read', 'repository.admin', 'repository.write']
39 39
40 40 def __init__(self, user, user_permissions, config, env):
41 41 self.user = user
42 42 self.user_permissions = user_permissions
43 43 self.config = config
44 44 self.env = env
45 45 self.stdin = sys.stdin
46 46
47 47 self.repo_name = None
48 48 self.repo_mode = None
49 49 self.store = ''
50 50 self.ini_path = ''
51 51
52 52 def _invalidate_cache(self, repo_name):
53 53 """
54 54 Set's cache for this repository for invalidation on next access
55 55
56 56 :param repo_name: full repo name, also a cache key
57 57 """
58 58 ScmModel().mark_for_invalidation(repo_name)
59 59
60 60 def has_write_perm(self):
61 61 permission = self.user_permissions.get(self.repo_name)
62 62 if permission in ['repository.write', 'repository.admin']:
63 63 return True
64 64
65 65 return False
66 66
67 67 def _check_permissions(self, action):
68 68 permission = self.user_permissions.get(self.repo_name)
69 69 log.debug('permission for %s on %s are: %s',
70 70 self.user, self.repo_name, permission)
71 71
72 72 if not permission:
73 73 log.error('user `%s` permissions to repo:%s are empty. Forbidding access.',
74 74 self.user, self.repo_name)
75 75 return -2
76 76
77 77 if action == 'pull':
78 78 if permission in self.read_perms:
79 79 log.info(
80 80 'READ Permissions for User "%s" detected to repo "%s"!',
81 81 self.user, self.repo_name)
82 82 return 0
83 83 else:
84 84 if permission in self.write_perms:
85 85 log.info(
86 'WRITE+ Permissions for User "%s" detected to repo "%s"!',
86 'WRITE, or Higher Permissions for User "%s" detected to repo "%s"!',
87 87 self.user, self.repo_name)
88 88 return 0
89 89
90 90 log.error('Cannot properly fetch or verify user `%s` permissions. '
91 91 'Permissions: %s, vcs action: %s',
92 92 self.user, permission, action)
93 93 return -2
94 94
95 95 def update_environment(self, action, extras=None):
96 96
97 97 scm_data = {
98 98 'ip': os.environ['SSH_CLIENT'].split()[0],
99 99 'username': self.user.username,
100 100 'user_id': self.user.user_id,
101 101 'action': action,
102 102 'repository': self.repo_name,
103 103 'scm': self.backend,
104 104 'config': self.ini_path,
105 105 'repo_store': self.store,
106 106 'make_lock': None,
107 107 'locked_by': [None, None],
108 108 'server_url': None,
109 109 'user_agent': 'ssh-user-agent',
110 110 'hooks': ['push', 'pull'],
111 111 'hooks_module': 'rhodecode.lib.hooks_daemon',
112 112 'is_shadow_repo': False,
113 113 'detect_force_push': False,
114 114 'check_branch_perms': False,
115 115
116 116 'SSH': True,
117 117 'SSH_PERMISSIONS': self.user_permissions.get(self.repo_name),
118 118 }
119 119 if extras:
120 120 scm_data.update(extras)
121 121 os.putenv("RC_SCM_DATA", json.dumps(scm_data))
122 122
123 123 def get_root_store(self):
124 124 root_store = self.store
125 125 if not root_store.endswith('/'):
126 126 # always append trailing slash
127 127 root_store = root_store + '/'
128 128 return root_store
129 129
130 130 def _handle_tunnel(self, extras):
131 131 # pre-auth
132 132 action = 'pull'
133 133 exit_code = self._check_permissions(action)
134 134 if exit_code:
135 135 return exit_code, False
136 136
137 137 req = self.env['request']
138 138 server_url = req.host_url + req.script_name
139 139 extras['server_url'] = server_url
140 140
141 141 log.debug('Using %s binaries from path %s', self.backend, self._path)
142 142 exit_code = self.tunnel.run(extras)
143 143
144 144 return exit_code, action == "push"
145 145
146 146 def run(self, tunnel_extras=None):
147 147 tunnel_extras = tunnel_extras or {}
148 148 extras = {}
149 149 extras.update(tunnel_extras)
150 150
151 151 callback_daemon, extras = prepare_callback_daemon(
152 152 extras, protocol=vcs_settings.HOOKS_PROTOCOL,
153 153 host=vcs_settings.HOOKS_HOST,
154 154 use_direct_calls=False)
155 155
156 156 with callback_daemon:
157 157 try:
158 158 return self._handle_tunnel(extras)
159 159 finally:
160 160 log.debug('Running cleanup with cache invalidation')
161 161 if self.repo_name:
162 162 self._invalidate_cache(self.repo_name)
@@ -1,598 +1,598 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2020 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 """
22 22 permissions model for RhodeCode
23 23 """
24 24 import collections
25 25 import logging
26 26 import traceback
27 27
28 28 from sqlalchemy.exc import DatabaseError
29 29
30 30 from rhodecode import events
31 31 from rhodecode.model import BaseModel
32 32 from rhodecode.model.db import (
33 33 User, Permission, UserToPerm, UserRepoToPerm, UserRepoGroupToPerm,
34 34 UserUserGroupToPerm, UserGroup, UserGroupToPerm, UserToRepoBranchPermission)
35 35 from rhodecode.lib.utils2 import str2bool, safe_int
36 36
37 37 log = logging.getLogger(__name__)
38 38
39 39
40 40 class PermissionModel(BaseModel):
41 41 """
42 42 Permissions model for RhodeCode
43 43 """
44 44
45 45 cls = Permission
46 46 global_perms = {
47 47 'default_repo_create': None,
48 48 # special case for create repos on write access to group
49 49 'default_repo_create_on_write': None,
50 50 'default_repo_group_create': None,
51 51 'default_user_group_create': None,
52 52 'default_fork_create': None,
53 53 'default_inherit_default_permissions': None,
54 54 'default_register': None,
55 55 'default_password_reset': None,
56 56 'default_extern_activate': None,
57 57
58 58 # object permissions below
59 59 'default_repo_perm': None,
60 60 'default_group_perm': None,
61 61 'default_user_group_perm': None,
62 62
63 63 # branch
64 64 'default_branch_perm': None,
65 65 }
66 66
67 67 def set_global_permission_choices(self, c_obj, gettext_translator):
68 68 _ = gettext_translator
69 69
70 70 c_obj.repo_perms_choices = [
71 71 ('repository.none', _('None'),),
72 72 ('repository.read', _('Read'),),
73 73 ('repository.write', _('Write'),),
74 74 ('repository.admin', _('Admin'),)]
75 75
76 76 c_obj.group_perms_choices = [
77 77 ('group.none', _('None'),),
78 78 ('group.read', _('Read'),),
79 79 ('group.write', _('Write'),),
80 80 ('group.admin', _('Admin'),)]
81 81
82 82 c_obj.user_group_perms_choices = [
83 83 ('usergroup.none', _('None'),),
84 84 ('usergroup.read', _('Read'),),
85 85 ('usergroup.write', _('Write'),),
86 86 ('usergroup.admin', _('Admin'),)]
87 87
88 88 c_obj.branch_perms_choices = [
89 89 ('branch.none', _('Protected/No Access'),),
90 90 ('branch.merge', _('Web merge'),),
91 91 ('branch.push', _('Push'),),
92 92 ('branch.push_force', _('Force Push'),)]
93 93
94 94 c_obj.register_choices = [
95 95 ('hg.register.none', _('Disabled')),
96 96 ('hg.register.manual_activate', _('Allowed with manual account activation')),
97 97 ('hg.register.auto_activate', _('Allowed with automatic account activation')),]
98 98
99 99 c_obj.password_reset_choices = [
100 100 ('hg.password_reset.enabled', _('Allow password recovery')),
101 101 ('hg.password_reset.hidden', _('Hide password recovery link')),
102 102 ('hg.password_reset.disabled', _('Disable password recovery')),]
103 103
104 104 c_obj.extern_activate_choices = [
105 105 ('hg.extern_activate.manual', _('Manual activation of external account')),
106 106 ('hg.extern_activate.auto', _('Automatic activation of external account')),]
107 107
108 108 c_obj.repo_create_choices = [
109 109 ('hg.create.none', _('Disabled')),
110 110 ('hg.create.repository', _('Enabled'))]
111 111
112 112 c_obj.repo_create_on_write_choices = [
113 113 ('hg.create.write_on_repogroup.false', _('Disabled')),
114 114 ('hg.create.write_on_repogroup.true', _('Enabled'))]
115 115
116 116 c_obj.user_group_create_choices = [
117 117 ('hg.usergroup.create.false', _('Disabled')),
118 118 ('hg.usergroup.create.true', _('Enabled'))]
119 119
120 120 c_obj.repo_group_create_choices = [
121 121 ('hg.repogroup.create.false', _('Disabled')),
122 122 ('hg.repogroup.create.true', _('Enabled'))]
123 123
124 124 c_obj.fork_choices = [
125 125 ('hg.fork.none', _('Disabled')),
126 126 ('hg.fork.repository', _('Enabled'))]
127 127
128 128 c_obj.inherit_default_permission_choices = [
129 129 ('hg.inherit_default_perms.false', _('Disabled')),
130 130 ('hg.inherit_default_perms.true', _('Enabled'))]
131 131
132 132 def get_default_perms(self, object_perms, suffix):
133 133 defaults = {}
134 134 for perm in object_perms:
135 135 # perms
136 136 if perm.permission.permission_name.startswith('repository.'):
137 137 defaults['default_repo_perm' + suffix] = perm.permission.permission_name
138 138
139 139 if perm.permission.permission_name.startswith('group.'):
140 140 defaults['default_group_perm' + suffix] = perm.permission.permission_name
141 141
142 142 if perm.permission.permission_name.startswith('usergroup.'):
143 143 defaults['default_user_group_perm' + suffix] = perm.permission.permission_name
144 144
145 145 # branch
146 146 if perm.permission.permission_name.startswith('branch.'):
147 147 defaults['default_branch_perm' + suffix] = perm.permission.permission_name
148 148
149 149 # creation of objects
150 150 if perm.permission.permission_name.startswith('hg.create.write_on_repogroup'):
151 151 defaults['default_repo_create_on_write' + suffix] = perm.permission.permission_name
152 152
153 153 elif perm.permission.permission_name.startswith('hg.create.'):
154 154 defaults['default_repo_create' + suffix] = perm.permission.permission_name
155 155
156 156 if perm.permission.permission_name.startswith('hg.fork.'):
157 157 defaults['default_fork_create' + suffix] = perm.permission.permission_name
158 158
159 159 if perm.permission.permission_name.startswith('hg.inherit_default_perms.'):
160 160 defaults['default_inherit_default_permissions' + suffix] = perm.permission.permission_name
161 161
162 162 if perm.permission.permission_name.startswith('hg.repogroup.'):
163 163 defaults['default_repo_group_create' + suffix] = perm.permission.permission_name
164 164
165 165 if perm.permission.permission_name.startswith('hg.usergroup.'):
166 166 defaults['default_user_group_create' + suffix] = perm.permission.permission_name
167 167
168 168 # registration and external account activation
169 169 if perm.permission.permission_name.startswith('hg.register.'):
170 170 defaults['default_register' + suffix] = perm.permission.permission_name
171 171
172 172 if perm.permission.permission_name.startswith('hg.password_reset.'):
173 173 defaults['default_password_reset' + suffix] = perm.permission.permission_name
174 174
175 175 if perm.permission.permission_name.startswith('hg.extern_activate.'):
176 176 defaults['default_extern_activate' + suffix] = perm.permission.permission_name
177 177
178 178 return defaults
179 179
180 180 def _make_new_user_perm(self, user, perm_name):
181 181 log.debug('Creating new user permission:%s', perm_name)
182 182 new = UserToPerm()
183 183 new.user = user
184 184 new.permission = Permission.get_by_key(perm_name)
185 185 return new
186 186
187 187 def _make_new_user_group_perm(self, user_group, perm_name):
188 188 log.debug('Creating new user group permission:%s', perm_name)
189 189 new = UserGroupToPerm()
190 190 new.users_group = user_group
191 191 new.permission = Permission.get_by_key(perm_name)
192 192 return new
193 193
194 194 def _keep_perm(self, perm_name, keep_fields):
195 195 def get_pat(field_name):
196 196 return {
197 197 # global perms
198 198 'default_repo_create': 'hg.create.',
199 199 # special case for create repos on write access to group
200 200 'default_repo_create_on_write': 'hg.create.write_on_repogroup.',
201 201 'default_repo_group_create': 'hg.repogroup.create.',
202 202 'default_user_group_create': 'hg.usergroup.create.',
203 203 'default_fork_create': 'hg.fork.',
204 204 'default_inherit_default_permissions': 'hg.inherit_default_perms.',
205 205
206 206 # application perms
207 207 'default_register': 'hg.register.',
208 208 'default_password_reset': 'hg.password_reset.',
209 209 'default_extern_activate': 'hg.extern_activate.',
210 210
211 211 # object permissions below
212 212 'default_repo_perm': 'repository.',
213 213 'default_group_perm': 'group.',
214 214 'default_user_group_perm': 'usergroup.',
215 215 # branch
216 216 'default_branch_perm': 'branch.',
217 217
218 218 }[field_name]
219 219 for field in keep_fields:
220 220 pat = get_pat(field)
221 221 if perm_name.startswith(pat):
222 222 return True
223 223 return False
224 224
225 225 def _clear_object_perm(self, object_perms, preserve=None):
226 226 preserve = preserve or []
227 227 _deleted = []
228 228 for perm in object_perms:
229 229 perm_name = perm.permission.permission_name
230 230 if not self._keep_perm(perm_name, keep_fields=preserve):
231 231 _deleted.append(perm_name)
232 232 self.sa.delete(perm)
233 233 return _deleted
234 234
235 235 def _clear_user_perms(self, user_id, preserve=None):
236 236 perms = self.sa.query(UserToPerm)\
237 237 .filter(UserToPerm.user_id == user_id)\
238 238 .all()
239 239 return self._clear_object_perm(perms, preserve=preserve)
240 240
241 241 def _clear_user_group_perms(self, user_group_id, preserve=None):
242 242 perms = self.sa.query(UserGroupToPerm)\
243 243 .filter(UserGroupToPerm.users_group_id == user_group_id)\
244 244 .all()
245 245 return self._clear_object_perm(perms, preserve=preserve)
246 246
247 247 def _set_new_object_perms(self, obj_type, object, form_result, preserve=None):
248 248 # clear current entries, to make this function idempotent
249 249 # it will fix even if we define more permissions or permissions
250 250 # are somehow missing
251 251 preserve = preserve or []
252 252 _global_perms = self.global_perms.copy()
253 253 if obj_type not in ['user', 'user_group']:
254 254 raise ValueError("obj_type must be on of 'user' or 'user_group'")
255 255 global_perms = len(_global_perms)
256 256 default_user_perms = len(Permission.DEFAULT_USER_PERMISSIONS)
257 257 if global_perms != default_user_perms:
258 258 raise Exception(
259 259 'Inconsistent permissions definition. Got {} vs {}'.format(
260 260 global_perms, default_user_perms))
261 261
262 262 if obj_type == 'user':
263 263 self._clear_user_perms(object.user_id, preserve)
264 264 if obj_type == 'user_group':
265 265 self._clear_user_group_perms(object.users_group_id, preserve)
266 266
267 267 # now kill the keys that we want to preserve from the form.
268 268 for key in preserve:
269 269 del _global_perms[key]
270 270
271 271 for k in _global_perms.copy():
272 272 _global_perms[k] = form_result[k]
273 273
274 274 # at that stage we validate all are passed inside form_result
275 275 for _perm_key, perm_value in _global_perms.items():
276 276 if perm_value is None:
277 277 raise ValueError('Missing permission for %s' % (_perm_key,))
278 278
279 279 if obj_type == 'user':
280 280 p = self._make_new_user_perm(object, perm_value)
281 281 self.sa.add(p)
282 282 if obj_type == 'user_group':
283 283 p = self._make_new_user_group_perm(object, perm_value)
284 284 self.sa.add(p)
285 285
286 286 def _set_new_user_perms(self, user, form_result, preserve=None):
287 287 return self._set_new_object_perms(
288 288 'user', user, form_result, preserve)
289 289
290 290 def _set_new_user_group_perms(self, user_group, form_result, preserve=None):
291 291 return self._set_new_object_perms(
292 292 'user_group', user_group, form_result, preserve)
293 293
294 294 def set_new_user_perms(self, user, form_result):
295 295 # calculate what to preserve from what is given in form_result
296 296 preserve = set(self.global_perms.keys()).difference(set(form_result.keys()))
297 297 return self._set_new_user_perms(user, form_result, preserve)
298 298
299 299 def set_new_user_group_perms(self, user_group, form_result):
300 300 # calculate what to preserve from what is given in form_result
301 301 preserve = set(self.global_perms.keys()).difference(set(form_result.keys()))
302 302 return self._set_new_user_group_perms(user_group, form_result, preserve)
303 303
304 304 def create_permissions(self):
305 305 """
306 306 Create permissions for whole system
307 307 """
308 308 for p in Permission.PERMS:
309 309 if not Permission.get_by_key(p[0]):
310 310 new_perm = Permission()
311 311 new_perm.permission_name = p[0]
312 312 new_perm.permission_longname = p[0] # translation err with p[1]
313 313 self.sa.add(new_perm)
314 314
315 315 def _create_default_object_permission(self, obj_type, obj, obj_perms,
316 316 force=False):
317 317 if obj_type not in ['user', 'user_group']:
318 318 raise ValueError("obj_type must be on of 'user' or 'user_group'")
319 319
320 320 def _get_group(perm_name):
321 321 return '.'.join(perm_name.split('.')[:1])
322 322
323 323 defined_perms_groups = map(
324 324 _get_group, (x.permission.permission_name for x in obj_perms))
325 325 log.debug('GOT ALREADY DEFINED:%s', obj_perms)
326 326
327 327 if force:
328 328 self._clear_object_perm(obj_perms)
329 329 self.sa.commit()
330 330 defined_perms_groups = []
331 331 # for every default permission that needs to be created, we check if
332 332 # it's group is already defined, if it's not we create default perm
333 333 for perm_name in Permission.DEFAULT_USER_PERMISSIONS:
334 334 gr = _get_group(perm_name)
335 335 if gr not in defined_perms_groups:
336 336 log.debug('GR:%s not found, creating permission %s',
337 337 gr, perm_name)
338 338 if obj_type == 'user':
339 339 new_perm = self._make_new_user_perm(obj, perm_name)
340 340 self.sa.add(new_perm)
341 341 if obj_type == 'user_group':
342 342 new_perm = self._make_new_user_group_perm(obj, perm_name)
343 343 self.sa.add(new_perm)
344 344
345 345 def create_default_user_permissions(self, user, force=False):
346 346 """
347 347 Creates only missing default permissions for user, if force is set it
348 348 resets the default permissions for that user
349 349
350 350 :param user:
351 351 :param force:
352 352 """
353 353 user = self._get_user(user)
354 354 obj_perms = UserToPerm.query().filter(UserToPerm.user == user).all()
355 355 return self._create_default_object_permission(
356 356 'user', user, obj_perms, force)
357 357
358 358 def create_default_user_group_permissions(self, user_group, force=False):
359 359 """
360 360 Creates only missing default permissions for user group, if force is
361 361 set it resets the default permissions for that user group
362 362
363 363 :param user_group:
364 364 :param force:
365 365 """
366 366 user_group = self._get_user_group(user_group)
367 367 obj_perms = UserToPerm.query().filter(UserGroupToPerm.users_group == user_group).all()
368 368 return self._create_default_object_permission(
369 369 'user_group', user_group, obj_perms, force)
370 370
371 371 def update_application_permissions(self, form_result):
372 372 if 'perm_user_id' in form_result:
373 373 perm_user = User.get(safe_int(form_result['perm_user_id']))
374 374 else:
375 375 # used mostly to do lookup for default user
376 376 perm_user = User.get_by_username(form_result['perm_user_name'])
377 377
378 378 try:
379 379 # stage 1 set anonymous access
380 380 if perm_user.username == User.DEFAULT_USER:
381 381 perm_user.active = str2bool(form_result['anonymous'])
382 382 self.sa.add(perm_user)
383 383
384 384 # stage 2 reset defaults and set them from form data
385 385 self._set_new_user_perms(perm_user, form_result, preserve=[
386 386 'default_repo_perm',
387 387 'default_group_perm',
388 388 'default_user_group_perm',
389 389 'default_branch_perm',
390 390
391 391 'default_repo_group_create',
392 392 'default_user_group_create',
393 393 'default_repo_create_on_write',
394 394 'default_repo_create',
395 395 'default_fork_create',
396 396 'default_inherit_default_permissions',])
397 397
398 398 self.sa.commit()
399 399 except (DatabaseError,):
400 400 log.error(traceback.format_exc())
401 401 self.sa.rollback()
402 402 raise
403 403
404 404 def update_user_permissions(self, form_result):
405 405 if 'perm_user_id' in form_result:
406 406 perm_user = User.get(safe_int(form_result['perm_user_id']))
407 407 else:
408 408 # used mostly to do lookup for default user
409 409 perm_user = User.get_by_username(form_result['perm_user_name'])
410 410 try:
411 411 # stage 2 reset defaults and set them from form data
412 412 self._set_new_user_perms(perm_user, form_result, preserve=[
413 413 'default_repo_perm',
414 414 'default_group_perm',
415 415 'default_user_group_perm',
416 416 'default_branch_perm',
417 417
418 418 'default_register',
419 419 'default_password_reset',
420 420 'default_extern_activate'])
421 421 self.sa.commit()
422 422 except (DatabaseError,):
423 423 log.error(traceback.format_exc())
424 424 self.sa.rollback()
425 425 raise
426 426
427 427 def update_user_group_permissions(self, form_result):
428 428 if 'perm_user_group_id' in form_result:
429 429 perm_user_group = UserGroup.get(safe_int(form_result['perm_user_group_id']))
430 430 else:
431 431 # used mostly to do lookup for default user
432 432 perm_user_group = UserGroup.get_by_group_name(form_result['perm_user_group_name'])
433 433 try:
434 434 # stage 2 reset defaults and set them from form data
435 435 self._set_new_user_group_perms(perm_user_group, form_result, preserve=[
436 436 'default_repo_perm',
437 437 'default_group_perm',
438 438 'default_user_group_perm',
439 439 'default_branch_perm',
440 440
441 441 'default_register',
442 442 'default_password_reset',
443 443 'default_extern_activate'])
444 444 self.sa.commit()
445 445 except (DatabaseError,):
446 446 log.error(traceback.format_exc())
447 447 self.sa.rollback()
448 448 raise
449 449
450 450 def update_object_permissions(self, form_result):
451 451 if 'perm_user_id' in form_result:
452 452 perm_user = User.get(safe_int(form_result['perm_user_id']))
453 453 else:
454 454 # used mostly to do lookup for default user
455 455 perm_user = User.get_by_username(form_result['perm_user_name'])
456 456 try:
457 457
458 458 # stage 2 reset defaults and set them from form data
459 459 self._set_new_user_perms(perm_user, form_result, preserve=[
460 460 'default_repo_group_create',
461 461 'default_user_group_create',
462 462 'default_repo_create_on_write',
463 463 'default_repo_create',
464 464 'default_fork_create',
465 465 'default_inherit_default_permissions',
466 466 'default_branch_perm',
467 467
468 468 'default_register',
469 469 'default_password_reset',
470 470 'default_extern_activate'])
471 471
472 472 # overwrite default repo permissions
473 473 if form_result['overwrite_default_repo']:
474 474 _def_name = form_result['default_repo_perm'].split('repository.')[-1]
475 475 _def = Permission.get_by_key('repository.' + _def_name)
476 476 for r2p in self.sa.query(UserRepoToPerm)\
477 477 .filter(UserRepoToPerm.user == perm_user)\
478 478 .all():
479 479 # don't reset PRIVATE repositories
480 480 if not r2p.repository.private:
481 481 r2p.permission = _def
482 482 self.sa.add(r2p)
483 483
484 484 # overwrite default repo group permissions
485 485 if form_result['overwrite_default_group']:
486 486 _def_name = form_result['default_group_perm'].split('group.')[-1]
487 487 _def = Permission.get_by_key('group.' + _def_name)
488 488 for g2p in self.sa.query(UserRepoGroupToPerm)\
489 489 .filter(UserRepoGroupToPerm.user == perm_user)\
490 490 .all():
491 491 g2p.permission = _def
492 492 self.sa.add(g2p)
493 493
494 494 # overwrite default user group permissions
495 495 if form_result['overwrite_default_user_group']:
496 496 _def_name = form_result['default_user_group_perm'].split('usergroup.')[-1]
497 497 # user groups
498 498 _def = Permission.get_by_key('usergroup.' + _def_name)
499 499 for g2p in self.sa.query(UserUserGroupToPerm)\
500 500 .filter(UserUserGroupToPerm.user == perm_user)\
501 501 .all():
502 502 g2p.permission = _def
503 503 self.sa.add(g2p)
504 504
505 505 # COMMIT
506 506 self.sa.commit()
507 507 except (DatabaseError,):
508 508 log.exception('Failed to set default object permissions')
509 509 self.sa.rollback()
510 510 raise
511 511
512 512 def update_branch_permissions(self, form_result):
513 513 if 'perm_user_id' in form_result:
514 514 perm_user = User.get(safe_int(form_result['perm_user_id']))
515 515 else:
516 516 # used mostly to do lookup for default user
517 517 perm_user = User.get_by_username(form_result['perm_user_name'])
518 518 try:
519 519
520 520 # stage 2 reset defaults and set them from form data
521 521 self._set_new_user_perms(perm_user, form_result, preserve=[
522 522 'default_repo_perm',
523 523 'default_group_perm',
524 524 'default_user_group_perm',
525 525
526 526 'default_repo_group_create',
527 527 'default_user_group_create',
528 528 'default_repo_create_on_write',
529 529 'default_repo_create',
530 530 'default_fork_create',
531 531 'default_inherit_default_permissions',
532 532
533 533 'default_register',
534 534 'default_password_reset',
535 535 'default_extern_activate'])
536 536
537 537 # overwrite default branch permissions
538 538 if form_result['overwrite_default_branch']:
539 539 _def_name = \
540 540 form_result['default_branch_perm'].split('branch.')[-1]
541 541
542 542 _def = Permission.get_by_key('branch.' + _def_name)
543 543
544 544 user_perms = UserToRepoBranchPermission.query()\
545 545 .join(UserToRepoBranchPermission.user_repo_to_perm)\
546 546 .filter(UserRepoToPerm.user == perm_user).all()
547 547
548 548 for g2p in user_perms:
549 549 g2p.permission = _def
550 550 self.sa.add(g2p)
551 551
552 552 # COMMIT
553 553 self.sa.commit()
554 554 except (DatabaseError,):
555 555 log.exception('Failed to set default branch permissions')
556 556 self.sa.rollback()
557 557 raise
558 558
559 559 def get_users_with_repo_write(self, db_repo):
560 560 write_plus = ['repository.write', 'repository.admin']
561 561 default_user_id = User.get_default_user_id()
562 562 user_write_permissions = collections.OrderedDict()
563 563
564 # write+ and DEFAULT user for inheritance
564 # write or higher and DEFAULT user for inheritance
565 565 for perm in db_repo.permissions():
566 566 if perm.permission in write_plus or perm.user_id == default_user_id:
567 567 user_write_permissions[perm.user_id] = perm
568 568 return user_write_permissions
569 569
570 570 def get_user_groups_with_repo_write(self, db_repo):
571 571 write_plus = ['repository.write', 'repository.admin']
572 572 user_group_write_permissions = collections.OrderedDict()
573 573
574 # write+ and DEFAULT user for inheritance
574 # write or higher and DEFAULT user for inheritance
575 575 for p in db_repo.permission_user_groups():
576 576 if p.permission in write_plus:
577 577 user_group_write_permissions[p.users_group_id] = p
578 578 return user_group_write_permissions
579 579
580 580 def trigger_permission_flush(self, affected_user_ids=None):
581 581 affected_user_ids or User.get_all_user_ids()
582 582 events.trigger(events.UserPermissionsChange(affected_user_ids))
583 583
584 584 def flush_user_permission_caches(self, changes, affected_user_ids=None):
585 585 affected_user_ids = affected_user_ids or []
586 586
587 587 for change in changes['added'] + changes['updated'] + changes['deleted']:
588 588 if change['type'] == 'user':
589 589 affected_user_ids.append(change['id'])
590 590 if change['type'] == 'user_group':
591 591 user_group = UserGroup.get(safe_int(change['id']))
592 592 if user_group:
593 593 group_members_ids = [x.user_id for x in user_group.members]
594 594 affected_user_ids.extend(group_members_ids)
595 595
596 596 self.trigger_permission_flush(affected_user_ids)
597 597
598 598 return affected_user_ids
@@ -1,245 +1,245 b''
1 1 <%namespace name="base" file="/base/base.mako"/>
2 2
3 3 <div class="panel panel-default">
4 4 <div class="panel-heading">
5 5 <h3 class="panel-title">${_('Repository Access Permissions')}</h3>
6 6 </div>
7 7 <div class="panel-body">
8 8 ${h.secure_form(h.route_path('edit_repo_perms', repo_name=c.repo_name), request=request)}
9 9 <table id="permissions_manage" class="rctable permissions">
10 10 <tr>
11 11 <th class="td-radio">${_('None')}</th>
12 12 <th class="td-radio">${_('Read')}</th>
13 13 <th class="td-radio">${_('Write')}</th>
14 14 <th class="td-radio">${_('Admin')}</th>
15 15 <th class="td-owner">${_('User/User Group')}</th>
16 16 <th class="td-action"></th>
17 17 <th class="td-action"></th>
18 18 </tr>
19 19 ## USERS
20 20 %for _user in c.rhodecode_db_repo.permissions():
21 21 %if getattr(_user, 'admin_row', None) or getattr(_user, 'owner_row', None):
22 22 <tr class="perm_admin_row">
23 23 <td class="td-radio">${h.radio('admin_perm_%s' % _user.user_id,'repository.none', disabled="disabled")}</td>
24 24 <td class="td-radio">${h.radio('admin_perm_%s' % _user.user_id,'repository.read', disabled="disabled")}</td>
25 25 <td class="td-radio">${h.radio('admin_perm_%s' % _user.user_id,'repository.write', disabled="disabled")}</td>
26 26 <td class="td-radio">${h.radio('admin_perm_%s' % _user.user_id,'repository.admin', 'repository.admin', disabled="disabled")}</td>
27 27 <td class="td-user">
28 28 ${base.gravatar(_user.email, 16, user=_user, tooltip=True)}
29 29 ${h.link_to_user(_user.username)}
30 30 %if getattr(_user, 'admin_row', None):
31 31 (${_('super-admin')})
32 32 %endif
33 33 %if getattr(_user, 'owner_row', None):
34 34 (${_('owner')})
35 35 %endif
36 36 </td>
37 37 <td></td>
38 38 <td class="quick_repo_menu">
39 39 % if c.rhodecode_user.is_admin:
40 40 <i class="icon-more"></i>
41 41 <div class="menu_items_container" style="display: none;">
42 42 <ul class="menu_items">
43 43 <li>
44 44 ${h.link_to('show permissions', h.route_path('edit_user_perms_summary', user_id=_user.user_id, _anchor='repositories-permissions'))}
45 45 </li>
46 46 </ul>
47 47 </div>
48 48 % endif
49 49 </td>
50 50 </tr>
51 51 %elif _user.username == h.DEFAULT_USER and c.rhodecode_db_repo.private:
52 52 <tr>
53 53 <td colspan="4">
54 54 <span class="private_repo_msg">
55 55 <strong title="${h.tooltip(_user.permission)}">${_('private repository')}</strong>
56 56 </span>
57 57 </td>
58 58 <td class="private_repo_msg">
59 59 ${base.gravatar(h.DEFAULT_USER_EMAIL, 16)}
60 60 ${h.DEFAULT_USER} - ${_('only users/user groups explicitly added here will have access')}</td>
61 61 <td class="td-action">
62 62 <span class="noselect tooltip btn btn-link btn-default" onclick="setPrivateRepo(this, false); return false" title="${_('Private repositories are only visible to people explicitly added as collaborators. Default permissions wont apply')}">
63 63 ${_('un-set private mode')}
64 64 </span>
65 65 </td>
66 66 <td class="quick_repo_menu">
67 67 % if c.rhodecode_user.is_admin:
68 68 <i class="icon-more"></i>
69 69 <div class="menu_items_container" style="display: none;">
70 70 <ul class="menu_items">
71 71 <li>
72 72 ${h.link_to('show permissions', h.route_path('admin_permissions_overview', _anchor='repositories-permissions'))}
73 73 </li>
74 74 </ul>
75 75 </div>
76 76 % endif
77 77 </td>
78 78 </tr>
79 79 %else:
80 80 <% used_by_n_rules = len(getattr(_user, 'branch_rules', None) or []) %>
81 81 <tr>
82 82 <td class="td-radio">${h.radio('u_perm_%s' % _user.user_id,'repository.none', checked=_user.permission=='repository.none', disabled="disabled" if (used_by_n_rules and _user.username != h.DEFAULT_USER) else None)}</td>
83 83 <td class="td-radio">${h.radio('u_perm_%s' % _user.user_id,'repository.read', checked=_user.permission=='repository.read', disabled="disabled" if (used_by_n_rules and _user.username != h.DEFAULT_USER) else None)}</td>
84 84 <td class="td-radio">${h.radio('u_perm_%s' % _user.user_id,'repository.write', checked=_user.permission=='repository.write')}</td>
85 85 <td class="td-radio">${h.radio('u_perm_%s' % _user.user_id,'repository.admin', checked=_user.permission=='repository.admin')}</td>
86 86 <td class="td-user">
87 87 ${base.gravatar(_user.email, 16, user=_user, tooltip=True)}
88 88 <span class="user">
89 89 % if _user.username == h.DEFAULT_USER:
90 90 ${h.DEFAULT_USER}
91 91 % if _user.active:
92 92 <span class="user-perm-help-text"> - ${_('permission for other logged in and anonymous users')}</span>
93 93 % else:
94 94 <span class="user-perm-help-text"> - ${_('permission for other logged in users')}</span>
95 95 % endif
96 96 % else:
97 97 % if getattr(_user, 'duplicate_perm', None):
98 98 <span class="user-perm-duplicate">
99 99 ${h.link_to_user(_user.username)}
100 100 <span class="tooltip" title="${_('This entry is a duplicate, most probably left-over from previously set permission. This user has a higher permission set, so this entry is inactive. Please revoke this permission manually.')}">(${_('inactive duplicate')})
101 101 </span>
102 102 </span>
103 103 % else:
104 104 ${h.link_to_user(_user.username)}
105 105 % endif
106 106
107 107 %if getattr(_user, 'branch_rules', None):
108 108 % if used_by_n_rules == 1:
109 (${_('used by {} branch rule, requires write+ permissions').format(used_by_n_rules)})
109 (${_('used by {} branch rule, requires write or higher permissions').format(used_by_n_rules)})
110 110 % else:
111 (${_('used by {} branch rules, requires write+ permissions').format(used_by_n_rules)})
111 (${_('used by {} branch rules, requires write or higher permissions').format(used_by_n_rules)})
112 112 % endif
113 113 %endif
114 114 % endif
115 115 </span>
116 116 </td>
117 117 <td class="td-action">
118 118 %if _user.username != h.DEFAULT_USER and getattr(_user, 'branch_rules', None) is None:
119 119 <span class="btn btn-link btn-danger revoke_perm"
120 120 member="${_user.user_id}" member_type="user">
121 121 ${_('Remove')}
122 122 </span>
123 123 %elif _user.username == h.DEFAULT_USER:
124 124 <span class="noselect tooltip btn btn-link btn-default" onclick="setPrivateRepo(this, true); return false" title="${_('Private repositories are only visible to people explicitly added as collaborators. Default permissions wont apply')}">
125 125 ${_('set private mode')}
126 126 </span>
127 127 %endif
128 128 </td>
129 129 <td class="quick_repo_menu">
130 130 % if c.rhodecode_user.is_admin:
131 131 <i class="icon-more"></i>
132 132 <div class="menu_items_container" style="display: none;">
133 133 <ul class="menu_items">
134 134 <li>
135 135 % if _user.username == h.DEFAULT_USER:
136 136 ${h.link_to('show permissions', h.route_path('admin_permissions_overview', _anchor='repositories-permissions'))}
137 137 % else:
138 138 ${h.link_to('show permissions', h.route_path('edit_user_perms_summary', user_id=_user.user_id, _anchor='repositories-permissions'))}
139 139 % endif
140 140 </li>
141 141 </ul>
142 142 </div>
143 143 % endif
144 144 </td>
145 145 </tr>
146 146 %endif
147 147 %endfor
148 148
149 149 ## USER GROUPS
150 150 %for _user_group in c.rhodecode_db_repo.permission_user_groups(with_members=True):
151 151 <tr>
152 152 <td class="td-radio">${h.radio('g_perm_%s' % _user_group.users_group_id,'repository.none', checked=_user_group.permission=='repository.none')}</td>
153 153 <td class="td-radio">${h.radio('g_perm_%s' % _user_group.users_group_id,'repository.read', checked=_user_group.permission=='repository.read')}</td>
154 154 <td class="td-radio">${h.radio('g_perm_%s' % _user_group.users_group_id,'repository.write', checked=_user_group.permission=='repository.write')}</td>
155 155 <td class="td-radio">${h.radio('g_perm_%s' % _user_group.users_group_id,'repository.admin', checked=_user_group.permission=='repository.admin')}</td>
156 156 <td class="td-componentname">
157 157 ${base.user_group_icon(_user_group, tooltip=True)}
158 158 %if c.is_super_admin:
159 159 <a href="${h.route_path('edit_user_group',user_group_id=_user_group.users_group_id)}">
160 160 ${_user_group.users_group_name}
161 161 </a>
162 162 %else:
163 163 ${h.link_to_group(_user_group.users_group_name)}
164 164 %endif
165 165 (${_('members')}: ${len(_user_group.members)})
166 166 </td>
167 167 <td class="td-action">
168 168 <span class="btn btn-link btn-danger revoke_perm"
169 169 member="${_user_group.users_group_id}" member_type="user_group">
170 170 ${_('Remove')}
171 171 </span>
172 172 </td>
173 173 <td class="quick_repo_menu">
174 174 % if c.rhodecode_user.is_admin:
175 175 <i class="icon-more"></i>
176 176 <div class="menu_items_container" style="display: none;">
177 177 <ul class="menu_items">
178 178 <li>
179 179 ${h.link_to('show permissions', h.route_path('edit_user_group_perms_summary', user_group_id=_user_group.users_group_id, _anchor='repositories-permissions'))}
180 180 </li>
181 181 </ul>
182 182 </div>
183 183 % endif
184 184 </td>
185 185 </tr>
186 186 %endfor
187 187 <tr class="new_members" id="add_perm_input"></tr>
188 188
189 189 <tr>
190 190 <td></td>
191 191 <td></td>
192 192 <td></td>
193 193 <td></td>
194 194 <td></td>
195 195 <td>
196 196 <span id="add_perm" class="link">
197 197 ${_('Add user/user group')}
198 198 </span>
199 199 </td>
200 200 <td></td>
201 201 </tr>
202 202
203 203 </table>
204 204
205 205 <div class="buttons">
206 206 ${h.submit('save',_('Save'),class_="btn btn-primary")}
207 207 ${h.reset('reset',_('Reset'),class_="btn btn-danger")}
208 208 </div>
209 209 ${h.end_form()}
210 210 </div>
211 211 </div>
212 212
213 213 <script type="text/javascript">
214 214 $('#add_perm').on('click', function(e){
215 215 addNewPermInput($(this), 'repository');
216 216 });
217 217 $('.revoke_perm').on('click', function(e){
218 218 markRevokePermInput($(this), 'repository');
219 219 });
220 220 quick_repo_menu();
221 221
222 222 var setPrivateRepo = function (elem, private) {
223 223 var $elem = $(elem)
224 224 if ($elem.hasClass('disabled')) {
225 225 return
226 226 }
227 227 $elem.addClass('disabled');
228 228 $elem.css({"opacity": 0.3})
229 229
230 230 var postData = {
231 231 'csrf_token': CSRF_TOKEN,
232 232 'private': private
233 233 };
234 234
235 235 var success = function(o) {
236 236 var defaultUrl = pyroutes.url('edit_repo_perms', {"repo_name": templateContext.repo_name});
237 237 window.location = o.redirect_url || defaultUrl;
238 238 };
239 239
240 240 ajaxPOST(
241 241 pyroutes.url('edit_repo_perms_set_private', {"repo_name": templateContext.repo_name}),
242 242 postData,
243 243 success);
244 244 }
245 245 </script>
General Comments 0
You need to be logged in to leave comments. Login now