##// END OF EJS Templates
utils: move repo_name_slug to utils2 to prevent import cycle on setup_db...
Thomas De Schampheleire -
r7251:401fe08b default
parent child Browse files
Show More
@@ -1,710 +1,673 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 # This program is free software: you can redistribute it and/or modify
2 # This program is free software: you can redistribute it and/or modify
3 # it under the terms of the GNU General Public License as published by
3 # it under the terms of the GNU General Public License as published by
4 # the Free Software Foundation, either version 3 of the License, or
4 # the Free Software Foundation, either version 3 of the License, or
5 # (at your option) any later version.
5 # (at your option) any later version.
6 #
6 #
7 # This program is distributed in the hope that it will be useful,
7 # This program is distributed in the hope that it will be useful,
8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 # GNU General Public License for more details.
10 # GNU General Public License for more details.
11 #
11 #
12 # You should have received a copy of the GNU General Public License
12 # You should have received a copy of the GNU General Public License
13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
14 """
14 """
15 kallithea.lib.utils
15 kallithea.lib.utils
16 ~~~~~~~~~~~~~~~~~~~
16 ~~~~~~~~~~~~~~~~~~~
17
17
18 Utilities library for Kallithea
18 Utilities library for Kallithea
19
19
20 This file was forked by the Kallithea project in July 2014.
20 This file was forked by the Kallithea project in July 2014.
21 Original author and date, and relevant copyright and licensing information is below:
21 Original author and date, and relevant copyright and licensing information is below:
22 :created_on: Apr 18, 2010
22 :created_on: Apr 18, 2010
23 :author: marcink
23 :author: marcink
24 :copyright: (c) 2013 RhodeCode GmbH, and others.
24 :copyright: (c) 2013 RhodeCode GmbH, and others.
25 :license: GPLv3, see LICENSE.md for more details.
25 :license: GPLv3, see LICENSE.md for more details.
26 """
26 """
27
27
28 import os
28 import os
29 import re
29 import re
30 import logging
30 import logging
31 import datetime
31 import datetime
32 import traceback
32 import traceback
33 import beaker
33 import beaker
34
34
35 from tg import request, response
35 from tg import request, response
36 from tg.i18n import ugettext as _
36 from tg.i18n import ugettext as _
37 from webhelpers.text import collapse, remove_formatting, strip_tags
38 from beaker.cache import _cache_decorate
37 from beaker.cache import _cache_decorate
39
38
40 from kallithea.lib.vcs.utils.hgcompat import ui, config
39 from kallithea.lib.vcs.utils.hgcompat import ui, config
41 from kallithea.lib.vcs.utils.helpers import get_scm
40 from kallithea.lib.vcs.utils.helpers import get_scm
42 from kallithea.lib.vcs.exceptions import VCSError
41 from kallithea.lib.vcs.exceptions import VCSError
43
42
44 from kallithea.lib.exceptions import HgsubversionImportError
43 from kallithea.lib.exceptions import HgsubversionImportError
45 from kallithea.model import meta
44 from kallithea.model import meta
46 from kallithea.model.db import Repository, User, Ui, \
45 from kallithea.model.db import Repository, User, Ui, \
47 UserLog, RepoGroup, Setting, UserGroup
46 UserLog, RepoGroup, Setting, UserGroup
48 from kallithea.model.repo_group import RepoGroupModel
47 from kallithea.model.repo_group import RepoGroupModel
49 from kallithea.lib.utils2 import safe_str, safe_unicode, get_current_authuser
48 from kallithea.lib.utils2 import safe_str, safe_unicode, get_current_authuser
50 from kallithea.lib.vcs.utils.fakemod import create_module
49 from kallithea.lib.vcs.utils.fakemod import create_module
51
50
52 log = logging.getLogger(__name__)
51 log = logging.getLogger(__name__)
53
52
54 REMOVED_REPO_PAT = re.compile(r'rm__\d{8}_\d{6}_\d{6}_.*')
53 REMOVED_REPO_PAT = re.compile(r'rm__\d{8}_\d{6}_\d{6}_.*')
55
54
56
55
57 def recursive_replace(str_, replace=' '):
58 """
59 Recursive replace of given sign to just one instance
60
61 :param str_: given string
62 :param replace: char to find and replace multiple instances
63
64 Examples::
65 >>> recursive_replace("Mighty---Mighty-Bo--sstones",'-')
66 'Mighty-Mighty-Bo-sstones'
67 """
68
69 if str_.find(replace * 2) == -1:
70 return str_
71 else:
72 str_ = str_.replace(replace * 2, replace)
73 return recursive_replace(str_, replace)
74
75
76 def repo_name_slug(value):
77 """
78 Return slug of name of repository
79 This function is called on each creation/modification
80 of repository to prevent bad names in repo
81 """
82
83 slug = remove_formatting(value)
84 slug = strip_tags(slug)
85
86 for c in """`?=[]\;'"<>,/~!@#$%^&*()+{}|: """:
87 slug = slug.replace(c, '-')
88 slug = recursive_replace(slug, '-')
89 slug = collapse(slug, '-')
90 return slug
91
92
93 #==============================================================================
56 #==============================================================================
94 # PERM DECORATOR HELPERS FOR EXTRACTING NAMES FOR PERM CHECKS
57 # PERM DECORATOR HELPERS FOR EXTRACTING NAMES FOR PERM CHECKS
95 #==============================================================================
58 #==============================================================================
96 def get_repo_slug(request):
59 def get_repo_slug(request):
97 _repo = request.environ['pylons.routes_dict'].get('repo_name')
60 _repo = request.environ['pylons.routes_dict'].get('repo_name')
98 if _repo:
61 if _repo:
99 _repo = _repo.rstrip('/')
62 _repo = _repo.rstrip('/')
100 return _repo
63 return _repo
101
64
102
65
103 def get_repo_group_slug(request):
66 def get_repo_group_slug(request):
104 _group = request.environ['pylons.routes_dict'].get('group_name')
67 _group = request.environ['pylons.routes_dict'].get('group_name')
105 if _group:
68 if _group:
106 _group = _group.rstrip('/')
69 _group = _group.rstrip('/')
107 return _group
70 return _group
108
71
109
72
110 def get_user_group_slug(request):
73 def get_user_group_slug(request):
111 _group = request.environ['pylons.routes_dict'].get('id')
74 _group = request.environ['pylons.routes_dict'].get('id')
112 _group = UserGroup.get(_group)
75 _group = UserGroup.get(_group)
113 if _group:
76 if _group:
114 return _group.users_group_name
77 return _group.users_group_name
115 return None
78 return None
116
79
117
80
118 def _extract_id_from_repo_name(repo_name):
81 def _extract_id_from_repo_name(repo_name):
119 if repo_name.startswith('/'):
82 if repo_name.startswith('/'):
120 repo_name = repo_name.lstrip('/')
83 repo_name = repo_name.lstrip('/')
121 by_id_match = re.match(r'^_(\d{1,})', repo_name)
84 by_id_match = re.match(r'^_(\d{1,})', repo_name)
122 if by_id_match:
85 if by_id_match:
123 return by_id_match.groups()[0]
86 return by_id_match.groups()[0]
124
87
125
88
126 def get_repo_by_id(repo_name):
89 def get_repo_by_id(repo_name):
127 """
90 """
128 Extracts repo_name by id from special urls. Example url is _11/repo_name
91 Extracts repo_name by id from special urls. Example url is _11/repo_name
129
92
130 :param repo_name:
93 :param repo_name:
131 :return: repo_name if matched else None
94 :return: repo_name if matched else None
132 """
95 """
133 _repo_id = _extract_id_from_repo_name(repo_name)
96 _repo_id = _extract_id_from_repo_name(repo_name)
134 if _repo_id:
97 if _repo_id:
135 from kallithea.model.db import Repository
98 from kallithea.model.db import Repository
136 repo = Repository.get(_repo_id)
99 repo = Repository.get(_repo_id)
137 if repo:
100 if repo:
138 # TODO: return repo instead of reponame? or would that be a layering violation?
101 # TODO: return repo instead of reponame? or would that be a layering violation?
139 return repo.repo_name
102 return repo.repo_name
140 return None
103 return None
141
104
142
105
143 def action_logger(user, action, repo, ipaddr='', commit=False):
106 def action_logger(user, action, repo, ipaddr='', commit=False):
144 """
107 """
145 Action logger for various actions made by users
108 Action logger for various actions made by users
146
109
147 :param user: user that made this action, can be a unique username string or
110 :param user: user that made this action, can be a unique username string or
148 object containing user_id attribute
111 object containing user_id attribute
149 :param action: action to log, should be on of predefined unique actions for
112 :param action: action to log, should be on of predefined unique actions for
150 easy translations
113 easy translations
151 :param repo: string name of repository or object containing repo_id,
114 :param repo: string name of repository or object containing repo_id,
152 that action was made on
115 that action was made on
153 :param ipaddr: optional IP address from what the action was made
116 :param ipaddr: optional IP address from what the action was made
154
117
155 """
118 """
156
119
157 # if we don't get explicit IP address try to get one from registered user
120 # if we don't get explicit IP address try to get one from registered user
158 # in tmpl context var
121 # in tmpl context var
159 if not ipaddr:
122 if not ipaddr:
160 ipaddr = getattr(get_current_authuser(), 'ip_addr', '')
123 ipaddr = getattr(get_current_authuser(), 'ip_addr', '')
161
124
162 if getattr(user, 'user_id', None):
125 if getattr(user, 'user_id', None):
163 user_obj = User.get(user.user_id)
126 user_obj = User.get(user.user_id)
164 elif isinstance(user, basestring):
127 elif isinstance(user, basestring):
165 user_obj = User.get_by_username(user)
128 user_obj = User.get_by_username(user)
166 else:
129 else:
167 raise Exception('You have to provide a user object or a username')
130 raise Exception('You have to provide a user object or a username')
168
131
169 if getattr(repo, 'repo_id', None):
132 if getattr(repo, 'repo_id', None):
170 repo_obj = Repository.get(repo.repo_id)
133 repo_obj = Repository.get(repo.repo_id)
171 repo_name = repo_obj.repo_name
134 repo_name = repo_obj.repo_name
172 elif isinstance(repo, basestring):
135 elif isinstance(repo, basestring):
173 repo_name = repo.lstrip('/')
136 repo_name = repo.lstrip('/')
174 repo_obj = Repository.get_by_repo_name(repo_name)
137 repo_obj = Repository.get_by_repo_name(repo_name)
175 else:
138 else:
176 repo_obj = None
139 repo_obj = None
177 repo_name = u''
140 repo_name = u''
178
141
179 user_log = UserLog()
142 user_log = UserLog()
180 user_log.user_id = user_obj.user_id
143 user_log.user_id = user_obj.user_id
181 user_log.username = user_obj.username
144 user_log.username = user_obj.username
182 user_log.action = safe_unicode(action)
145 user_log.action = safe_unicode(action)
183
146
184 user_log.repository = repo_obj
147 user_log.repository = repo_obj
185 user_log.repository_name = repo_name
148 user_log.repository_name = repo_name
186
149
187 user_log.action_date = datetime.datetime.now()
150 user_log.action_date = datetime.datetime.now()
188 user_log.user_ip = ipaddr
151 user_log.user_ip = ipaddr
189 meta.Session().add(user_log)
152 meta.Session().add(user_log)
190
153
191 log.info('Logging action:%s on %s by user:%s ip:%s',
154 log.info('Logging action:%s on %s by user:%s ip:%s',
192 action, safe_unicode(repo), user_obj, ipaddr)
155 action, safe_unicode(repo), user_obj, ipaddr)
193 if commit:
156 if commit:
194 meta.Session().commit()
157 meta.Session().commit()
195
158
196
159
197 def get_filesystem_repos(path):
160 def get_filesystem_repos(path):
198 """
161 """
199 Scans given path for repos and return (name,(type,path)) tuple
162 Scans given path for repos and return (name,(type,path)) tuple
200
163
201 :param path: path to scan for repositories
164 :param path: path to scan for repositories
202 :param recursive: recursive search and return names with subdirs in front
165 :param recursive: recursive search and return names with subdirs in front
203 """
166 """
204
167
205 # remove ending slash for better results
168 # remove ending slash for better results
206 path = safe_str(path.rstrip(os.sep))
169 path = safe_str(path.rstrip(os.sep))
207 log.debug('now scanning in %s', path)
170 log.debug('now scanning in %s', path)
208
171
209 def isdir(*n):
172 def isdir(*n):
210 return os.path.isdir(os.path.join(*n))
173 return os.path.isdir(os.path.join(*n))
211
174
212 for root, dirs, _files in os.walk(path):
175 for root, dirs, _files in os.walk(path):
213 recurse_dirs = []
176 recurse_dirs = []
214 for subdir in dirs:
177 for subdir in dirs:
215 # skip removed repos
178 # skip removed repos
216 if REMOVED_REPO_PAT.match(subdir):
179 if REMOVED_REPO_PAT.match(subdir):
217 continue
180 continue
218
181
219 # skip .<something> dirs TODO: rly? then we should prevent creating them ...
182 # skip .<something> dirs TODO: rly? then we should prevent creating them ...
220 if subdir.startswith('.'):
183 if subdir.startswith('.'):
221 continue
184 continue
222
185
223 cur_path = os.path.join(root, subdir)
186 cur_path = os.path.join(root, subdir)
224 if isdir(cur_path, '.git'):
187 if isdir(cur_path, '.git'):
225 log.warning('ignoring non-bare Git repo: %s', cur_path)
188 log.warning('ignoring non-bare Git repo: %s', cur_path)
226 continue
189 continue
227
190
228 if (isdir(cur_path, '.hg') or
191 if (isdir(cur_path, '.hg') or
229 isdir(cur_path, '.svn') or
192 isdir(cur_path, '.svn') or
230 isdir(cur_path, 'objects') and (isdir(cur_path, 'refs') or
193 isdir(cur_path, 'objects') and (isdir(cur_path, 'refs') or
231 os.path.isfile(os.path.join(cur_path, 'packed-refs')))):
194 os.path.isfile(os.path.join(cur_path, 'packed-refs')))):
232
195
233 if not os.access(cur_path, os.R_OK) or not os.access(cur_path, os.X_OK):
196 if not os.access(cur_path, os.R_OK) or not os.access(cur_path, os.X_OK):
234 log.warning('ignoring repo path without access: %s', cur_path)
197 log.warning('ignoring repo path without access: %s', cur_path)
235 continue
198 continue
236
199
237 if not os.access(cur_path, os.W_OK):
200 if not os.access(cur_path, os.W_OK):
238 log.warning('repo path without write access: %s', cur_path)
201 log.warning('repo path without write access: %s', cur_path)
239
202
240 try:
203 try:
241 scm_info = get_scm(cur_path)
204 scm_info = get_scm(cur_path)
242 assert cur_path.startswith(path)
205 assert cur_path.startswith(path)
243 repo_path = cur_path[len(path) + 1:]
206 repo_path = cur_path[len(path) + 1:]
244 yield repo_path, scm_info
207 yield repo_path, scm_info
245 continue # no recursion
208 continue # no recursion
246 except VCSError:
209 except VCSError:
247 # We should perhaps ignore such broken repos, but especially
210 # We should perhaps ignore such broken repos, but especially
248 # the bare git detection is unreliable so we dive into it
211 # the bare git detection is unreliable so we dive into it
249 pass
212 pass
250
213
251 recurse_dirs.append(subdir)
214 recurse_dirs.append(subdir)
252
215
253 dirs[:] = recurse_dirs
216 dirs[:] = recurse_dirs
254
217
255
218
256 def is_valid_repo_uri(repo_type, url, ui):
219 def is_valid_repo_uri(repo_type, url, ui):
257 """Check if the url seems like a valid remote repo location - raise an Exception if any problems"""
220 """Check if the url seems like a valid remote repo location - raise an Exception if any problems"""
258 if repo_type == 'hg':
221 if repo_type == 'hg':
259 from kallithea.lib.vcs.backends.hg.repository import MercurialRepository
222 from kallithea.lib.vcs.backends.hg.repository import MercurialRepository
260 if url.startswith('http') or url.startswith('ssh'):
223 if url.startswith('http') or url.startswith('ssh'):
261 # initially check if it's at least the proper URL
224 # initially check if it's at least the proper URL
262 # or does it pass basic auth
225 # or does it pass basic auth
263 MercurialRepository._check_url(url, ui)
226 MercurialRepository._check_url(url, ui)
264 elif url.startswith('svn+http'):
227 elif url.startswith('svn+http'):
265 try:
228 try:
266 from hgsubversion.svnrepo import svnremoterepo
229 from hgsubversion.svnrepo import svnremoterepo
267 except ImportError:
230 except ImportError:
268 raise HgsubversionImportError(_('Unable to activate hgsubversion support. '
231 raise HgsubversionImportError(_('Unable to activate hgsubversion support. '
269 'The "hgsubversion" library is missing'))
232 'The "hgsubversion" library is missing'))
270 svnremoterepo(ui, url).svn.uuid
233 svnremoterepo(ui, url).svn.uuid
271 elif url.startswith('git+http'):
234 elif url.startswith('git+http'):
272 raise NotImplementedError()
235 raise NotImplementedError()
273 else:
236 else:
274 raise Exception('URI %s not allowed' % (url,))
237 raise Exception('URI %s not allowed' % (url,))
275
238
276 elif repo_type == 'git':
239 elif repo_type == 'git':
277 from kallithea.lib.vcs.backends.git.repository import GitRepository
240 from kallithea.lib.vcs.backends.git.repository import GitRepository
278 if url.startswith('http') or url.startswith('git'):
241 if url.startswith('http') or url.startswith('git'):
279 # initially check if it's at least the proper URL
242 # initially check if it's at least the proper URL
280 # or does it pass basic auth
243 # or does it pass basic auth
281 GitRepository._check_url(url)
244 GitRepository._check_url(url)
282 elif url.startswith('svn+http'):
245 elif url.startswith('svn+http'):
283 raise NotImplementedError()
246 raise NotImplementedError()
284 elif url.startswith('hg+http'):
247 elif url.startswith('hg+http'):
285 raise NotImplementedError()
248 raise NotImplementedError()
286 else:
249 else:
287 raise Exception('URI %s not allowed' % (url))
250 raise Exception('URI %s not allowed' % (url))
288
251
289
252
290 def is_valid_repo(repo_name, base_path, scm=None):
253 def is_valid_repo(repo_name, base_path, scm=None):
291 """
254 """
292 Returns True if given path is a valid repository False otherwise.
255 Returns True if given path is a valid repository False otherwise.
293 If scm param is given also compare if given scm is the same as expected
256 If scm param is given also compare if given scm is the same as expected
294 from scm parameter
257 from scm parameter
295
258
296 :param repo_name:
259 :param repo_name:
297 :param base_path:
260 :param base_path:
298 :param scm:
261 :param scm:
299
262
300 :return True: if given path is a valid repository
263 :return True: if given path is a valid repository
301 """
264 """
302 full_path = os.path.join(safe_str(base_path), safe_str(repo_name))
265 full_path = os.path.join(safe_str(base_path), safe_str(repo_name))
303
266
304 try:
267 try:
305 scm_ = get_scm(full_path)
268 scm_ = get_scm(full_path)
306 if scm:
269 if scm:
307 return scm_[0] == scm
270 return scm_[0] == scm
308 return True
271 return True
309 except VCSError:
272 except VCSError:
310 return False
273 return False
311
274
312
275
313 def is_valid_repo_group(repo_group_name, base_path, skip_path_check=False):
276 def is_valid_repo_group(repo_group_name, base_path, skip_path_check=False):
314 """
277 """
315 Returns True if given path is a repository group False otherwise
278 Returns True if given path is a repository group False otherwise
316
279
317 :param repo_name:
280 :param repo_name:
318 :param base_path:
281 :param base_path:
319 """
282 """
320 full_path = os.path.join(safe_str(base_path), safe_str(repo_group_name))
283 full_path = os.path.join(safe_str(base_path), safe_str(repo_group_name))
321
284
322 # check if it's not a repo
285 # check if it's not a repo
323 if is_valid_repo(repo_group_name, base_path):
286 if is_valid_repo(repo_group_name, base_path):
324 return False
287 return False
325
288
326 try:
289 try:
327 # we need to check bare git repos at higher level
290 # we need to check bare git repos at higher level
328 # since we might match branches/hooks/info/objects or possible
291 # since we might match branches/hooks/info/objects or possible
329 # other things inside bare git repo
292 # other things inside bare git repo
330 get_scm(os.path.dirname(full_path))
293 get_scm(os.path.dirname(full_path))
331 return False
294 return False
332 except VCSError:
295 except VCSError:
333 pass
296 pass
334
297
335 # check if it's a valid path
298 # check if it's a valid path
336 if skip_path_check or os.path.isdir(full_path):
299 if skip_path_check or os.path.isdir(full_path):
337 return True
300 return True
338
301
339 return False
302 return False
340
303
341
304
342 # propagated from mercurial documentation
305 # propagated from mercurial documentation
343 ui_sections = ['alias', 'auth',
306 ui_sections = ['alias', 'auth',
344 'decode/encode', 'defaults',
307 'decode/encode', 'defaults',
345 'diff', 'email',
308 'diff', 'email',
346 'extensions', 'format',
309 'extensions', 'format',
347 'merge-patterns', 'merge-tools',
310 'merge-patterns', 'merge-tools',
348 'hooks', 'http_proxy',
311 'hooks', 'http_proxy',
349 'smtp', 'patch',
312 'smtp', 'patch',
350 'paths', 'profiling',
313 'paths', 'profiling',
351 'server', 'trusted',
314 'server', 'trusted',
352 'ui', 'web', ]
315 'ui', 'web', ]
353
316
354
317
355 def make_ui(read_from='file', path=None, clear_session=True):
318 def make_ui(read_from='file', path=None, clear_session=True):
356 """
319 """
357 A function that will read python rc files or database
320 A function that will read python rc files or database
358 and make an mercurial ui object from read options
321 and make an mercurial ui object from read options
359
322
360 :param path: path to mercurial config file
323 :param path: path to mercurial config file
361 :param read_from: read from 'file' or 'db'
324 :param read_from: read from 'file' or 'db'
362 """
325 """
363
326
364 baseui = ui.ui()
327 baseui = ui.ui()
365
328
366 # clean the baseui object
329 # clean the baseui object
367 baseui._ocfg = config.config()
330 baseui._ocfg = config.config()
368 baseui._ucfg = config.config()
331 baseui._ucfg = config.config()
369 baseui._tcfg = config.config()
332 baseui._tcfg = config.config()
370
333
371 if read_from == 'file':
334 if read_from == 'file':
372 if not os.path.isfile(path):
335 if not os.path.isfile(path):
373 log.debug('hgrc file is not present at %s, skipping...', path)
336 log.debug('hgrc file is not present at %s, skipping...', path)
374 return False
337 return False
375 log.debug('reading hgrc from %s', path)
338 log.debug('reading hgrc from %s', path)
376 cfg = config.config()
339 cfg = config.config()
377 cfg.read(path)
340 cfg.read(path)
378 for section in ui_sections:
341 for section in ui_sections:
379 for k, v in cfg.items(section):
342 for k, v in cfg.items(section):
380 log.debug('settings ui from file: [%s] %s=%s', section, k, v)
343 log.debug('settings ui from file: [%s] %s=%s', section, k, v)
381 baseui.setconfig(safe_str(section), safe_str(k), safe_str(v))
344 baseui.setconfig(safe_str(section), safe_str(k), safe_str(v))
382
345
383 elif read_from == 'db':
346 elif read_from == 'db':
384 sa = meta.Session()
347 sa = meta.Session()
385 ret = sa.query(Ui).all()
348 ret = sa.query(Ui).all()
386
349
387 hg_ui = ret
350 hg_ui = ret
388 for ui_ in hg_ui:
351 for ui_ in hg_ui:
389 if ui_.ui_active:
352 if ui_.ui_active:
390 ui_val = '' if ui_.ui_value is None else safe_str(ui_.ui_value)
353 ui_val = '' if ui_.ui_value is None else safe_str(ui_.ui_value)
391 log.debug('settings ui from db: [%s] %s=%r', ui_.ui_section,
354 log.debug('settings ui from db: [%s] %s=%r', ui_.ui_section,
392 ui_.ui_key, ui_val)
355 ui_.ui_key, ui_val)
393 baseui.setconfig(safe_str(ui_.ui_section), safe_str(ui_.ui_key),
356 baseui.setconfig(safe_str(ui_.ui_section), safe_str(ui_.ui_key),
394 ui_val)
357 ui_val)
395 if clear_session:
358 if clear_session:
396 meta.Session.remove()
359 meta.Session.remove()
397
360
398 # force set push_ssl requirement to False, Kallithea handles that
361 # force set push_ssl requirement to False, Kallithea handles that
399 baseui.setconfig('web', 'push_ssl', False)
362 baseui.setconfig('web', 'push_ssl', False)
400 baseui.setconfig('web', 'allow_push', '*')
363 baseui.setconfig('web', 'allow_push', '*')
401 # prevent interactive questions for ssh password / passphrase
364 # prevent interactive questions for ssh password / passphrase
402 ssh = baseui.config('ui', 'ssh', default='ssh')
365 ssh = baseui.config('ui', 'ssh', default='ssh')
403 baseui.setconfig('ui', 'ssh', '%s -oBatchMode=yes -oIdentitiesOnly=yes' % ssh)
366 baseui.setconfig('ui', 'ssh', '%s -oBatchMode=yes -oIdentitiesOnly=yes' % ssh)
404
367
405 return baseui
368 return baseui
406
369
407
370
408 def set_app_settings(config):
371 def set_app_settings(config):
409 """
372 """
410 Updates app config with new settings from database
373 Updates app config with new settings from database
411
374
412 :param config:
375 :param config:
413 """
376 """
414 try:
377 try:
415 hgsettings = Setting.get_app_settings()
378 hgsettings = Setting.get_app_settings()
416 for k, v in hgsettings.items():
379 for k, v in hgsettings.items():
417 config[k] = v
380 config[k] = v
418 finally:
381 finally:
419 meta.Session.remove()
382 meta.Session.remove()
420
383
421
384
422 def set_vcs_config(config):
385 def set_vcs_config(config):
423 """
386 """
424 Patch VCS config with some Kallithea specific stuff
387 Patch VCS config with some Kallithea specific stuff
425
388
426 :param config: kallithea.CONFIG
389 :param config: kallithea.CONFIG
427 """
390 """
428 from kallithea.lib.vcs import conf
391 from kallithea.lib.vcs import conf
429 from kallithea.lib.utils2 import aslist
392 from kallithea.lib.utils2 import aslist
430 conf.settings.BACKENDS = {
393 conf.settings.BACKENDS = {
431 'hg': 'kallithea.lib.vcs.backends.hg.MercurialRepository',
394 'hg': 'kallithea.lib.vcs.backends.hg.MercurialRepository',
432 'git': 'kallithea.lib.vcs.backends.git.GitRepository',
395 'git': 'kallithea.lib.vcs.backends.git.GitRepository',
433 }
396 }
434
397
435 conf.settings.GIT_EXECUTABLE_PATH = config.get('git_path', 'git')
398 conf.settings.GIT_EXECUTABLE_PATH = config.get('git_path', 'git')
436 conf.settings.GIT_REV_FILTER = config.get('git_rev_filter', '--all').strip()
399 conf.settings.GIT_REV_FILTER = config.get('git_rev_filter', '--all').strip()
437 conf.settings.DEFAULT_ENCODINGS = aslist(config.get('default_encoding',
400 conf.settings.DEFAULT_ENCODINGS = aslist(config.get('default_encoding',
438 'utf-8'), sep=',')
401 'utf-8'), sep=',')
439
402
440
403
441 def set_indexer_config(config):
404 def set_indexer_config(config):
442 """
405 """
443 Update Whoosh index mapping
406 Update Whoosh index mapping
444
407
445 :param config: kallithea.CONFIG
408 :param config: kallithea.CONFIG
446 """
409 """
447 from kallithea.config import conf
410 from kallithea.config import conf
448
411
449 log.debug('adding extra into INDEX_EXTENSIONS')
412 log.debug('adding extra into INDEX_EXTENSIONS')
450 conf.INDEX_EXTENSIONS.extend(re.split('\s+', config.get('index.extensions', '')))
413 conf.INDEX_EXTENSIONS.extend(re.split('\s+', config.get('index.extensions', '')))
451
414
452 log.debug('adding extra into INDEX_FILENAMES')
415 log.debug('adding extra into INDEX_FILENAMES')
453 conf.INDEX_FILENAMES.extend(re.split('\s+', config.get('index.filenames', '')))
416 conf.INDEX_FILENAMES.extend(re.split('\s+', config.get('index.filenames', '')))
454
417
455
418
456 def map_groups(path):
419 def map_groups(path):
457 """
420 """
458 Given a full path to a repository, create all nested groups that this
421 Given a full path to a repository, create all nested groups that this
459 repo is inside. This function creates parent-child relationships between
422 repo is inside. This function creates parent-child relationships between
460 groups and creates default perms for all new groups.
423 groups and creates default perms for all new groups.
461
424
462 :param paths: full path to repository
425 :param paths: full path to repository
463 """
426 """
464 sa = meta.Session()
427 sa = meta.Session()
465 groups = path.split(Repository.url_sep())
428 groups = path.split(Repository.url_sep())
466 parent = None
429 parent = None
467 group = None
430 group = None
468
431
469 # last element is repo in nested groups structure
432 # last element is repo in nested groups structure
470 groups = groups[:-1]
433 groups = groups[:-1]
471 rgm = RepoGroupModel()
434 rgm = RepoGroupModel()
472 owner = User.get_first_admin()
435 owner = User.get_first_admin()
473 for lvl, group_name in enumerate(groups):
436 for lvl, group_name in enumerate(groups):
474 group_name = u'/'.join(groups[:lvl] + [group_name])
437 group_name = u'/'.join(groups[:lvl] + [group_name])
475 group = RepoGroup.get_by_group_name(group_name)
438 group = RepoGroup.get_by_group_name(group_name)
476 desc = '%s group' % group_name
439 desc = '%s group' % group_name
477
440
478 # skip folders that are now removed repos
441 # skip folders that are now removed repos
479 if REMOVED_REPO_PAT.match(group_name):
442 if REMOVED_REPO_PAT.match(group_name):
480 break
443 break
481
444
482 if group is None:
445 if group is None:
483 log.debug('creating group level: %s group_name: %s',
446 log.debug('creating group level: %s group_name: %s',
484 lvl, group_name)
447 lvl, group_name)
485 group = RepoGroup(group_name, parent)
448 group = RepoGroup(group_name, parent)
486 group.group_description = desc
449 group.group_description = desc
487 group.owner = owner
450 group.owner = owner
488 sa.add(group)
451 sa.add(group)
489 rgm._create_default_perms(group)
452 rgm._create_default_perms(group)
490 sa.flush()
453 sa.flush()
491
454
492 parent = group
455 parent = group
493 return group
456 return group
494
457
495
458
496 def repo2db_mapper(initial_repo_list, remove_obsolete=False,
459 def repo2db_mapper(initial_repo_list, remove_obsolete=False,
497 install_git_hooks=False, user=None, overwrite_git_hooks=False):
460 install_git_hooks=False, user=None, overwrite_git_hooks=False):
498 """
461 """
499 maps all repos given in initial_repo_list, non existing repositories
462 maps all repos given in initial_repo_list, non existing repositories
500 are created, if remove_obsolete is True it also check for db entries
463 are created, if remove_obsolete is True it also check for db entries
501 that are not in initial_repo_list and removes them.
464 that are not in initial_repo_list and removes them.
502
465
503 :param initial_repo_list: list of repositories found by scanning methods
466 :param initial_repo_list: list of repositories found by scanning methods
504 :param remove_obsolete: check for obsolete entries in database
467 :param remove_obsolete: check for obsolete entries in database
505 :param install_git_hooks: if this is True, also check and install git hook
468 :param install_git_hooks: if this is True, also check and install git hook
506 for a repo if missing
469 for a repo if missing
507 :param overwrite_git_hooks: if this is True, overwrite any existing git hooks
470 :param overwrite_git_hooks: if this is True, overwrite any existing git hooks
508 that may be encountered (even if user-deployed)
471 that may be encountered (even if user-deployed)
509 """
472 """
510 from kallithea.model.repo import RepoModel
473 from kallithea.model.repo import RepoModel
511 from kallithea.model.scm import ScmModel
474 from kallithea.model.scm import ScmModel
512 sa = meta.Session()
475 sa = meta.Session()
513 repo_model = RepoModel()
476 repo_model = RepoModel()
514 if user is None:
477 if user is None:
515 user = User.get_first_admin()
478 user = User.get_first_admin()
516 added = []
479 added = []
517
480
518 # creation defaults
481 # creation defaults
519 defs = Setting.get_default_repo_settings(strip_prefix=True)
482 defs = Setting.get_default_repo_settings(strip_prefix=True)
520 enable_statistics = defs.get('repo_enable_statistics')
483 enable_statistics = defs.get('repo_enable_statistics')
521 enable_locking = defs.get('repo_enable_locking')
484 enable_locking = defs.get('repo_enable_locking')
522 enable_downloads = defs.get('repo_enable_downloads')
485 enable_downloads = defs.get('repo_enable_downloads')
523 private = defs.get('repo_private')
486 private = defs.get('repo_private')
524
487
525 for name, repo in initial_repo_list.items():
488 for name, repo in initial_repo_list.items():
526 group = map_groups(name)
489 group = map_groups(name)
527 unicode_name = safe_unicode(name)
490 unicode_name = safe_unicode(name)
528 db_repo = repo_model.get_by_repo_name(unicode_name)
491 db_repo = repo_model.get_by_repo_name(unicode_name)
529 # found repo that is on filesystem not in Kallithea database
492 # found repo that is on filesystem not in Kallithea database
530 if not db_repo:
493 if not db_repo:
531 log.info('repository %s not found, creating now', name)
494 log.info('repository %s not found, creating now', name)
532 added.append(name)
495 added.append(name)
533 desc = (repo.description
496 desc = (repo.description
534 if repo.description != 'unknown'
497 if repo.description != 'unknown'
535 else '%s repository' % name)
498 else '%s repository' % name)
536
499
537 new_repo = repo_model._create_repo(
500 new_repo = repo_model._create_repo(
538 repo_name=name,
501 repo_name=name,
539 repo_type=repo.alias,
502 repo_type=repo.alias,
540 description=desc,
503 description=desc,
541 repo_group=getattr(group, 'group_id', None),
504 repo_group=getattr(group, 'group_id', None),
542 owner=user,
505 owner=user,
543 enable_locking=enable_locking,
506 enable_locking=enable_locking,
544 enable_downloads=enable_downloads,
507 enable_downloads=enable_downloads,
545 enable_statistics=enable_statistics,
508 enable_statistics=enable_statistics,
546 private=private,
509 private=private,
547 state=Repository.STATE_CREATED
510 state=Repository.STATE_CREATED
548 )
511 )
549 sa.commit()
512 sa.commit()
550 # we added that repo just now, and make sure it has githook
513 # we added that repo just now, and make sure it has githook
551 # installed, and updated server info
514 # installed, and updated server info
552 if new_repo.repo_type == 'git':
515 if new_repo.repo_type == 'git':
553 git_repo = new_repo.scm_instance
516 git_repo = new_repo.scm_instance
554 ScmModel().install_git_hooks(git_repo)
517 ScmModel().install_git_hooks(git_repo)
555 # update repository server-info
518 # update repository server-info
556 log.debug('Running update server info')
519 log.debug('Running update server info')
557 git_repo._update_server_info()
520 git_repo._update_server_info()
558 new_repo.update_changeset_cache()
521 new_repo.update_changeset_cache()
559 elif install_git_hooks:
522 elif install_git_hooks:
560 if db_repo.repo_type == 'git':
523 if db_repo.repo_type == 'git':
561 ScmModel().install_git_hooks(db_repo.scm_instance, force_create=overwrite_git_hooks)
524 ScmModel().install_git_hooks(db_repo.scm_instance, force_create=overwrite_git_hooks)
562
525
563 removed = []
526 removed = []
564 # remove from database those repositories that are not in the filesystem
527 # remove from database those repositories that are not in the filesystem
565 unicode_initial_repo_list = set(safe_unicode(name) for name in initial_repo_list)
528 unicode_initial_repo_list = set(safe_unicode(name) for name in initial_repo_list)
566 for repo in sa.query(Repository).all():
529 for repo in sa.query(Repository).all():
567 if repo.repo_name not in unicode_initial_repo_list:
530 if repo.repo_name not in unicode_initial_repo_list:
568 if remove_obsolete:
531 if remove_obsolete:
569 log.debug("Removing non-existing repository found in db `%s`",
532 log.debug("Removing non-existing repository found in db `%s`",
570 repo.repo_name)
533 repo.repo_name)
571 try:
534 try:
572 RepoModel().delete(repo, forks='detach', fs_remove=False)
535 RepoModel().delete(repo, forks='detach', fs_remove=False)
573 sa.commit()
536 sa.commit()
574 except Exception:
537 except Exception:
575 #don't hold further removals on error
538 #don't hold further removals on error
576 log.error(traceback.format_exc())
539 log.error(traceback.format_exc())
577 sa.rollback()
540 sa.rollback()
578 removed.append(repo.repo_name)
541 removed.append(repo.repo_name)
579 return added, removed
542 return added, removed
580
543
581
544
582 def load_rcextensions(root_path):
545 def load_rcextensions(root_path):
583 import kallithea
546 import kallithea
584 from kallithea.config import conf
547 from kallithea.config import conf
585
548
586 path = os.path.join(root_path, 'rcextensions', '__init__.py')
549 path = os.path.join(root_path, 'rcextensions', '__init__.py')
587 if os.path.isfile(path):
550 if os.path.isfile(path):
588 rcext = create_module('rc', path)
551 rcext = create_module('rc', path)
589 EXT = kallithea.EXTENSIONS = rcext
552 EXT = kallithea.EXTENSIONS = rcext
590 log.debug('Found rcextensions now loading %s...', rcext)
553 log.debug('Found rcextensions now loading %s...', rcext)
591
554
592 # Additional mappings that are not present in the pygments lexers
555 # Additional mappings that are not present in the pygments lexers
593 conf.LANGUAGES_EXTENSIONS_MAP.update(getattr(EXT, 'EXTRA_MAPPINGS', {}))
556 conf.LANGUAGES_EXTENSIONS_MAP.update(getattr(EXT, 'EXTRA_MAPPINGS', {}))
594
557
595 # OVERRIDE OUR EXTENSIONS FROM RC-EXTENSIONS (if present)
558 # OVERRIDE OUR EXTENSIONS FROM RC-EXTENSIONS (if present)
596
559
597 if getattr(EXT, 'INDEX_EXTENSIONS', []):
560 if getattr(EXT, 'INDEX_EXTENSIONS', []):
598 log.debug('settings custom INDEX_EXTENSIONS')
561 log.debug('settings custom INDEX_EXTENSIONS')
599 conf.INDEX_EXTENSIONS = getattr(EXT, 'INDEX_EXTENSIONS', [])
562 conf.INDEX_EXTENSIONS = getattr(EXT, 'INDEX_EXTENSIONS', [])
600
563
601 # ADDITIONAL MAPPINGS
564 # ADDITIONAL MAPPINGS
602 log.debug('adding extra into INDEX_EXTENSIONS')
565 log.debug('adding extra into INDEX_EXTENSIONS')
603 conf.INDEX_EXTENSIONS.extend(getattr(EXT, 'EXTRA_INDEX_EXTENSIONS', []))
566 conf.INDEX_EXTENSIONS.extend(getattr(EXT, 'EXTRA_INDEX_EXTENSIONS', []))
604
567
605 # auto check if the module is not missing any data, set to default if is
568 # auto check if the module is not missing any data, set to default if is
606 # this will help autoupdate new feature of rcext module
569 # this will help autoupdate new feature of rcext module
607 #from kallithea.config import rcextensions
570 #from kallithea.config import rcextensions
608 #for k in dir(rcextensions):
571 #for k in dir(rcextensions):
609 # if not k.startswith('_') and not hasattr(EXT, k):
572 # if not k.startswith('_') and not hasattr(EXT, k):
610 # setattr(EXT, k, getattr(rcextensions, k))
573 # setattr(EXT, k, getattr(rcextensions, k))
611
574
612
575
613 #==============================================================================
576 #==============================================================================
614 # MISC
577 # MISC
615 #==============================================================================
578 #==============================================================================
616
579
617 def check_git_version():
580 def check_git_version():
618 """
581 """
619 Checks what version of git is installed in system, and issues a warning
582 Checks what version of git is installed in system, and issues a warning
620 if it's too old for Kallithea to work properly.
583 if it's too old for Kallithea to work properly.
621 """
584 """
622 from kallithea import BACKENDS
585 from kallithea import BACKENDS
623 from kallithea.lib.vcs.backends.git.repository import GitRepository
586 from kallithea.lib.vcs.backends.git.repository import GitRepository
624 from kallithea.lib.vcs.conf import settings
587 from kallithea.lib.vcs.conf import settings
625 from distutils.version import StrictVersion
588 from distutils.version import StrictVersion
626
589
627 if 'git' not in BACKENDS:
590 if 'git' not in BACKENDS:
628 return None
591 return None
629
592
630 stdout, stderr = GitRepository._run_git_command(['--version'], _bare=True,
593 stdout, stderr = GitRepository._run_git_command(['--version'], _bare=True,
631 _safe=True)
594 _safe=True)
632
595
633 m = re.search("\d+.\d+.\d+", stdout)
596 m = re.search("\d+.\d+.\d+", stdout)
634 if m:
597 if m:
635 ver = StrictVersion(m.group(0))
598 ver = StrictVersion(m.group(0))
636 else:
599 else:
637 ver = StrictVersion('0.0.0')
600 ver = StrictVersion('0.0.0')
638
601
639 req_ver = StrictVersion('1.7.4')
602 req_ver = StrictVersion('1.7.4')
640
603
641 log.debug('Git executable: "%s" version %s detected: %s',
604 log.debug('Git executable: "%s" version %s detected: %s',
642 settings.GIT_EXECUTABLE_PATH, ver, stdout)
605 settings.GIT_EXECUTABLE_PATH, ver, stdout)
643 if stderr:
606 if stderr:
644 log.warning('Error detecting git version: %r', stderr)
607 log.warning('Error detecting git version: %r', stderr)
645 elif ver < req_ver:
608 elif ver < req_ver:
646 log.warning('Kallithea detected git version %s, which is too old '
609 log.warning('Kallithea detected git version %s, which is too old '
647 'for the system to function properly. '
610 'for the system to function properly. '
648 'Please upgrade to version %s or later.' % (ver, req_ver))
611 'Please upgrade to version %s or later.' % (ver, req_ver))
649 return ver
612 return ver
650
613
651
614
652 #===============================================================================
615 #===============================================================================
653 # CACHE RELATED METHODS
616 # CACHE RELATED METHODS
654 #===============================================================================
617 #===============================================================================
655
618
656 # set cache regions for beaker so celery can utilise it
619 # set cache regions for beaker so celery can utilise it
657 def setup_cache_regions(settings):
620 def setup_cache_regions(settings):
658 # Create dict with just beaker cache configs with prefix stripped
621 # Create dict with just beaker cache configs with prefix stripped
659 cache_settings = {'regions': None}
622 cache_settings = {'regions': None}
660 prefix = 'beaker.cache.'
623 prefix = 'beaker.cache.'
661 for key in settings:
624 for key in settings:
662 if key.startswith(prefix):
625 if key.startswith(prefix):
663 name = key[len(prefix):]
626 name = key[len(prefix):]
664 cache_settings[name] = settings[key]
627 cache_settings[name] = settings[key]
665 # Find all regions, apply defaults, and apply to beaker
628 # Find all regions, apply defaults, and apply to beaker
666 if cache_settings['regions']:
629 if cache_settings['regions']:
667 for region in cache_settings['regions'].split(','):
630 for region in cache_settings['regions'].split(','):
668 region = region.strip()
631 region = region.strip()
669 prefix = region + '.'
632 prefix = region + '.'
670 region_settings = {}
633 region_settings = {}
671 for key in cache_settings:
634 for key in cache_settings:
672 if key.startswith(prefix):
635 if key.startswith(prefix):
673 name = key[len(prefix):]
636 name = key[len(prefix):]
674 region_settings[name] = cache_settings[key]
637 region_settings[name] = cache_settings[key]
675 region_settings.setdefault('expire',
638 region_settings.setdefault('expire',
676 cache_settings.get('expire', '60'))
639 cache_settings.get('expire', '60'))
677 region_settings.setdefault('lock_dir',
640 region_settings.setdefault('lock_dir',
678 cache_settings.get('lock_dir'))
641 cache_settings.get('lock_dir'))
679 region_settings.setdefault('data_dir',
642 region_settings.setdefault('data_dir',
680 cache_settings.get('data_dir'))
643 cache_settings.get('data_dir'))
681 region_settings.setdefault('type',
644 region_settings.setdefault('type',
682 cache_settings.get('type', 'memory'))
645 cache_settings.get('type', 'memory'))
683 beaker.cache.cache_regions[region] = region_settings
646 beaker.cache.cache_regions[region] = region_settings
684
647
685
648
686 def conditional_cache(region, prefix, condition, func):
649 def conditional_cache(region, prefix, condition, func):
687 """
650 """
688
651
689 Conditional caching function use like::
652 Conditional caching function use like::
690 def _c(arg):
653 def _c(arg):
691 #heavy computation function
654 #heavy computation function
692 return data
655 return data
693
656
694 # depending from condition the compute is wrapped in cache or not
657 # depending from condition the compute is wrapped in cache or not
695 compute = conditional_cache('short_term', 'cache_desc', condition=True, func=func)
658 compute = conditional_cache('short_term', 'cache_desc', condition=True, func=func)
696 return compute(arg)
659 return compute(arg)
697
660
698 :param region: name of cache region
661 :param region: name of cache region
699 :param prefix: cache region prefix
662 :param prefix: cache region prefix
700 :param condition: condition for cache to be triggered, and return data cached
663 :param condition: condition for cache to be triggered, and return data cached
701 :param func: wrapped heavy function to compute
664 :param func: wrapped heavy function to compute
702
665
703 """
666 """
704 wrapped = func
667 wrapped = func
705 if condition:
668 if condition:
706 log.debug('conditional_cache: True, wrapping call of '
669 log.debug('conditional_cache: True, wrapping call of '
707 'func: %s into %s region cache' % (region, func))
670 'func: %s into %s region cache' % (region, func))
708 wrapped = _cache_decorate((prefix,), None, None, region)(func)
671 wrapped = _cache_decorate((prefix,), None, None, region)(func)
709
672
710 return wrapped
673 return wrapped
@@ -1,648 +1,687 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 # This program is free software: you can redistribute it and/or modify
2 # This program is free software: you can redistribute it and/or modify
3 # it under the terms of the GNU General Public License as published by
3 # it under the terms of the GNU General Public License as published by
4 # the Free Software Foundation, either version 3 of the License, or
4 # the Free Software Foundation, either version 3 of the License, or
5 # (at your option) any later version.
5 # (at your option) any later version.
6 #
6 #
7 # This program is distributed in the hope that it will be useful,
7 # This program is distributed in the hope that it will be useful,
8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 # GNU General Public License for more details.
10 # GNU General Public License for more details.
11 #
11 #
12 # You should have received a copy of the GNU General Public License
12 # You should have received a copy of the GNU General Public License
13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
14 """
14 """
15 kallithea.lib.utils2
15 kallithea.lib.utils2
16 ~~~~~~~~~~~~~~~~~~~~
16 ~~~~~~~~~~~~~~~~~~~~
17
17
18 Some simple helper functions
18 Some simple helper functions.
19 Note: all these functions should be independent of Kallithea classes, i.e.
20 models, controllers, etc. to prevent import cycles.
19
21
20 This file was forked by the Kallithea project in July 2014.
22 This file was forked by the Kallithea project in July 2014.
21 Original author and date, and relevant copyright and licensing information is below:
23 Original author and date, and relevant copyright and licensing information is below:
22 :created_on: Jan 5, 2011
24 :created_on: Jan 5, 2011
23 :author: marcink
25 :author: marcink
24 :copyright: (c) 2013 RhodeCode GmbH, and others.
26 :copyright: (c) 2013 RhodeCode GmbH, and others.
25 :license: GPLv3, see LICENSE.md for more details.
27 :license: GPLv3, see LICENSE.md for more details.
26 """
28 """
27
29
28
30
29 import os
31 import os
30 import re
32 import re
31 import sys
33 import sys
32 import time
34 import time
33 import uuid
35 import uuid
34 import datetime
36 import datetime
35 import urllib
37 import urllib
36 import binascii
38 import binascii
37
39
38 import webob
40 import webob
39 import urlobject
41 import urlobject
42 from webhelpers.text import collapse, remove_formatting, strip_tags
40
43
41 from tg.i18n import ugettext as _, ungettext
44 from tg.i18n import ugettext as _, ungettext
42 from kallithea.lib.vcs.utils.lazy import LazyProperty
45 from kallithea.lib.vcs.utils.lazy import LazyProperty
43 from kallithea.lib.compat import json
46 from kallithea.lib.compat import json
44
47
45
48
46 def str2bool(_str):
49 def str2bool(_str):
47 """
50 """
48 returns True/False value from given string, it tries to translate the
51 returns True/False value from given string, it tries to translate the
49 string into boolean
52 string into boolean
50
53
51 :param _str: string value to translate into boolean
54 :param _str: string value to translate into boolean
52 :rtype: boolean
55 :rtype: boolean
53 :returns: boolean from given string
56 :returns: boolean from given string
54 """
57 """
55 if _str is None:
58 if _str is None:
56 return False
59 return False
57 if _str in (True, False):
60 if _str in (True, False):
58 return _str
61 return _str
59 _str = str(_str).strip().lower()
62 _str = str(_str).strip().lower()
60 return _str in ('t', 'true', 'y', 'yes', 'on', '1')
63 return _str in ('t', 'true', 'y', 'yes', 'on', '1')
61
64
62
65
63 def aslist(obj, sep=None, strip=True):
66 def aslist(obj, sep=None, strip=True):
64 """
67 """
65 Returns given string separated by sep as list
68 Returns given string separated by sep as list
66
69
67 :param obj:
70 :param obj:
68 :param sep:
71 :param sep:
69 :param strip:
72 :param strip:
70 """
73 """
71 if isinstance(obj, (basestring)):
74 if isinstance(obj, (basestring)):
72 lst = obj.split(sep)
75 lst = obj.split(sep)
73 if strip:
76 if strip:
74 lst = [v.strip() for v in lst]
77 lst = [v.strip() for v in lst]
75 return lst
78 return lst
76 elif isinstance(obj, (list, tuple)):
79 elif isinstance(obj, (list, tuple)):
77 return obj
80 return obj
78 elif obj is None:
81 elif obj is None:
79 return []
82 return []
80 else:
83 else:
81 return [obj]
84 return [obj]
82
85
83
86
84 def convert_line_endings(line, mode):
87 def convert_line_endings(line, mode):
85 """
88 """
86 Converts a given line "line end" according to given mode
89 Converts a given line "line end" according to given mode
87
90
88 Available modes are::
91 Available modes are::
89 0 - Unix
92 0 - Unix
90 1 - Mac
93 1 - Mac
91 2 - DOS
94 2 - DOS
92
95
93 :param line: given line to convert
96 :param line: given line to convert
94 :param mode: mode to convert to
97 :param mode: mode to convert to
95 :rtype: str
98 :rtype: str
96 :return: converted line according to mode
99 :return: converted line according to mode
97 """
100 """
98 from string import replace
101 from string import replace
99
102
100 if mode == 0:
103 if mode == 0:
101 line = replace(line, '\r\n', '\n')
104 line = replace(line, '\r\n', '\n')
102 line = replace(line, '\r', '\n')
105 line = replace(line, '\r', '\n')
103 elif mode == 1:
106 elif mode == 1:
104 line = replace(line, '\r\n', '\r')
107 line = replace(line, '\r\n', '\r')
105 line = replace(line, '\n', '\r')
108 line = replace(line, '\n', '\r')
106 elif mode == 2:
109 elif mode == 2:
107 line = re.sub("\r(?!\n)|(?<!\r)\n", "\r\n", line)
110 line = re.sub("\r(?!\n)|(?<!\r)\n", "\r\n", line)
108 return line
111 return line
109
112
110
113
111 def detect_mode(line, default):
114 def detect_mode(line, default):
112 """
115 """
113 Detects line break for given line, if line break couldn't be found
116 Detects line break for given line, if line break couldn't be found
114 given default value is returned
117 given default value is returned
115
118
116 :param line: str line
119 :param line: str line
117 :param default: default
120 :param default: default
118 :rtype: int
121 :rtype: int
119 :return: value of line end on of 0 - Unix, 1 - Mac, 2 - DOS
122 :return: value of line end on of 0 - Unix, 1 - Mac, 2 - DOS
120 """
123 """
121 if line.endswith('\r\n'):
124 if line.endswith('\r\n'):
122 return 2
125 return 2
123 elif line.endswith('\n'):
126 elif line.endswith('\n'):
124 return 0
127 return 0
125 elif line.endswith('\r'):
128 elif line.endswith('\r'):
126 return 1
129 return 1
127 else:
130 else:
128 return default
131 return default
129
132
130
133
131 def generate_api_key():
134 def generate_api_key():
132 """
135 """
133 Generates a random (presumably unique) API key.
136 Generates a random (presumably unique) API key.
134
137
135 This value is used in URLs and "Bearer" HTTP Authorization headers,
138 This value is used in URLs and "Bearer" HTTP Authorization headers,
136 which in practice means it should only contain URL-safe characters
139 which in practice means it should only contain URL-safe characters
137 (RFC 3986):
140 (RFC 3986):
138
141
139 unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~"
142 unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~"
140 """
143 """
141 # Hexadecimal certainly qualifies as URL-safe.
144 # Hexadecimal certainly qualifies as URL-safe.
142 return binascii.hexlify(os.urandom(20))
145 return binascii.hexlify(os.urandom(20))
143
146
144
147
145 def safe_int(val, default=None):
148 def safe_int(val, default=None):
146 """
149 """
147 Returns int() of val if val is not convertable to int use default
150 Returns int() of val if val is not convertable to int use default
148 instead
151 instead
149
152
150 :param val:
153 :param val:
151 :param default:
154 :param default:
152 """
155 """
153
156
154 try:
157 try:
155 val = int(val)
158 val = int(val)
156 except (ValueError, TypeError):
159 except (ValueError, TypeError):
157 val = default
160 val = default
158
161
159 return val
162 return val
160
163
161
164
162 def safe_unicode(str_, from_encoding=None):
165 def safe_unicode(str_, from_encoding=None):
163 """
166 """
164 safe unicode function. Does few trick to turn str_ into unicode
167 safe unicode function. Does few trick to turn str_ into unicode
165
168
166 In case of UnicodeDecode error we try to return it with encoding detected
169 In case of UnicodeDecode error we try to return it with encoding detected
167 by chardet library if it fails fallback to unicode with errors replaced
170 by chardet library if it fails fallback to unicode with errors replaced
168
171
169 :param str_: string to decode
172 :param str_: string to decode
170 :rtype: unicode
173 :rtype: unicode
171 :returns: unicode object
174 :returns: unicode object
172 """
175 """
173 if isinstance(str_, unicode):
176 if isinstance(str_, unicode):
174 return str_
177 return str_
175
178
176 if not from_encoding:
179 if not from_encoding:
177 import kallithea
180 import kallithea
178 DEFAULT_ENCODINGS = aslist(kallithea.CONFIG.get('default_encoding',
181 DEFAULT_ENCODINGS = aslist(kallithea.CONFIG.get('default_encoding',
179 'utf-8'), sep=',')
182 'utf-8'), sep=',')
180 from_encoding = DEFAULT_ENCODINGS
183 from_encoding = DEFAULT_ENCODINGS
181
184
182 if not isinstance(from_encoding, (list, tuple)):
185 if not isinstance(from_encoding, (list, tuple)):
183 from_encoding = [from_encoding]
186 from_encoding = [from_encoding]
184
187
185 try:
188 try:
186 return unicode(str_)
189 return unicode(str_)
187 except UnicodeDecodeError:
190 except UnicodeDecodeError:
188 pass
191 pass
189
192
190 for enc in from_encoding:
193 for enc in from_encoding:
191 try:
194 try:
192 return unicode(str_, enc)
195 return unicode(str_, enc)
193 except UnicodeDecodeError:
196 except UnicodeDecodeError:
194 pass
197 pass
195
198
196 try:
199 try:
197 import chardet
200 import chardet
198 encoding = chardet.detect(str_)['encoding']
201 encoding = chardet.detect(str_)['encoding']
199 if encoding is None:
202 if encoding is None:
200 raise Exception()
203 raise Exception()
201 return str_.decode(encoding)
204 return str_.decode(encoding)
202 except (ImportError, UnicodeDecodeError, Exception):
205 except (ImportError, UnicodeDecodeError, Exception):
203 return unicode(str_, from_encoding[0], 'replace')
206 return unicode(str_, from_encoding[0], 'replace')
204
207
205
208
206 def safe_str(unicode_, to_encoding=None):
209 def safe_str(unicode_, to_encoding=None):
207 """
210 """
208 safe str function. Does few trick to turn unicode_ into string
211 safe str function. Does few trick to turn unicode_ into string
209
212
210 In case of UnicodeEncodeError we try to return it with encoding detected
213 In case of UnicodeEncodeError we try to return it with encoding detected
211 by chardet library if it fails fallback to string with errors replaced
214 by chardet library if it fails fallback to string with errors replaced
212
215
213 :param unicode_: unicode to encode
216 :param unicode_: unicode to encode
214 :rtype: str
217 :rtype: str
215 :returns: str object
218 :returns: str object
216 """
219 """
217
220
218 # if it's not basestr cast to str
221 # if it's not basestr cast to str
219 if not isinstance(unicode_, basestring):
222 if not isinstance(unicode_, basestring):
220 return str(unicode_)
223 return str(unicode_)
221
224
222 if isinstance(unicode_, str):
225 if isinstance(unicode_, str):
223 return unicode_
226 return unicode_
224
227
225 if not to_encoding:
228 if not to_encoding:
226 import kallithea
229 import kallithea
227 DEFAULT_ENCODINGS = aslist(kallithea.CONFIG.get('default_encoding',
230 DEFAULT_ENCODINGS = aslist(kallithea.CONFIG.get('default_encoding',
228 'utf-8'), sep=',')
231 'utf-8'), sep=',')
229 to_encoding = DEFAULT_ENCODINGS
232 to_encoding = DEFAULT_ENCODINGS
230
233
231 if not isinstance(to_encoding, (list, tuple)):
234 if not isinstance(to_encoding, (list, tuple)):
232 to_encoding = [to_encoding]
235 to_encoding = [to_encoding]
233
236
234 for enc in to_encoding:
237 for enc in to_encoding:
235 try:
238 try:
236 return unicode_.encode(enc)
239 return unicode_.encode(enc)
237 except UnicodeEncodeError:
240 except UnicodeEncodeError:
238 pass
241 pass
239
242
240 try:
243 try:
241 import chardet
244 import chardet
242 encoding = chardet.detect(unicode_)['encoding']
245 encoding = chardet.detect(unicode_)['encoding']
243 if encoding is None:
246 if encoding is None:
244 raise UnicodeEncodeError()
247 raise UnicodeEncodeError()
245
248
246 return unicode_.encode(encoding)
249 return unicode_.encode(encoding)
247 except (ImportError, UnicodeEncodeError):
250 except (ImportError, UnicodeEncodeError):
248 return unicode_.encode(to_encoding[0], 'replace')
251 return unicode_.encode(to_encoding[0], 'replace')
249
252
250
253
251 def remove_suffix(s, suffix):
254 def remove_suffix(s, suffix):
252 if s.endswith(suffix):
255 if s.endswith(suffix):
253 s = s[:-1 * len(suffix)]
256 s = s[:-1 * len(suffix)]
254 return s
257 return s
255
258
256
259
257 def remove_prefix(s, prefix):
260 def remove_prefix(s, prefix):
258 if s.startswith(prefix):
261 if s.startswith(prefix):
259 s = s[len(prefix):]
262 s = s[len(prefix):]
260 return s
263 return s
261
264
262
265
263 def age(prevdate, show_short_version=False, now=None):
266 def age(prevdate, show_short_version=False, now=None):
264 """
267 """
265 turns a datetime into an age string.
268 turns a datetime into an age string.
266 If show_short_version is True, then it will generate a not so accurate but shorter string,
269 If show_short_version is True, then it will generate a not so accurate but shorter string,
267 example: 2days ago, instead of 2 days and 23 hours ago.
270 example: 2days ago, instead of 2 days and 23 hours ago.
268
271
269 :param prevdate: datetime object
272 :param prevdate: datetime object
270 :param show_short_version: if it should approximate the date and return a shorter string
273 :param show_short_version: if it should approximate the date and return a shorter string
271 :rtype: unicode
274 :rtype: unicode
272 :returns: unicode words describing age
275 :returns: unicode words describing age
273 """
276 """
274 now = now or datetime.datetime.now()
277 now = now or datetime.datetime.now()
275 order = ['year', 'month', 'day', 'hour', 'minute', 'second']
278 order = ['year', 'month', 'day', 'hour', 'minute', 'second']
276 deltas = {}
279 deltas = {}
277 future = False
280 future = False
278
281
279 if prevdate > now:
282 if prevdate > now:
280 now, prevdate = prevdate, now
283 now, prevdate = prevdate, now
281 future = True
284 future = True
282 if future:
285 if future:
283 prevdate = prevdate.replace(microsecond=0)
286 prevdate = prevdate.replace(microsecond=0)
284 # Get date parts deltas
287 # Get date parts deltas
285 from dateutil import relativedelta
288 from dateutil import relativedelta
286 for part in order:
289 for part in order:
287 d = relativedelta.relativedelta(now, prevdate)
290 d = relativedelta.relativedelta(now, prevdate)
288 deltas[part] = getattr(d, part + 's')
291 deltas[part] = getattr(d, part + 's')
289
292
290 # Fix negative offsets (there is 1 second between 10:59:59 and 11:00:00,
293 # Fix negative offsets (there is 1 second between 10:59:59 and 11:00:00,
291 # not 1 hour, -59 minutes and -59 seconds)
294 # not 1 hour, -59 minutes and -59 seconds)
292 for num, length in [(5, 60), (4, 60), (3, 24)]: # seconds, minutes, hours
295 for num, length in [(5, 60), (4, 60), (3, 24)]: # seconds, minutes, hours
293 part = order[num]
296 part = order[num]
294 carry_part = order[num - 1]
297 carry_part = order[num - 1]
295
298
296 if deltas[part] < 0:
299 if deltas[part] < 0:
297 deltas[part] += length
300 deltas[part] += length
298 deltas[carry_part] -= 1
301 deltas[carry_part] -= 1
299
302
300 # Same thing for days except that the increment depends on the (variable)
303 # Same thing for days except that the increment depends on the (variable)
301 # number of days in the month
304 # number of days in the month
302 month_lengths = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
305 month_lengths = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
303 if deltas['day'] < 0:
306 if deltas['day'] < 0:
304 if prevdate.month == 2 and (prevdate.year % 4 == 0 and
307 if prevdate.month == 2 and (prevdate.year % 4 == 0 and
305 (prevdate.year % 100 != 0 or prevdate.year % 400 == 0)):
308 (prevdate.year % 100 != 0 or prevdate.year % 400 == 0)):
306 deltas['day'] += 29
309 deltas['day'] += 29
307 else:
310 else:
308 deltas['day'] += month_lengths[prevdate.month - 1]
311 deltas['day'] += month_lengths[prevdate.month - 1]
309
312
310 deltas['month'] -= 1
313 deltas['month'] -= 1
311
314
312 if deltas['month'] < 0:
315 if deltas['month'] < 0:
313 deltas['month'] += 12
316 deltas['month'] += 12
314 deltas['year'] -= 1
317 deltas['year'] -= 1
315
318
316 # In short version, we want nicer handling of ages of more than a year
319 # In short version, we want nicer handling of ages of more than a year
317 if show_short_version:
320 if show_short_version:
318 if deltas['year'] == 1:
321 if deltas['year'] == 1:
319 # ages between 1 and 2 years: show as months
322 # ages between 1 and 2 years: show as months
320 deltas['month'] += 12
323 deltas['month'] += 12
321 deltas['year'] = 0
324 deltas['year'] = 0
322 if deltas['year'] >= 2:
325 if deltas['year'] >= 2:
323 # ages 2+ years: round
326 # ages 2+ years: round
324 if deltas['month'] > 6:
327 if deltas['month'] > 6:
325 deltas['year'] += 1
328 deltas['year'] += 1
326 deltas['month'] = 0
329 deltas['month'] = 0
327
330
328 # Format the result
331 # Format the result
329 fmt_funcs = {
332 fmt_funcs = {
330 'year': lambda d: ungettext(u'%d year', '%d years', d) % d,
333 'year': lambda d: ungettext(u'%d year', '%d years', d) % d,
331 'month': lambda d: ungettext(u'%d month', '%d months', d) % d,
334 'month': lambda d: ungettext(u'%d month', '%d months', d) % d,
332 'day': lambda d: ungettext(u'%d day', '%d days', d) % d,
335 'day': lambda d: ungettext(u'%d day', '%d days', d) % d,
333 'hour': lambda d: ungettext(u'%d hour', '%d hours', d) % d,
336 'hour': lambda d: ungettext(u'%d hour', '%d hours', d) % d,
334 'minute': lambda d: ungettext(u'%d minute', '%d minutes', d) % d,
337 'minute': lambda d: ungettext(u'%d minute', '%d minutes', d) % d,
335 'second': lambda d: ungettext(u'%d second', '%d seconds', d) % d,
338 'second': lambda d: ungettext(u'%d second', '%d seconds', d) % d,
336 }
339 }
337
340
338 for i, part in enumerate(order):
341 for i, part in enumerate(order):
339 value = deltas[part]
342 value = deltas[part]
340 if value == 0:
343 if value == 0:
341 continue
344 continue
342
345
343 if i < 5:
346 if i < 5:
344 sub_part = order[i + 1]
347 sub_part = order[i + 1]
345 sub_value = deltas[sub_part]
348 sub_value = deltas[sub_part]
346 else:
349 else:
347 sub_value = 0
350 sub_value = 0
348
351
349 if sub_value == 0 or show_short_version:
352 if sub_value == 0 or show_short_version:
350 if future:
353 if future:
351 return _('in %s') % fmt_funcs[part](value)
354 return _('in %s') % fmt_funcs[part](value)
352 else:
355 else:
353 return _('%s ago') % fmt_funcs[part](value)
356 return _('%s ago') % fmt_funcs[part](value)
354 if future:
357 if future:
355 return _('in %s and %s') % (fmt_funcs[part](value),
358 return _('in %s and %s') % (fmt_funcs[part](value),
356 fmt_funcs[sub_part](sub_value))
359 fmt_funcs[sub_part](sub_value))
357 else:
360 else:
358 return _('%s and %s ago') % (fmt_funcs[part](value),
361 return _('%s and %s ago') % (fmt_funcs[part](value),
359 fmt_funcs[sub_part](sub_value))
362 fmt_funcs[sub_part](sub_value))
360
363
361 return _('just now')
364 return _('just now')
362
365
363
366
364 def uri_filter(uri):
367 def uri_filter(uri):
365 """
368 """
366 Removes user:password from given url string
369 Removes user:password from given url string
367
370
368 :param uri:
371 :param uri:
369 :rtype: unicode
372 :rtype: unicode
370 :returns: filtered list of strings
373 :returns: filtered list of strings
371 """
374 """
372 if not uri:
375 if not uri:
373 return ''
376 return ''
374
377
375 proto = ''
378 proto = ''
376
379
377 for pat in ('https://', 'http://', 'git://'):
380 for pat in ('https://', 'http://', 'git://'):
378 if uri.startswith(pat):
381 if uri.startswith(pat):
379 uri = uri[len(pat):]
382 uri = uri[len(pat):]
380 proto = pat
383 proto = pat
381 break
384 break
382
385
383 # remove passwords and username
386 # remove passwords and username
384 uri = uri[uri.find('@') + 1:]
387 uri = uri[uri.find('@') + 1:]
385
388
386 # get the port
389 # get the port
387 cred_pos = uri.find(':')
390 cred_pos = uri.find(':')
388 if cred_pos == -1:
391 if cred_pos == -1:
389 host, port = uri, None
392 host, port = uri, None
390 else:
393 else:
391 host, port = uri[:cred_pos], uri[cred_pos + 1:]
394 host, port = uri[:cred_pos], uri[cred_pos + 1:]
392
395
393 return filter(None, [proto, host, port])
396 return filter(None, [proto, host, port])
394
397
395
398
396 def credentials_filter(uri):
399 def credentials_filter(uri):
397 """
400 """
398 Returns a url with removed credentials
401 Returns a url with removed credentials
399
402
400 :param uri:
403 :param uri:
401 """
404 """
402
405
403 uri = uri_filter(uri)
406 uri = uri_filter(uri)
404 # check if we have port
407 # check if we have port
405 if len(uri) > 2 and uri[2]:
408 if len(uri) > 2 and uri[2]:
406 uri[2] = ':' + uri[2]
409 uri[2] = ':' + uri[2]
407
410
408 return ''.join(uri)
411 return ''.join(uri)
409
412
410
413
411 def get_clone_url(uri_tmpl, qualified_home_url, repo_name, repo_id, **override):
414 def get_clone_url(uri_tmpl, qualified_home_url, repo_name, repo_id, **override):
412 parsed_url = urlobject.URLObject(qualified_home_url)
415 parsed_url = urlobject.URLObject(qualified_home_url)
413 decoded_path = safe_unicode(urllib.unquote(parsed_url.path.rstrip('/')))
416 decoded_path = safe_unicode(urllib.unquote(parsed_url.path.rstrip('/')))
414 args = {
417 args = {
415 'scheme': parsed_url.scheme,
418 'scheme': parsed_url.scheme,
416 'user': '',
419 'user': '',
417 'netloc': parsed_url.netloc+decoded_path, # path if we use proxy-prefix
420 'netloc': parsed_url.netloc+decoded_path, # path if we use proxy-prefix
418 'prefix': decoded_path,
421 'prefix': decoded_path,
419 'repo': repo_name,
422 'repo': repo_name,
420 'repoid': str(repo_id)
423 'repoid': str(repo_id)
421 }
424 }
422 args.update(override)
425 args.update(override)
423 args['user'] = urllib.quote(safe_str(args['user']))
426 args['user'] = urllib.quote(safe_str(args['user']))
424
427
425 for k, v in args.items():
428 for k, v in args.items():
426 uri_tmpl = uri_tmpl.replace('{%s}' % k, v)
429 uri_tmpl = uri_tmpl.replace('{%s}' % k, v)
427
430
428 # remove leading @ sign if it's present. Case of empty user
431 # remove leading @ sign if it's present. Case of empty user
429 url_obj = urlobject.URLObject(uri_tmpl)
432 url_obj = urlobject.URLObject(uri_tmpl)
430 url = url_obj.with_netloc(url_obj.netloc.lstrip('@'))
433 url = url_obj.with_netloc(url_obj.netloc.lstrip('@'))
431
434
432 return safe_unicode(url)
435 return safe_unicode(url)
433
436
434
437
435 def get_changeset_safe(repo, rev):
438 def get_changeset_safe(repo, rev):
436 """
439 """
437 Safe version of get_changeset if this changeset doesn't exists for a
440 Safe version of get_changeset if this changeset doesn't exists for a
438 repo it returns a Dummy one instead
441 repo it returns a Dummy one instead
439
442
440 :param repo:
443 :param repo:
441 :param rev:
444 :param rev:
442 """
445 """
443 from kallithea.lib.vcs.backends.base import BaseRepository
446 from kallithea.lib.vcs.backends.base import BaseRepository
444 from kallithea.lib.vcs.exceptions import RepositoryError
447 from kallithea.lib.vcs.exceptions import RepositoryError
445 from kallithea.lib.vcs.backends.base import EmptyChangeset
448 from kallithea.lib.vcs.backends.base import EmptyChangeset
446 if not isinstance(repo, BaseRepository):
449 if not isinstance(repo, BaseRepository):
447 raise Exception('You must pass an Repository '
450 raise Exception('You must pass an Repository '
448 'object as first argument got %s', type(repo))
451 'object as first argument got %s', type(repo))
449
452
450 try:
453 try:
451 cs = repo.get_changeset(rev)
454 cs = repo.get_changeset(rev)
452 except (RepositoryError, LookupError):
455 except (RepositoryError, LookupError):
453 cs = EmptyChangeset(requested_revision=rev)
456 cs = EmptyChangeset(requested_revision=rev)
454 return cs
457 return cs
455
458
456
459
457 def datetime_to_time(dt):
460 def datetime_to_time(dt):
458 if dt:
461 if dt:
459 return time.mktime(dt.timetuple())
462 return time.mktime(dt.timetuple())
460
463
461
464
462 def time_to_datetime(tm):
465 def time_to_datetime(tm):
463 if tm:
466 if tm:
464 if isinstance(tm, basestring):
467 if isinstance(tm, basestring):
465 try:
468 try:
466 tm = float(tm)
469 tm = float(tm)
467 except ValueError:
470 except ValueError:
468 return
471 return
469 return datetime.datetime.fromtimestamp(tm)
472 return datetime.datetime.fromtimestamp(tm)
470
473
471
474
472 # Must match regexp in kallithea/public/js/base.js MentionsAutoComplete()
475 # Must match regexp in kallithea/public/js/base.js MentionsAutoComplete()
473 # Check char before @ - it must not look like we are in an email addresses.
476 # Check char before @ - it must not look like we are in an email addresses.
474 # Matching is greedy so we don't have to look beyond the end.
477 # Matching is greedy so we don't have to look beyond the end.
475 MENTIONS_REGEX = re.compile(r'(?:^|(?<=[^a-zA-Z0-9]))@([a-zA-Z0-9][-_.a-zA-Z0-9]*[a-zA-Z0-9])')
478 MENTIONS_REGEX = re.compile(r'(?:^|(?<=[^a-zA-Z0-9]))@([a-zA-Z0-9][-_.a-zA-Z0-9]*[a-zA-Z0-9])')
476
479
477
480
478 def extract_mentioned_usernames(text):
481 def extract_mentioned_usernames(text):
479 r"""
482 r"""
480 Returns list of (possible) usernames @mentioned in given text.
483 Returns list of (possible) usernames @mentioned in given text.
481
484
482 >>> extract_mentioned_usernames('@1-2.a_X,@1234 not@not @ddd@not @n @ee @ff @gg, @gg;@hh @n\n@zz,')
485 >>> extract_mentioned_usernames('@1-2.a_X,@1234 not@not @ddd@not @n @ee @ff @gg, @gg;@hh @n\n@zz,')
483 ['1-2.a_X', '1234', 'ddd', 'ee', 'ff', 'gg', 'hh', 'zz']
486 ['1-2.a_X', '1234', 'ddd', 'ee', 'ff', 'gg', 'hh', 'zz']
484 """
487 """
485 return MENTIONS_REGEX.findall(text)
488 return MENTIONS_REGEX.findall(text)
486
489
487
490
488 def extract_mentioned_users(text):
491 def extract_mentioned_users(text):
489 """ Returns set of actual database Users @mentioned in given text. """
492 """ Returns set of actual database Users @mentioned in given text. """
490 from kallithea.model.db import User
493 from kallithea.model.db import User
491 result = set()
494 result = set()
492 for name in extract_mentioned_usernames(text):
495 for name in extract_mentioned_usernames(text):
493 user = User.get_by_username(name, case_insensitive=True)
496 user = User.get_by_username(name, case_insensitive=True)
494 if user is not None and not user.is_default_user:
497 if user is not None and not user.is_default_user:
495 result.add(user)
498 result.add(user)
496 return result
499 return result
497
500
498
501
499 class AttributeDict(dict):
502 class AttributeDict(dict):
500 def __getattr__(self, attr):
503 def __getattr__(self, attr):
501 return self.get(attr, None)
504 return self.get(attr, None)
502 __setattr__ = dict.__setitem__
505 __setattr__ = dict.__setitem__
503 __delattr__ = dict.__delitem__
506 __delattr__ = dict.__delitem__
504
507
505
508
506 def fix_PATH(os_=None):
509 def fix_PATH(os_=None):
507 """
510 """
508 Get current active python path, and append it to PATH variable to fix issues
511 Get current active python path, and append it to PATH variable to fix issues
509 of subprocess calls and different python versions
512 of subprocess calls and different python versions
510 """
513 """
511 if os_ is None:
514 if os_ is None:
512 import os
515 import os
513 else:
516 else:
514 os = os_
517 os = os_
515
518
516 cur_path = os.path.split(sys.executable)[0]
519 cur_path = os.path.split(sys.executable)[0]
517 if not os.environ['PATH'].startswith(cur_path):
520 if not os.environ['PATH'].startswith(cur_path):
518 os.environ['PATH'] = '%s:%s' % (cur_path, os.environ['PATH'])
521 os.environ['PATH'] = '%s:%s' % (cur_path, os.environ['PATH'])
519
522
520
523
521 def obfuscate_url_pw(engine):
524 def obfuscate_url_pw(engine):
522 from sqlalchemy.engine import url as sa_url
525 from sqlalchemy.engine import url as sa_url
523 from sqlalchemy.exc import ArgumentError
526 from sqlalchemy.exc import ArgumentError
524 try:
527 try:
525 _url = sa_url.make_url(engine or '')
528 _url = sa_url.make_url(engine or '')
526 except ArgumentError:
529 except ArgumentError:
527 return engine
530 return engine
528 if _url.password:
531 if _url.password:
529 _url.password = 'XXXXX'
532 _url.password = 'XXXXX'
530 return str(_url)
533 return str(_url)
531
534
532
535
533 def get_server_url(environ):
536 def get_server_url(environ):
534 req = webob.Request(environ)
537 req = webob.Request(environ)
535 return req.host_url + req.script_name
538 return req.host_url + req.script_name
536
539
537
540
538 def _extract_extras(env=None):
541 def _extract_extras(env=None):
539 """
542 """
540 Extracts the Kallithea extras data from os.environ, and wraps it into named
543 Extracts the Kallithea extras data from os.environ, and wraps it into named
541 AttributeDict object
544 AttributeDict object
542 """
545 """
543 if not env:
546 if not env:
544 env = os.environ
547 env = os.environ
545
548
546 try:
549 try:
547 extras = json.loads(env['KALLITHEA_EXTRAS'])
550 extras = json.loads(env['KALLITHEA_EXTRAS'])
548 except KeyError:
551 except KeyError:
549 extras = {}
552 extras = {}
550
553
551 try:
554 try:
552 for k in ['username', 'repository', 'locked_by', 'scm', 'make_lock',
555 for k in ['username', 'repository', 'locked_by', 'scm', 'make_lock',
553 'action', 'ip']:
556 'action', 'ip']:
554 extras[k]
557 extras[k]
555 except KeyError as e:
558 except KeyError as e:
556 raise Exception('Missing key %s in os.environ %s' % (e, extras))
559 raise Exception('Missing key %s in os.environ %s' % (e, extras))
557
560
558 return AttributeDict(extras)
561 return AttributeDict(extras)
559
562
560
563
561 def _set_extras(extras):
564 def _set_extras(extras):
562 # RC_SCM_DATA can probably be removed in the future, but for compatibility now...
565 # RC_SCM_DATA can probably be removed in the future, but for compatibility now...
563 os.environ['KALLITHEA_EXTRAS'] = os.environ['RC_SCM_DATA'] = json.dumps(extras)
566 os.environ['KALLITHEA_EXTRAS'] = os.environ['RC_SCM_DATA'] = json.dumps(extras)
564
567
565
568
566 def get_current_authuser():
569 def get_current_authuser():
567 """
570 """
568 Gets kallithea user from threadlocal tmpl_context variable if it's
571 Gets kallithea user from threadlocal tmpl_context variable if it's
569 defined, else returns None.
572 defined, else returns None.
570 """
573 """
571 from tg import tmpl_context
574 from tg import tmpl_context
572 if hasattr(tmpl_context, 'authuser'):
575 if hasattr(tmpl_context, 'authuser'):
573 return tmpl_context.authuser
576 return tmpl_context.authuser
574
577
575 return None
578 return None
576
579
577
580
578 class OptionalAttr(object):
581 class OptionalAttr(object):
579 """
582 """
580 Special Optional Option that defines other attribute. Example::
583 Special Optional Option that defines other attribute. Example::
581
584
582 def test(apiuser, userid=Optional(OAttr('apiuser')):
585 def test(apiuser, userid=Optional(OAttr('apiuser')):
583 user = Optional.extract(userid)
586 user = Optional.extract(userid)
584 # calls
587 # calls
585
588
586 """
589 """
587
590
588 def __init__(self, attr_name):
591 def __init__(self, attr_name):
589 self.attr_name = attr_name
592 self.attr_name = attr_name
590
593
591 def __repr__(self):
594 def __repr__(self):
592 return '<OptionalAttr:%s>' % self.attr_name
595 return '<OptionalAttr:%s>' % self.attr_name
593
596
594 def __call__(self):
597 def __call__(self):
595 return self
598 return self
596
599
597
600
598 # alias
601 # alias
599 OAttr = OptionalAttr
602 OAttr = OptionalAttr
600
603
601
604
602 class Optional(object):
605 class Optional(object):
603 """
606 """
604 Defines an optional parameter::
607 Defines an optional parameter::
605
608
606 param = param.getval() if isinstance(param, Optional) else param
609 param = param.getval() if isinstance(param, Optional) else param
607 param = param() if isinstance(param, Optional) else param
610 param = param() if isinstance(param, Optional) else param
608
611
609 is equivalent of::
612 is equivalent of::
610
613
611 param = Optional.extract(param)
614 param = Optional.extract(param)
612
615
613 """
616 """
614
617
615 def __init__(self, type_):
618 def __init__(self, type_):
616 self.type_ = type_
619 self.type_ = type_
617
620
618 def __repr__(self):
621 def __repr__(self):
619 return '<Optional:%s>' % self.type_.__repr__()
622 return '<Optional:%s>' % self.type_.__repr__()
620
623
621 def __call__(self):
624 def __call__(self):
622 return self.getval()
625 return self.getval()
623
626
624 def getval(self):
627 def getval(self):
625 """
628 """
626 returns value from this Optional instance
629 returns value from this Optional instance
627 """
630 """
628 if isinstance(self.type_, OAttr):
631 if isinstance(self.type_, OAttr):
629 # use params name
632 # use params name
630 return self.type_.attr_name
633 return self.type_.attr_name
631 return self.type_
634 return self.type_
632
635
633 @classmethod
636 @classmethod
634 def extract(cls, val):
637 def extract(cls, val):
635 """
638 """
636 Extracts value from Optional() instance
639 Extracts value from Optional() instance
637
640
638 :param val:
641 :param val:
639 :return: original value if it's not Optional instance else
642 :return: original value if it's not Optional instance else
640 value of instance
643 value of instance
641 """
644 """
642 if isinstance(val, cls):
645 if isinstance(val, cls):
643 return val.getval()
646 return val.getval()
644 return val
647 return val
645
648
646
649
647 def urlreadable(s, _cleanstringsub=re.compile('[^-a-zA-Z0-9./]+').sub):
650 def urlreadable(s, _cleanstringsub=re.compile('[^-a-zA-Z0-9./]+').sub):
648 return _cleanstringsub('_', safe_str(s)).rstrip('_')
651 return _cleanstringsub('_', safe_str(s)).rstrip('_')
652
653
654 def recursive_replace(str_, replace=' '):
655 """
656 Recursive replace of given sign to just one instance
657
658 :param str_: given string
659 :param replace: char to find and replace multiple instances
660
661 Examples::
662 >>> recursive_replace("Mighty---Mighty-Bo--sstones",'-')
663 'Mighty-Mighty-Bo-sstones'
664 """
665
666 if str_.find(replace * 2) == -1:
667 return str_
668 else:
669 str_ = str_.replace(replace * 2, replace)
670 return recursive_replace(str_, replace)
671
672
673 def repo_name_slug(value):
674 """
675 Return slug of name of repository
676 This function is called on each creation/modification
677 of repository to prevent bad names in repo
678 """
679
680 slug = remove_formatting(value)
681 slug = strip_tags(slug)
682
683 for c in """`?=[]\;'"<>,/~!@#$%^&*()+{}|: """:
684 slug = slug.replace(c, '-')
685 slug = recursive_replace(slug, '-')
686 slug = collapse(slug, '-')
687 return slug
@@ -1,745 +1,745 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 # This program is free software: you can redistribute it and/or modify
2 # This program is free software: you can redistribute it and/or modify
3 # it under the terms of the GNU General Public License as published by
3 # it under the terms of the GNU General Public License as published by
4 # the Free Software Foundation, either version 3 of the License, or
4 # the Free Software Foundation, either version 3 of the License, or
5 # (at your option) any later version.
5 # (at your option) any later version.
6 #
6 #
7 # This program is distributed in the hope that it will be useful,
7 # This program is distributed in the hope that it will be useful,
8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 # GNU General Public License for more details.
10 # GNU General Public License for more details.
11 #
11 #
12 # You should have received a copy of the GNU General Public License
12 # You should have received a copy of the GNU General Public License
13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
14 """
14 """
15 kallithea.model.repo
15 kallithea.model.repo
16 ~~~~~~~~~~~~~~~~~~~~
16 ~~~~~~~~~~~~~~~~~~~~
17
17
18 Repository model for kallithea
18 Repository model for kallithea
19
19
20 This file was forked by the Kallithea project in July 2014.
20 This file was forked by the Kallithea project in July 2014.
21 Original author and date, and relevant copyright and licensing information is below:
21 Original author and date, and relevant copyright and licensing information is below:
22 :created_on: Jun 5, 2010
22 :created_on: Jun 5, 2010
23 :author: marcink
23 :author: marcink
24 :copyright: (c) 2013 RhodeCode GmbH, and others.
24 :copyright: (c) 2013 RhodeCode GmbH, and others.
25 :license: GPLv3, see LICENSE.md for more details.
25 :license: GPLv3, see LICENSE.md for more details.
26
26
27 """
27 """
28
28
29 import os
29 import os
30 import shutil
30 import shutil
31 import logging
31 import logging
32 import traceback
32 import traceback
33 from datetime import datetime
33 from datetime import datetime
34 from sqlalchemy.orm import subqueryload
34 from sqlalchemy.orm import subqueryload
35
35
36 import kallithea.lib.utils
36 import kallithea.lib.utils2
37 from kallithea.lib.utils import make_ui, is_valid_repo_uri
37 from kallithea.lib.utils import make_ui, is_valid_repo_uri
38 from kallithea.lib.vcs.backends import get_backend
38 from kallithea.lib.vcs.backends import get_backend
39 from kallithea.lib.utils2 import LazyProperty, safe_str, safe_unicode, \
39 from kallithea.lib.utils2 import LazyProperty, safe_str, safe_unicode, \
40 remove_prefix, obfuscate_url_pw, get_current_authuser
40 remove_prefix, obfuscate_url_pw, get_current_authuser
41 from kallithea.lib.caching_query import FromCache
41 from kallithea.lib.caching_query import FromCache
42 from kallithea.lib.hooks import log_delete_repository
42 from kallithea.lib.hooks import log_delete_repository
43
43
44 from kallithea.model.db import Repository, UserRepoToPerm, UserGroupRepoToPerm, \
44 from kallithea.model.db import Repository, UserRepoToPerm, UserGroupRepoToPerm, \
45 UserRepoGroupToPerm, UserGroupRepoGroupToPerm, User, Permission, Session, \
45 UserRepoGroupToPerm, UserGroupRepoGroupToPerm, User, Permission, Session, \
46 Statistics, UserGroup, Ui, RepoGroup, RepositoryField
46 Statistics, UserGroup, Ui, RepoGroup, RepositoryField
47
47
48 from kallithea.lib import helpers as h
48 from kallithea.lib import helpers as h
49 from kallithea.lib.auth import HasRepoPermissionLevel, HasUserGroupPermissionLevel
49 from kallithea.lib.auth import HasRepoPermissionLevel, HasUserGroupPermissionLevel
50 from kallithea.lib.exceptions import AttachedForksError
50 from kallithea.lib.exceptions import AttachedForksError
51 from kallithea.model.scm import UserGroupList
51 from kallithea.model.scm import UserGroupList
52
52
53 log = logging.getLogger(__name__)
53 log = logging.getLogger(__name__)
54
54
55
55
56 class RepoModel(object):
56 class RepoModel(object):
57
57
58 URL_SEPARATOR = Repository.url_sep()
58 URL_SEPARATOR = Repository.url_sep()
59
59
60 def _create_default_perms(self, repository, private):
60 def _create_default_perms(self, repository, private):
61 # create default permission
61 # create default permission
62 default = 'repository.read'
62 default = 'repository.read'
63 def_user = User.get_default_user()
63 def_user = User.get_default_user()
64 for p in def_user.user_perms:
64 for p in def_user.user_perms:
65 if p.permission.permission_name.startswith('repository.'):
65 if p.permission.permission_name.startswith('repository.'):
66 default = p.permission.permission_name
66 default = p.permission.permission_name
67 break
67 break
68
68
69 default_perm = 'repository.none' if private else default
69 default_perm = 'repository.none' if private else default
70
70
71 repo_to_perm = UserRepoToPerm()
71 repo_to_perm = UserRepoToPerm()
72 repo_to_perm.permission = Permission.get_by_key(default_perm)
72 repo_to_perm.permission = Permission.get_by_key(default_perm)
73
73
74 repo_to_perm.repository = repository
74 repo_to_perm.repository = repository
75 repo_to_perm.user_id = def_user.user_id
75 repo_to_perm.user_id = def_user.user_id
76 Session().add(repo_to_perm)
76 Session().add(repo_to_perm)
77
77
78 return repo_to_perm
78 return repo_to_perm
79
79
80 @LazyProperty
80 @LazyProperty
81 def repos_path(self):
81 def repos_path(self):
82 """
82 """
83 Gets the repositories root path from database
83 Gets the repositories root path from database
84 """
84 """
85
85
86 q = Ui.query().filter(Ui.ui_key == '/').one()
86 q = Ui.query().filter(Ui.ui_key == '/').one()
87 return q.ui_value
87 return q.ui_value
88
88
89 def get(self, repo_id, cache=False):
89 def get(self, repo_id, cache=False):
90 repo = Repository.query() \
90 repo = Repository.query() \
91 .filter(Repository.repo_id == repo_id)
91 .filter(Repository.repo_id == repo_id)
92
92
93 if cache:
93 if cache:
94 repo = repo.options(FromCache("sql_cache_short",
94 repo = repo.options(FromCache("sql_cache_short",
95 "get_repo_%s" % repo_id))
95 "get_repo_%s" % repo_id))
96 return repo.scalar()
96 return repo.scalar()
97
97
98 def get_repo(self, repository):
98 def get_repo(self, repository):
99 return Repository.guess_instance(repository)
99 return Repository.guess_instance(repository)
100
100
101 def get_by_repo_name(self, repo_name, cache=False):
101 def get_by_repo_name(self, repo_name, cache=False):
102 repo = Repository.query() \
102 repo = Repository.query() \
103 .filter(Repository.repo_name == repo_name)
103 .filter(Repository.repo_name == repo_name)
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_name))
107 "get_repo_%s" % repo_name))
108 return repo.scalar()
108 return repo.scalar()
109
109
110 def get_all_user_repos(self, user):
110 def get_all_user_repos(self, user):
111 """
111 """
112 Gets all repositories that user have at least read access
112 Gets all repositories that user have at least read access
113
113
114 :param user:
114 :param user:
115 """
115 """
116 from kallithea.lib.auth import AuthUser
116 from kallithea.lib.auth import AuthUser
117 user = User.guess_instance(user)
117 user = User.guess_instance(user)
118 repos = AuthUser(dbuser=user).permissions['repositories']
118 repos = AuthUser(dbuser=user).permissions['repositories']
119 access_check = lambda r: r[1] in ['repository.read',
119 access_check = lambda r: r[1] in ['repository.read',
120 'repository.write',
120 'repository.write',
121 'repository.admin']
121 'repository.admin']
122 repos = [x[0] for x in filter(access_check, repos.items())]
122 repos = [x[0] for x in filter(access_check, repos.items())]
123 return Repository.query().filter(Repository.repo_name.in_(repos))
123 return Repository.query().filter(Repository.repo_name.in_(repos))
124
124
125 def get_users_js(self):
125 def get_users_js(self):
126 users = User.query() \
126 users = User.query() \
127 .filter(User.active == True) \
127 .filter(User.active == True) \
128 .order_by(User.name, User.lastname) \
128 .order_by(User.name, User.lastname) \
129 .all()
129 .all()
130 return [
130 return [
131 {
131 {
132 'id': u.user_id,
132 'id': u.user_id,
133 'fname': h.escape(u.name),
133 'fname': h.escape(u.name),
134 'lname': h.escape(u.lastname),
134 'lname': h.escape(u.lastname),
135 'nname': u.username,
135 'nname': u.username,
136 'gravatar_lnk': h.gravatar_url(u.email, size=28, default='default'),
136 'gravatar_lnk': h.gravatar_url(u.email, size=28, default='default'),
137 'gravatar_size': 14,
137 'gravatar_size': 14,
138 } for u in users]
138 } for u in users]
139
139
140 @classmethod
140 @classmethod
141 def _render_datatable(cls, tmpl, *args, **kwargs):
141 def _render_datatable(cls, tmpl, *args, **kwargs):
142 import kallithea
142 import kallithea
143 from tg import tmpl_context as c, request, app_globals
143 from tg import tmpl_context as c, request, app_globals
144 from tg.i18n import ugettext as _
144 from tg.i18n import ugettext as _
145
145
146 _tmpl_lookup = app_globals.mako_lookup
146 _tmpl_lookup = app_globals.mako_lookup
147 template = _tmpl_lookup.get_template('data_table/_dt_elements.html')
147 template = _tmpl_lookup.get_template('data_table/_dt_elements.html')
148
148
149 tmpl = template.get_def(tmpl)
149 tmpl = template.get_def(tmpl)
150 kwargs.update(dict(_=_, h=h, c=c, request=request))
150 kwargs.update(dict(_=_, h=h, c=c, request=request))
151 return tmpl.render(*args, **kwargs)
151 return tmpl.render(*args, **kwargs)
152
152
153 def get_repos_as_dict(self, repos_list, repo_groups_list=None,
153 def get_repos_as_dict(self, repos_list, repo_groups_list=None,
154 admin=False,
154 admin=False,
155 short_name=False):
155 short_name=False):
156 """Return repository list for use by DataTable.
156 """Return repository list for use by DataTable.
157 repos_list: list of repositories - but will be filtered for read permission.
157 repos_list: list of repositories - but will be filtered for read permission.
158 repo_groups_list: added at top of list without permission check.
158 repo_groups_list: added at top of list without permission check.
159 admin: return data for action column.
159 admin: return data for action column.
160 """
160 """
161 _render = self._render_datatable
161 _render = self._render_datatable
162 from tg import tmpl_context as c
162 from tg import tmpl_context as c
163
163
164 def repo_lnk(name, rtype, rstate, private, fork_of):
164 def repo_lnk(name, rtype, rstate, private, fork_of):
165 return _render('repo_name', name, rtype, rstate, private, fork_of,
165 return _render('repo_name', name, rtype, rstate, private, fork_of,
166 short_name=short_name)
166 short_name=short_name)
167
167
168 def last_change(last_change):
168 def last_change(last_change):
169 return _render("last_change", last_change)
169 return _render("last_change", last_change)
170
170
171 def rss_lnk(repo_name):
171 def rss_lnk(repo_name):
172 return _render("rss", repo_name)
172 return _render("rss", repo_name)
173
173
174 def atom_lnk(repo_name):
174 def atom_lnk(repo_name):
175 return _render("atom", repo_name)
175 return _render("atom", repo_name)
176
176
177 def last_rev(repo_name, cs_cache):
177 def last_rev(repo_name, cs_cache):
178 return _render('revision', repo_name, cs_cache.get('revision'),
178 return _render('revision', repo_name, cs_cache.get('revision'),
179 cs_cache.get('raw_id'), cs_cache.get('author'),
179 cs_cache.get('raw_id'), cs_cache.get('author'),
180 cs_cache.get('message'))
180 cs_cache.get('message'))
181
181
182 def desc(desc):
182 def desc(desc):
183 return h.urlify_text(desc, truncate=80, stylize=c.visual.stylify_metalabels)
183 return h.urlify_text(desc, truncate=80, stylize=c.visual.stylify_metalabels)
184
184
185 def state(repo_state):
185 def state(repo_state):
186 return _render("repo_state", repo_state)
186 return _render("repo_state", repo_state)
187
187
188 def repo_actions(repo_name):
188 def repo_actions(repo_name):
189 return _render('repo_actions', repo_name)
189 return _render('repo_actions', repo_name)
190
190
191 def owner_actions(owner_id, username):
191 def owner_actions(owner_id, username):
192 return _render('user_name', owner_id, username)
192 return _render('user_name', owner_id, username)
193
193
194 repos_data = []
194 repos_data = []
195
195
196 for gr in repo_groups_list or []:
196 for gr in repo_groups_list or []:
197 repos_data.append(dict(
197 repos_data.append(dict(
198 raw_name='\0' + gr.name, # sort before repositories
198 raw_name='\0' + gr.name, # sort before repositories
199 just_name=gr.name,
199 just_name=gr.name,
200 name=_render('group_name_html', group_name=gr.group_name, name=gr.name),
200 name=_render('group_name_html', group_name=gr.group_name, name=gr.name),
201 desc=gr.group_description))
201 desc=gr.group_description))
202
202
203 for repo in repos_list:
203 for repo in repos_list:
204 if not HasRepoPermissionLevel('read')(repo.repo_name, 'get_repos_as_dict check'):
204 if not HasRepoPermissionLevel('read')(repo.repo_name, 'get_repos_as_dict check'):
205 continue
205 continue
206 cs_cache = repo.changeset_cache
206 cs_cache = repo.changeset_cache
207 row = {
207 row = {
208 "raw_name": repo.repo_name,
208 "raw_name": repo.repo_name,
209 "just_name": repo.just_name,
209 "just_name": repo.just_name,
210 "name": repo_lnk(repo.repo_name, repo.repo_type,
210 "name": repo_lnk(repo.repo_name, repo.repo_type,
211 repo.repo_state, repo.private, repo.fork),
211 repo.repo_state, repo.private, repo.fork),
212 "last_change_iso": repo.last_db_change.isoformat(),
212 "last_change_iso": repo.last_db_change.isoformat(),
213 "last_change": last_change(repo.last_db_change),
213 "last_change": last_change(repo.last_db_change),
214 "last_changeset": last_rev(repo.repo_name, cs_cache),
214 "last_changeset": last_rev(repo.repo_name, cs_cache),
215 "last_rev_raw": cs_cache.get('revision'),
215 "last_rev_raw": cs_cache.get('revision'),
216 "desc": desc(repo.description),
216 "desc": desc(repo.description),
217 "owner": h.person(repo.owner),
217 "owner": h.person(repo.owner),
218 "state": state(repo.repo_state),
218 "state": state(repo.repo_state),
219 "rss": rss_lnk(repo.repo_name),
219 "rss": rss_lnk(repo.repo_name),
220 "atom": atom_lnk(repo.repo_name),
220 "atom": atom_lnk(repo.repo_name),
221 }
221 }
222 if admin:
222 if admin:
223 row.update({
223 row.update({
224 "action": repo_actions(repo.repo_name),
224 "action": repo_actions(repo.repo_name),
225 "owner": owner_actions(repo.owner_id,
225 "owner": owner_actions(repo.owner_id,
226 h.person(repo.owner))
226 h.person(repo.owner))
227 })
227 })
228 repos_data.append(row)
228 repos_data.append(row)
229
229
230 return {
230 return {
231 "sort": "name",
231 "sort": "name",
232 "dir": "asc",
232 "dir": "asc",
233 "records": repos_data
233 "records": repos_data
234 }
234 }
235
235
236 def _get_defaults(self, repo_name):
236 def _get_defaults(self, repo_name):
237 """
237 """
238 Gets information about repository, and returns a dict for
238 Gets information about repository, and returns a dict for
239 usage in forms
239 usage in forms
240
240
241 :param repo_name:
241 :param repo_name:
242 """
242 """
243
243
244 repo_info = Repository.get_by_repo_name(repo_name)
244 repo_info = Repository.get_by_repo_name(repo_name)
245
245
246 if repo_info is None:
246 if repo_info is None:
247 return None
247 return None
248
248
249 defaults = repo_info.get_dict()
249 defaults = repo_info.get_dict()
250 defaults['repo_name'] = repo_info.just_name
250 defaults['repo_name'] = repo_info.just_name
251 defaults['repo_group'] = repo_info.group_id
251 defaults['repo_group'] = repo_info.group_id
252
252
253 for strip, k in [(0, 'repo_type'), (1, 'repo_enable_downloads'),
253 for strip, k in [(0, 'repo_type'), (1, 'repo_enable_downloads'),
254 (1, 'repo_description'), (1, 'repo_enable_locking'),
254 (1, 'repo_description'), (1, 'repo_enable_locking'),
255 (1, 'repo_landing_rev'), (0, 'clone_uri'),
255 (1, 'repo_landing_rev'), (0, 'clone_uri'),
256 (1, 'repo_private'), (1, 'repo_enable_statistics')]:
256 (1, 'repo_private'), (1, 'repo_enable_statistics')]:
257 attr = k
257 attr = k
258 if strip:
258 if strip:
259 attr = remove_prefix(k, 'repo_')
259 attr = remove_prefix(k, 'repo_')
260
260
261 val = defaults[attr]
261 val = defaults[attr]
262 if k == 'repo_landing_rev':
262 if k == 'repo_landing_rev':
263 val = ':'.join(defaults[attr])
263 val = ':'.join(defaults[attr])
264 defaults[k] = val
264 defaults[k] = val
265 if k == 'clone_uri':
265 if k == 'clone_uri':
266 defaults['clone_uri_hidden'] = repo_info.clone_uri_hidden
266 defaults['clone_uri_hidden'] = repo_info.clone_uri_hidden
267
267
268 # fill owner
268 # fill owner
269 if repo_info.owner:
269 if repo_info.owner:
270 defaults.update({'owner': repo_info.owner.username})
270 defaults.update({'owner': repo_info.owner.username})
271 else:
271 else:
272 replacement_user = User.query().filter(User.admin ==
272 replacement_user = User.query().filter(User.admin ==
273 True).first().username
273 True).first().username
274 defaults.update({'owner': replacement_user})
274 defaults.update({'owner': replacement_user})
275
275
276 # fill repository users
276 # fill repository users
277 for p in repo_info.repo_to_perm:
277 for p in repo_info.repo_to_perm:
278 defaults.update({'u_perm_%s' % p.user.username:
278 defaults.update({'u_perm_%s' % p.user.username:
279 p.permission.permission_name})
279 p.permission.permission_name})
280
280
281 # fill repository groups
281 # fill repository groups
282 for p in repo_info.users_group_to_perm:
282 for p in repo_info.users_group_to_perm:
283 defaults.update({'g_perm_%s' % p.users_group.users_group_name:
283 defaults.update({'g_perm_%s' % p.users_group.users_group_name:
284 p.permission.permission_name})
284 p.permission.permission_name})
285
285
286 return defaults
286 return defaults
287
287
288 def update(self, repo, **kwargs):
288 def update(self, repo, **kwargs):
289 try:
289 try:
290 cur_repo = Repository.guess_instance(repo)
290 cur_repo = Repository.guess_instance(repo)
291 org_repo_name = cur_repo.repo_name
291 org_repo_name = cur_repo.repo_name
292 if 'owner' in kwargs:
292 if 'owner' in kwargs:
293 cur_repo.owner = User.get_by_username(kwargs['owner'])
293 cur_repo.owner = User.get_by_username(kwargs['owner'])
294
294
295 if 'repo_group' in kwargs:
295 if 'repo_group' in kwargs:
296 assert kwargs['repo_group'] != u'-1', kwargs # RepoForm should have converted to None
296 assert kwargs['repo_group'] != u'-1', kwargs # RepoForm should have converted to None
297 cur_repo.group = RepoGroup.get(kwargs['repo_group'])
297 cur_repo.group = RepoGroup.get(kwargs['repo_group'])
298 cur_repo.repo_name = cur_repo.get_new_name(cur_repo.just_name)
298 cur_repo.repo_name = cur_repo.get_new_name(cur_repo.just_name)
299 log.debug('Updating repo %s with params:%s', cur_repo, kwargs)
299 log.debug('Updating repo %s with params:%s', cur_repo, kwargs)
300 for k in ['repo_enable_downloads',
300 for k in ['repo_enable_downloads',
301 'repo_description',
301 'repo_description',
302 'repo_enable_locking',
302 'repo_enable_locking',
303 'repo_landing_rev',
303 'repo_landing_rev',
304 'repo_private',
304 'repo_private',
305 'repo_enable_statistics',
305 'repo_enable_statistics',
306 ]:
306 ]:
307 if k in kwargs:
307 if k in kwargs:
308 setattr(cur_repo, remove_prefix(k, 'repo_'), kwargs[k])
308 setattr(cur_repo, remove_prefix(k, 'repo_'), kwargs[k])
309 clone_uri = kwargs.get('clone_uri')
309 clone_uri = kwargs.get('clone_uri')
310 if clone_uri is not None and clone_uri != cur_repo.clone_uri_hidden:
310 if clone_uri is not None and clone_uri != cur_repo.clone_uri_hidden:
311 # clone_uri is modified - if given a value, check it is valid
311 # clone_uri is modified - if given a value, check it is valid
312 if clone_uri != '':
312 if clone_uri != '':
313 # will raise exception on error
313 # will raise exception on error
314 is_valid_repo_uri(cur_repo.repo_type, clone_uri, make_ui('db', clear_session=False))
314 is_valid_repo_uri(cur_repo.repo_type, clone_uri, make_ui('db', clear_session=False))
315 cur_repo.clone_uri = clone_uri
315 cur_repo.clone_uri = clone_uri
316
316
317 if 'repo_name' in kwargs:
317 if 'repo_name' in kwargs:
318 repo_name = kwargs['repo_name']
318 repo_name = kwargs['repo_name']
319 if kallithea.lib.utils.repo_name_slug(repo_name) != repo_name:
319 if kallithea.lib.utils2.repo_name_slug(repo_name) != repo_name:
320 raise Exception('invalid repo name %s' % repo_name)
320 raise Exception('invalid repo name %s' % repo_name)
321 cur_repo.repo_name = cur_repo.get_new_name(repo_name)
321 cur_repo.repo_name = cur_repo.get_new_name(repo_name)
322
322
323 # if private flag is set, reset default permission to NONE
323 # if private flag is set, reset default permission to NONE
324 if kwargs.get('repo_private'):
324 if kwargs.get('repo_private'):
325 EMPTY_PERM = 'repository.none'
325 EMPTY_PERM = 'repository.none'
326 RepoModel().grant_user_permission(
326 RepoModel().grant_user_permission(
327 repo=cur_repo, user='default', perm=EMPTY_PERM
327 repo=cur_repo, user='default', perm=EMPTY_PERM
328 )
328 )
329 # handle extra fields
329 # handle extra fields
330 for field in filter(lambda k: k.startswith(RepositoryField.PREFIX),
330 for field in filter(lambda k: k.startswith(RepositoryField.PREFIX),
331 kwargs):
331 kwargs):
332 k = RepositoryField.un_prefix_key(field)
332 k = RepositoryField.un_prefix_key(field)
333 ex_field = RepositoryField.get_by_key_name(key=k, repo=cur_repo)
333 ex_field = RepositoryField.get_by_key_name(key=k, repo=cur_repo)
334 if ex_field:
334 if ex_field:
335 ex_field.field_value = kwargs[field]
335 ex_field.field_value = kwargs[field]
336
336
337 if org_repo_name != cur_repo.repo_name:
337 if org_repo_name != cur_repo.repo_name:
338 # rename repository
338 # rename repository
339 self._rename_filesystem_repo(old=org_repo_name, new=cur_repo.repo_name)
339 self._rename_filesystem_repo(old=org_repo_name, new=cur_repo.repo_name)
340
340
341 return cur_repo
341 return cur_repo
342 except Exception:
342 except Exception:
343 log.error(traceback.format_exc())
343 log.error(traceback.format_exc())
344 raise
344 raise
345
345
346 def _create_repo(self, repo_name, repo_type, description, owner,
346 def _create_repo(self, repo_name, repo_type, description, owner,
347 private=False, clone_uri=None, repo_group=None,
347 private=False, clone_uri=None, repo_group=None,
348 landing_rev='rev:tip', fork_of=None,
348 landing_rev='rev:tip', fork_of=None,
349 copy_fork_permissions=False, enable_statistics=False,
349 copy_fork_permissions=False, enable_statistics=False,
350 enable_locking=False, enable_downloads=False,
350 enable_locking=False, enable_downloads=False,
351 copy_group_permissions=False, state=Repository.STATE_PENDING):
351 copy_group_permissions=False, state=Repository.STATE_PENDING):
352 """
352 """
353 Create repository inside database with PENDING state. This should only be
353 Create repository inside database with PENDING state. This should only be
354 executed by create() repo, with exception of importing existing repos.
354 executed by create() repo, with exception of importing existing repos.
355
355
356 """
356 """
357 from kallithea.model.scm import ScmModel
357 from kallithea.model.scm import ScmModel
358
358
359 owner = User.guess_instance(owner)
359 owner = User.guess_instance(owner)
360 fork_of = Repository.guess_instance(fork_of)
360 fork_of = Repository.guess_instance(fork_of)
361 repo_group = RepoGroup.guess_instance(repo_group)
361 repo_group = RepoGroup.guess_instance(repo_group)
362 try:
362 try:
363 repo_name = safe_unicode(repo_name)
363 repo_name = safe_unicode(repo_name)
364 description = safe_unicode(description)
364 description = safe_unicode(description)
365 # repo name is just a name of repository
365 # repo name is just a name of repository
366 # while repo_name_full is a full qualified name that is combined
366 # while repo_name_full is a full qualified name that is combined
367 # with name and path of group
367 # with name and path of group
368 repo_name_full = repo_name
368 repo_name_full = repo_name
369 repo_name = repo_name.split(self.URL_SEPARATOR)[-1]
369 repo_name = repo_name.split(self.URL_SEPARATOR)[-1]
370 if kallithea.lib.utils.repo_name_slug(repo_name) != repo_name:
370 if kallithea.lib.utils2.repo_name_slug(repo_name) != repo_name:
371 raise Exception('invalid repo name %s' % repo_name)
371 raise Exception('invalid repo name %s' % repo_name)
372
372
373 new_repo = Repository()
373 new_repo = Repository()
374 new_repo.repo_state = state
374 new_repo.repo_state = state
375 new_repo.enable_statistics = False
375 new_repo.enable_statistics = False
376 new_repo.repo_name = repo_name_full
376 new_repo.repo_name = repo_name_full
377 new_repo.repo_type = repo_type
377 new_repo.repo_type = repo_type
378 new_repo.owner = owner
378 new_repo.owner = owner
379 new_repo.group = repo_group
379 new_repo.group = repo_group
380 new_repo.description = description or repo_name
380 new_repo.description = description or repo_name
381 new_repo.private = private
381 new_repo.private = private
382 if clone_uri:
382 if clone_uri:
383 # will raise exception on error
383 # will raise exception on error
384 is_valid_repo_uri(repo_type, clone_uri, make_ui('db', clear_session=False))
384 is_valid_repo_uri(repo_type, clone_uri, make_ui('db', clear_session=False))
385 new_repo.clone_uri = clone_uri
385 new_repo.clone_uri = clone_uri
386 new_repo.landing_rev = landing_rev
386 new_repo.landing_rev = landing_rev
387
387
388 new_repo.enable_statistics = enable_statistics
388 new_repo.enable_statistics = enable_statistics
389 new_repo.enable_locking = enable_locking
389 new_repo.enable_locking = enable_locking
390 new_repo.enable_downloads = enable_downloads
390 new_repo.enable_downloads = enable_downloads
391
391
392 if repo_group:
392 if repo_group:
393 new_repo.enable_locking = repo_group.enable_locking
393 new_repo.enable_locking = repo_group.enable_locking
394
394
395 if fork_of:
395 if fork_of:
396 parent_repo = fork_of
396 parent_repo = fork_of
397 new_repo.fork = parent_repo
397 new_repo.fork = parent_repo
398
398
399 Session().add(new_repo)
399 Session().add(new_repo)
400
400
401 if fork_of and copy_fork_permissions:
401 if fork_of and copy_fork_permissions:
402 repo = fork_of
402 repo = fork_of
403 user_perms = UserRepoToPerm.query() \
403 user_perms = UserRepoToPerm.query() \
404 .filter(UserRepoToPerm.repository == repo).all()
404 .filter(UserRepoToPerm.repository == repo).all()
405 group_perms = UserGroupRepoToPerm.query() \
405 group_perms = UserGroupRepoToPerm.query() \
406 .filter(UserGroupRepoToPerm.repository == repo).all()
406 .filter(UserGroupRepoToPerm.repository == repo).all()
407
407
408 for perm in user_perms:
408 for perm in user_perms:
409 UserRepoToPerm.create(perm.user, new_repo, perm.permission)
409 UserRepoToPerm.create(perm.user, new_repo, perm.permission)
410
410
411 for perm in group_perms:
411 for perm in group_perms:
412 UserGroupRepoToPerm.create(perm.users_group, new_repo,
412 UserGroupRepoToPerm.create(perm.users_group, new_repo,
413 perm.permission)
413 perm.permission)
414
414
415 elif repo_group and copy_group_permissions:
415 elif repo_group and copy_group_permissions:
416
416
417 user_perms = UserRepoGroupToPerm.query() \
417 user_perms = UserRepoGroupToPerm.query() \
418 .filter(UserRepoGroupToPerm.group == repo_group).all()
418 .filter(UserRepoGroupToPerm.group == repo_group).all()
419
419
420 group_perms = UserGroupRepoGroupToPerm.query() \
420 group_perms = UserGroupRepoGroupToPerm.query() \
421 .filter(UserGroupRepoGroupToPerm.group == repo_group).all()
421 .filter(UserGroupRepoGroupToPerm.group == repo_group).all()
422
422
423 for perm in user_perms:
423 for perm in user_perms:
424 perm_name = perm.permission.permission_name.replace('group.', 'repository.')
424 perm_name = perm.permission.permission_name.replace('group.', 'repository.')
425 perm_obj = Permission.get_by_key(perm_name)
425 perm_obj = Permission.get_by_key(perm_name)
426 UserRepoToPerm.create(perm.user, new_repo, perm_obj)
426 UserRepoToPerm.create(perm.user, new_repo, perm_obj)
427
427
428 for perm in group_perms:
428 for perm in group_perms:
429 perm_name = perm.permission.permission_name.replace('group.', 'repository.')
429 perm_name = perm.permission.permission_name.replace('group.', 'repository.')
430 perm_obj = Permission.get_by_key(perm_name)
430 perm_obj = Permission.get_by_key(perm_name)
431 UserGroupRepoToPerm.create(perm.users_group, new_repo, perm_obj)
431 UserGroupRepoToPerm.create(perm.users_group, new_repo, perm_obj)
432
432
433 else:
433 else:
434 self._create_default_perms(new_repo, private)
434 self._create_default_perms(new_repo, private)
435
435
436 # now automatically start following this repository as owner
436 # now automatically start following this repository as owner
437 ScmModel().toggle_following_repo(new_repo.repo_id, owner.user_id)
437 ScmModel().toggle_following_repo(new_repo.repo_id, owner.user_id)
438 # we need to flush here, in order to check if database won't
438 # we need to flush here, in order to check if database won't
439 # throw any exceptions, create filesystem dirs at the very end
439 # throw any exceptions, create filesystem dirs at the very end
440 Session().flush()
440 Session().flush()
441 return new_repo
441 return new_repo
442 except Exception:
442 except Exception:
443 log.error(traceback.format_exc())
443 log.error(traceback.format_exc())
444 raise
444 raise
445
445
446 def create(self, form_data, cur_user):
446 def create(self, form_data, cur_user):
447 """
447 """
448 Create repository using celery tasks
448 Create repository using celery tasks
449
449
450 :param form_data:
450 :param form_data:
451 :param cur_user:
451 :param cur_user:
452 """
452 """
453 from kallithea.lib.celerylib import tasks
453 from kallithea.lib.celerylib import tasks
454 return tasks.create_repo(form_data, cur_user)
454 return tasks.create_repo(form_data, cur_user)
455
455
456 def _update_permissions(self, repo, perms_new=None, perms_updates=None,
456 def _update_permissions(self, repo, perms_new=None, perms_updates=None,
457 check_perms=True):
457 check_perms=True):
458 if not perms_new:
458 if not perms_new:
459 perms_new = []
459 perms_new = []
460 if not perms_updates:
460 if not perms_updates:
461 perms_updates = []
461 perms_updates = []
462
462
463 # update permissions
463 # update permissions
464 for member, perm, member_type in perms_updates:
464 for member, perm, member_type in perms_updates:
465 if member_type == 'user':
465 if member_type == 'user':
466 # this updates existing one
466 # this updates existing one
467 self.grant_user_permission(
467 self.grant_user_permission(
468 repo=repo, user=member, perm=perm
468 repo=repo, user=member, perm=perm
469 )
469 )
470 else:
470 else:
471 # check if we have permissions to alter this usergroup's access
471 # check if we have permissions to alter this usergroup's access
472 if not check_perms or HasUserGroupPermissionLevel('read')(member):
472 if not check_perms or HasUserGroupPermissionLevel('read')(member):
473 self.grant_user_group_permission(
473 self.grant_user_group_permission(
474 repo=repo, group_name=member, perm=perm
474 repo=repo, group_name=member, perm=perm
475 )
475 )
476 # set new permissions
476 # set new permissions
477 for member, perm, member_type in perms_new:
477 for member, perm, member_type in perms_new:
478 if member_type == 'user':
478 if member_type == 'user':
479 self.grant_user_permission(
479 self.grant_user_permission(
480 repo=repo, user=member, perm=perm
480 repo=repo, user=member, perm=perm
481 )
481 )
482 else:
482 else:
483 # check if we have permissions to alter this usergroup's access
483 # check if we have permissions to alter this usergroup's access
484 if not check_perms or HasUserGroupPermissionLevel('read')(member):
484 if not check_perms or HasUserGroupPermissionLevel('read')(member):
485 self.grant_user_group_permission(
485 self.grant_user_group_permission(
486 repo=repo, group_name=member, perm=perm
486 repo=repo, group_name=member, perm=perm
487 )
487 )
488
488
489 def create_fork(self, form_data, cur_user):
489 def create_fork(self, form_data, cur_user):
490 """
490 """
491 Simple wrapper into executing celery task for fork creation
491 Simple wrapper into executing celery task for fork creation
492
492
493 :param form_data:
493 :param form_data:
494 :param cur_user:
494 :param cur_user:
495 """
495 """
496 from kallithea.lib.celerylib import tasks
496 from kallithea.lib.celerylib import tasks
497 return tasks.create_repo_fork(form_data, cur_user)
497 return tasks.create_repo_fork(form_data, cur_user)
498
498
499 def delete(self, repo, forks=None, fs_remove=True, cur_user=None):
499 def delete(self, repo, forks=None, fs_remove=True, cur_user=None):
500 """
500 """
501 Delete given repository, forks parameter defines what do do with
501 Delete given repository, forks parameter defines what do do with
502 attached forks. Throws AttachedForksError if deleted repo has attached
502 attached forks. Throws AttachedForksError if deleted repo has attached
503 forks
503 forks
504
504
505 :param repo:
505 :param repo:
506 :param forks: str 'delete' or 'detach'
506 :param forks: str 'delete' or 'detach'
507 :param fs_remove: remove(archive) repo from filesystem
507 :param fs_remove: remove(archive) repo from filesystem
508 """
508 """
509 if not cur_user:
509 if not cur_user:
510 cur_user = getattr(get_current_authuser(), 'username', None)
510 cur_user = getattr(get_current_authuser(), 'username', None)
511 repo = Repository.guess_instance(repo)
511 repo = Repository.guess_instance(repo)
512 if repo is not None:
512 if repo is not None:
513 if forks == 'detach':
513 if forks == 'detach':
514 for r in repo.forks:
514 for r in repo.forks:
515 r.fork = None
515 r.fork = None
516 elif forks == 'delete':
516 elif forks == 'delete':
517 for r in repo.forks:
517 for r in repo.forks:
518 self.delete(r, forks='delete')
518 self.delete(r, forks='delete')
519 elif [f for f in repo.forks]:
519 elif [f for f in repo.forks]:
520 raise AttachedForksError()
520 raise AttachedForksError()
521
521
522 old_repo_dict = repo.get_dict()
522 old_repo_dict = repo.get_dict()
523 try:
523 try:
524 Session().delete(repo)
524 Session().delete(repo)
525 if fs_remove:
525 if fs_remove:
526 self._delete_filesystem_repo(repo)
526 self._delete_filesystem_repo(repo)
527 else:
527 else:
528 log.debug('skipping removal from filesystem')
528 log.debug('skipping removal from filesystem')
529 log_delete_repository(old_repo_dict,
529 log_delete_repository(old_repo_dict,
530 deleted_by=cur_user)
530 deleted_by=cur_user)
531 except Exception:
531 except Exception:
532 log.error(traceback.format_exc())
532 log.error(traceback.format_exc())
533 raise
533 raise
534
534
535 def grant_user_permission(self, repo, user, perm):
535 def grant_user_permission(self, repo, user, perm):
536 """
536 """
537 Grant permission for user on given repository, or update existing one
537 Grant permission for user on given repository, or update existing one
538 if found
538 if found
539
539
540 :param repo: Instance of Repository, repository_id, or repository name
540 :param repo: Instance of Repository, repository_id, or repository name
541 :param user: Instance of User, user_id or username
541 :param user: Instance of User, user_id or username
542 :param perm: Instance of Permission, or permission_name
542 :param perm: Instance of Permission, or permission_name
543 """
543 """
544 user = User.guess_instance(user)
544 user = User.guess_instance(user)
545 repo = Repository.guess_instance(repo)
545 repo = Repository.guess_instance(repo)
546 permission = Permission.guess_instance(perm)
546 permission = Permission.guess_instance(perm)
547
547
548 # check if we have that permission already
548 # check if we have that permission already
549 obj = UserRepoToPerm.query() \
549 obj = UserRepoToPerm.query() \
550 .filter(UserRepoToPerm.user == user) \
550 .filter(UserRepoToPerm.user == user) \
551 .filter(UserRepoToPerm.repository == repo) \
551 .filter(UserRepoToPerm.repository == repo) \
552 .scalar()
552 .scalar()
553 if obj is None:
553 if obj is None:
554 # create new !
554 # create new !
555 obj = UserRepoToPerm()
555 obj = UserRepoToPerm()
556 Session().add(obj)
556 Session().add(obj)
557 obj.repository = repo
557 obj.repository = repo
558 obj.user = user
558 obj.user = user
559 obj.permission = permission
559 obj.permission = permission
560 log.debug('Granted perm %s to %s on %s', perm, user, repo)
560 log.debug('Granted perm %s to %s on %s', perm, user, repo)
561 return obj
561 return obj
562
562
563 def revoke_user_permission(self, repo, user):
563 def revoke_user_permission(self, repo, user):
564 """
564 """
565 Revoke permission for user on given repository
565 Revoke permission for user on given repository
566
566
567 :param repo: Instance of Repository, repository_id, or repository name
567 :param repo: Instance of Repository, repository_id, or repository name
568 :param user: Instance of User, user_id or username
568 :param user: Instance of User, user_id or username
569 """
569 """
570
570
571 user = User.guess_instance(user)
571 user = User.guess_instance(user)
572 repo = Repository.guess_instance(repo)
572 repo = Repository.guess_instance(repo)
573
573
574 obj = UserRepoToPerm.query() \
574 obj = UserRepoToPerm.query() \
575 .filter(UserRepoToPerm.repository == repo) \
575 .filter(UserRepoToPerm.repository == repo) \
576 .filter(UserRepoToPerm.user == user) \
576 .filter(UserRepoToPerm.user == user) \
577 .scalar()
577 .scalar()
578 if obj is not None:
578 if obj is not None:
579 Session().delete(obj)
579 Session().delete(obj)
580 log.debug('Revoked perm on %s on %s', repo, user)
580 log.debug('Revoked perm on %s on %s', repo, user)
581
581
582 def grant_user_group_permission(self, repo, group_name, perm):
582 def grant_user_group_permission(self, repo, group_name, perm):
583 """
583 """
584 Grant permission for user group on given repository, or update
584 Grant permission for user group on given repository, or update
585 existing one if found
585 existing one if found
586
586
587 :param repo: Instance of Repository, repository_id, or repository name
587 :param repo: Instance of Repository, repository_id, or repository name
588 :param group_name: Instance of UserGroup, users_group_id,
588 :param group_name: Instance of UserGroup, users_group_id,
589 or user group name
589 or user group name
590 :param perm: Instance of Permission, or permission_name
590 :param perm: Instance of Permission, or permission_name
591 """
591 """
592 repo = Repository.guess_instance(repo)
592 repo = Repository.guess_instance(repo)
593 group_name = UserGroup.guess_instance(group_name)
593 group_name = UserGroup.guess_instance(group_name)
594 permission = Permission.guess_instance(perm)
594 permission = Permission.guess_instance(perm)
595
595
596 # check if we have that permission already
596 # check if we have that permission already
597 obj = UserGroupRepoToPerm.query() \
597 obj = UserGroupRepoToPerm.query() \
598 .filter(UserGroupRepoToPerm.users_group == group_name) \
598 .filter(UserGroupRepoToPerm.users_group == group_name) \
599 .filter(UserGroupRepoToPerm.repository == repo) \
599 .filter(UserGroupRepoToPerm.repository == repo) \
600 .scalar()
600 .scalar()
601
601
602 if obj is None:
602 if obj is None:
603 # create new
603 # create new
604 obj = UserGroupRepoToPerm()
604 obj = UserGroupRepoToPerm()
605 Session().add(obj)
605 Session().add(obj)
606
606
607 obj.repository = repo
607 obj.repository = repo
608 obj.users_group = group_name
608 obj.users_group = group_name
609 obj.permission = permission
609 obj.permission = permission
610 log.debug('Granted perm %s to %s on %s', perm, group_name, repo)
610 log.debug('Granted perm %s to %s on %s', perm, group_name, repo)
611 return obj
611 return obj
612
612
613 def revoke_user_group_permission(self, repo, group_name):
613 def revoke_user_group_permission(self, repo, group_name):
614 """
614 """
615 Revoke permission for user group on given repository
615 Revoke permission for user group on given repository
616
616
617 :param repo: Instance of Repository, repository_id, or repository name
617 :param repo: Instance of Repository, repository_id, or repository name
618 :param group_name: Instance of UserGroup, users_group_id,
618 :param group_name: Instance of UserGroup, users_group_id,
619 or user group name
619 or user group name
620 """
620 """
621 repo = Repository.guess_instance(repo)
621 repo = Repository.guess_instance(repo)
622 group_name = UserGroup.guess_instance(group_name)
622 group_name = UserGroup.guess_instance(group_name)
623
623
624 obj = UserGroupRepoToPerm.query() \
624 obj = UserGroupRepoToPerm.query() \
625 .filter(UserGroupRepoToPerm.repository == repo) \
625 .filter(UserGroupRepoToPerm.repository == repo) \
626 .filter(UserGroupRepoToPerm.users_group == group_name) \
626 .filter(UserGroupRepoToPerm.users_group == group_name) \
627 .scalar()
627 .scalar()
628 if obj is not None:
628 if obj is not None:
629 Session().delete(obj)
629 Session().delete(obj)
630 log.debug('Revoked perm to %s on %s', repo, group_name)
630 log.debug('Revoked perm to %s on %s', repo, group_name)
631
631
632 def delete_stats(self, repo_name):
632 def delete_stats(self, repo_name):
633 """
633 """
634 removes stats for given repo
634 removes stats for given repo
635
635
636 :param repo_name:
636 :param repo_name:
637 """
637 """
638 repo = Repository.guess_instance(repo_name)
638 repo = Repository.guess_instance(repo_name)
639 try:
639 try:
640 obj = Statistics.query() \
640 obj = Statistics.query() \
641 .filter(Statistics.repository == repo).scalar()
641 .filter(Statistics.repository == repo).scalar()
642 if obj is not None:
642 if obj is not None:
643 Session().delete(obj)
643 Session().delete(obj)
644 except Exception:
644 except Exception:
645 log.error(traceback.format_exc())
645 log.error(traceback.format_exc())
646 raise
646 raise
647
647
648 def _create_filesystem_repo(self, repo_name, repo_type, repo_group,
648 def _create_filesystem_repo(self, repo_name, repo_type, repo_group,
649 clone_uri=None, repo_store_location=None):
649 clone_uri=None, repo_store_location=None):
650 """
650 """
651 Makes repository on filesystem. Operation is group aware, meaning that it will create
651 Makes repository on filesystem. Operation is group aware, meaning that it will create
652 a repository within a group, and alter the paths accordingly to the group location.
652 a repository within a group, and alter the paths accordingly to the group location.
653
653
654 Note: clone_uri is low level and not validated - it might be a file system path used for validated cloning
654 Note: clone_uri is low level and not validated - it might be a file system path used for validated cloning
655 """
655 """
656 from kallithea.lib.utils import is_valid_repo, is_valid_repo_group
656 from kallithea.lib.utils import is_valid_repo, is_valid_repo_group
657 from kallithea.model.scm import ScmModel
657 from kallithea.model.scm import ScmModel
658
658
659 if '/' in repo_name:
659 if '/' in repo_name:
660 raise ValueError('repo_name must not contain groups got `%s`' % repo_name)
660 raise ValueError('repo_name must not contain groups got `%s`' % repo_name)
661
661
662 if isinstance(repo_group, RepoGroup):
662 if isinstance(repo_group, RepoGroup):
663 new_parent_path = os.sep.join(repo_group.full_path_splitted)
663 new_parent_path = os.sep.join(repo_group.full_path_splitted)
664 else:
664 else:
665 new_parent_path = repo_group or ''
665 new_parent_path = repo_group or ''
666
666
667 if repo_store_location:
667 if repo_store_location:
668 _paths = [repo_store_location]
668 _paths = [repo_store_location]
669 else:
669 else:
670 _paths = [self.repos_path, new_parent_path, repo_name]
670 _paths = [self.repos_path, new_parent_path, repo_name]
671 # we need to make it str for mercurial
671 # we need to make it str for mercurial
672 repo_path = os.path.join(*map(lambda x: safe_str(x), _paths))
672 repo_path = os.path.join(*map(lambda x: safe_str(x), _paths))
673
673
674 # check if this path is not a repository
674 # check if this path is not a repository
675 if is_valid_repo(repo_path, self.repos_path):
675 if is_valid_repo(repo_path, self.repos_path):
676 raise Exception('This path %s is a valid repository' % repo_path)
676 raise Exception('This path %s is a valid repository' % repo_path)
677
677
678 # check if this path is a group
678 # check if this path is a group
679 if is_valid_repo_group(repo_path, self.repos_path):
679 if is_valid_repo_group(repo_path, self.repos_path):
680 raise Exception('This path %s is a valid group' % repo_path)
680 raise Exception('This path %s is a valid group' % repo_path)
681
681
682 log.info('creating repo %s in %s from url: `%s`',
682 log.info('creating repo %s in %s from url: `%s`',
683 repo_name, safe_unicode(repo_path),
683 repo_name, safe_unicode(repo_path),
684 obfuscate_url_pw(clone_uri))
684 obfuscate_url_pw(clone_uri))
685
685
686 backend = get_backend(repo_type)
686 backend = get_backend(repo_type)
687
687
688 if repo_type == 'hg':
688 if repo_type == 'hg':
689 baseui = make_ui('db', clear_session=False)
689 baseui = make_ui('db', clear_session=False)
690 # patch and reset hooks section of UI config to not run any
690 # patch and reset hooks section of UI config to not run any
691 # hooks on creating remote repo
691 # hooks on creating remote repo
692 for k, v in baseui.configitems('hooks'):
692 for k, v in baseui.configitems('hooks'):
693 baseui.setconfig('hooks', k, None)
693 baseui.setconfig('hooks', k, None)
694
694
695 repo = backend(repo_path, create=True, src_url=clone_uri, baseui=baseui)
695 repo = backend(repo_path, create=True, src_url=clone_uri, baseui=baseui)
696 elif repo_type == 'git':
696 elif repo_type == 'git':
697 repo = backend(repo_path, create=True, src_url=clone_uri, bare=True)
697 repo = backend(repo_path, create=True, src_url=clone_uri, bare=True)
698 # add kallithea hook into this repo
698 # add kallithea hook into this repo
699 ScmModel().install_git_hooks(repo=repo)
699 ScmModel().install_git_hooks(repo=repo)
700 else:
700 else:
701 raise Exception('Not supported repo_type %s expected hg/git' % repo_type)
701 raise Exception('Not supported repo_type %s expected hg/git' % repo_type)
702
702
703 log.debug('Created repo %s with %s backend',
703 log.debug('Created repo %s with %s backend',
704 safe_unicode(repo_name), safe_unicode(repo_type))
704 safe_unicode(repo_name), safe_unicode(repo_type))
705 return repo
705 return repo
706
706
707 def _rename_filesystem_repo(self, old, new):
707 def _rename_filesystem_repo(self, old, new):
708 """
708 """
709 renames repository on filesystem
709 renames repository on filesystem
710
710
711 :param old: old name
711 :param old: old name
712 :param new: new name
712 :param new: new name
713 """
713 """
714 log.info('renaming repo from %s to %s', old, new)
714 log.info('renaming repo from %s to %s', old, new)
715
715
716 old_path = safe_str(os.path.join(self.repos_path, old))
716 old_path = safe_str(os.path.join(self.repos_path, old))
717 new_path = safe_str(os.path.join(self.repos_path, new))
717 new_path = safe_str(os.path.join(self.repos_path, new))
718 if os.path.isdir(new_path):
718 if os.path.isdir(new_path):
719 raise Exception(
719 raise Exception(
720 'Was trying to rename to already existing dir %s' % new_path
720 'Was trying to rename to already existing dir %s' % new_path
721 )
721 )
722 shutil.move(old_path, new_path)
722 shutil.move(old_path, new_path)
723
723
724 def _delete_filesystem_repo(self, repo):
724 def _delete_filesystem_repo(self, repo):
725 """
725 """
726 removes repo from filesystem, the removal is actually done by
726 removes repo from filesystem, the removal is actually done by
727 renaming dir to a 'rm__*' prefix which Kallithea will skip.
727 renaming dir to a 'rm__*' prefix which Kallithea will skip.
728 It can be undeleted later by reverting the rename.
728 It can be undeleted later by reverting the rename.
729
729
730 :param repo: repo object
730 :param repo: repo object
731 """
731 """
732 rm_path = safe_str(os.path.join(self.repos_path, repo.repo_name))
732 rm_path = safe_str(os.path.join(self.repos_path, repo.repo_name))
733 log.info("Removing %s", rm_path)
733 log.info("Removing %s", rm_path)
734
734
735 _now = datetime.now()
735 _now = datetime.now()
736 _ms = str(_now.microsecond).rjust(6, '0')
736 _ms = str(_now.microsecond).rjust(6, '0')
737 _d = 'rm__%s__%s' % (_now.strftime('%Y%m%d_%H%M%S_' + _ms),
737 _d = 'rm__%s__%s' % (_now.strftime('%Y%m%d_%H%M%S_' + _ms),
738 repo.just_name)
738 repo.just_name)
739 if repo.group:
739 if repo.group:
740 args = repo.group.full_path_splitted + [_d]
740 args = repo.group.full_path_splitted + [_d]
741 _d = os.path.join(*args)
741 _d = os.path.join(*args)
742 if os.path.exists(rm_path):
742 if os.path.exists(rm_path):
743 shutil.move(rm_path, safe_str(os.path.join(self.repos_path, _d)))
743 shutil.move(rm_path, safe_str(os.path.join(self.repos_path, _d)))
744 else:
744 else:
745 log.error("Can't find repo to delete in %r", rm_path)
745 log.error("Can't find repo to delete in %r", rm_path)
@@ -1,542 +1,542 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 # This program is free software: you can redistribute it and/or modify
2 # This program is free software: you can redistribute it and/or modify
3 # it under the terms of the GNU General Public License as published by
3 # it under the terms of the GNU General Public License as published by
4 # the Free Software Foundation, either version 3 of the License, or
4 # the Free Software Foundation, either version 3 of the License, or
5 # (at your option) any later version.
5 # (at your option) any later version.
6 #
6 #
7 # This program is distributed in the hope that it will be useful,
7 # This program is distributed in the hope that it will be useful,
8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 # GNU General Public License for more details.
10 # GNU General Public License for more details.
11 #
11 #
12 # You should have received a copy of the GNU General Public License
12 # You should have received a copy of the GNU General Public License
13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
14 """
14 """
15 kallithea.model.repo_group
15 kallithea.model.repo_group
16 ~~~~~~~~~~~~~~~~~~~~~~~~~~
16 ~~~~~~~~~~~~~~~~~~~~~~~~~~
17
17
18 repo group model for Kallithea
18 repo group model for Kallithea
19
19
20 This file was forked by the Kallithea project in July 2014.
20 This file was forked by the Kallithea project in July 2014.
21 Original author and date, and relevant copyright and licensing information is below:
21 Original author and date, and relevant copyright and licensing information is below:
22 :created_on: Jan 25, 2011
22 :created_on: Jan 25, 2011
23 :author: marcink
23 :author: marcink
24 :copyright: (c) 2013 RhodeCode GmbH, and others.
24 :copyright: (c) 2013 RhodeCode GmbH, and others.
25 :license: GPLv3, see LICENSE.md for more details.
25 :license: GPLv3, see LICENSE.md for more details.
26 """
26 """
27
27
28
28
29 import os
29 import os
30 import logging
30 import logging
31 import traceback
31 import traceback
32 import shutil
32 import shutil
33 import datetime
33 import datetime
34
34
35 import kallithea.lib.utils
35 import kallithea.lib.utils2
36 from kallithea.lib.utils2 import LazyProperty
36 from kallithea.lib.utils2 import LazyProperty
37
37
38 from kallithea.model.db import RepoGroup, Session, Ui, UserRepoGroupToPerm, \
38 from kallithea.model.db import RepoGroup, Session, Ui, UserRepoGroupToPerm, \
39 User, Permission, UserGroupRepoGroupToPerm, UserGroup, Repository
39 User, Permission, UserGroupRepoGroupToPerm, UserGroup, Repository
40
40
41 log = logging.getLogger(__name__)
41 log = logging.getLogger(__name__)
42
42
43
43
44 class RepoGroupModel(object):
44 class RepoGroupModel(object):
45
45
46 @LazyProperty
46 @LazyProperty
47 def repos_path(self):
47 def repos_path(self):
48 """
48 """
49 Gets the repositories root path from database
49 Gets the repositories root path from database
50 """
50 """
51
51
52 q = Ui.get_by_key('paths', '/')
52 q = Ui.get_by_key('paths', '/')
53 return q.ui_value
53 return q.ui_value
54
54
55 def _create_default_perms(self, new_group):
55 def _create_default_perms(self, new_group):
56 # create default permission
56 # create default permission
57 default_perm = 'group.read'
57 default_perm = 'group.read'
58 def_user = User.get_default_user()
58 def_user = User.get_default_user()
59 for p in def_user.user_perms:
59 for p in def_user.user_perms:
60 if p.permission.permission_name.startswith('group.'):
60 if p.permission.permission_name.startswith('group.'):
61 default_perm = p.permission.permission_name
61 default_perm = p.permission.permission_name
62 break
62 break
63
63
64 repo_group_to_perm = UserRepoGroupToPerm()
64 repo_group_to_perm = UserRepoGroupToPerm()
65 repo_group_to_perm.permission = Permission.get_by_key(default_perm)
65 repo_group_to_perm.permission = Permission.get_by_key(default_perm)
66
66
67 repo_group_to_perm.group = new_group
67 repo_group_to_perm.group = new_group
68 repo_group_to_perm.user_id = def_user.user_id
68 repo_group_to_perm.user_id = def_user.user_id
69 Session().add(repo_group_to_perm)
69 Session().add(repo_group_to_perm)
70 return repo_group_to_perm
70 return repo_group_to_perm
71
71
72 def _create_group(self, group_name):
72 def _create_group(self, group_name):
73 """
73 """
74 makes repository group on filesystem
74 makes repository group on filesystem
75
75
76 :param repo_name:
76 :param repo_name:
77 :param parent_id:
77 :param parent_id:
78 """
78 """
79
79
80 create_path = os.path.join(self.repos_path, group_name)
80 create_path = os.path.join(self.repos_path, group_name)
81 log.debug('creating new group in %s', create_path)
81 log.debug('creating new group in %s', create_path)
82
82
83 if os.path.isdir(create_path):
83 if os.path.isdir(create_path):
84 raise Exception('That directory already exists !')
84 raise Exception('That directory already exists !')
85
85
86 os.makedirs(create_path)
86 os.makedirs(create_path)
87 log.debug('Created group in %s', create_path)
87 log.debug('Created group in %s', create_path)
88
88
89 def _rename_group(self, old, new):
89 def _rename_group(self, old, new):
90 """
90 """
91 Renames a group on filesystem
91 Renames a group on filesystem
92
92
93 :param group_name:
93 :param group_name:
94 """
94 """
95
95
96 if old == new:
96 if old == new:
97 log.debug('skipping group rename')
97 log.debug('skipping group rename')
98 return
98 return
99
99
100 log.debug('renaming repository group from %s to %s', old, new)
100 log.debug('renaming repository group from %s to %s', old, new)
101
101
102 old_path = os.path.join(self.repos_path, old)
102 old_path = os.path.join(self.repos_path, old)
103 new_path = os.path.join(self.repos_path, new)
103 new_path = os.path.join(self.repos_path, new)
104
104
105 log.debug('renaming repos paths from %s to %s', old_path, new_path)
105 log.debug('renaming repos paths from %s to %s', old_path, new_path)
106
106
107 if os.path.isdir(new_path):
107 if os.path.isdir(new_path):
108 raise Exception('Was trying to rename to already '
108 raise Exception('Was trying to rename to already '
109 'existing dir %s' % new_path)
109 'existing dir %s' % new_path)
110 shutil.move(old_path, new_path)
110 shutil.move(old_path, new_path)
111
111
112 def _delete_group(self, group, force_delete=False):
112 def _delete_group(self, group, force_delete=False):
113 """
113 """
114 Deletes a group from a filesystem
114 Deletes a group from a filesystem
115
115
116 :param group: instance of group from database
116 :param group: instance of group from database
117 :param force_delete: use shutil rmtree to remove all objects
117 :param force_delete: use shutil rmtree to remove all objects
118 """
118 """
119 paths = group.full_path.split(RepoGroup.url_sep())
119 paths = group.full_path.split(RepoGroup.url_sep())
120 paths = os.sep.join(paths)
120 paths = os.sep.join(paths)
121
121
122 rm_path = os.path.join(self.repos_path, paths)
122 rm_path = os.path.join(self.repos_path, paths)
123 log.info("Removing group %s", rm_path)
123 log.info("Removing group %s", rm_path)
124 # delete only if that path really exists
124 # delete only if that path really exists
125 if os.path.isdir(rm_path):
125 if os.path.isdir(rm_path):
126 if force_delete:
126 if force_delete:
127 shutil.rmtree(rm_path)
127 shutil.rmtree(rm_path)
128 else:
128 else:
129 # archive that group
129 # archive that group
130 _now = datetime.datetime.now()
130 _now = datetime.datetime.now()
131 _ms = str(_now.microsecond).rjust(6, '0')
131 _ms = str(_now.microsecond).rjust(6, '0')
132 _d = 'rm__%s_GROUP_%s' % (_now.strftime('%Y%m%d_%H%M%S_' + _ms),
132 _d = 'rm__%s_GROUP_%s' % (_now.strftime('%Y%m%d_%H%M%S_' + _ms),
133 group.name)
133 group.name)
134 shutil.move(rm_path, os.path.join(self.repos_path, _d))
134 shutil.move(rm_path, os.path.join(self.repos_path, _d))
135
135
136 def create(self, group_name, group_description, owner, parent=None,
136 def create(self, group_name, group_description, owner, parent=None,
137 just_db=False, copy_permissions=False):
137 just_db=False, copy_permissions=False):
138 try:
138 try:
139 if kallithea.lib.utils.repo_name_slug(group_name) != group_name:
139 if kallithea.lib.utils2.repo_name_slug(group_name) != group_name:
140 raise Exception('invalid repo group name %s' % group_name)
140 raise Exception('invalid repo group name %s' % group_name)
141
141
142 owner = User.guess_instance(owner)
142 owner = User.guess_instance(owner)
143 parent_group = RepoGroup.guess_instance(parent)
143 parent_group = RepoGroup.guess_instance(parent)
144 new_repo_group = RepoGroup()
144 new_repo_group = RepoGroup()
145 new_repo_group.owner = owner
145 new_repo_group.owner = owner
146 new_repo_group.group_description = group_description or group_name
146 new_repo_group.group_description = group_description or group_name
147 new_repo_group.parent_group = parent_group
147 new_repo_group.parent_group = parent_group
148 new_repo_group.group_name = new_repo_group.get_new_name(group_name)
148 new_repo_group.group_name = new_repo_group.get_new_name(group_name)
149
149
150 Session().add(new_repo_group)
150 Session().add(new_repo_group)
151
151
152 # create an ADMIN permission for owner except if we're super admin,
152 # create an ADMIN permission for owner except if we're super admin,
153 # later owner should go into the owner field of groups
153 # later owner should go into the owner field of groups
154 if not owner.is_admin:
154 if not owner.is_admin:
155 self.grant_user_permission(repo_group=new_repo_group,
155 self.grant_user_permission(repo_group=new_repo_group,
156 user=owner, perm='group.admin')
156 user=owner, perm='group.admin')
157
157
158 if parent_group and copy_permissions:
158 if parent_group and copy_permissions:
159 # copy permissions from parent
159 # copy permissions from parent
160 user_perms = UserRepoGroupToPerm.query() \
160 user_perms = UserRepoGroupToPerm.query() \
161 .filter(UserRepoGroupToPerm.group == parent_group).all()
161 .filter(UserRepoGroupToPerm.group == parent_group).all()
162
162
163 group_perms = UserGroupRepoGroupToPerm.query() \
163 group_perms = UserGroupRepoGroupToPerm.query() \
164 .filter(UserGroupRepoGroupToPerm.group == parent_group).all()
164 .filter(UserGroupRepoGroupToPerm.group == parent_group).all()
165
165
166 for perm in user_perms:
166 for perm in user_perms:
167 # don't copy over the permission for user who is creating
167 # don't copy over the permission for user who is creating
168 # this group, if he is not super admin he get's admin
168 # this group, if he is not super admin he get's admin
169 # permission set above
169 # permission set above
170 if perm.user != owner or owner.is_admin:
170 if perm.user != owner or owner.is_admin:
171 UserRepoGroupToPerm.create(perm.user, new_repo_group, perm.permission)
171 UserRepoGroupToPerm.create(perm.user, new_repo_group, perm.permission)
172
172
173 for perm in group_perms:
173 for perm in group_perms:
174 UserGroupRepoGroupToPerm.create(perm.users_group, new_repo_group, perm.permission)
174 UserGroupRepoGroupToPerm.create(perm.users_group, new_repo_group, perm.permission)
175 else:
175 else:
176 self._create_default_perms(new_repo_group)
176 self._create_default_perms(new_repo_group)
177
177
178 if not just_db:
178 if not just_db:
179 # we need to flush here, in order to check if database won't
179 # we need to flush here, in order to check if database won't
180 # throw any exceptions, create filesystem dirs at the very end
180 # throw any exceptions, create filesystem dirs at the very end
181 Session().flush()
181 Session().flush()
182 self._create_group(new_repo_group.group_name)
182 self._create_group(new_repo_group.group_name)
183
183
184 return new_repo_group
184 return new_repo_group
185 except Exception:
185 except Exception:
186 log.error(traceback.format_exc())
186 log.error(traceback.format_exc())
187 raise
187 raise
188
188
189 def _update_permissions(self, repo_group, perms_new=None,
189 def _update_permissions(self, repo_group, perms_new=None,
190 perms_updates=None, recursive=None,
190 perms_updates=None, recursive=None,
191 check_perms=True):
191 check_perms=True):
192 from kallithea.model.repo import RepoModel
192 from kallithea.model.repo import RepoModel
193 from kallithea.lib.auth import HasUserGroupPermissionLevel
193 from kallithea.lib.auth import HasUserGroupPermissionLevel
194
194
195 if not perms_new:
195 if not perms_new:
196 perms_new = []
196 perms_new = []
197 if not perms_updates:
197 if not perms_updates:
198 perms_updates = []
198 perms_updates = []
199
199
200 def _set_perm_user(obj, user, perm):
200 def _set_perm_user(obj, user, perm):
201 if isinstance(obj, RepoGroup):
201 if isinstance(obj, RepoGroup):
202 self.grant_user_permission(repo_group=obj, user=user, perm=perm)
202 self.grant_user_permission(repo_group=obj, user=user, perm=perm)
203 elif isinstance(obj, Repository):
203 elif isinstance(obj, Repository):
204 user = User.guess_instance(user)
204 user = User.guess_instance(user)
205
205
206 # private repos will not allow to change the default permissions
206 # private repos will not allow to change the default permissions
207 # using recursive mode
207 # using recursive mode
208 if obj.private and user.is_default_user:
208 if obj.private and user.is_default_user:
209 return
209 return
210
210
211 # we set group permission but we have to switch to repo
211 # we set group permission but we have to switch to repo
212 # permission
212 # permission
213 perm = perm.replace('group.', 'repository.')
213 perm = perm.replace('group.', 'repository.')
214 RepoModel().grant_user_permission(
214 RepoModel().grant_user_permission(
215 repo=obj, user=user, perm=perm
215 repo=obj, user=user, perm=perm
216 )
216 )
217
217
218 def _set_perm_group(obj, users_group, perm):
218 def _set_perm_group(obj, users_group, perm):
219 if isinstance(obj, RepoGroup):
219 if isinstance(obj, RepoGroup):
220 self.grant_user_group_permission(repo_group=obj,
220 self.grant_user_group_permission(repo_group=obj,
221 group_name=users_group,
221 group_name=users_group,
222 perm=perm)
222 perm=perm)
223 elif isinstance(obj, Repository):
223 elif isinstance(obj, Repository):
224 # we set group permission but we have to switch to repo
224 # we set group permission but we have to switch to repo
225 # permission
225 # permission
226 perm = perm.replace('group.', 'repository.')
226 perm = perm.replace('group.', 'repository.')
227 RepoModel().grant_user_group_permission(
227 RepoModel().grant_user_group_permission(
228 repo=obj, group_name=users_group, perm=perm
228 repo=obj, group_name=users_group, perm=perm
229 )
229 )
230
230
231 # start updates
231 # start updates
232 updates = []
232 updates = []
233 log.debug('Now updating permissions for %s in recursive mode:%s',
233 log.debug('Now updating permissions for %s in recursive mode:%s',
234 repo_group, recursive)
234 repo_group, recursive)
235
235
236 for obj in repo_group.recursive_groups_and_repos():
236 for obj in repo_group.recursive_groups_and_repos():
237 # iterated obj is an instance of a repos group or repository in
237 # iterated obj is an instance of a repos group or repository in
238 # that group, recursive option can be: none, repos, groups, all
238 # that group, recursive option can be: none, repos, groups, all
239 if recursive == 'all':
239 if recursive == 'all':
240 pass
240 pass
241 elif recursive == 'repos':
241 elif recursive == 'repos':
242 # skip groups, other than this one
242 # skip groups, other than this one
243 if isinstance(obj, RepoGroup) and not obj == repo_group:
243 if isinstance(obj, RepoGroup) and not obj == repo_group:
244 continue
244 continue
245 elif recursive == 'groups':
245 elif recursive == 'groups':
246 # skip repos
246 # skip repos
247 if isinstance(obj, Repository):
247 if isinstance(obj, Repository):
248 continue
248 continue
249 else: # recursive == 'none': # DEFAULT don't apply to iterated objects
249 else: # recursive == 'none': # DEFAULT don't apply to iterated objects
250 obj = repo_group
250 obj = repo_group
251 # also we do a break at the end of this loop.
251 # also we do a break at the end of this loop.
252
252
253 # update permissions
253 # update permissions
254 for member, perm, member_type in perms_updates:
254 for member, perm, member_type in perms_updates:
255 ## set for user
255 ## set for user
256 if member_type == 'user':
256 if member_type == 'user':
257 # this updates also current one if found
257 # this updates also current one if found
258 _set_perm_user(obj, user=member, perm=perm)
258 _set_perm_user(obj, user=member, perm=perm)
259 ## set for user group
259 ## set for user group
260 else:
260 else:
261 # check if we have permissions to alter this usergroup's access
261 # check if we have permissions to alter this usergroup's access
262 if not check_perms or HasUserGroupPermissionLevel('read')(member):
262 if not check_perms or HasUserGroupPermissionLevel('read')(member):
263 _set_perm_group(obj, users_group=member, perm=perm)
263 _set_perm_group(obj, users_group=member, perm=perm)
264 # set new permissions
264 # set new permissions
265 for member, perm, member_type in perms_new:
265 for member, perm, member_type in perms_new:
266 if member_type == 'user':
266 if member_type == 'user':
267 _set_perm_user(obj, user=member, perm=perm)
267 _set_perm_user(obj, user=member, perm=perm)
268 else:
268 else:
269 # check if we have permissions to alter this usergroup's access
269 # check if we have permissions to alter this usergroup's access
270 if not check_perms or HasUserGroupPermissionLevel('read')(member):
270 if not check_perms or HasUserGroupPermissionLevel('read')(member):
271 _set_perm_group(obj, users_group=member, perm=perm)
271 _set_perm_group(obj, users_group=member, perm=perm)
272 updates.append(obj)
272 updates.append(obj)
273 # if it's not recursive call for all,repos,groups
273 # if it's not recursive call for all,repos,groups
274 # break the loop and don't proceed with other changes
274 # break the loop and don't proceed with other changes
275 if recursive not in ['all', 'repos', 'groups']:
275 if recursive not in ['all', 'repos', 'groups']:
276 break
276 break
277
277
278 return updates
278 return updates
279
279
280 def update(self, repo_group, kwargs):
280 def update(self, repo_group, kwargs):
281 try:
281 try:
282 repo_group = RepoGroup.guess_instance(repo_group)
282 repo_group = RepoGroup.guess_instance(repo_group)
283 old_path = repo_group.full_path
283 old_path = repo_group.full_path
284
284
285 # change properties
285 # change properties
286 if 'group_description' in kwargs:
286 if 'group_description' in kwargs:
287 repo_group.group_description = kwargs['group_description']
287 repo_group.group_description = kwargs['group_description']
288 if 'parent_group_id' in kwargs:
288 if 'parent_group_id' in kwargs:
289 repo_group.parent_group_id = kwargs['parent_group_id']
289 repo_group.parent_group_id = kwargs['parent_group_id']
290 if 'enable_locking' in kwargs:
290 if 'enable_locking' in kwargs:
291 repo_group.enable_locking = kwargs['enable_locking']
291 repo_group.enable_locking = kwargs['enable_locking']
292
292
293 if 'parent_group_id' in kwargs:
293 if 'parent_group_id' in kwargs:
294 assert kwargs['parent_group_id'] != u'-1', kwargs # RepoGroupForm should have converted to None
294 assert kwargs['parent_group_id'] != u'-1', kwargs # RepoGroupForm should have converted to None
295 repo_group.parent_group = RepoGroup.get(kwargs['parent_group_id'])
295 repo_group.parent_group = RepoGroup.get(kwargs['parent_group_id'])
296 if 'group_name' in kwargs:
296 if 'group_name' in kwargs:
297 group_name = kwargs['group_name']
297 group_name = kwargs['group_name']
298 if kallithea.lib.utils.repo_name_slug(group_name) != group_name:
298 if kallithea.lib.utils2.repo_name_slug(group_name) != group_name:
299 raise Exception('invalid repo group name %s' % group_name)
299 raise Exception('invalid repo group name %s' % group_name)
300 repo_group.group_name = repo_group.get_new_name(group_name)
300 repo_group.group_name = repo_group.get_new_name(group_name)
301 new_path = repo_group.full_path
301 new_path = repo_group.full_path
302 Session().add(repo_group)
302 Session().add(repo_group)
303
303
304 # iterate over all members of this groups and do fixes
304 # iterate over all members of this groups and do fixes
305 # set locking if given
305 # set locking if given
306 # if obj is a repoGroup also fix the name of the group according
306 # if obj is a repoGroup also fix the name of the group according
307 # to the parent
307 # to the parent
308 # if obj is a Repo fix it's name
308 # if obj is a Repo fix it's name
309 # this can be potentially heavy operation
309 # this can be potentially heavy operation
310 for obj in repo_group.recursive_groups_and_repos():
310 for obj in repo_group.recursive_groups_and_repos():
311 # set the value from it's parent
311 # set the value from it's parent
312 obj.enable_locking = repo_group.enable_locking
312 obj.enable_locking = repo_group.enable_locking
313 if isinstance(obj, RepoGroup):
313 if isinstance(obj, RepoGroup):
314 new_name = obj.get_new_name(obj.name)
314 new_name = obj.get_new_name(obj.name)
315 log.debug('Fixing group %s to new name %s' \
315 log.debug('Fixing group %s to new name %s' \
316 % (obj.group_name, new_name))
316 % (obj.group_name, new_name))
317 obj.group_name = new_name
317 obj.group_name = new_name
318 elif isinstance(obj, Repository):
318 elif isinstance(obj, Repository):
319 # we need to get all repositories from this new group and
319 # we need to get all repositories from this new group and
320 # rename them accordingly to new group path
320 # rename them accordingly to new group path
321 new_name = obj.get_new_name(obj.just_name)
321 new_name = obj.get_new_name(obj.just_name)
322 log.debug('Fixing repo %s to new name %s' \
322 log.debug('Fixing repo %s to new name %s' \
323 % (obj.repo_name, new_name))
323 % (obj.repo_name, new_name))
324 obj.repo_name = new_name
324 obj.repo_name = new_name
325
325
326 self._rename_group(old_path, new_path)
326 self._rename_group(old_path, new_path)
327
327
328 return repo_group
328 return repo_group
329 except Exception:
329 except Exception:
330 log.error(traceback.format_exc())
330 log.error(traceback.format_exc())
331 raise
331 raise
332
332
333 def delete(self, repo_group, force_delete=False):
333 def delete(self, repo_group, force_delete=False):
334 repo_group = RepoGroup.guess_instance(repo_group)
334 repo_group = RepoGroup.guess_instance(repo_group)
335 try:
335 try:
336 Session().delete(repo_group)
336 Session().delete(repo_group)
337 self._delete_group(repo_group, force_delete)
337 self._delete_group(repo_group, force_delete)
338 except Exception:
338 except Exception:
339 log.error('Error removing repo_group %s', repo_group)
339 log.error('Error removing repo_group %s', repo_group)
340 raise
340 raise
341
341
342 def add_permission(self, repo_group, obj, obj_type, perm, recursive):
342 def add_permission(self, repo_group, obj, obj_type, perm, recursive):
343 from kallithea.model.repo import RepoModel
343 from kallithea.model.repo import RepoModel
344 repo_group = RepoGroup.guess_instance(repo_group)
344 repo_group = RepoGroup.guess_instance(repo_group)
345 perm = Permission.guess_instance(perm)
345 perm = Permission.guess_instance(perm)
346
346
347 for el in repo_group.recursive_groups_and_repos():
347 for el in repo_group.recursive_groups_and_repos():
348 # iterated obj is an instance of a repos group or repository in
348 # iterated obj is an instance of a repos group or repository in
349 # that group, recursive option can be: none, repos, groups, all
349 # that group, recursive option can be: none, repos, groups, all
350 if recursive == 'all':
350 if recursive == 'all':
351 pass
351 pass
352 elif recursive == 'repos':
352 elif recursive == 'repos':
353 # skip groups, other than this one
353 # skip groups, other than this one
354 if isinstance(el, RepoGroup) and not el == repo_group:
354 if isinstance(el, RepoGroup) and not el == repo_group:
355 continue
355 continue
356 elif recursive == 'groups':
356 elif recursive == 'groups':
357 # skip repos
357 # skip repos
358 if isinstance(el, Repository):
358 if isinstance(el, Repository):
359 continue
359 continue
360 else: # recursive == 'none': # DEFAULT don't apply to iterated objects
360 else: # recursive == 'none': # DEFAULT don't apply to iterated objects
361 el = repo_group
361 el = repo_group
362 # also we do a break at the end of this loop.
362 # also we do a break at the end of this loop.
363
363
364 if isinstance(el, RepoGroup):
364 if isinstance(el, RepoGroup):
365 if obj_type == 'user':
365 if obj_type == 'user':
366 RepoGroupModel().grant_user_permission(el, user=obj, perm=perm)
366 RepoGroupModel().grant_user_permission(el, user=obj, perm=perm)
367 elif obj_type == 'user_group':
367 elif obj_type == 'user_group':
368 RepoGroupModel().grant_user_group_permission(el, group_name=obj, perm=perm)
368 RepoGroupModel().grant_user_group_permission(el, group_name=obj, perm=perm)
369 else:
369 else:
370 raise Exception('undefined object type %s' % obj_type)
370 raise Exception('undefined object type %s' % obj_type)
371 elif isinstance(el, Repository):
371 elif isinstance(el, Repository):
372 # for repos we need to hotfix the name of permission
372 # for repos we need to hotfix the name of permission
373 _perm = perm.permission_name.replace('group.', 'repository.')
373 _perm = perm.permission_name.replace('group.', 'repository.')
374 if obj_type == 'user':
374 if obj_type == 'user':
375 RepoModel().grant_user_permission(el, user=obj, perm=_perm)
375 RepoModel().grant_user_permission(el, user=obj, perm=_perm)
376 elif obj_type == 'user_group':
376 elif obj_type == 'user_group':
377 RepoModel().grant_user_group_permission(el, group_name=obj, perm=_perm)
377 RepoModel().grant_user_group_permission(el, group_name=obj, perm=_perm)
378 else:
378 else:
379 raise Exception('undefined object type %s' % obj_type)
379 raise Exception('undefined object type %s' % obj_type)
380 else:
380 else:
381 raise Exception('el should be instance of Repository or '
381 raise Exception('el should be instance of Repository or '
382 'RepositoryGroup got %s instead' % type(el))
382 'RepositoryGroup got %s instead' % type(el))
383
383
384 # if it's not recursive call for all,repos,groups
384 # if it's not recursive call for all,repos,groups
385 # break the loop and don't proceed with other changes
385 # break the loop and don't proceed with other changes
386 if recursive not in ['all', 'repos', 'groups']:
386 if recursive not in ['all', 'repos', 'groups']:
387 break
387 break
388
388
389 def delete_permission(self, repo_group, obj, obj_type, recursive):
389 def delete_permission(self, repo_group, obj, obj_type, recursive):
390 """
390 """
391 Revokes permission for repo_group for given obj(user or users_group),
391 Revokes permission for repo_group for given obj(user or users_group),
392 obj_type can be user or user group
392 obj_type can be user or user group
393
393
394 :param repo_group:
394 :param repo_group:
395 :param obj: user or user group id
395 :param obj: user or user group id
396 :param obj_type: user or user group type
396 :param obj_type: user or user group type
397 :param recursive: recurse to all children of group
397 :param recursive: recurse to all children of group
398 """
398 """
399 from kallithea.model.repo import RepoModel
399 from kallithea.model.repo import RepoModel
400 repo_group = RepoGroup.guess_instance(repo_group)
400 repo_group = RepoGroup.guess_instance(repo_group)
401
401
402 for el in repo_group.recursive_groups_and_repos():
402 for el in repo_group.recursive_groups_and_repos():
403 # iterated obj is an instance of a repos group or repository in
403 # iterated obj is an instance of a repos group or repository in
404 # that group, recursive option can be: none, repos, groups, all
404 # that group, recursive option can be: none, repos, groups, all
405 if recursive == 'all':
405 if recursive == 'all':
406 pass
406 pass
407 elif recursive == 'repos':
407 elif recursive == 'repos':
408 # skip groups, other than this one
408 # skip groups, other than this one
409 if isinstance(el, RepoGroup) and not el == repo_group:
409 if isinstance(el, RepoGroup) and not el == repo_group:
410 continue
410 continue
411 elif recursive == 'groups':
411 elif recursive == 'groups':
412 # skip repos
412 # skip repos
413 if isinstance(el, Repository):
413 if isinstance(el, Repository):
414 continue
414 continue
415 else: # recursive == 'none': # DEFAULT don't apply to iterated objects
415 else: # recursive == 'none': # DEFAULT don't apply to iterated objects
416 el = repo_group
416 el = repo_group
417 # also we do a break at the end of this loop.
417 # also we do a break at the end of this loop.
418
418
419 if isinstance(el, RepoGroup):
419 if isinstance(el, RepoGroup):
420 if obj_type == 'user':
420 if obj_type == 'user':
421 RepoGroupModel().revoke_user_permission(el, user=obj)
421 RepoGroupModel().revoke_user_permission(el, user=obj)
422 elif obj_type == 'user_group':
422 elif obj_type == 'user_group':
423 RepoGroupModel().revoke_user_group_permission(el, group_name=obj)
423 RepoGroupModel().revoke_user_group_permission(el, group_name=obj)
424 else:
424 else:
425 raise Exception('undefined object type %s' % obj_type)
425 raise Exception('undefined object type %s' % obj_type)
426 elif isinstance(el, Repository):
426 elif isinstance(el, Repository):
427 if obj_type == 'user':
427 if obj_type == 'user':
428 RepoModel().revoke_user_permission(el, user=obj)
428 RepoModel().revoke_user_permission(el, user=obj)
429 elif obj_type == 'user_group':
429 elif obj_type == 'user_group':
430 RepoModel().revoke_user_group_permission(el, group_name=obj)
430 RepoModel().revoke_user_group_permission(el, group_name=obj)
431 else:
431 else:
432 raise Exception('undefined object type %s' % obj_type)
432 raise Exception('undefined object type %s' % obj_type)
433 else:
433 else:
434 raise Exception('el should be instance of Repository or '
434 raise Exception('el should be instance of Repository or '
435 'RepositoryGroup got %s instead' % type(el))
435 'RepositoryGroup got %s instead' % type(el))
436
436
437 # if it's not recursive call for all,repos,groups
437 # if it's not recursive call for all,repos,groups
438 # break the loop and don't proceed with other changes
438 # break the loop and don't proceed with other changes
439 if recursive not in ['all', 'repos', 'groups']:
439 if recursive not in ['all', 'repos', 'groups']:
440 break
440 break
441
441
442 def grant_user_permission(self, repo_group, user, perm):
442 def grant_user_permission(self, repo_group, user, perm):
443 """
443 """
444 Grant permission for user on given repository group, or update
444 Grant permission for user on given repository group, or update
445 existing one if found
445 existing one if found
446
446
447 :param repo_group: Instance of RepoGroup, repositories_group_id,
447 :param repo_group: Instance of RepoGroup, repositories_group_id,
448 or repositories_group name
448 or repositories_group name
449 :param user: Instance of User, user_id or username
449 :param user: Instance of User, user_id or username
450 :param perm: Instance of Permission, or permission_name
450 :param perm: Instance of Permission, or permission_name
451 """
451 """
452
452
453 repo_group = RepoGroup.guess_instance(repo_group)
453 repo_group = RepoGroup.guess_instance(repo_group)
454 user = User.guess_instance(user)
454 user = User.guess_instance(user)
455 permission = Permission.guess_instance(perm)
455 permission = Permission.guess_instance(perm)
456
456
457 # check if we have that permission already
457 # check if we have that permission already
458 obj = UserRepoGroupToPerm.query() \
458 obj = UserRepoGroupToPerm.query() \
459 .filter(UserRepoGroupToPerm.user == user) \
459 .filter(UserRepoGroupToPerm.user == user) \
460 .filter(UserRepoGroupToPerm.group == repo_group) \
460 .filter(UserRepoGroupToPerm.group == repo_group) \
461 .scalar()
461 .scalar()
462 if obj is None:
462 if obj is None:
463 # create new !
463 # create new !
464 obj = UserRepoGroupToPerm()
464 obj = UserRepoGroupToPerm()
465 Session().add(obj)
465 Session().add(obj)
466 obj.group = repo_group
466 obj.group = repo_group
467 obj.user = user
467 obj.user = user
468 obj.permission = permission
468 obj.permission = permission
469 log.debug('Granted perm %s to %s on %s', perm, user, repo_group)
469 log.debug('Granted perm %s to %s on %s', perm, user, repo_group)
470 return obj
470 return obj
471
471
472 def revoke_user_permission(self, repo_group, user):
472 def revoke_user_permission(self, repo_group, user):
473 """
473 """
474 Revoke permission for user on given repository group
474 Revoke permission for user on given repository group
475
475
476 :param repo_group: Instance of RepoGroup, repositories_group_id,
476 :param repo_group: Instance of RepoGroup, repositories_group_id,
477 or repositories_group name
477 or repositories_group name
478 :param user: Instance of User, user_id or username
478 :param user: Instance of User, user_id or username
479 """
479 """
480
480
481 repo_group = RepoGroup.guess_instance(repo_group)
481 repo_group = RepoGroup.guess_instance(repo_group)
482 user = User.guess_instance(user)
482 user = User.guess_instance(user)
483
483
484 obj = UserRepoGroupToPerm.query() \
484 obj = UserRepoGroupToPerm.query() \
485 .filter(UserRepoGroupToPerm.user == user) \
485 .filter(UserRepoGroupToPerm.user == user) \
486 .filter(UserRepoGroupToPerm.group == repo_group) \
486 .filter(UserRepoGroupToPerm.group == repo_group) \
487 .scalar()
487 .scalar()
488 if obj is not None:
488 if obj is not None:
489 Session().delete(obj)
489 Session().delete(obj)
490 log.debug('Revoked perm on %s on %s', repo_group, user)
490 log.debug('Revoked perm on %s on %s', repo_group, user)
491
491
492 def grant_user_group_permission(self, repo_group, group_name, perm):
492 def grant_user_group_permission(self, repo_group, group_name, perm):
493 """
493 """
494 Grant permission for user group on given repository group, or update
494 Grant permission for user group on given repository group, or update
495 existing one if found
495 existing one if found
496
496
497 :param repo_group: Instance of RepoGroup, repositories_group_id,
497 :param repo_group: Instance of RepoGroup, repositories_group_id,
498 or repositories_group name
498 or repositories_group name
499 :param group_name: Instance of UserGroup, users_group_id,
499 :param group_name: Instance of UserGroup, users_group_id,
500 or user group name
500 or user group name
501 :param perm: Instance of Permission, or permission_name
501 :param perm: Instance of Permission, or permission_name
502 """
502 """
503 repo_group = RepoGroup.guess_instance(repo_group)
503 repo_group = RepoGroup.guess_instance(repo_group)
504 group_name = UserGroup.guess_instance(group_name)
504 group_name = UserGroup.guess_instance(group_name)
505 permission = Permission.guess_instance(perm)
505 permission = Permission.guess_instance(perm)
506
506
507 # check if we have that permission already
507 # check if we have that permission already
508 obj = UserGroupRepoGroupToPerm.query() \
508 obj = UserGroupRepoGroupToPerm.query() \
509 .filter(UserGroupRepoGroupToPerm.group == repo_group) \
509 .filter(UserGroupRepoGroupToPerm.group == repo_group) \
510 .filter(UserGroupRepoGroupToPerm.users_group == group_name) \
510 .filter(UserGroupRepoGroupToPerm.users_group == group_name) \
511 .scalar()
511 .scalar()
512
512
513 if obj is None:
513 if obj is None:
514 # create new
514 # create new
515 obj = UserGroupRepoGroupToPerm()
515 obj = UserGroupRepoGroupToPerm()
516 Session().add(obj)
516 Session().add(obj)
517
517
518 obj.group = repo_group
518 obj.group = repo_group
519 obj.users_group = group_name
519 obj.users_group = group_name
520 obj.permission = permission
520 obj.permission = permission
521 log.debug('Granted perm %s to %s on %s', perm, group_name, repo_group)
521 log.debug('Granted perm %s to %s on %s', perm, group_name, repo_group)
522 return obj
522 return obj
523
523
524 def revoke_user_group_permission(self, repo_group, group_name):
524 def revoke_user_group_permission(self, repo_group, group_name):
525 """
525 """
526 Revoke permission for user group on given repository group
526 Revoke permission for user group on given repository group
527
527
528 :param repo_group: Instance of RepoGroup, repositories_group_id,
528 :param repo_group: Instance of RepoGroup, repositories_group_id,
529 or repositories_group name
529 or repositories_group name
530 :param group_name: Instance of UserGroup, users_group_id,
530 :param group_name: Instance of UserGroup, users_group_id,
531 or user group name
531 or user group name
532 """
532 """
533 repo_group = RepoGroup.guess_instance(repo_group)
533 repo_group = RepoGroup.guess_instance(repo_group)
534 group_name = UserGroup.guess_instance(group_name)
534 group_name = UserGroup.guess_instance(group_name)
535
535
536 obj = UserGroupRepoGroupToPerm.query() \
536 obj = UserGroupRepoGroupToPerm.query() \
537 .filter(UserGroupRepoGroupToPerm.group == repo_group) \
537 .filter(UserGroupRepoGroupToPerm.group == repo_group) \
538 .filter(UserGroupRepoGroupToPerm.users_group == group_name) \
538 .filter(UserGroupRepoGroupToPerm.users_group == group_name) \
539 .scalar()
539 .scalar()
540 if obj is not None:
540 if obj is not None:
541 Session().delete(obj)
541 Session().delete(obj)
542 log.debug('Revoked perm to %s on %s', repo_group, group_name)
542 log.debug('Revoked perm to %s on %s', repo_group, group_name)
@@ -1,819 +1,819 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 # This program is free software: you can redistribute it and/or modify
2 # This program is free software: you can redistribute it and/or modify
3 # it under the terms of the GNU General Public License as published by
3 # it under the terms of the GNU General Public License as published by
4 # the Free Software Foundation, either version 3 of the License, or
4 # the Free Software Foundation, either version 3 of the License, or
5 # (at your option) any later version.
5 # (at your option) any later version.
6 #
6 #
7 # This program is distributed in the hope that it will be useful,
7 # This program is distributed in the hope that it will be useful,
8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 # GNU General Public License for more details.
10 # GNU General Public License for more details.
11 #
11 #
12 # You should have received a copy of the GNU General Public License
12 # You should have received a copy of the GNU General Public License
13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
14 """
14 """
15 Set of generic validators
15 Set of generic validators
16 """
16 """
17
17
18 import os
18 import os
19 import re
19 import re
20 import formencode
20 import formencode
21 import logging
21 import logging
22 from collections import defaultdict
22 from collections import defaultdict
23 from tg.i18n import ugettext as _
23 from tg.i18n import ugettext as _
24 from sqlalchemy import func
24 from sqlalchemy import func
25 from webhelpers.pylonslib.secure_form import authentication_token
25 from webhelpers.pylonslib.secure_form import authentication_token
26 import sqlalchemy
26 import sqlalchemy
27
27
28 from formencode.validators import (
28 from formencode.validators import (
29 UnicodeString, OneOf, Int, Number, Regex, Email, Bool, StringBoolean, Set,
29 UnicodeString, OneOf, Int, Number, Regex, Email, Bool, StringBoolean, Set,
30 NotEmpty, IPAddress, CIDR, String, FancyValidator
30 NotEmpty, IPAddress, CIDR, String, FancyValidator
31 )
31 )
32 from kallithea.lib.compat import OrderedSet
32 from kallithea.lib.compat import OrderedSet
33 from kallithea.lib import ipaddr
33 from kallithea.lib import ipaddr
34 from kallithea.lib.utils import repo_name_slug, is_valid_repo_uri
34 from kallithea.lib.utils import is_valid_repo_uri
35 from kallithea.lib.utils2 import str2bool, aslist
35 from kallithea.lib.utils2 import str2bool, aslist, repo_name_slug
36 from kallithea.model.db import RepoGroup, Repository, UserGroup, User
36 from kallithea.model.db import RepoGroup, Repository, UserGroup, User
37 from kallithea.lib.exceptions import LdapImportError
37 from kallithea.lib.exceptions import LdapImportError
38 from kallithea.config.routing import ADMIN_PREFIX
38 from kallithea.config.routing import ADMIN_PREFIX
39 from kallithea.lib.auth import HasRepoGroupPermissionLevel, HasPermissionAny
39 from kallithea.lib.auth import HasRepoGroupPermissionLevel, HasPermissionAny
40
40
41 # silence warnings and pylint
41 # silence warnings and pylint
42 UnicodeString, OneOf, Int, Number, Regex, Email, Bool, StringBoolean, Set, \
42 UnicodeString, OneOf, Int, Number, Regex, Email, Bool, StringBoolean, Set, \
43 NotEmpty, IPAddress, CIDR, String, FancyValidator
43 NotEmpty, IPAddress, CIDR, String, FancyValidator
44
44
45 log = logging.getLogger(__name__)
45 log = logging.getLogger(__name__)
46
46
47
47
48 def UniqueListFromString():
48 def UniqueListFromString():
49 class _UniqueListFromString(formencode.FancyValidator):
49 class _UniqueListFromString(formencode.FancyValidator):
50 """
50 """
51 Split value on ',' and make unique while preserving order
51 Split value on ',' and make unique while preserving order
52 """
52 """
53 messages = dict(
53 messages = dict(
54 empty=_('Value cannot be an empty list'),
54 empty=_('Value cannot be an empty list'),
55 missing_value=_('Value cannot be an empty list'),
55 missing_value=_('Value cannot be an empty list'),
56 )
56 )
57
57
58 def _to_python(self, value, state):
58 def _to_python(self, value, state):
59 value = aslist(value, ',')
59 value = aslist(value, ',')
60 seen = set()
60 seen = set()
61 return [c for c in value if not (c in seen or seen.add(c))]
61 return [c for c in value if not (c in seen or seen.add(c))]
62
62
63 def empty_value(self, value):
63 def empty_value(self, value):
64 return []
64 return []
65
65
66 return _UniqueListFromString
66 return _UniqueListFromString
67
67
68
68
69 def ValidUsername(edit=False, old_data=None):
69 def ValidUsername(edit=False, old_data=None):
70 old_data = old_data or {}
70 old_data = old_data or {}
71
71
72 class _validator(formencode.validators.FancyValidator):
72 class _validator(formencode.validators.FancyValidator):
73 messages = {
73 messages = {
74 'username_exists': _('Username "%(username)s" already exists'),
74 'username_exists': _('Username "%(username)s" already exists'),
75 'system_invalid_username':
75 'system_invalid_username':
76 _('Username "%(username)s" cannot be used'),
76 _('Username "%(username)s" cannot be used'),
77 'invalid_username':
77 'invalid_username':
78 _('Username may only contain alphanumeric characters '
78 _('Username may only contain alphanumeric characters '
79 'underscores, periods or dashes and must begin with an '
79 'underscores, periods or dashes and must begin with an '
80 'alphanumeric character or underscore')
80 'alphanumeric character or underscore')
81 }
81 }
82
82
83 def validate_python(self, value, state):
83 def validate_python(self, value, state):
84 if value in ['default', 'new_user']:
84 if value in ['default', 'new_user']:
85 msg = self.message('system_invalid_username', state, username=value)
85 msg = self.message('system_invalid_username', state, username=value)
86 raise formencode.Invalid(msg, value, state)
86 raise formencode.Invalid(msg, value, state)
87 # check if user is unique
87 # check if user is unique
88 old_un = None
88 old_un = None
89 if edit:
89 if edit:
90 old_un = User.get(old_data.get('user_id')).username
90 old_un = User.get(old_data.get('user_id')).username
91
91
92 if old_un != value or not edit:
92 if old_un != value or not edit:
93 if User.get_by_username(value, case_insensitive=True):
93 if User.get_by_username(value, case_insensitive=True):
94 msg = self.message('username_exists', state, username=value)
94 msg = self.message('username_exists', state, username=value)
95 raise formencode.Invalid(msg, value, state)
95 raise formencode.Invalid(msg, value, state)
96
96
97 if re.match(r'^[a-zA-Z0-9\_]{1}[a-zA-Z0-9\-\_\.]*$', value) is None:
97 if re.match(r'^[a-zA-Z0-9\_]{1}[a-zA-Z0-9\-\_\.]*$', value) is None:
98 msg = self.message('invalid_username', state)
98 msg = self.message('invalid_username', state)
99 raise formencode.Invalid(msg, value, state)
99 raise formencode.Invalid(msg, value, state)
100 return _validator
100 return _validator
101
101
102
102
103 def ValidRegex(msg=None):
103 def ValidRegex(msg=None):
104 class _validator(formencode.validators.Regex):
104 class _validator(formencode.validators.Regex):
105 messages = dict(invalid=msg or _('The input is not valid'))
105 messages = dict(invalid=msg or _('The input is not valid'))
106 return _validator
106 return _validator
107
107
108
108
109 def ValidRepoUser():
109 def ValidRepoUser():
110 class _validator(formencode.validators.FancyValidator):
110 class _validator(formencode.validators.FancyValidator):
111 messages = {
111 messages = {
112 'invalid_username': _('Username %(username)s is not valid')
112 'invalid_username': _('Username %(username)s is not valid')
113 }
113 }
114
114
115 def validate_python(self, value, state):
115 def validate_python(self, value, state):
116 try:
116 try:
117 User.query().filter(User.active == True) \
117 User.query().filter(User.active == True) \
118 .filter(User.username == value).one()
118 .filter(User.username == value).one()
119 except sqlalchemy.exc.InvalidRequestError: # NoResultFound/MultipleResultsFound
119 except sqlalchemy.exc.InvalidRequestError: # NoResultFound/MultipleResultsFound
120 msg = self.message('invalid_username', state, username=value)
120 msg = self.message('invalid_username', state, username=value)
121 raise formencode.Invalid(msg, value, state,
121 raise formencode.Invalid(msg, value, state,
122 error_dict=dict(username=msg)
122 error_dict=dict(username=msg)
123 )
123 )
124
124
125 return _validator
125 return _validator
126
126
127
127
128 def ValidUserGroup(edit=False, old_data=None):
128 def ValidUserGroup(edit=False, old_data=None):
129 old_data = old_data or {}
129 old_data = old_data or {}
130
130
131 class _validator(formencode.validators.FancyValidator):
131 class _validator(formencode.validators.FancyValidator):
132 messages = {
132 messages = {
133 'invalid_group': _('Invalid user group name'),
133 'invalid_group': _('Invalid user group name'),
134 'group_exist': _('User group "%(usergroup)s" already exists'),
134 'group_exist': _('User group "%(usergroup)s" already exists'),
135 'invalid_usergroup_name':
135 'invalid_usergroup_name':
136 _('user group name may only contain alphanumeric '
136 _('user group name may only contain alphanumeric '
137 'characters underscores, periods or dashes and must begin '
137 'characters underscores, periods or dashes and must begin '
138 'with alphanumeric character')
138 'with alphanumeric character')
139 }
139 }
140
140
141 def validate_python(self, value, state):
141 def validate_python(self, value, state):
142 if value in ['default']:
142 if value in ['default']:
143 msg = self.message('invalid_group', state)
143 msg = self.message('invalid_group', state)
144 raise formencode.Invalid(msg, value, state,
144 raise formencode.Invalid(msg, value, state,
145 error_dict=dict(users_group_name=msg)
145 error_dict=dict(users_group_name=msg)
146 )
146 )
147 # check if group is unique
147 # check if group is unique
148 old_ugname = None
148 old_ugname = None
149 if edit:
149 if edit:
150 old_id = old_data.get('users_group_id')
150 old_id = old_data.get('users_group_id')
151 old_ugname = UserGroup.get(old_id).users_group_name
151 old_ugname = UserGroup.get(old_id).users_group_name
152
152
153 if old_ugname != value or not edit:
153 if old_ugname != value or not edit:
154 is_existing_group = UserGroup.get_by_group_name(value,
154 is_existing_group = UserGroup.get_by_group_name(value,
155 case_insensitive=True)
155 case_insensitive=True)
156 if is_existing_group:
156 if is_existing_group:
157 msg = self.message('group_exist', state, usergroup=value)
157 msg = self.message('group_exist', state, usergroup=value)
158 raise formencode.Invalid(msg, value, state,
158 raise formencode.Invalid(msg, value, state,
159 error_dict=dict(users_group_name=msg)
159 error_dict=dict(users_group_name=msg)
160 )
160 )
161
161
162 if re.match(r'^[a-zA-Z0-9]{1}[a-zA-Z0-9\-\_\.]+$', value) is None:
162 if re.match(r'^[a-zA-Z0-9]{1}[a-zA-Z0-9\-\_\.]+$', value) is None:
163 msg = self.message('invalid_usergroup_name', state)
163 msg = self.message('invalid_usergroup_name', state)
164 raise formencode.Invalid(msg, value, state,
164 raise formencode.Invalid(msg, value, state,
165 error_dict=dict(users_group_name=msg)
165 error_dict=dict(users_group_name=msg)
166 )
166 )
167
167
168 return _validator
168 return _validator
169
169
170
170
171 def ValidRepoGroup(edit=False, old_data=None):
171 def ValidRepoGroup(edit=False, old_data=None):
172 old_data = old_data or {}
172 old_data = old_data or {}
173
173
174 class _validator(formencode.validators.FancyValidator):
174 class _validator(formencode.validators.FancyValidator):
175 messages = {
175 messages = {
176 'parent_group_id': _('Cannot assign this group as parent'),
176 'parent_group_id': _('Cannot assign this group as parent'),
177 'group_exists': _('Group "%(group_name)s" already exists'),
177 'group_exists': _('Group "%(group_name)s" already exists'),
178 'repo_exists':
178 'repo_exists':
179 _('Repository with name "%(group_name)s" already exists')
179 _('Repository with name "%(group_name)s" already exists')
180 }
180 }
181
181
182 def validate_python(self, value, state):
182 def validate_python(self, value, state):
183 # TODO WRITE VALIDATIONS
183 # TODO WRITE VALIDATIONS
184 group_name = value.get('group_name')
184 group_name = value.get('group_name')
185 parent_group_id = value.get('parent_group_id')
185 parent_group_id = value.get('parent_group_id')
186
186
187 # slugify repo group just in case :)
187 # slugify repo group just in case :)
188 slug = repo_name_slug(group_name)
188 slug = repo_name_slug(group_name)
189
189
190 # check for parent of self
190 # check for parent of self
191 parent_of_self = lambda: (
191 parent_of_self = lambda: (
192 old_data['group_id'] == parent_group_id
192 old_data['group_id'] == parent_group_id
193 if parent_group_id else False
193 if parent_group_id else False
194 )
194 )
195 if edit and parent_of_self():
195 if edit and parent_of_self():
196 msg = self.message('parent_group_id', state)
196 msg = self.message('parent_group_id', state)
197 raise formencode.Invalid(msg, value, state,
197 raise formencode.Invalid(msg, value, state,
198 error_dict=dict(parent_group_id=msg)
198 error_dict=dict(parent_group_id=msg)
199 )
199 )
200
200
201 old_gname = None
201 old_gname = None
202 if edit:
202 if edit:
203 old_gname = RepoGroup.get(old_data.get('group_id')).group_name
203 old_gname = RepoGroup.get(old_data.get('group_id')).group_name
204
204
205 if old_gname != group_name or not edit:
205 if old_gname != group_name or not edit:
206
206
207 # check group
207 # check group
208 gr = RepoGroup.query() \
208 gr = RepoGroup.query() \
209 .filter(func.lower(RepoGroup.group_name) == func.lower(slug)) \
209 .filter(func.lower(RepoGroup.group_name) == func.lower(slug)) \
210 .filter(RepoGroup.parent_group_id == parent_group_id) \
210 .filter(RepoGroup.parent_group_id == parent_group_id) \
211 .scalar()
211 .scalar()
212 if gr is not None:
212 if gr is not None:
213 msg = self.message('group_exists', state, group_name=slug)
213 msg = self.message('group_exists', state, group_name=slug)
214 raise formencode.Invalid(msg, value, state,
214 raise formencode.Invalid(msg, value, state,
215 error_dict=dict(group_name=msg)
215 error_dict=dict(group_name=msg)
216 )
216 )
217
217
218 # check for same repo
218 # check for same repo
219 repo = Repository.query() \
219 repo = Repository.query() \
220 .filter(func.lower(Repository.repo_name) == func.lower(slug)) \
220 .filter(func.lower(Repository.repo_name) == func.lower(slug)) \
221 .scalar()
221 .scalar()
222 if repo is not None:
222 if repo is not None:
223 msg = self.message('repo_exists', state, group_name=slug)
223 msg = self.message('repo_exists', state, group_name=slug)
224 raise formencode.Invalid(msg, value, state,
224 raise formencode.Invalid(msg, value, state,
225 error_dict=dict(group_name=msg)
225 error_dict=dict(group_name=msg)
226 )
226 )
227
227
228 return _validator
228 return _validator
229
229
230
230
231 def ValidPassword():
231 def ValidPassword():
232 class _validator(formencode.validators.FancyValidator):
232 class _validator(formencode.validators.FancyValidator):
233 messages = {
233 messages = {
234 'invalid_password':
234 'invalid_password':
235 _('Invalid characters (non-ascii) in password')
235 _('Invalid characters (non-ascii) in password')
236 }
236 }
237
237
238 def validate_python(self, value, state):
238 def validate_python(self, value, state):
239 try:
239 try:
240 (value or '').decode('ascii')
240 (value or '').decode('ascii')
241 except UnicodeError:
241 except UnicodeError:
242 msg = self.message('invalid_password', state)
242 msg = self.message('invalid_password', state)
243 raise formencode.Invalid(msg, value, state,)
243 raise formencode.Invalid(msg, value, state,)
244 return _validator
244 return _validator
245
245
246
246
247 def ValidOldPassword(username):
247 def ValidOldPassword(username):
248 class _validator(formencode.validators.FancyValidator):
248 class _validator(formencode.validators.FancyValidator):
249 messages = {
249 messages = {
250 'invalid_password': _('Invalid old password')
250 'invalid_password': _('Invalid old password')
251 }
251 }
252
252
253 def validate_python(self, value, state):
253 def validate_python(self, value, state):
254 from kallithea.lib import auth_modules
254 from kallithea.lib import auth_modules
255 if auth_modules.authenticate(username, value, '') is None:
255 if auth_modules.authenticate(username, value, '') is None:
256 msg = self.message('invalid_password', state)
256 msg = self.message('invalid_password', state)
257 raise formencode.Invalid(msg, value, state,
257 raise formencode.Invalid(msg, value, state,
258 error_dict=dict(current_password=msg)
258 error_dict=dict(current_password=msg)
259 )
259 )
260 return _validator
260 return _validator
261
261
262
262
263 def ValidPasswordsMatch(password_field, password_confirmation_field):
263 def ValidPasswordsMatch(password_field, password_confirmation_field):
264 class _validator(formencode.validators.FancyValidator):
264 class _validator(formencode.validators.FancyValidator):
265 messages = {
265 messages = {
266 'password_mismatch': _('Passwords do not match'),
266 'password_mismatch': _('Passwords do not match'),
267 }
267 }
268
268
269 def validate_python(self, value, state):
269 def validate_python(self, value, state):
270 if value.get(password_field) != value[password_confirmation_field]:
270 if value.get(password_field) != value[password_confirmation_field]:
271 msg = self.message('password_mismatch', state)
271 msg = self.message('password_mismatch', state)
272 raise formencode.Invalid(msg, value, state,
272 raise formencode.Invalid(msg, value, state,
273 error_dict={password_field:msg, password_confirmation_field: msg}
273 error_dict={password_field:msg, password_confirmation_field: msg}
274 )
274 )
275 return _validator
275 return _validator
276
276
277
277
278 def ValidAuth():
278 def ValidAuth():
279 class _validator(formencode.validators.FancyValidator):
279 class _validator(formencode.validators.FancyValidator):
280 messages = {
280 messages = {
281 'invalid_auth': _(u'Invalid username or password'),
281 'invalid_auth': _(u'Invalid username or password'),
282 }
282 }
283
283
284 def validate_python(self, value, state):
284 def validate_python(self, value, state):
285 from kallithea.lib import auth_modules
285 from kallithea.lib import auth_modules
286
286
287 password = value['password']
287 password = value['password']
288 username = value['username']
288 username = value['username']
289
289
290 # authenticate returns unused dict but has called
290 # authenticate returns unused dict but has called
291 # plugin._authenticate which has create_or_update'ed the username user in db
291 # plugin._authenticate which has create_or_update'ed the username user in db
292 if auth_modules.authenticate(username, password) is None:
292 if auth_modules.authenticate(username, password) is None:
293 user = User.get_by_username_or_email(username)
293 user = User.get_by_username_or_email(username)
294 if user and not user.active:
294 if user and not user.active:
295 log.warning('user %s is disabled', username)
295 log.warning('user %s is disabled', username)
296 msg = self.message('invalid_auth', state)
296 msg = self.message('invalid_auth', state)
297 raise formencode.Invalid(msg, value, state,
297 raise formencode.Invalid(msg, value, state,
298 error_dict=dict(username=' ', password=msg)
298 error_dict=dict(username=' ', password=msg)
299 )
299 )
300 else:
300 else:
301 log.warning('user %s failed to authenticate', username)
301 log.warning('user %s failed to authenticate', username)
302 msg = self.message('invalid_auth', state)
302 msg = self.message('invalid_auth', state)
303 raise formencode.Invalid(msg, value, state,
303 raise formencode.Invalid(msg, value, state,
304 error_dict=dict(username=' ', password=msg)
304 error_dict=dict(username=' ', password=msg)
305 )
305 )
306 return _validator
306 return _validator
307
307
308
308
309 def ValidAuthToken():
309 def ValidAuthToken():
310 class _validator(formencode.validators.FancyValidator):
310 class _validator(formencode.validators.FancyValidator):
311 messages = {
311 messages = {
312 'invalid_token': _('Token mismatch')
312 'invalid_token': _('Token mismatch')
313 }
313 }
314
314
315 def validate_python(self, value, state):
315 def validate_python(self, value, state):
316 if value != authentication_token():
316 if value != authentication_token():
317 msg = self.message('invalid_token', state)
317 msg = self.message('invalid_token', state)
318 raise formencode.Invalid(msg, value, state)
318 raise formencode.Invalid(msg, value, state)
319 return _validator
319 return _validator
320
320
321
321
322 def ValidRepoName(edit=False, old_data=None):
322 def ValidRepoName(edit=False, old_data=None):
323 old_data = old_data or {}
323 old_data = old_data or {}
324
324
325 class _validator(formencode.validators.FancyValidator):
325 class _validator(formencode.validators.FancyValidator):
326 messages = {
326 messages = {
327 'invalid_repo_name':
327 'invalid_repo_name':
328 _('Repository name %(repo)s is not allowed'),
328 _('Repository name %(repo)s is not allowed'),
329 'repository_exists':
329 'repository_exists':
330 _('Repository named %(repo)s already exists'),
330 _('Repository named %(repo)s already exists'),
331 'repository_in_group_exists': _('Repository "%(repo)s" already '
331 'repository_in_group_exists': _('Repository "%(repo)s" already '
332 'exists in group "%(group)s"'),
332 'exists in group "%(group)s"'),
333 'same_group_exists': _('Repository group with name "%(repo)s" '
333 'same_group_exists': _('Repository group with name "%(repo)s" '
334 'already exists')
334 'already exists')
335 }
335 }
336
336
337 def _to_python(self, value, state):
337 def _to_python(self, value, state):
338 repo_name = repo_name_slug(value.get('repo_name', ''))
338 repo_name = repo_name_slug(value.get('repo_name', ''))
339 repo_group = value.get('repo_group')
339 repo_group = value.get('repo_group')
340 if repo_group:
340 if repo_group:
341 gr = RepoGroup.get(repo_group)
341 gr = RepoGroup.get(repo_group)
342 group_path = gr.full_path
342 group_path = gr.full_path
343 group_name = gr.group_name
343 group_name = gr.group_name
344 # value needs to be aware of group name in order to check
344 # value needs to be aware of group name in order to check
345 # db key This is an actual just the name to store in the
345 # db key This is an actual just the name to store in the
346 # database
346 # database
347 repo_name_full = group_path + RepoGroup.url_sep() + repo_name
347 repo_name_full = group_path + RepoGroup.url_sep() + repo_name
348 else:
348 else:
349 group_name = group_path = ''
349 group_name = group_path = ''
350 repo_name_full = repo_name
350 repo_name_full = repo_name
351
351
352 value['repo_name'] = repo_name
352 value['repo_name'] = repo_name
353 value['repo_name_full'] = repo_name_full
353 value['repo_name_full'] = repo_name_full
354 value['group_path'] = group_path
354 value['group_path'] = group_path
355 value['group_name'] = group_name
355 value['group_name'] = group_name
356 return value
356 return value
357
357
358 def validate_python(self, value, state):
358 def validate_python(self, value, state):
359 repo_name = value.get('repo_name')
359 repo_name = value.get('repo_name')
360 repo_name_full = value.get('repo_name_full')
360 repo_name_full = value.get('repo_name_full')
361 group_path = value.get('group_path')
361 group_path = value.get('group_path')
362 group_name = value.get('group_name')
362 group_name = value.get('group_name')
363
363
364 if repo_name in [ADMIN_PREFIX, '']:
364 if repo_name in [ADMIN_PREFIX, '']:
365 msg = self.message('invalid_repo_name', state, repo=repo_name)
365 msg = self.message('invalid_repo_name', state, repo=repo_name)
366 raise formencode.Invalid(msg, value, state,
366 raise formencode.Invalid(msg, value, state,
367 error_dict=dict(repo_name=msg)
367 error_dict=dict(repo_name=msg)
368 )
368 )
369
369
370 rename = old_data.get('repo_name') != repo_name_full
370 rename = old_data.get('repo_name') != repo_name_full
371 create = not edit
371 create = not edit
372 if rename or create:
372 if rename or create:
373 repo = Repository.get_by_repo_name(repo_name_full, case_insensitive=True)
373 repo = Repository.get_by_repo_name(repo_name_full, case_insensitive=True)
374 repo_group = RepoGroup.get_by_group_name(repo_name_full, case_insensitive=True)
374 repo_group = RepoGroup.get_by_group_name(repo_name_full, case_insensitive=True)
375 if group_path != '':
375 if group_path != '':
376 if repo is not None:
376 if repo is not None:
377 msg = self.message('repository_in_group_exists', state,
377 msg = self.message('repository_in_group_exists', state,
378 repo=repo.repo_name, group=group_name)
378 repo=repo.repo_name, group=group_name)
379 raise formencode.Invalid(msg, value, state,
379 raise formencode.Invalid(msg, value, state,
380 error_dict=dict(repo_name=msg)
380 error_dict=dict(repo_name=msg)
381 )
381 )
382 elif repo_group is not None:
382 elif repo_group is not None:
383 msg = self.message('same_group_exists', state,
383 msg = self.message('same_group_exists', state,
384 repo=repo_name)
384 repo=repo_name)
385 raise formencode.Invalid(msg, value, state,
385 raise formencode.Invalid(msg, value, state,
386 error_dict=dict(repo_name=msg)
386 error_dict=dict(repo_name=msg)
387 )
387 )
388 elif repo is not None:
388 elif repo is not None:
389 msg = self.message('repository_exists', state,
389 msg = self.message('repository_exists', state,
390 repo=repo.repo_name)
390 repo=repo.repo_name)
391 raise formencode.Invalid(msg, value, state,
391 raise formencode.Invalid(msg, value, state,
392 error_dict=dict(repo_name=msg)
392 error_dict=dict(repo_name=msg)
393 )
393 )
394 return value
394 return value
395 return _validator
395 return _validator
396
396
397
397
398 def ValidForkName(*args, **kwargs):
398 def ValidForkName(*args, **kwargs):
399 return ValidRepoName(*args, **kwargs)
399 return ValidRepoName(*args, **kwargs)
400
400
401
401
402 def SlugifyName():
402 def SlugifyName():
403 class _validator(formencode.validators.FancyValidator):
403 class _validator(formencode.validators.FancyValidator):
404
404
405 def _to_python(self, value, state):
405 def _to_python(self, value, state):
406 return repo_name_slug(value)
406 return repo_name_slug(value)
407
407
408 def validate_python(self, value, state):
408 def validate_python(self, value, state):
409 pass
409 pass
410
410
411 return _validator
411 return _validator
412
412
413
413
414 def ValidCloneUri():
414 def ValidCloneUri():
415 from kallithea.lib.utils import make_ui
415 from kallithea.lib.utils import make_ui
416
416
417 class _validator(formencode.validators.FancyValidator):
417 class _validator(formencode.validators.FancyValidator):
418 messages = {
418 messages = {
419 'clone_uri': _('Invalid repository URL'),
419 'clone_uri': _('Invalid repository URL'),
420 'invalid_clone_uri': _('Invalid repository URL. It must be a '
420 'invalid_clone_uri': _('Invalid repository URL. It must be a '
421 'valid http, https, ssh, svn+http or svn+https URL'),
421 'valid http, https, ssh, svn+http or svn+https URL'),
422 }
422 }
423
423
424 def validate_python(self, value, state):
424 def validate_python(self, value, state):
425 repo_type = value.get('repo_type')
425 repo_type = value.get('repo_type')
426 url = value.get('clone_uri')
426 url = value.get('clone_uri')
427
427
428 if url and url != value.get('clone_uri_hidden'):
428 if url and url != value.get('clone_uri_hidden'):
429 try:
429 try:
430 is_valid_repo_uri(repo_type, url, make_ui('db', clear_session=False))
430 is_valid_repo_uri(repo_type, url, make_ui('db', clear_session=False))
431 except Exception:
431 except Exception:
432 log.exception('URL validation failed')
432 log.exception('URL validation failed')
433 msg = self.message('clone_uri', state)
433 msg = self.message('clone_uri', state)
434 raise formencode.Invalid(msg, value, state,
434 raise formencode.Invalid(msg, value, state,
435 error_dict=dict(clone_uri=msg)
435 error_dict=dict(clone_uri=msg)
436 )
436 )
437 return _validator
437 return _validator
438
438
439
439
440 def ValidForkType(old_data=None):
440 def ValidForkType(old_data=None):
441 old_data = old_data or {}
441 old_data = old_data or {}
442
442
443 class _validator(formencode.validators.FancyValidator):
443 class _validator(formencode.validators.FancyValidator):
444 messages = {
444 messages = {
445 'invalid_fork_type': _('Fork has to be the same type as parent')
445 'invalid_fork_type': _('Fork has to be the same type as parent')
446 }
446 }
447
447
448 def validate_python(self, value, state):
448 def validate_python(self, value, state):
449 if old_data['repo_type'] != value:
449 if old_data['repo_type'] != value:
450 msg = self.message('invalid_fork_type', state)
450 msg = self.message('invalid_fork_type', state)
451 raise formencode.Invalid(msg, value, state,
451 raise formencode.Invalid(msg, value, state,
452 error_dict=dict(repo_type=msg)
452 error_dict=dict(repo_type=msg)
453 )
453 )
454 return _validator
454 return _validator
455
455
456
456
457 def CanWriteGroup(old_data=None):
457 def CanWriteGroup(old_data=None):
458 class _validator(formencode.validators.FancyValidator):
458 class _validator(formencode.validators.FancyValidator):
459 messages = {
459 messages = {
460 'permission_denied': _("You don't have permissions "
460 'permission_denied': _("You don't have permissions "
461 "to create repository in this group"),
461 "to create repository in this group"),
462 'permission_denied_root': _("no permission to create repository "
462 'permission_denied_root': _("no permission to create repository "
463 "in root location")
463 "in root location")
464 }
464 }
465
465
466 def _to_python(self, value, state):
466 def _to_python(self, value, state):
467 # root location
467 # root location
468 if value == -1:
468 if value == -1:
469 return None
469 return None
470 return value
470 return value
471
471
472 def validate_python(self, value, state):
472 def validate_python(self, value, state):
473 gr = RepoGroup.get(value)
473 gr = RepoGroup.get(value)
474 gr_name = gr.group_name if gr is not None else None # None means ROOT location
474 gr_name = gr.group_name if gr is not None else None # None means ROOT location
475
475
476 # create repositories with write permission on group is set to true
476 # create repositories with write permission on group is set to true
477 create_on_write = HasPermissionAny('hg.create.write_on_repogroup.true')()
477 create_on_write = HasPermissionAny('hg.create.write_on_repogroup.true')()
478 group_admin = HasRepoGroupPermissionLevel('admin')(gr_name,
478 group_admin = HasRepoGroupPermissionLevel('admin')(gr_name,
479 'can write into group validator')
479 'can write into group validator')
480 group_write = HasRepoGroupPermissionLevel('write')(gr_name,
480 group_write = HasRepoGroupPermissionLevel('write')(gr_name,
481 'can write into group validator')
481 'can write into group validator')
482 forbidden = not (group_admin or (group_write and create_on_write))
482 forbidden = not (group_admin or (group_write and create_on_write))
483 can_create_repos = HasPermissionAny('hg.admin', 'hg.create.repository')
483 can_create_repos = HasPermissionAny('hg.admin', 'hg.create.repository')
484 gid = (old_data['repo_group'].get('group_id')
484 gid = (old_data['repo_group'].get('group_id')
485 if (old_data and 'repo_group' in old_data) else None)
485 if (old_data and 'repo_group' in old_data) else None)
486 value_changed = gid != value
486 value_changed = gid != value
487 new = not old_data
487 new = not old_data
488 # do check if we changed the value, there's a case that someone got
488 # do check if we changed the value, there's a case that someone got
489 # revoked write permissions to a repository, he still created, we
489 # revoked write permissions to a repository, he still created, we
490 # don't need to check permission if he didn't change the value of
490 # don't need to check permission if he didn't change the value of
491 # groups in form box
491 # groups in form box
492 if value_changed or new:
492 if value_changed or new:
493 # parent group need to be existing
493 # parent group need to be existing
494 if gr and forbidden:
494 if gr and forbidden:
495 msg = self.message('permission_denied', state)
495 msg = self.message('permission_denied', state)
496 raise formencode.Invalid(msg, value, state,
496 raise formencode.Invalid(msg, value, state,
497 error_dict=dict(repo_type=msg)
497 error_dict=dict(repo_type=msg)
498 )
498 )
499 ## check if we can write to root location !
499 ## check if we can write to root location !
500 elif gr is None and not can_create_repos():
500 elif gr is None and not can_create_repos():
501 msg = self.message('permission_denied_root', state)
501 msg = self.message('permission_denied_root', state)
502 raise formencode.Invalid(msg, value, state,
502 raise formencode.Invalid(msg, value, state,
503 error_dict=dict(repo_type=msg)
503 error_dict=dict(repo_type=msg)
504 )
504 )
505
505
506 return _validator
506 return _validator
507
507
508
508
509 def CanCreateGroup(can_create_in_root=False):
509 def CanCreateGroup(can_create_in_root=False):
510 class _validator(formencode.validators.FancyValidator):
510 class _validator(formencode.validators.FancyValidator):
511 messages = {
511 messages = {
512 'permission_denied': _("You don't have permissions "
512 'permission_denied': _("You don't have permissions "
513 "to create a group in this location")
513 "to create a group in this location")
514 }
514 }
515
515
516 def to_python(self, value, state):
516 def to_python(self, value, state):
517 # root location
517 # root location
518 if value == -1:
518 if value == -1:
519 return None
519 return None
520 return value
520 return value
521
521
522 def validate_python(self, value, state):
522 def validate_python(self, value, state):
523 gr = RepoGroup.get(value)
523 gr = RepoGroup.get(value)
524 gr_name = gr.group_name if gr is not None else None # None means ROOT location
524 gr_name = gr.group_name if gr is not None else None # None means ROOT location
525
525
526 if can_create_in_root and gr is None:
526 if can_create_in_root and gr is None:
527 # we can create in root, we're fine no validations required
527 # we can create in root, we're fine no validations required
528 return
528 return
529
529
530 forbidden_in_root = gr is None and not can_create_in_root
530 forbidden_in_root = gr is None and not can_create_in_root
531 forbidden = not HasRepoGroupPermissionLevel('admin')(gr_name, 'can create group validator')
531 forbidden = not HasRepoGroupPermissionLevel('admin')(gr_name, 'can create group validator')
532 if forbidden_in_root or forbidden:
532 if forbidden_in_root or forbidden:
533 msg = self.message('permission_denied', state)
533 msg = self.message('permission_denied', state)
534 raise formencode.Invalid(msg, value, state,
534 raise formencode.Invalid(msg, value, state,
535 error_dict=dict(parent_group_id=msg)
535 error_dict=dict(parent_group_id=msg)
536 )
536 )
537
537
538 return _validator
538 return _validator
539
539
540
540
541 def ValidPerms(type_='repo'):
541 def ValidPerms(type_='repo'):
542 if type_ == 'repo_group':
542 if type_ == 'repo_group':
543 EMPTY_PERM = 'group.none'
543 EMPTY_PERM = 'group.none'
544 elif type_ == 'repo':
544 elif type_ == 'repo':
545 EMPTY_PERM = 'repository.none'
545 EMPTY_PERM = 'repository.none'
546 elif type_ == 'user_group':
546 elif type_ == 'user_group':
547 EMPTY_PERM = 'usergroup.none'
547 EMPTY_PERM = 'usergroup.none'
548
548
549 class _validator(formencode.validators.FancyValidator):
549 class _validator(formencode.validators.FancyValidator):
550 messages = {
550 messages = {
551 'perm_new_member_name':
551 'perm_new_member_name':
552 _('This username or user group name is not valid')
552 _('This username or user group name is not valid')
553 }
553 }
554
554
555 def to_python(self, value, state):
555 def to_python(self, value, state):
556 perms_update = OrderedSet()
556 perms_update = OrderedSet()
557 perms_new = OrderedSet()
557 perms_new = OrderedSet()
558 # build a list of permission to update and new permission to create
558 # build a list of permission to update and new permission to create
559
559
560 # CLEAN OUT ORG VALUE FROM NEW MEMBERS, and group them using
560 # CLEAN OUT ORG VALUE FROM NEW MEMBERS, and group them using
561 new_perms_group = defaultdict(dict)
561 new_perms_group = defaultdict(dict)
562 for k, v in value.copy().iteritems():
562 for k, v in value.copy().iteritems():
563 if k.startswith('perm_new_member'):
563 if k.startswith('perm_new_member'):
564 del value[k]
564 del value[k]
565 _type, part = k.split('perm_new_member_')
565 _type, part = k.split('perm_new_member_')
566 args = part.split('_')
566 args = part.split('_')
567 if len(args) == 1:
567 if len(args) == 1:
568 new_perms_group[args[0]]['perm'] = v
568 new_perms_group[args[0]]['perm'] = v
569 elif len(args) == 2:
569 elif len(args) == 2:
570 _key, pos = args
570 _key, pos = args
571 new_perms_group[pos][_key] = v
571 new_perms_group[pos][_key] = v
572
572
573 # fill new permissions in order of how they were added
573 # fill new permissions in order of how they were added
574 for k in sorted(map(int, new_perms_group.keys())):
574 for k in sorted(map(int, new_perms_group.keys())):
575 perm_dict = new_perms_group[str(k)]
575 perm_dict = new_perms_group[str(k)]
576 new_member = perm_dict.get('name')
576 new_member = perm_dict.get('name')
577 new_perm = perm_dict.get('perm')
577 new_perm = perm_dict.get('perm')
578 new_type = perm_dict.get('type')
578 new_type = perm_dict.get('type')
579 if new_member and new_perm and new_type:
579 if new_member and new_perm and new_type:
580 perms_new.add((new_member, new_perm, new_type))
580 perms_new.add((new_member, new_perm, new_type))
581
581
582 for k, v in value.iteritems():
582 for k, v in value.iteritems():
583 if k.startswith('u_perm_') or k.startswith('g_perm_'):
583 if k.startswith('u_perm_') or k.startswith('g_perm_'):
584 member = k[7:]
584 member = k[7:]
585 t = {'u': 'user',
585 t = {'u': 'user',
586 'g': 'users_group'
586 'g': 'users_group'
587 }[k[0]]
587 }[k[0]]
588 if member == User.DEFAULT_USER:
588 if member == User.DEFAULT_USER:
589 if str2bool(value.get('repo_private')):
589 if str2bool(value.get('repo_private')):
590 # set none for default when updating to
590 # set none for default when updating to
591 # private repo protects against form manipulation
591 # private repo protects against form manipulation
592 v = EMPTY_PERM
592 v = EMPTY_PERM
593 perms_update.add((member, v, t))
593 perms_update.add((member, v, t))
594
594
595 value['perms_updates'] = list(perms_update)
595 value['perms_updates'] = list(perms_update)
596 value['perms_new'] = list(perms_new)
596 value['perms_new'] = list(perms_new)
597
597
598 # update permissions
598 # update permissions
599 for k, v, t in perms_new:
599 for k, v, t in perms_new:
600 try:
600 try:
601 if t is 'user':
601 if t is 'user':
602 self.user_db = User.query() \
602 self.user_db = User.query() \
603 .filter(User.active == True) \
603 .filter(User.active == True) \
604 .filter(User.username == k).one()
604 .filter(User.username == k).one()
605 if t is 'users_group':
605 if t is 'users_group':
606 self.user_db = UserGroup.query() \
606 self.user_db = UserGroup.query() \
607 .filter(UserGroup.users_group_active == True) \
607 .filter(UserGroup.users_group_active == True) \
608 .filter(UserGroup.users_group_name == k).one()
608 .filter(UserGroup.users_group_name == k).one()
609
609
610 except Exception:
610 except Exception:
611 log.exception('Updated permission failed')
611 log.exception('Updated permission failed')
612 msg = self.message('perm_new_member_type', state)
612 msg = self.message('perm_new_member_type', state)
613 raise formencode.Invalid(msg, value, state,
613 raise formencode.Invalid(msg, value, state,
614 error_dict=dict(perm_new_member_name=msg)
614 error_dict=dict(perm_new_member_name=msg)
615 )
615 )
616 return value
616 return value
617 return _validator
617 return _validator
618
618
619
619
620 def ValidSettings():
620 def ValidSettings():
621 class _validator(formencode.validators.FancyValidator):
621 class _validator(formencode.validators.FancyValidator):
622 def _to_python(self, value, state):
622 def _to_python(self, value, state):
623 # settings form for users that are not admin
623 # settings form for users that are not admin
624 # can't edit certain parameters, it's extra backup if they mangle
624 # can't edit certain parameters, it's extra backup if they mangle
625 # with forms
625 # with forms
626
626
627 forbidden_params = [
627 forbidden_params = [
628 'user', 'repo_type', 'repo_enable_locking',
628 'user', 'repo_type', 'repo_enable_locking',
629 'repo_enable_downloads', 'repo_enable_statistics'
629 'repo_enable_downloads', 'repo_enable_statistics'
630 ]
630 ]
631
631
632 for param in forbidden_params:
632 for param in forbidden_params:
633 if param in value:
633 if param in value:
634 del value[param]
634 del value[param]
635 return value
635 return value
636
636
637 def validate_python(self, value, state):
637 def validate_python(self, value, state):
638 pass
638 pass
639 return _validator
639 return _validator
640
640
641
641
642 def ValidPath():
642 def ValidPath():
643 class _validator(formencode.validators.FancyValidator):
643 class _validator(formencode.validators.FancyValidator):
644 messages = {
644 messages = {
645 'invalid_path': _('This is not a valid path')
645 'invalid_path': _('This is not a valid path')
646 }
646 }
647
647
648 def validate_python(self, value, state):
648 def validate_python(self, value, state):
649 if not os.path.isdir(value):
649 if not os.path.isdir(value):
650 msg = self.message('invalid_path', state)
650 msg = self.message('invalid_path', state)
651 raise formencode.Invalid(msg, value, state,
651 raise formencode.Invalid(msg, value, state,
652 error_dict=dict(paths_root_path=msg)
652 error_dict=dict(paths_root_path=msg)
653 )
653 )
654 return _validator
654 return _validator
655
655
656
656
657 def UniqSystemEmail(old_data=None):
657 def UniqSystemEmail(old_data=None):
658 old_data = old_data or {}
658 old_data = old_data or {}
659
659
660 class _validator(formencode.validators.FancyValidator):
660 class _validator(formencode.validators.FancyValidator):
661 messages = {
661 messages = {
662 'email_taken': _('This email address is already in use')
662 'email_taken': _('This email address is already in use')
663 }
663 }
664
664
665 def _to_python(self, value, state):
665 def _to_python(self, value, state):
666 return value.lower()
666 return value.lower()
667
667
668 def validate_python(self, value, state):
668 def validate_python(self, value, state):
669 if (old_data.get('email') or '').lower() != value:
669 if (old_data.get('email') or '').lower() != value:
670 user = User.get_by_email(value)
670 user = User.get_by_email(value)
671 if user is not None:
671 if user is not None:
672 msg = self.message('email_taken', state)
672 msg = self.message('email_taken', state)
673 raise formencode.Invalid(msg, value, state,
673 raise formencode.Invalid(msg, value, state,
674 error_dict=dict(email=msg)
674 error_dict=dict(email=msg)
675 )
675 )
676 return _validator
676 return _validator
677
677
678
678
679 def ValidSystemEmail():
679 def ValidSystemEmail():
680 class _validator(formencode.validators.FancyValidator):
680 class _validator(formencode.validators.FancyValidator):
681 messages = {
681 messages = {
682 'non_existing_email': _('Email address "%(email)s" not found')
682 'non_existing_email': _('Email address "%(email)s" not found')
683 }
683 }
684
684
685 def _to_python(self, value, state):
685 def _to_python(self, value, state):
686 return value.lower()
686 return value.lower()
687
687
688 def validate_python(self, value, state):
688 def validate_python(self, value, state):
689 user = User.get_by_email(value)
689 user = User.get_by_email(value)
690 if user is None:
690 if user is None:
691 msg = self.message('non_existing_email', state, email=value)
691 msg = self.message('non_existing_email', state, email=value)
692 raise formencode.Invalid(msg, value, state,
692 raise formencode.Invalid(msg, value, state,
693 error_dict=dict(email=msg)
693 error_dict=dict(email=msg)
694 )
694 )
695
695
696 return _validator
696 return _validator
697
697
698
698
699 def LdapLibValidator():
699 def LdapLibValidator():
700 class _validator(formencode.validators.FancyValidator):
700 class _validator(formencode.validators.FancyValidator):
701 messages = {
701 messages = {
702
702
703 }
703 }
704
704
705 def validate_python(self, value, state):
705 def validate_python(self, value, state):
706 try:
706 try:
707 import ldap
707 import ldap
708 ldap # pyflakes silence !
708 ldap # pyflakes silence !
709 except ImportError:
709 except ImportError:
710 raise LdapImportError()
710 raise LdapImportError()
711
711
712 return _validator
712 return _validator
713
713
714
714
715 def AttrLoginValidator():
715 def AttrLoginValidator():
716 class _validator(formencode.validators.UnicodeString):
716 class _validator(formencode.validators.UnicodeString):
717 messages = {
717 messages = {
718 'invalid_cn':
718 'invalid_cn':
719 _('The LDAP Login attribute of the CN must be specified - '
719 _('The LDAP Login attribute of the CN must be specified - '
720 'this is the name of the attribute that is equivalent '
720 'this is the name of the attribute that is equivalent '
721 'to "username"')
721 'to "username"')
722 }
722 }
723 messages['empty'] = messages['invalid_cn']
723 messages['empty'] = messages['invalid_cn']
724
724
725 return _validator
725 return _validator
726
726
727
727
728 def ValidIp():
728 def ValidIp():
729 class _validator(CIDR):
729 class _validator(CIDR):
730 messages = dict(
730 messages = dict(
731 badFormat=_('Please enter a valid IPv4 or IPv6 address'),
731 badFormat=_('Please enter a valid IPv4 or IPv6 address'),
732 illegalBits=_('The network size (bits) must be within the range'
732 illegalBits=_('The network size (bits) must be within the range'
733 ' of 0-32 (not %(bits)r)')
733 ' of 0-32 (not %(bits)r)')
734 )
734 )
735
735
736 def to_python(self, value, state):
736 def to_python(self, value, state):
737 v = super(_validator, self).to_python(value, state)
737 v = super(_validator, self).to_python(value, state)
738 v = v.strip()
738 v = v.strip()
739 net = ipaddr.IPNetwork(address=v)
739 net = ipaddr.IPNetwork(address=v)
740 if isinstance(net, ipaddr.IPv4Network):
740 if isinstance(net, ipaddr.IPv4Network):
741 # if IPv4 doesn't end with a mask, add /32
741 # if IPv4 doesn't end with a mask, add /32
742 if '/' not in value:
742 if '/' not in value:
743 v += '/32'
743 v += '/32'
744 if isinstance(net, ipaddr.IPv6Network):
744 if isinstance(net, ipaddr.IPv6Network):
745 # if IPv6 doesn't end with a mask, add /128
745 # if IPv6 doesn't end with a mask, add /128
746 if '/' not in value:
746 if '/' not in value:
747 v += '/128'
747 v += '/128'
748 return v
748 return v
749
749
750 def validate_python(self, value, state):
750 def validate_python(self, value, state):
751 try:
751 try:
752 addr = value.strip()
752 addr = value.strip()
753 # this raises an ValueError if address is not IPv4 or IPv6
753 # this raises an ValueError if address is not IPv4 or IPv6
754 ipaddr.IPNetwork(address=addr)
754 ipaddr.IPNetwork(address=addr)
755 except ValueError:
755 except ValueError:
756 raise formencode.Invalid(self.message('badFormat', state),
756 raise formencode.Invalid(self.message('badFormat', state),
757 value, state)
757 value, state)
758
758
759 return _validator
759 return _validator
760
760
761
761
762 def FieldKey():
762 def FieldKey():
763 class _validator(formencode.validators.FancyValidator):
763 class _validator(formencode.validators.FancyValidator):
764 messages = dict(
764 messages = dict(
765 badFormat=_('Key name can only consist of letters, '
765 badFormat=_('Key name can only consist of letters, '
766 'underscore, dash or numbers')
766 'underscore, dash or numbers')
767 )
767 )
768
768
769 def validate_python(self, value, state):
769 def validate_python(self, value, state):
770 if not re.match('[a-zA-Z0-9_-]+$', value):
770 if not re.match('[a-zA-Z0-9_-]+$', value):
771 raise formencode.Invalid(self.message('badFormat', state),
771 raise formencode.Invalid(self.message('badFormat', state),
772 value, state)
772 value, state)
773 return _validator
773 return _validator
774
774
775
775
776 def BasePath():
776 def BasePath():
777 class _validator(formencode.validators.FancyValidator):
777 class _validator(formencode.validators.FancyValidator):
778 messages = dict(
778 messages = dict(
779 badPath=_('Filename cannot be inside a directory')
779 badPath=_('Filename cannot be inside a directory')
780 )
780 )
781
781
782 def _to_python(self, value, state):
782 def _to_python(self, value, state):
783 return value
783 return value
784
784
785 def validate_python(self, value, state):
785 def validate_python(self, value, state):
786 if value != os.path.basename(value):
786 if value != os.path.basename(value):
787 raise formencode.Invalid(self.message('badPath', state),
787 raise formencode.Invalid(self.message('badPath', state),
788 value, state)
788 value, state)
789 return _validator
789 return _validator
790
790
791
791
792 def ValidAuthPlugins():
792 def ValidAuthPlugins():
793 class _validator(formencode.validators.FancyValidator):
793 class _validator(formencode.validators.FancyValidator):
794 messages = dict(
794 messages = dict(
795 import_duplicate=_('Plugins %(loaded)s and %(next_to_load)s both export the same name')
795 import_duplicate=_('Plugins %(loaded)s and %(next_to_load)s both export the same name')
796 )
796 )
797
797
798 def _to_python(self, value, state):
798 def _to_python(self, value, state):
799 # filter empty values
799 # filter empty values
800 return filter(lambda s: s not in [None, ''], value)
800 return filter(lambda s: s not in [None, ''], value)
801
801
802 def validate_python(self, value, state):
802 def validate_python(self, value, state):
803 from kallithea.lib import auth_modules
803 from kallithea.lib import auth_modules
804 module_list = value
804 module_list = value
805 unique_names = {}
805 unique_names = {}
806 try:
806 try:
807 for module in module_list:
807 for module in module_list:
808 plugin = auth_modules.loadplugin(module)
808 plugin = auth_modules.loadplugin(module)
809 plugin_name = plugin.name
809 plugin_name = plugin.name
810 if plugin_name in unique_names:
810 if plugin_name in unique_names:
811 msg = self.message('import_duplicate', state,
811 msg = self.message('import_duplicate', state,
812 loaded=unique_names[plugin_name],
812 loaded=unique_names[plugin_name],
813 next_to_load=plugin_name)
813 next_to_load=plugin_name)
814 raise formencode.Invalid(msg, value, state)
814 raise formencode.Invalid(msg, value, state)
815 unique_names[plugin_name] = plugin
815 unique_names[plugin_name] = plugin
816 except (ImportError, AttributeError, TypeError) as e:
816 except (ImportError, AttributeError, TypeError) as e:
817 raise formencode.Invalid(str(e), value, state)
817 raise formencode.Invalid(str(e), value, state)
818
818
819 return _validator
819 return _validator
General Comments 0
You need to be logged in to leave comments. Login now