##// END OF EJS Templates
repositories: drop the word "dashboard" - it is just a list of repositories and groups
Mads Kiilerich -
r6274:19f15cde default
parent child Browse files
Show More
@@ -1,463 +1,463 b''
1 1 # -*- coding: utf-8 -*-
2 2 # This program is free software: you can redistribute it and/or modify
3 3 # it under the terms of the GNU General Public License as published by
4 4 # the Free Software Foundation, either version 3 of the License, or
5 5 # (at your option) any later version.
6 6 #
7 7 # This program is distributed in the hope that it will be useful,
8 8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 10 # GNU General Public License for more details.
11 11 #
12 12 # You should have received a copy of the GNU General Public License
13 13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
14 14 """
15 15 kallithea.controllers.admin.settings
16 16 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
17 17
18 18 settings controller for Kallithea admin
19 19
20 20 This file was forked by the Kallithea project in July 2014.
21 21 Original author and date, and relevant copyright and licensing information is below:
22 22 :created_on: Jul 14, 2010
23 23 :author: marcink
24 24 :copyright: (c) 2013 RhodeCode GmbH, and others.
25 25 :license: GPLv3, see LICENSE.md for more details.
26 26 """
27 27
28 28 import logging
29 29 import traceback
30 30 import formencode
31 31
32 32 from formencode import htmlfill
33 33 from pylons import request, tmpl_context as c, config
34 34 from pylons.i18n.translation import _
35 35 from webob.exc import HTTPFound
36 36
37 37 from kallithea.config.routing import url
38 38 from kallithea.lib import helpers as h
39 39 from kallithea.lib.auth import LoginRequired, HasPermissionAnyDecorator
40 40 from kallithea.lib.base import BaseController, render
41 41 from kallithea.lib.celerylib import tasks
42 42 from kallithea.lib.exceptions import HgsubversionImportError
43 43 from kallithea.lib.utils import repo2db_mapper, set_app_settings
44 44 from kallithea.model.db import Ui, Repository, Setting
45 45 from kallithea.model.forms import ApplicationSettingsForm, \
46 46 ApplicationUiSettingsForm, ApplicationVisualisationForm
47 47 from kallithea.model.scm import ScmModel
48 48 from kallithea.model.notification import EmailNotificationModel
49 49 from kallithea.model.meta import Session
50 50 from kallithea.lib.utils2 import str2bool, safe_unicode
51 51 log = logging.getLogger(__name__)
52 52
53 53
54 54 class SettingsController(BaseController):
55 55 """REST Controller styled on the Atom Publishing Protocol"""
56 56 # To properly map this controller, ensure your config/routing.py
57 57 # file has a resource setup:
58 58 # map.resource('setting', 'settings', controller='admin/settings',
59 59 # path_prefix='/admin', name_prefix='admin_')
60 60
61 61 @LoginRequired()
62 62 def __before__(self):
63 63 super(SettingsController, self).__before__()
64 64
65 65 def _get_hg_ui_settings(self):
66 66 ret = Ui.query().all()
67 67
68 68 settings = {}
69 69 for each in ret:
70 70 k = each.ui_section + '_' + each.ui_key
71 71 v = each.ui_value
72 72 if k == 'paths_/':
73 73 k = 'paths_root_path'
74 74
75 75 k = k.replace('.', '_')
76 76
77 77 if each.ui_section in ['hooks', 'extensions']:
78 78 v = each.ui_active
79 79
80 80 settings[k] = v
81 81 return settings
82 82
83 83 @HasPermissionAnyDecorator('hg.admin')
84 84 def settings_vcs(self):
85 85 c.active = 'vcs'
86 86 if request.POST:
87 87 application_form = ApplicationUiSettingsForm()()
88 88 try:
89 89 form_result = application_form.to_python(dict(request.POST))
90 90 except formencode.Invalid as errors:
91 91 return htmlfill.render(
92 92 render('admin/settings/settings.html'),
93 93 defaults=errors.value,
94 94 errors=errors.error_dict or {},
95 95 prefix_error=False,
96 96 encoding="UTF-8",
97 97 force_defaults=False)
98 98
99 99 try:
100 100 if c.visual.allow_repo_location_change:
101 101 sett = Ui.get_by_key('paths', '/')
102 102 sett.ui_value = form_result['paths_root_path']
103 103
104 104 #HOOKS
105 105 sett = Ui.get_by_key('hooks', Ui.HOOK_UPDATE)
106 106 sett.ui_active = form_result['hooks_changegroup_update']
107 107
108 108 sett = Ui.get_by_key('hooks', Ui.HOOK_REPO_SIZE)
109 109 sett.ui_active = form_result['hooks_changegroup_repo_size']
110 110
111 111 sett = Ui.get_by_key('hooks', Ui.HOOK_PUSH)
112 112 sett.ui_active = form_result['hooks_changegroup_push_logger']
113 113
114 114 sett = Ui.get_by_key('hooks', Ui.HOOK_PULL)
115 115 sett.ui_active = form_result['hooks_outgoing_pull_logger']
116 116
117 117 ## EXTENSIONS
118 118 sett = Ui.get_or_create('extensions', 'largefiles')
119 119 sett.ui_active = form_result['extensions_largefiles']
120 120
121 121 sett = Ui.get_or_create('extensions', 'hgsubversion')
122 122 sett.ui_active = form_result['extensions_hgsubversion']
123 123 if sett.ui_active:
124 124 try:
125 125 import hgsubversion # pragma: no cover
126 126 except ImportError:
127 127 raise HgsubversionImportError
128 128
129 129 # sett = Ui.get_or_create('extensions', 'hggit')
130 130 # sett.ui_active = form_result['extensions_hggit']
131 131
132 132 Session().commit()
133 133
134 134 h.flash(_('Updated VCS settings'), category='success')
135 135
136 136 except HgsubversionImportError:
137 137 log.error(traceback.format_exc())
138 138 h.flash(_('Unable to activate hgsubversion support. '
139 139 'The "hgsubversion" library is missing'),
140 140 category='error')
141 141
142 142 except Exception:
143 143 log.error(traceback.format_exc())
144 144 h.flash(_('Error occurred while updating '
145 145 'application settings'), category='error')
146 146
147 147 defaults = Setting.get_app_settings()
148 148 defaults.update(self._get_hg_ui_settings())
149 149
150 150 return htmlfill.render(
151 151 render('admin/settings/settings.html'),
152 152 defaults=defaults,
153 153 encoding="UTF-8",
154 154 force_defaults=False)
155 155
156 156 @HasPermissionAnyDecorator('hg.admin')
157 157 def settings_mapping(self):
158 158 c.active = 'mapping'
159 159 if request.POST:
160 160 rm_obsolete = request.POST.get('destroy', False)
161 161 install_git_hooks = request.POST.get('hooks', False)
162 162 overwrite_git_hooks = request.POST.get('hooks_overwrite', False);
163 163 invalidate_cache = request.POST.get('invalidate', False)
164 164 log.debug('rescanning repo location with destroy obsolete=%s, '
165 165 'install git hooks=%s and '
166 166 'overwrite git hooks=%s' % (rm_obsolete, install_git_hooks, overwrite_git_hooks))
167 167
168 168 if invalidate_cache:
169 169 log.debug('invalidating all repositories cache')
170 170 for repo in Repository.query():
171 171 ScmModel().mark_for_invalidation(repo.repo_name)
172 172
173 173 filesystem_repos = ScmModel().repo_scan()
174 174 added, removed = repo2db_mapper(filesystem_repos, rm_obsolete,
175 175 install_git_hooks=install_git_hooks,
176 176 user=c.authuser.username,
177 177 overwrite_git_hooks=overwrite_git_hooks)
178 178 h.flash(h.literal(_('Repositories successfully rescanned. Added: %s. Removed: %s.') %
179 179 (', '.join(h.link_to(safe_unicode(repo_name), h.url('summary_home', repo_name=repo_name))
180 180 for repo_name in added) or '-',
181 181 ', '.join(h.escape(safe_unicode(repo_name)) for repo_name in removed) or '-')),
182 182 category='success')
183 183 raise HTTPFound(location=url('admin_settings_mapping'))
184 184
185 185 defaults = Setting.get_app_settings()
186 186 defaults.update(self._get_hg_ui_settings())
187 187
188 188 return htmlfill.render(
189 189 render('admin/settings/settings.html'),
190 190 defaults=defaults,
191 191 encoding="UTF-8",
192 192 force_defaults=False)
193 193
194 194 @HasPermissionAnyDecorator('hg.admin')
195 195 def settings_global(self):
196 196 c.active = 'global'
197 197 if request.POST:
198 198 application_form = ApplicationSettingsForm()()
199 199 try:
200 200 form_result = application_form.to_python(dict(request.POST))
201 201 except formencode.Invalid as errors:
202 202 return htmlfill.render(
203 203 render('admin/settings/settings.html'),
204 204 defaults=errors.value,
205 205 errors=errors.error_dict or {},
206 206 prefix_error=False,
207 207 encoding="UTF-8",
208 208 force_defaults=False)
209 209
210 210 try:
211 211 for setting in (
212 212 'title',
213 213 'realm',
214 214 'ga_code',
215 215 'captcha_public_key',
216 216 'captcha_private_key',
217 217 ):
218 218 Setting.create_or_update(setting, form_result[setting])
219 219
220 220 Session().commit()
221 221 set_app_settings(config)
222 222 h.flash(_('Updated application settings'), category='success')
223 223
224 224 except Exception:
225 225 log.error(traceback.format_exc())
226 226 h.flash(_('Error occurred while updating '
227 227 'application settings'),
228 228 category='error')
229 229
230 230 raise HTTPFound(location=url('admin_settings_global'))
231 231
232 232 defaults = Setting.get_app_settings()
233 233 defaults.update(self._get_hg_ui_settings())
234 234
235 235 return htmlfill.render(
236 236 render('admin/settings/settings.html'),
237 237 defaults=defaults,
238 238 encoding="UTF-8",
239 239 force_defaults=False)
240 240
241 241 @HasPermissionAnyDecorator('hg.admin')
242 242 def settings_visual(self):
243 243 c.active = 'visual'
244 244 if request.POST:
245 245 application_form = ApplicationVisualisationForm()()
246 246 try:
247 247 form_result = application_form.to_python(dict(request.POST))
248 248 except formencode.Invalid as errors:
249 249 return htmlfill.render(
250 250 render('admin/settings/settings.html'),
251 251 defaults=errors.value,
252 252 errors=errors.error_dict or {},
253 253 prefix_error=False,
254 254 encoding="UTF-8",
255 255 force_defaults=False)
256 256
257 257 try:
258 258 settings = [
259 259 ('show_public_icon', 'show_public_icon', 'bool'),
260 260 ('show_private_icon', 'show_private_icon', 'bool'),
261 261 ('stylify_metatags', 'stylify_metatags', 'bool'),
262 262 ('repository_fields', 'repository_fields', 'bool'),
263 ('dashboard_items', 'dashboard_items', 'int'),
263 ('dashboard_items', 'page_size', 'int'),
264 264 ('admin_grid_items', 'admin_grid_items', 'int'),
265 265 ('show_version', 'show_version', 'bool'),
266 266 ('use_gravatar', 'use_gravatar', 'bool'),
267 267 ('gravatar_url', 'gravatar_url', 'unicode'),
268 268 ('clone_uri_tmpl', 'clone_uri_tmpl', 'unicode'),
269 269 ]
270 270 for setting, form_key, type_ in settings:
271 271 Setting.create_or_update(setting, form_result[form_key], type_)
272 272
273 273 Session().commit()
274 274 set_app_settings(config)
275 275 h.flash(_('Updated visualisation settings'),
276 276 category='success')
277 277
278 278 except Exception:
279 279 log.error(traceback.format_exc())
280 280 h.flash(_('Error occurred during updating '
281 281 'visualisation settings'),
282 282 category='error')
283 283
284 284 raise HTTPFound(location=url('admin_settings_visual'))
285 285
286 286 defaults = Setting.get_app_settings()
287 287 defaults.update(self._get_hg_ui_settings())
288 288
289 289 return htmlfill.render(
290 290 render('admin/settings/settings.html'),
291 291 defaults=defaults,
292 292 encoding="UTF-8",
293 293 force_defaults=False)
294 294
295 295 @HasPermissionAnyDecorator('hg.admin')
296 296 def settings_email(self):
297 297 c.active = 'email'
298 298 if request.POST:
299 299 test_email = request.POST.get('test_email')
300 300 test_email_subj = 'Kallithea test email'
301 301 test_body = ('Kallithea Email test, '
302 302 'Kallithea version: %s' % c.kallithea_version)
303 303 if not test_email:
304 304 h.flash(_('Please enter email address'), category='error')
305 305 raise HTTPFound(location=url('admin_settings_email'))
306 306
307 307 test_email_txt_body = EmailNotificationModel() \
308 308 .get_email_tmpl(EmailNotificationModel.TYPE_DEFAULT,
309 309 'txt', body=test_body)
310 310 test_email_html_body = EmailNotificationModel() \
311 311 .get_email_tmpl(EmailNotificationModel.TYPE_DEFAULT,
312 312 'html', body=test_body)
313 313
314 314 recipients = [test_email] if test_email else None
315 315
316 316 tasks.send_email(recipients, test_email_subj,
317 317 test_email_txt_body, test_email_html_body)
318 318
319 319 h.flash(_('Send email task created'), category='success')
320 320 raise HTTPFound(location=url('admin_settings_email'))
321 321
322 322 defaults = Setting.get_app_settings()
323 323 defaults.update(self._get_hg_ui_settings())
324 324
325 325 import kallithea
326 326 c.ini = kallithea.CONFIG
327 327
328 328 return htmlfill.render(
329 329 render('admin/settings/settings.html'),
330 330 defaults=defaults,
331 331 encoding="UTF-8",
332 332 force_defaults=False)
333 333
334 334 @HasPermissionAnyDecorator('hg.admin')
335 335 def settings_hooks(self):
336 336 c.active = 'hooks'
337 337 if request.POST:
338 338 if c.visual.allow_custom_hooks_settings:
339 339 ui_key = request.POST.get('new_hook_ui_key')
340 340 ui_value = request.POST.get('new_hook_ui_value')
341 341
342 342 hook_id = request.POST.get('hook_id')
343 343
344 344 try:
345 345 ui_key = ui_key and ui_key.strip()
346 346 if ui_value and ui_key:
347 347 Ui.create_or_update_hook(ui_key, ui_value)
348 348 h.flash(_('Added new hook'), category='success')
349 349 elif hook_id:
350 350 Ui.delete(hook_id)
351 351 Session().commit()
352 352
353 353 # check for edits
354 354 update = False
355 355 _d = request.POST.dict_of_lists()
356 356 for k, v in zip(_d.get('hook_ui_key', []),
357 357 _d.get('hook_ui_value_new', [])):
358 358 Ui.create_or_update_hook(k, v)
359 359 update = True
360 360
361 361 if update:
362 362 h.flash(_('Updated hooks'), category='success')
363 363 Session().commit()
364 364 except Exception:
365 365 log.error(traceback.format_exc())
366 366 h.flash(_('Error occurred during hook creation'),
367 367 category='error')
368 368
369 369 raise HTTPFound(location=url('admin_settings_hooks'))
370 370
371 371 defaults = Setting.get_app_settings()
372 372 defaults.update(self._get_hg_ui_settings())
373 373
374 374 c.hooks = Ui.get_builtin_hooks()
375 375 c.custom_hooks = Ui.get_custom_hooks()
376 376
377 377 return htmlfill.render(
378 378 render('admin/settings/settings.html'),
379 379 defaults=defaults,
380 380 encoding="UTF-8",
381 381 force_defaults=False)
382 382
383 383 @HasPermissionAnyDecorator('hg.admin')
384 384 def settings_search(self):
385 385 c.active = 'search'
386 386 if request.POST:
387 387 repo_location = self._get_hg_ui_settings()['paths_root_path']
388 388 full_index = request.POST.get('full_index', False)
389 389 tasks.whoosh_index(repo_location, full_index)
390 390 h.flash(_('Whoosh reindex task scheduled'), category='success')
391 391 raise HTTPFound(location=url('admin_settings_search'))
392 392
393 393 defaults = Setting.get_app_settings()
394 394 defaults.update(self._get_hg_ui_settings())
395 395
396 396 return htmlfill.render(
397 397 render('admin/settings/settings.html'),
398 398 defaults=defaults,
399 399 encoding="UTF-8",
400 400 force_defaults=False)
401 401
402 402 @HasPermissionAnyDecorator('hg.admin')
403 403 def settings_system(self):
404 404 c.active = 'system'
405 405
406 406 defaults = Setting.get_app_settings()
407 407 defaults.update(self._get_hg_ui_settings())
408 408
409 409 import kallithea
410 410 c.ini = kallithea.CONFIG
411 411 c.update_url = defaults.get('update_url')
412 412 server_info = Setting.get_server_info()
413 413 for key, val in server_info.iteritems():
414 414 setattr(c, key, val)
415 415
416 416 return htmlfill.render(
417 417 render('admin/settings/settings.html'),
418 418 defaults=defaults,
419 419 encoding="UTF-8",
420 420 force_defaults=False)
421 421
422 422 @HasPermissionAnyDecorator('hg.admin')
423 423 def settings_system_update(self):
424 424 import json
425 425 import urllib2
426 426 from kallithea.lib.verlib import NormalizedVersion
427 427 from kallithea import __version__
428 428
429 429 defaults = Setting.get_app_settings()
430 430 defaults.update(self._get_hg_ui_settings())
431 431 _update_url = defaults.get('update_url', '')
432 432 _update_url = "" # FIXME: disabled
433 433
434 434 _err = lambda s: '<div class="alert alert-danger">%s</div>' % (s)
435 435 try:
436 436 import kallithea
437 437 ver = kallithea.__version__
438 438 log.debug('Checking for upgrade on `%s` server', _update_url)
439 439 opener = urllib2.build_opener()
440 440 opener.addheaders = [('User-agent', 'Kallithea-SCM/%s' % ver)]
441 441 response = opener.open(_update_url)
442 442 response_data = response.read()
443 443 data = json.loads(response_data)
444 444 except urllib2.URLError as e:
445 445 log.error(traceback.format_exc())
446 446 return _err('Failed to contact upgrade server: %r' % e)
447 447 except ValueError as e:
448 448 log.error(traceback.format_exc())
449 449 return _err('Bad data sent from update server')
450 450
451 451 latest = data['versions'][0]
452 452
453 453 c.update_url = _update_url
454 454 c.latest_data = latest
455 455 c.latest_ver = latest['version']
456 456 c.cur_ver = __version__
457 457 c.should_upgrade = False
458 458
459 459 if NormalizedVersion(c.latest_ver) > NormalizedVersion(c.cur_ver):
460 460 c.should_upgrade = True
461 461 c.important_notices = latest['general']
462 462
463 463 return render('admin/settings/settings_system_update.html'),
@@ -1,528 +1,528 b''
1 1 # -*- coding: utf-8 -*-
2 2 # This program is free software: you can redistribute it and/or modify
3 3 # it under the terms of the GNU General Public License as published by
4 4 # the Free Software Foundation, either version 3 of the License, or
5 5 # (at your option) any later version.
6 6 #
7 7 # This program is distributed in the hope that it will be useful,
8 8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 10 # GNU General Public License for more details.
11 11 #
12 12 # You should have received a copy of the GNU General Public License
13 13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
14 14
15 15 """
16 16 kallithea.lib.base
17 17 ~~~~~~~~~~~~~~~~~~
18 18
19 19 The base Controller API
20 20 Provides the BaseController class for subclassing. And usage in different
21 21 controllers
22 22
23 23 This file was forked by the Kallithea project in July 2014.
24 24 Original author and date, and relevant copyright and licensing information is below:
25 25 :created_on: Oct 06, 2010
26 26 :author: marcink
27 27 :copyright: (c) 2013 RhodeCode GmbH, and others.
28 28 :license: GPLv3, see LICENSE.md for more details.
29 29 """
30 30
31 31 import datetime
32 32 import logging
33 33 import time
34 34 import traceback
35 35
36 36 import webob.exc
37 37 import paste.httpexceptions
38 38 import paste.auth.basic
39 39 import paste.httpheaders
40 40
41 41 from pylons import config, tmpl_context as c, request, session
42 42 from pylons.controllers import WSGIController
43 43 from pylons.templating import render_mako as render # don't remove this import
44 44 from pylons.i18n.translation import _
45 45
46 46 from kallithea import __version__, BACKENDS
47 47
48 48 from kallithea.config.routing import url
49 49 from kallithea.lib.utils2 import str2bool, safe_unicode, AttributeDict, \
50 50 safe_str, safe_int
51 51 from kallithea.lib import auth_modules
52 52 from kallithea.lib.auth import AuthUser, HasPermissionAnyMiddleware
53 53 from kallithea.lib.utils import get_repo_slug
54 54 from kallithea.lib.exceptions import UserCreationError
55 55 from kallithea.lib.vcs.exceptions import RepositoryError, EmptyRepositoryError, ChangesetDoesNotExistError
56 56 from kallithea.model import meta
57 57
58 58 from kallithea.model.db import PullRequest, Repository, Ui, User, Setting
59 59 from kallithea.model.notification import NotificationModel
60 60 from kallithea.model.scm import ScmModel
61 61
62 62 log = logging.getLogger(__name__)
63 63
64 64
65 65 def _filter_proxy(ip):
66 66 """
67 67 HEADERS can have multiple ips inside the left-most being the original
68 68 client, and each successive proxy that passed the request adding the IP
69 69 address where it received the request from.
70 70
71 71 :param ip:
72 72 """
73 73 if ',' in ip:
74 74 _ips = ip.split(',')
75 75 _first_ip = _ips[0].strip()
76 76 log.debug('Got multiple IPs %s, using %s', ','.join(_ips), _first_ip)
77 77 return _first_ip
78 78 return ip
79 79
80 80
81 81 def _get_ip_addr(environ):
82 82 proxy_key = 'HTTP_X_REAL_IP'
83 83 proxy_key2 = 'HTTP_X_FORWARDED_FOR'
84 84 def_key = 'REMOTE_ADDR'
85 85
86 86 ip = environ.get(proxy_key)
87 87 if ip:
88 88 return _filter_proxy(ip)
89 89
90 90 ip = environ.get(proxy_key2)
91 91 if ip:
92 92 return _filter_proxy(ip)
93 93
94 94 ip = environ.get(def_key, '0.0.0.0')
95 95 return _filter_proxy(ip)
96 96
97 97
98 98 def _get_access_path(environ):
99 99 path = environ.get('PATH_INFO')
100 100 org_req = environ.get('pylons.original_request')
101 101 if org_req:
102 102 path = org_req.environ.get('PATH_INFO')
103 103 return path
104 104
105 105
106 106 def log_in_user(user, remember, is_external_auth):
107 107 """
108 108 Log a `User` in and update session and cookies. If `remember` is True,
109 109 the session cookie is set to expire in a year; otherwise, it expires at
110 110 the end of the browser session.
111 111
112 112 Returns populated `AuthUser` object.
113 113 """
114 114 user.update_lastlogin()
115 115 meta.Session().commit()
116 116
117 117 auth_user = AuthUser(dbuser=user,
118 118 is_external_auth=is_external_auth)
119 119 # It should not be possible to explicitly log in as the default user.
120 120 assert not auth_user.is_default_user
121 121 auth_user.is_authenticated = True
122 122
123 123 # Start new session to prevent session fixation attacks.
124 124 session.invalidate()
125 125 session['authuser'] = cookie = auth_user.to_cookie()
126 126
127 127 # If they want to be remembered, update the cookie.
128 128 # NOTE: Assumes that beaker defaults to browser session cookie.
129 129 if remember:
130 130 t = datetime.datetime.now() + datetime.timedelta(days=365)
131 131 session._set_cookie_expires(t)
132 132
133 133 session.save()
134 134
135 135 log.info('user %s is now authenticated and stored in '
136 136 'session, session attrs %s', user.username, cookie)
137 137
138 138 # dumps session attrs back to cookie
139 139 session._update_cookie_out()
140 140
141 141 return auth_user
142 142
143 143
144 144 class BasicAuth(paste.auth.basic.AuthBasicAuthenticator):
145 145
146 146 def __init__(self, realm, authfunc, auth_http_code=None):
147 147 self.realm = realm
148 148 self.authfunc = authfunc
149 149 self._rc_auth_http_code = auth_http_code
150 150
151 151 def build_authentication(self):
152 152 head = paste.httpheaders.WWW_AUTHENTICATE.tuples('Basic realm="%s"' % self.realm)
153 153 if self._rc_auth_http_code and self._rc_auth_http_code == '403':
154 154 # return 403 if alternative http return code is specified in
155 155 # Kallithea config
156 156 return paste.httpexceptions.HTTPForbidden(headers=head)
157 157 return paste.httpexceptions.HTTPUnauthorized(headers=head)
158 158
159 159 def authenticate(self, environ):
160 160 authorization = paste.httpheaders.AUTHORIZATION(environ)
161 161 if not authorization:
162 162 return self.build_authentication()
163 163 (authmeth, auth) = authorization.split(' ', 1)
164 164 if 'basic' != authmeth.lower():
165 165 return self.build_authentication()
166 166 auth = auth.strip().decode('base64')
167 167 _parts = auth.split(':', 1)
168 168 if len(_parts) == 2:
169 169 username, password = _parts
170 170 if self.authfunc(username, password, environ) is not None:
171 171 return username
172 172 return self.build_authentication()
173 173
174 174 __call__ = authenticate
175 175
176 176
177 177 class BaseVCSController(object):
178 178
179 179 def __init__(self, application, config):
180 180 self.application = application
181 181 self.config = config
182 182 # base path of repo locations
183 183 self.basepath = self.config['base_path']
184 184 # authenticate this VCS request using the authentication modules
185 185 self.authenticate = BasicAuth('', auth_modules.authenticate,
186 186 config.get('auth_ret_code'))
187 187 self.ip_addr = '0.0.0.0'
188 188
189 189 def _handle_request(self, environ, start_response):
190 190 raise NotImplementedError()
191 191
192 192 def _get_by_id(self, repo_name):
193 193 """
194 194 Gets a special pattern _<ID> from clone url and tries to replace it
195 195 with a repository_name for support of _<ID> permanent URLs
196 196
197 197 :param repo_name:
198 198 """
199 199
200 200 data = repo_name.split('/')
201 201 if len(data) >= 2:
202 202 from kallithea.lib.utils import get_repo_by_id
203 203 by_id_match = get_repo_by_id(repo_name)
204 204 if by_id_match:
205 205 data[1] = safe_str(by_id_match)
206 206
207 207 return '/'.join(data)
208 208
209 209 def _invalidate_cache(self, repo_name):
210 210 """
211 211 Sets cache for this repository for invalidation on next access
212 212
213 213 :param repo_name: full repo name, also a cache key
214 214 """
215 215 ScmModel().mark_for_invalidation(repo_name)
216 216
217 217 def _check_permission(self, action, user, repo_name, ip_addr=None):
218 218 """
219 219 Checks permissions using action (push/pull) user and repository
220 220 name
221 221
222 222 :param action: push or pull action
223 223 :param user: `User` instance
224 224 :param repo_name: repository name
225 225 """
226 226 # check IP
227 227 ip_allowed = AuthUser.check_ip_allowed(user, ip_addr)
228 228 if ip_allowed:
229 229 log.info('Access for IP:%s allowed', ip_addr)
230 230 else:
231 231 return False
232 232
233 233 if action == 'push':
234 234 if not HasPermissionAnyMiddleware('repository.write',
235 235 'repository.admin')(user,
236 236 repo_name):
237 237 return False
238 238
239 239 else:
240 240 #any other action need at least read permission
241 241 if not HasPermissionAnyMiddleware('repository.read',
242 242 'repository.write',
243 243 'repository.admin')(user,
244 244 repo_name):
245 245 return False
246 246
247 247 return True
248 248
249 249 def _get_ip_addr(self, environ):
250 250 return _get_ip_addr(environ)
251 251
252 252 def _check_locking_state(self, environ, action, repo, user_id):
253 253 """
254 254 Checks locking on this repository, if locking is enabled and lock is
255 255 present returns a tuple of make_lock, locked, locked_by.
256 256 make_lock can have 3 states None (do nothing) True, make lock
257 257 False release lock, This value is later propagated to hooks, which
258 258 do the locking. Think about this as signals passed to hooks what to do.
259 259
260 260 """
261 261 locked = False # defines that locked error should be thrown to user
262 262 make_lock = None
263 263 repo = Repository.get_by_repo_name(repo)
264 264 user = User.get(user_id)
265 265
266 266 # this is kind of hacky, but due to how mercurial handles client-server
267 267 # server see all operation on changeset; bookmarks, phases and
268 268 # obsolescence marker in different transaction, we don't want to check
269 269 # locking on those
270 270 obsolete_call = environ['QUERY_STRING'] in ['cmd=listkeys',]
271 271 locked_by = repo.locked
272 272 if repo and repo.enable_locking and not obsolete_call:
273 273 if action == 'push':
274 274 #check if it's already locked !, if it is compare users
275 275 user_id, _date = repo.locked
276 276 if user.user_id == user_id:
277 277 log.debug('Got push from user %s, now unlocking', user)
278 278 # unlock if we have push from user who locked
279 279 make_lock = False
280 280 else:
281 281 # we're not the same user who locked, ban with 423 !
282 282 locked = True
283 283 if action == 'pull':
284 284 if repo.locked[0] and repo.locked[1]:
285 285 locked = True
286 286 else:
287 287 log.debug('Setting lock on repo %s by %s', repo, user)
288 288 make_lock = True
289 289
290 290 else:
291 291 log.debug('Repository %s do not have locking enabled', repo)
292 292 log.debug('FINAL locking values make_lock:%s,locked:%s,locked_by:%s',
293 293 make_lock, locked, locked_by)
294 294 return make_lock, locked, locked_by
295 295
296 296 def __call__(self, environ, start_response):
297 297 start = time.time()
298 298 try:
299 299 return self._handle_request(environ, start_response)
300 300 finally:
301 301 log = logging.getLogger('kallithea.' + self.__class__.__name__)
302 302 log.debug('Request time: %.3fs', time.time() - start)
303 303 meta.Session.remove()
304 304
305 305
306 306 class BaseController(WSGIController):
307 307
308 308 def __before__(self):
309 309 """
310 310 __before__ is called before controller methods and after __call__
311 311 """
312 312 c.kallithea_version = __version__
313 313 rc_config = Setting.get_app_settings()
314 314
315 315 # Visual options
316 316 c.visual = AttributeDict({})
317 317
318 318 ## DB stored
319 319 c.visual.show_public_icon = str2bool(rc_config.get('show_public_icon'))
320 320 c.visual.show_private_icon = str2bool(rc_config.get('show_private_icon'))
321 321 c.visual.stylify_metatags = str2bool(rc_config.get('stylify_metatags'))
322 c.visual.dashboard_items = safe_int(rc_config.get('dashboard_items', 100))
322 c.visual.page_size = safe_int(rc_config.get('dashboard_items', 100))
323 323 c.visual.admin_grid_items = safe_int(rc_config.get('admin_grid_items', 100))
324 324 c.visual.repository_fields = str2bool(rc_config.get('repository_fields'))
325 325 c.visual.show_version = str2bool(rc_config.get('show_version'))
326 326 c.visual.use_gravatar = str2bool(rc_config.get('use_gravatar'))
327 327 c.visual.gravatar_url = rc_config.get('gravatar_url')
328 328
329 329 c.ga_code = rc_config.get('ga_code')
330 330 # TODO: replace undocumented backwards compatibility hack with db upgrade and rename ga_code
331 331 if c.ga_code and '<' not in c.ga_code:
332 332 c.ga_code = '''<script type="text/javascript">
333 333 var _gaq = _gaq || [];
334 334 _gaq.push(['_setAccount', '%s']);
335 335 _gaq.push(['_trackPageview']);
336 336
337 337 (function() {
338 338 var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
339 339 ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
340 340 var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
341 341 })();
342 342 </script>''' % c.ga_code
343 343 c.site_name = rc_config.get('title')
344 344 c.clone_uri_tmpl = rc_config.get('clone_uri_tmpl')
345 345
346 346 ## INI stored
347 347 c.visual.allow_repo_location_change = str2bool(config.get('allow_repo_location_change', True))
348 348 c.visual.allow_custom_hooks_settings = str2bool(config.get('allow_custom_hooks_settings', True))
349 349
350 350 c.instance_id = config.get('instance_id')
351 351 c.issues_url = config.get('bugtracker', url('issues_url'))
352 352 # END CONFIG VARS
353 353
354 354 c.repo_name = get_repo_slug(request) # can be empty
355 355 c.backends = BACKENDS.keys()
356 356 c.unread_notifications = NotificationModel() \
357 357 .get_unread_cnt_for_user(c.authuser.user_id)
358 358
359 359 self.cut_off_limit = safe_int(config.get('cut_off_limit'))
360 360
361 361 c.my_pr_count = PullRequest.query(reviewer_id=c.authuser.user_id, include_closed=False).count()
362 362
363 363 self.sa = meta.Session
364 364 self.scm_model = ScmModel(self.sa)
365 365
366 366 @staticmethod
367 367 def _determine_auth_user(api_key, session_authuser):
368 368 """
369 369 Create an `AuthUser` object given the API key (if any) and the
370 370 value of the authuser session cookie.
371 371 """
372 372
373 373 # Authenticate by API key
374 374 if api_key:
375 375 # when using API_KEY we are sure user exists.
376 376 return AuthUser(dbuser=User.get_by_api_key(api_key),
377 377 is_external_auth=True)
378 378
379 379 # Authenticate by session cookie
380 380 # In ancient login sessions, 'authuser' may not be a dict.
381 381 # In that case, the user will have to log in again.
382 382 # v0.3 and earlier included an 'is_authenticated' key; if present,
383 383 # this must be True.
384 384 if isinstance(session_authuser, dict) and session_authuser.get('is_authenticated', True):
385 385 try:
386 386 return AuthUser.from_cookie(session_authuser)
387 387 except UserCreationError as e:
388 388 # container auth or other auth functions that create users on
389 389 # the fly can throw UserCreationError to signal issues with
390 390 # user creation. Explanation should be provided in the
391 391 # exception object.
392 392 from kallithea.lib import helpers as h
393 393 h.flash(e, 'error', logf=log.error)
394 394
395 395 # Authenticate by auth_container plugin (if enabled)
396 396 if any(
397 397 auth_modules.importplugin(name).is_container_auth
398 398 for name in Setting.get_auth_plugins()
399 399 ):
400 400 try:
401 401 user_info = auth_modules.authenticate('', '', request.environ)
402 402 except UserCreationError as e:
403 403 from kallithea.lib import helpers as h
404 404 h.flash(e, 'error', logf=log.error)
405 405 else:
406 406 if user_info is not None:
407 407 username = user_info['username']
408 408 user = User.get_by_username(username, case_insensitive=True)
409 409 return log_in_user(user, remember=False,
410 410 is_external_auth=True)
411 411
412 412 # User is anonymous
413 413 return AuthUser()
414 414
415 415 def __call__(self, environ, start_response):
416 416 """Invoke the Controller"""
417 417
418 418 # WSGIController.__call__ dispatches to the Controller method
419 419 # the request is routed to. This routing information is
420 420 # available in environ['pylons.routes_dict']
421 421 try:
422 422 self.ip_addr = _get_ip_addr(environ)
423 423 # make sure that we update permissions each time we call controller
424 424
425 425 #set globals for auth user
426 426 self.authuser = c.authuser = request.user = self._determine_auth_user(
427 427 request.GET.get('api_key'),
428 428 session.get('authuser'),
429 429 )
430 430
431 431 log.info('IP: %s User: %s accessed %s',
432 432 self.ip_addr, self.authuser,
433 433 safe_unicode(_get_access_path(environ)),
434 434 )
435 435 return WSGIController.__call__(self, environ, start_response)
436 436 finally:
437 437 meta.Session.remove()
438 438
439 439
440 440 class BaseRepoController(BaseController):
441 441 """
442 442 Base class for controllers responsible for loading all needed data for
443 443 repository loaded items are
444 444
445 445 c.db_repo_scm_instance: instance of scm repository
446 446 c.db_repo: instance of db
447 447 c.repository_followers: number of followers
448 448 c.repository_forks: number of forks
449 449 c.repository_following: weather the current user is following the current repo
450 450 """
451 451
452 452 def __before__(self):
453 453 super(BaseRepoController, self).__before__()
454 454 if c.repo_name: # extracted from routes
455 455 _dbr = Repository.get_by_repo_name(c.repo_name)
456 456 if not _dbr:
457 457 return
458 458
459 459 log.debug('Found repository in database %s with state `%s`',
460 460 safe_unicode(_dbr), safe_unicode(_dbr.repo_state))
461 461 route = getattr(request.environ.get('routes.route'), 'name', '')
462 462
463 463 # allow to delete repos that are somehow damages in filesystem
464 464 if route in ['delete_repo']:
465 465 return
466 466
467 467 if _dbr.repo_state in [Repository.STATE_PENDING]:
468 468 if route in ['repo_creating_home']:
469 469 return
470 470 check_url = url('repo_creating_home', repo_name=c.repo_name)
471 471 raise webob.exc.HTTPFound(location=check_url)
472 472
473 473 dbr = c.db_repo = _dbr
474 474 c.db_repo_scm_instance = c.db_repo.scm_instance
475 475 if c.db_repo_scm_instance is None:
476 476 log.error('%s this repository is present in database but it '
477 477 'cannot be created as an scm instance', c.repo_name)
478 478 from kallithea.lib import helpers as h
479 479 h.flash(h.literal(_('Repository not found in the filesystem')),
480 480 category='error')
481 481 raise paste.httpexceptions.HTTPNotFound()
482 482
483 483 # some globals counter for menu
484 484 c.repository_followers = self.scm_model.get_followers(dbr)
485 485 c.repository_forks = self.scm_model.get_forks(dbr)
486 486 c.repository_pull_requests = self.scm_model.get_pull_requests(dbr)
487 487 c.repository_following = self.scm_model.is_following_repo(
488 488 c.repo_name, self.authuser.user_id)
489 489
490 490 @staticmethod
491 491 def _get_ref_rev(repo, ref_type, ref_name, returnempty=False):
492 492 """
493 493 Safe way to get changeset. If error occurs show error.
494 494 """
495 495 from kallithea.lib import helpers as h
496 496 try:
497 497 return repo.scm_instance.get_ref_revision(ref_type, ref_name)
498 498 except EmptyRepositoryError as e:
499 499 if returnempty:
500 500 return repo.scm_instance.EMPTY_CHANGESET
501 501 h.flash(h.literal(_('There are no changesets yet')),
502 502 category='error')
503 503 raise webob.exc.HTTPNotFound()
504 504 except ChangesetDoesNotExistError as e:
505 505 h.flash(h.literal(_('Changeset not found')),
506 506 category='error')
507 507 raise webob.exc.HTTPNotFound()
508 508 except RepositoryError as e:
509 509 log.error(traceback.format_exc())
510 510 h.flash(safe_str(e), category='error')
511 511 raise webob.exc.HTTPBadRequest()
512 512
513 513
514 514 class WSGIResultCloseCallback(object):
515 515 """Wrap a WSGI result and let close call close after calling the
516 516 close method on the result.
517 517 """
518 518 def __init__(self, result, close):
519 519 self._result = result
520 520 self._close = close
521 521
522 522 def __iter__(self):
523 523 return iter(self._result)
524 524
525 525 def close(self):
526 526 if hasattr(self._result, 'close'):
527 527 self._result.close()
528 528 self._close()
@@ -1,499 +1,499 b''
1 1 # -*- coding: utf-8 -*-
2 2 # This program is free software: you can redistribute it and/or modify
3 3 # it under the terms of the GNU General Public License as published by
4 4 # the Free Software Foundation, either version 3 of the License, or
5 5 # (at your option) any later version.
6 6 #
7 7 # This program is distributed in the hope that it will be useful,
8 8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 10 # GNU General Public License for more details.
11 11 #
12 12 # You should have received a copy of the GNU General Public License
13 13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
14 14 """
15 15 kallithea.lib.db_manage
16 16 ~~~~~~~~~~~~~~~~~~~~~~~
17 17
18 18 Database creation, and setup module for Kallithea. Used for creation
19 19 of database as well as for migration operations
20 20
21 21 This file was forked by the Kallithea project in July 2014.
22 22 Original author and date, and relevant copyright and licensing information is below:
23 23 :created_on: Apr 10, 2010
24 24 :author: marcink
25 25 :copyright: (c) 2013 RhodeCode GmbH, and others.
26 26 :license: GPLv3, see LICENSE.md for more details.
27 27 """
28 28
29 29 import os
30 30 import sys
31 31 import time
32 32 import uuid
33 33 import logging
34 34 from os.path import dirname
35 35
36 36 import alembic.config
37 37 import alembic.command
38 38
39 39 from kallithea.lib.paster_commands.common import ask_ok
40 40 from kallithea.model.user import UserModel
41 41 from kallithea.model.base import init_model
42 42 from kallithea.model.db import User, Permission, Ui, \
43 43 Setting, UserToPerm, RepoGroup, \
44 44 UserRepoGroupToPerm, CacheInvalidation, Repository
45 45
46 46 from sqlalchemy.engine import create_engine
47 47 from kallithea.model.repo_group import RepoGroupModel
48 48 #from kallithea.model import meta
49 49 from kallithea.model.meta import Session, Base
50 50 from kallithea.model.repo import RepoModel
51 51 from kallithea.model.permission import PermissionModel
52 52
53 53
54 54 log = logging.getLogger(__name__)
55 55
56 56
57 57 def notify(msg):
58 58 """
59 59 Notification for migrations messages
60 60 """
61 61 ml = len(msg) + (4 * 2)
62 62 print('\n%s\n*** %s ***\n%s' % ('*' * ml, msg, '*' * ml)).upper()
63 63
64 64
65 65 class DbManage(object):
66 66 def __init__(self, log_sql, dbconf, root, tests=False, SESSION=None, cli_args=None):
67 67 self.dbname = dbconf.split('/')[-1]
68 68 self.tests = tests
69 69 self.root = root
70 70 self.dburi = dbconf
71 71 self.log_sql = log_sql
72 72 self.db_exists = False
73 73 self.cli_args = cli_args or {}
74 74 self.init_db(SESSION=SESSION)
75 75
76 76 def _ask_ok(self, msg):
77 77 """Invoke ask_ok unless the force_ask option provides the answer"""
78 78 force_ask = self.cli_args.get('force_ask')
79 79 if force_ask is not None:
80 80 return force_ask
81 81 return ask_ok(msg)
82 82
83 83 def init_db(self, SESSION=None):
84 84 if SESSION:
85 85 self.sa = SESSION
86 86 else:
87 87 #init new sessions
88 88 engine = create_engine(self.dburi, echo=self.log_sql)
89 89 init_model(engine)
90 90 self.sa = Session()
91 91
92 92 def create_tables(self, override=False):
93 93 """
94 94 Create a auth database
95 95 """
96 96
97 97 log.info("Any existing database is going to be destroyed")
98 98 if self.tests:
99 99 destroy = True
100 100 else:
101 101 destroy = self._ask_ok('Are you sure to destroy old database ? [y/n]')
102 102 if not destroy:
103 103 print 'Nothing done.'
104 104 sys.exit(0)
105 105 if destroy:
106 106 Base.metadata.drop_all()
107 107
108 108 checkfirst = not override
109 109 Base.metadata.create_all(checkfirst=checkfirst)
110 110
111 111 # Create an Alembic configuration and generate the version table,
112 112 # "stamping" it with the most recent Alembic migration revision, to
113 113 # tell Alembic that all the schema upgrades are already in effect.
114 114 alembic_cfg = alembic.config.Config()
115 115 alembic_cfg.set_main_option('script_location', 'kallithea:alembic')
116 116 alembic_cfg.set_main_option('sqlalchemy.url', self.dburi)
117 117 # This command will give an error in an Alembic multi-head scenario,
118 118 # but in practice, such a scenario should not come up during database
119 119 # creation, even during development.
120 120 alembic.command.stamp(alembic_cfg, 'head')
121 121
122 122 log.info('Created tables for %s', self.dbname)
123 123
124 124 def fix_repo_paths(self):
125 125 """
126 126 Fixes a old kallithea version path into new one without a '*'
127 127 """
128 128
129 129 paths = self.sa.query(Ui) \
130 130 .filter(Ui.ui_key == '/') \
131 131 .scalar()
132 132
133 133 paths.ui_value = paths.ui_value.replace('*', '')
134 134
135 135 self.sa.add(paths)
136 136 self.sa.commit()
137 137
138 138 def fix_default_user(self):
139 139 """
140 140 Fixes a old default user with some 'nicer' default values,
141 141 used mostly for anonymous access
142 142 """
143 143 def_user = self.sa.query(User) \
144 144 .filter(User.username == User.DEFAULT_USER) \
145 145 .one()
146 146
147 147 def_user.name = 'Anonymous'
148 148 def_user.lastname = 'User'
149 149 def_user.email = 'anonymous@kallithea-scm.org'
150 150
151 151 self.sa.add(def_user)
152 152 self.sa.commit()
153 153
154 154 def fix_settings(self):
155 155 """
156 156 Fixes kallithea settings adds ga_code key for google analytics
157 157 """
158 158
159 159 hgsettings3 = Setting('ga_code', '')
160 160
161 161 self.sa.add(hgsettings3)
162 162 self.sa.commit()
163 163
164 164 def admin_prompt(self, second=False):
165 165 if not self.tests:
166 166 import getpass
167 167
168 168 username = self.cli_args.get('username')
169 169 password = self.cli_args.get('password')
170 170 email = self.cli_args.get('email')
171 171
172 172 def get_password():
173 173 password = getpass.getpass('Specify admin password '
174 174 '(min 6 chars):')
175 175 confirm = getpass.getpass('Confirm password:')
176 176
177 177 if password != confirm:
178 178 log.error('passwords mismatch')
179 179 return False
180 180 if len(password) < 6:
181 181 log.error('password is to short use at least 6 characters')
182 182 return False
183 183
184 184 return password
185 185 if username is None:
186 186 username = raw_input('Specify admin username:')
187 187 if password is None:
188 188 password = get_password()
189 189 if not password:
190 190 #second try
191 191 password = get_password()
192 192 if not password:
193 193 sys.exit()
194 194 if email is None:
195 195 email = raw_input('Specify admin email:')
196 196 self.create_user(username, password, email, True)
197 197 else:
198 198 log.info('creating admin and regular test users')
199 199 from kallithea.tests.base import TEST_USER_ADMIN_LOGIN, \
200 200 TEST_USER_ADMIN_PASS, TEST_USER_ADMIN_EMAIL, \
201 201 TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS, \
202 202 TEST_USER_REGULAR_EMAIL, TEST_USER_REGULAR2_LOGIN, \
203 203 TEST_USER_REGULAR2_PASS, TEST_USER_REGULAR2_EMAIL
204 204
205 205 self.create_user(TEST_USER_ADMIN_LOGIN, TEST_USER_ADMIN_PASS,
206 206 TEST_USER_ADMIN_EMAIL, True)
207 207
208 208 self.create_user(TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS,
209 209 TEST_USER_REGULAR_EMAIL, False)
210 210
211 211 self.create_user(TEST_USER_REGULAR2_LOGIN, TEST_USER_REGULAR2_PASS,
212 212 TEST_USER_REGULAR2_EMAIL, False)
213 213
214 214 def create_ui_settings(self, repo_store_path):
215 215 """
216 216 Creates ui settings, fills out hooks
217 217 """
218 218
219 219 #HOOKS
220 220 hooks1_key = Ui.HOOK_UPDATE
221 221 hooks1_ = self.sa.query(Ui) \
222 222 .filter(Ui.ui_key == hooks1_key).scalar()
223 223
224 224 hooks1 = Ui() if hooks1_ is None else hooks1_
225 225 hooks1.ui_section = 'hooks'
226 226 hooks1.ui_key = hooks1_key
227 227 hooks1.ui_value = 'hg update >&2'
228 228 hooks1.ui_active = False
229 229 self.sa.add(hooks1)
230 230
231 231 hooks2_key = Ui.HOOK_REPO_SIZE
232 232 hooks2_ = self.sa.query(Ui) \
233 233 .filter(Ui.ui_key == hooks2_key).scalar()
234 234 hooks2 = Ui() if hooks2_ is None else hooks2_
235 235 hooks2.ui_section = 'hooks'
236 236 hooks2.ui_key = hooks2_key
237 237 hooks2.ui_value = 'python:kallithea.lib.hooks.repo_size'
238 238 self.sa.add(hooks2)
239 239
240 240 hooks3 = Ui()
241 241 hooks3.ui_section = 'hooks'
242 242 hooks3.ui_key = Ui.HOOK_PUSH
243 243 hooks3.ui_value = 'python:kallithea.lib.hooks.log_push_action'
244 244 self.sa.add(hooks3)
245 245
246 246 hooks4 = Ui()
247 247 hooks4.ui_section = 'hooks'
248 248 hooks4.ui_key = Ui.HOOK_PRE_PUSH
249 249 hooks4.ui_value = 'python:kallithea.lib.hooks.pre_push'
250 250 self.sa.add(hooks4)
251 251
252 252 hooks5 = Ui()
253 253 hooks5.ui_section = 'hooks'
254 254 hooks5.ui_key = Ui.HOOK_PULL
255 255 hooks5.ui_value = 'python:kallithea.lib.hooks.log_pull_action'
256 256 self.sa.add(hooks5)
257 257
258 258 hooks6 = Ui()
259 259 hooks6.ui_section = 'hooks'
260 260 hooks6.ui_key = Ui.HOOK_PRE_PULL
261 261 hooks6.ui_value = 'python:kallithea.lib.hooks.pre_pull'
262 262 self.sa.add(hooks6)
263 263
264 264 # enable largefiles
265 265 largefiles = Ui()
266 266 largefiles.ui_section = 'extensions'
267 267 largefiles.ui_key = 'largefiles'
268 268 largefiles.ui_value = ''
269 269 self.sa.add(largefiles)
270 270
271 271 # set default largefiles cache dir, defaults to
272 272 # /repo location/.cache/largefiles
273 273 largefiles = Ui()
274 274 largefiles.ui_section = 'largefiles'
275 275 largefiles.ui_key = 'usercache'
276 276 largefiles.ui_value = os.path.join(repo_store_path, '.cache',
277 277 'largefiles')
278 278 self.sa.add(largefiles)
279 279
280 280 # enable hgsubversion disabled by default
281 281 hgsubversion = Ui()
282 282 hgsubversion.ui_section = 'extensions'
283 283 hgsubversion.ui_key = 'hgsubversion'
284 284 hgsubversion.ui_value = ''
285 285 hgsubversion.ui_active = False
286 286 self.sa.add(hgsubversion)
287 287
288 288 # enable hggit disabled by default
289 289 hggit = Ui()
290 290 hggit.ui_section = 'extensions'
291 291 hggit.ui_key = 'hggit'
292 292 hggit.ui_value = ''
293 293 hggit.ui_active = False
294 294 self.sa.add(hggit)
295 295
296 296 def create_auth_plugin_options(self, skip_existing=False):
297 297 """
298 298 Create default auth plugin settings, and make it active
299 299
300 300 :param skip_existing:
301 301 """
302 302
303 303 for k, v, t in [('auth_plugins', 'kallithea.lib.auth_modules.auth_internal', 'list'),
304 304 ('auth_internal_enabled', 'True', 'bool')]:
305 305 if skip_existing and Setting.get_by_name(k) != None:
306 306 log.debug('Skipping option %s', k)
307 307 continue
308 308 setting = Setting(k, v, t)
309 309 self.sa.add(setting)
310 310
311 311 def create_default_options(self, skip_existing=False):
312 312 """Creates default settings"""
313 313
314 314 for k, v, t in [
315 315 ('default_repo_enable_locking', False, 'bool'),
316 316 ('default_repo_enable_downloads', False, 'bool'),
317 317 ('default_repo_enable_statistics', False, 'bool'),
318 318 ('default_repo_private', False, 'bool'),
319 319 ('default_repo_type', 'hg', 'unicode')]:
320 320
321 321 if skip_existing and Setting.get_by_name(k) is not None:
322 322 log.debug('Skipping option %s', k)
323 323 continue
324 324 setting = Setting(k, v, t)
325 325 self.sa.add(setting)
326 326
327 327 def fixup_groups(self):
328 328 def_usr = User.get_default_user()
329 329 for g in RepoGroup.query().all():
330 330 g.group_name = g.get_new_name(g.name)
331 331 self.sa.add(g)
332 332 # get default perm
333 333 default = UserRepoGroupToPerm.query() \
334 334 .filter(UserRepoGroupToPerm.group == g) \
335 335 .filter(UserRepoGroupToPerm.user == def_usr) \
336 336 .scalar()
337 337
338 338 if default is None:
339 339 log.debug('missing default permission for group %s adding', g)
340 340 perm_obj = RepoGroupModel()._create_default_perms(g)
341 341 self.sa.add(perm_obj)
342 342
343 343 def reset_permissions(self, username):
344 344 """
345 345 Resets permissions to default state, useful when old systems had
346 346 bad permissions, we must clean them up
347 347
348 348 :param username:
349 349 """
350 350 default_user = User.get_by_username(username)
351 351 if not default_user:
352 352 return
353 353
354 354 u2p = UserToPerm.query() \
355 355 .filter(UserToPerm.user == default_user).all()
356 356 fixed = False
357 357 if len(u2p) != len(Permission.DEFAULT_USER_PERMISSIONS):
358 358 for p in u2p:
359 359 Session().delete(p)
360 360 fixed = True
361 361 self.populate_default_permissions()
362 362 return fixed
363 363
364 364 def update_repo_info(self):
365 365 for repo in Repository.query():
366 366 repo.update_changeset_cache()
367 367
368 368 def config_prompt(self, test_repo_path='', retries=3):
369 369 _path = self.cli_args.get('repos_location')
370 370 if retries == 3:
371 371 log.info('Setting up repositories config')
372 372
373 373 if _path is not None:
374 374 path = _path
375 375 elif not self.tests and not test_repo_path:
376 376 path = raw_input(
377 377 'Enter a valid absolute path to store repositories. '
378 378 'All repositories in that path will be added automatically:'
379 379 )
380 380 else:
381 381 path = test_repo_path
382 382 path_ok = True
383 383
384 384 # check proper dir
385 385 if not os.path.isdir(path):
386 386 path_ok = False
387 387 log.error('Given path %s is not a valid directory', path)
388 388
389 389 elif not os.path.isabs(path):
390 390 path_ok = False
391 391 log.error('Given path %s is not an absolute path', path)
392 392
393 393 # check if path is at least readable.
394 394 if not os.access(path, os.R_OK):
395 395 path_ok = False
396 396 log.error('Given path %s is not readable', path)
397 397
398 398 # check write access, warn user about non writeable paths
399 399 elif not os.access(path, os.W_OK) and path_ok:
400 400 log.warning('No write permission to given path %s', path)
401 401 if not self._ask_ok('Given path %s is not writeable, do you want to '
402 402 'continue with read only mode ? [y/n]' % (path,)):
403 403 log.error('Canceled by user')
404 404 sys.exit(-1)
405 405
406 406 if retries == 0:
407 407 sys.exit('max retries reached')
408 408 if not path_ok:
409 409 if _path is not None:
410 410 sys.exit('Invalid repo path: %s' % _path)
411 411 retries -= 1
412 412 return self.config_prompt(test_repo_path, retries) # recursing!!!
413 413
414 414 real_path = os.path.normpath(os.path.realpath(path))
415 415
416 416 if real_path != os.path.normpath(path):
417 417 log.warning('Using normalized path %s instead of %s', real_path, path)
418 418
419 419 return real_path
420 420
421 421 def create_settings(self, path):
422 422
423 423 self.create_ui_settings(path)
424 424
425 425 ui_config = [
426 426 ('web', 'allow_archive', 'gz zip bz2'),
427 427 ('web', 'baseurl', '/'),
428 428 ('paths', '/', path),
429 429 #('phases', 'publish', 'false')
430 430 ]
431 431 for section, key, value in ui_config:
432 432 ui_conf = Ui()
433 433 setattr(ui_conf, 'ui_section', section)
434 434 setattr(ui_conf, 'ui_key', key)
435 435 setattr(ui_conf, 'ui_value', value)
436 436 self.sa.add(ui_conf)
437 437
438 438 settings = [
439 439 ('realm', 'Kallithea', 'unicode'),
440 440 ('title', '', 'unicode'),
441 441 ('ga_code', '', 'unicode'),
442 442 ('show_public_icon', True, 'bool'),
443 443 ('show_private_icon', True, 'bool'),
444 444 ('stylify_metatags', False, 'bool'),
445 ('dashboard_items', 100, 'int'),
445 ('dashboard_items', 100, 'int'), # TODO: call it page_size
446 446 ('admin_grid_items', 25, 'int'),
447 447 ('show_version', True, 'bool'),
448 448 ('use_gravatar', True, 'bool'),
449 449 ('gravatar_url', User.DEFAULT_GRAVATAR_URL, 'unicode'),
450 450 ('clone_uri_tmpl', Repository.DEFAULT_CLONE_URI, 'unicode'),
451 451 ('update_url', Setting.DEFAULT_UPDATE_URL, 'unicode'),
452 452 ]
453 453 for key, val, type_ in settings:
454 454 sett = Setting(key, val, type_)
455 455 self.sa.add(sett)
456 456
457 457 self.create_auth_plugin_options()
458 458 self.create_default_options()
459 459
460 460 log.info('created ui config')
461 461
462 462 def create_user(self, username, password, email='', admin=False):
463 463 log.info('creating user %s', username)
464 464 UserModel().create_or_update(username, password, email,
465 465 firstname=u'Kallithea', lastname=u'Admin',
466 466 active=True, admin=admin,
467 467 extern_type=User.DEFAULT_AUTH_TYPE)
468 468
469 469 def create_default_user(self):
470 470 log.info('creating default user')
471 471 # create default user for handling default permissions.
472 472 user = UserModel().create_or_update(username=User.DEFAULT_USER,
473 473 password=str(uuid.uuid1())[:20],
474 474 email='anonymous@kallithea-scm.org',
475 475 firstname=u'Anonymous',
476 476 lastname=u'User')
477 477 # based on configuration options activate/deactivate this user which
478 478 # controls anonymous access
479 479 if self.cli_args.get('public_access') is False:
480 480 log.info('Public access disabled')
481 481 user.active = False
482 482 Session().commit()
483 483
484 484 def create_permissions(self):
485 485 """
486 486 Creates all permissions defined in the system
487 487 """
488 488 # module.(access|create|change|delete)_[name]
489 489 # module.(none|read|write|admin)
490 490 log.info('creating permissions')
491 491 PermissionModel(self.sa).create_permissions()
492 492
493 493 def populate_default_permissions(self):
494 494 """
495 495 Populate default permissions. It will create only the default
496 496 permissions that are missing, and not alter already defined ones
497 497 """
498 498 log.info('creating default user permissions')
499 499 PermissionModel(self.sa).create_default_permissions(user=User.DEFAULT_USER)
@@ -1,564 +1,564 b''
1 1 # -*- coding: utf-8 -*-
2 2 # This program is free software: you can redistribute it and/or modify
3 3 # it under the terms of the GNU General Public License as published by
4 4 # the Free Software Foundation, either version 3 of the License, or
5 5 # (at your option) any later version.
6 6 #
7 7 # This program is distributed in the hope that it will be useful,
8 8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 10 # GNU General Public License for more details.
11 11 #
12 12 # You should have received a copy of the GNU General Public License
13 13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
14 14 """
15 15 these are form validation classes
16 16 http://formencode.org/module-formencode.validators.html
17 17 for list of all available validators
18 18
19 19 we can create our own validators
20 20
21 21 The table below outlines the options which can be used in a schema in addition to the validators themselves
22 22 pre_validators [] These validators will be applied before the schema
23 23 chained_validators [] These validators will be applied after the schema
24 24 allow_extra_fields False If True, then it is not an error when keys that aren't associated with a validator are present
25 25 filter_extra_fields False If True, then keys that aren't associated with a validator are removed
26 26 if_key_missing NoDefault If this is given, then any keys that aren't available but are expected will be replaced with this value (and then validated). This does not override a present .if_missing attribute on validators. NoDefault is a special FormEncode class to mean that no default values has been specified and therefore missing keys shouldn't take a default value.
27 27 ignore_key_missing False If True, then missing keys will be missing in the result, if the validator doesn't have .if_missing on it already
28 28
29 29
30 30 <name> = formencode.validators.<name of validator>
31 31 <name> must equal form name
32 32 list=[1,2,3,4,5]
33 33 for SELECT use formencode.All(OneOf(list), Int())
34 34
35 35 """
36 36 import logging
37 37
38 38 import formencode
39 39 from formencode import All
40 40
41 41 from pylons.i18n.translation import _
42 42
43 43 from kallithea import BACKENDS
44 44 from kallithea.model import validators as v
45 45
46 46 log = logging.getLogger(__name__)
47 47
48 48
49 49 class LoginForm(formencode.Schema):
50 50 allow_extra_fields = True
51 51 filter_extra_fields = True
52 52 username = v.UnicodeString(
53 53 strip=True,
54 54 min=1,
55 55 not_empty=True,
56 56 messages={
57 57 'empty': _('Please enter a login'),
58 58 'tooShort': _('Enter a value %(min)i characters long or more')}
59 59 )
60 60
61 61 password = v.UnicodeString(
62 62 strip=False,
63 63 min=3,
64 64 not_empty=True,
65 65 messages={
66 66 'empty': _('Please enter a password'),
67 67 'tooShort': _('Enter %(min)i characters or more')}
68 68 )
69 69
70 70 remember = v.StringBoolean(if_missing=False)
71 71
72 72 chained_validators = [v.ValidAuth()]
73 73
74 74
75 75 def PasswordChangeForm(username):
76 76 class _PasswordChangeForm(formencode.Schema):
77 77 allow_extra_fields = True
78 78 filter_extra_fields = True
79 79
80 80 current_password = v.ValidOldPassword(username)(not_empty=True)
81 81 new_password = All(v.ValidPassword(), v.UnicodeString(strip=False, min=6))
82 82 new_password_confirmation = All(v.ValidPassword(), v.UnicodeString(strip=False, min=6))
83 83
84 84 chained_validators = [v.ValidPasswordsMatch('new_password',
85 85 'new_password_confirmation')]
86 86 return _PasswordChangeForm
87 87
88 88
89 89 def UserForm(edit=False, old_data=None):
90 90 old_data = old_data or {}
91 91 class _UserForm(formencode.Schema):
92 92 allow_extra_fields = True
93 93 filter_extra_fields = True
94 94 username = All(v.UnicodeString(strip=True, min=1, not_empty=True),
95 95 v.ValidUsername(edit, old_data))
96 96 if edit:
97 97 new_password = All(
98 98 v.ValidPassword(),
99 99 v.UnicodeString(strip=False, min=6, not_empty=False)
100 100 )
101 101 password_confirmation = All(
102 102 v.ValidPassword(),
103 103 v.UnicodeString(strip=False, min=6, not_empty=False),
104 104 )
105 105 admin = v.StringBoolean(if_missing=False)
106 106 chained_validators = [v.ValidPasswordsMatch('new_password',
107 107 'password_confirmation')]
108 108 else:
109 109 password = All(
110 110 v.ValidPassword(),
111 111 v.UnicodeString(strip=False, min=6, not_empty=True)
112 112 )
113 113 password_confirmation = All(
114 114 v.ValidPassword(),
115 115 v.UnicodeString(strip=False, min=6, not_empty=False)
116 116 )
117 117 chained_validators = [v.ValidPasswordsMatch('password',
118 118 'password_confirmation')]
119 119
120 120 active = v.StringBoolean(if_missing=False)
121 121 firstname = v.UnicodeString(strip=True, min=1, not_empty=False)
122 122 lastname = v.UnicodeString(strip=True, min=1, not_empty=False)
123 123 email = All(v.Email(not_empty=True), v.UniqSystemEmail(old_data))
124 124 extern_name = v.UnicodeString(strip=True, if_missing=None)
125 125 extern_type = v.UnicodeString(strip=True, if_missing=None)
126 126 return _UserForm
127 127
128 128
129 129 def UserGroupForm(edit=False, old_data=None, available_members=None):
130 130 old_data = old_data or {}
131 131 available_members = available_members or []
132 132 class _UserGroupForm(formencode.Schema):
133 133 allow_extra_fields = True
134 134 filter_extra_fields = True
135 135
136 136 users_group_name = All(
137 137 v.UnicodeString(strip=True, min=1, not_empty=True),
138 138 v.ValidUserGroup(edit, old_data)
139 139 )
140 140 user_group_description = v.UnicodeString(strip=True, min=1,
141 141 not_empty=False)
142 142
143 143 users_group_active = v.StringBoolean(if_missing=False)
144 144
145 145 if edit:
146 146 users_group_members = v.OneOf(
147 147 available_members, hideList=False, testValueList=True,
148 148 if_missing=None, not_empty=False
149 149 )
150 150
151 151 return _UserGroupForm
152 152
153 153
154 154 def RepoGroupForm(edit=False, old_data=None, repo_groups=None,
155 155 can_create_in_root=False):
156 156 old_data = old_data or {}
157 157 repo_groups = repo_groups or []
158 158 repo_group_ids = [rg[0] for rg in repo_groups]
159 159 class _RepoGroupForm(formencode.Schema):
160 160 allow_extra_fields = True
161 161 filter_extra_fields = False
162 162
163 163 group_name = All(v.UnicodeString(strip=True, min=1, not_empty=True),
164 164 v.SlugifyName(),
165 165 v.ValidRegex(msg=_('Name must not contain only digits'))(r'(?!^\d+$)^.+$'))
166 166 group_description = v.UnicodeString(strip=True, min=1,
167 167 not_empty=False)
168 168 group_copy_permissions = v.StringBoolean(if_missing=False)
169 169
170 170 if edit:
171 171 #FIXME: do a special check that we cannot move a group to one of
172 172 #its children
173 173 pass
174 174
175 175 group_parent_id = All(v.CanCreateGroup(can_create_in_root),
176 176 v.OneOf(repo_group_ids, hideList=False,
177 177 testValueList=True,
178 178 if_missing=None, not_empty=True),
179 179 v.Int(min=-1, not_empty=True))
180 180 enable_locking = v.StringBoolean(if_missing=False)
181 181 chained_validators = [v.ValidRepoGroup(edit, old_data)]
182 182
183 183 return _RepoGroupForm
184 184
185 185
186 186 def RegisterForm(edit=False, old_data=None):
187 187 class _RegisterForm(formencode.Schema):
188 188 allow_extra_fields = True
189 189 filter_extra_fields = True
190 190 username = All(
191 191 v.ValidUsername(edit, old_data),
192 192 v.UnicodeString(strip=True, min=1, not_empty=True)
193 193 )
194 194 password = All(
195 195 v.ValidPassword(),
196 196 v.UnicodeString(strip=False, min=6, not_empty=True)
197 197 )
198 198 password_confirmation = All(
199 199 v.ValidPassword(),
200 200 v.UnicodeString(strip=False, min=6, not_empty=True)
201 201 )
202 202 active = v.StringBoolean(if_missing=False)
203 203 firstname = v.UnicodeString(strip=True, min=1, not_empty=False)
204 204 lastname = v.UnicodeString(strip=True, min=1, not_empty=False)
205 205 email = All(v.Email(not_empty=True), v.UniqSystemEmail(old_data))
206 206
207 207 chained_validators = [v.ValidPasswordsMatch('password',
208 208 'password_confirmation')]
209 209
210 210 return _RegisterForm
211 211
212 212
213 213 def PasswordResetRequestForm():
214 214 class _PasswordResetRequestForm(formencode.Schema):
215 215 allow_extra_fields = True
216 216 filter_extra_fields = True
217 217 email = v.Email(not_empty=True)
218 218 return _PasswordResetRequestForm
219 219
220 220 def PasswordResetConfirmationForm():
221 221 class _PasswordResetConfirmationForm(formencode.Schema):
222 222 allow_extra_fields = True
223 223 filter_extra_fields = True
224 224
225 225 email = v.UnicodeString(strip=True, not_empty=True)
226 226 timestamp = v.Number(strip=True, not_empty=True)
227 227 token = v.UnicodeString(strip=True, not_empty=True)
228 228 password = All(v.ValidPassword(), v.UnicodeString(strip=False, min=6))
229 229 password_confirm = All(v.ValidPassword(), v.UnicodeString(strip=False, min=6))
230 230
231 231 chained_validators = [v.ValidPasswordsMatch('password',
232 232 'password_confirm')]
233 233 return _PasswordResetConfirmationForm
234 234
235 235 def RepoForm(edit=False, old_data=None, supported_backends=BACKENDS.keys(),
236 236 repo_groups=None, landing_revs=None):
237 237 old_data = old_data or {}
238 238 repo_groups = repo_groups or []
239 239 landing_revs = landing_revs or []
240 240 repo_group_ids = [rg[0] for rg in repo_groups]
241 241 class _RepoForm(formencode.Schema):
242 242 allow_extra_fields = True
243 243 filter_extra_fields = False
244 244 repo_name = All(v.UnicodeString(strip=True, min=1, not_empty=True),
245 245 v.SlugifyName())
246 246 repo_group = All(v.CanWriteGroup(old_data),
247 247 v.OneOf(repo_group_ids, hideList=True),
248 248 v.Int(min=-1, not_empty=True))
249 249 repo_type = v.OneOf(supported_backends, required=False,
250 250 if_missing=old_data.get('repo_type'))
251 251 repo_description = v.UnicodeString(strip=True, min=1, not_empty=False)
252 252 repo_private = v.StringBoolean(if_missing=False)
253 253 repo_landing_rev = v.OneOf(landing_revs, hideList=True)
254 254 repo_copy_permissions = v.StringBoolean(if_missing=False)
255 255 clone_uri = All(v.UnicodeString(strip=True, min=1, not_empty=False))
256 256
257 257 repo_enable_statistics = v.StringBoolean(if_missing=False)
258 258 repo_enable_downloads = v.StringBoolean(if_missing=False)
259 259 repo_enable_locking = v.StringBoolean(if_missing=False)
260 260
261 261 if edit:
262 262 owner = All(v.UnicodeString(not_empty=True), v.ValidRepoUser())
263 263 # Not a real field - just for reference for validation:
264 264 # clone_uri_hidden = v.UnicodeString(if_missing='')
265 265
266 266 chained_validators = [v.ValidCloneUri(),
267 267 v.ValidRepoName(edit, old_data)]
268 268 return _RepoForm
269 269
270 270
271 271 def RepoPermsForm():
272 272 class _RepoPermsForm(formencode.Schema):
273 273 allow_extra_fields = True
274 274 filter_extra_fields = False
275 275 chained_validators = [v.ValidPerms(type_='repo')]
276 276 return _RepoPermsForm
277 277
278 278
279 279 def RepoGroupPermsForm(valid_recursive_choices):
280 280 class _RepoGroupPermsForm(formencode.Schema):
281 281 allow_extra_fields = True
282 282 filter_extra_fields = False
283 283 recursive = v.OneOf(valid_recursive_choices)
284 284 chained_validators = [v.ValidPerms(type_='repo_group')]
285 285 return _RepoGroupPermsForm
286 286
287 287
288 288 def UserGroupPermsForm():
289 289 class _UserPermsForm(formencode.Schema):
290 290 allow_extra_fields = True
291 291 filter_extra_fields = False
292 292 chained_validators = [v.ValidPerms(type_='user_group')]
293 293 return _UserPermsForm
294 294
295 295
296 296 def RepoFieldForm():
297 297 class _RepoFieldForm(formencode.Schema):
298 298 filter_extra_fields = True
299 299 allow_extra_fields = True
300 300
301 301 new_field_key = All(v.FieldKey(),
302 302 v.UnicodeString(strip=True, min=3, not_empty=True))
303 303 new_field_value = v.UnicodeString(not_empty=False, if_missing='')
304 304 new_field_type = v.OneOf(['str', 'unicode', 'list', 'tuple'],
305 305 if_missing='str')
306 306 new_field_label = v.UnicodeString(not_empty=False)
307 307 new_field_desc = v.UnicodeString(not_empty=False)
308 308
309 309 return _RepoFieldForm
310 310
311 311
312 312 def RepoForkForm(edit=False, old_data=None, supported_backends=BACKENDS.keys(),
313 313 repo_groups=None, landing_revs=None):
314 314 old_data = old_data or {}
315 315 repo_groups = repo_groups or []
316 316 landing_revs = landing_revs or []
317 317 repo_group_ids = [rg[0] for rg in repo_groups]
318 318 class _RepoForkForm(formencode.Schema):
319 319 allow_extra_fields = True
320 320 filter_extra_fields = False
321 321 repo_name = All(v.UnicodeString(strip=True, min=1, not_empty=True),
322 322 v.SlugifyName())
323 323 repo_group = All(v.CanWriteGroup(),
324 324 v.OneOf(repo_group_ids, hideList=True),
325 325 v.Int(min=-1, not_empty=True))
326 326 repo_type = All(v.ValidForkType(old_data), v.OneOf(supported_backends))
327 327 description = v.UnicodeString(strip=True, min=1, not_empty=True)
328 328 private = v.StringBoolean(if_missing=False)
329 329 copy_permissions = v.StringBoolean(if_missing=False)
330 330 update_after_clone = v.StringBoolean(if_missing=False)
331 331 fork_parent_id = v.UnicodeString()
332 332 chained_validators = [v.ValidForkName(edit, old_data)]
333 333 landing_rev = v.OneOf(landing_revs, hideList=True)
334 334
335 335 return _RepoForkForm
336 336
337 337
338 338 def ApplicationSettingsForm():
339 339 class _ApplicationSettingsForm(formencode.Schema):
340 340 allow_extra_fields = True
341 341 filter_extra_fields = False
342 342 title = v.UnicodeString(strip=True, not_empty=False)
343 343 realm = v.UnicodeString(strip=True, min=1, not_empty=True)
344 344 ga_code = v.UnicodeString(strip=True, min=1, not_empty=False)
345 345 captcha_public_key = v.UnicodeString(strip=True, min=1, not_empty=False)
346 346 captcha_private_key = v.UnicodeString(strip=True, min=1, not_empty=False)
347 347
348 348 return _ApplicationSettingsForm
349 349
350 350
351 351 def ApplicationVisualisationForm():
352 352 class _ApplicationVisualisationForm(formencode.Schema):
353 353 allow_extra_fields = True
354 354 filter_extra_fields = False
355 355 show_public_icon = v.StringBoolean(if_missing=False)
356 356 show_private_icon = v.StringBoolean(if_missing=False)
357 357 stylify_metatags = v.StringBoolean(if_missing=False)
358 358
359 359 repository_fields = v.StringBoolean(if_missing=False)
360 360 lightweight_journal = v.StringBoolean(if_missing=False)
361 dashboard_items = v.Int(min=5, not_empty=True)
361 page_size = v.Int(min=5, not_empty=True)
362 362 admin_grid_items = v.Int(min=5, not_empty=True)
363 363 show_version = v.StringBoolean(if_missing=False)
364 364 use_gravatar = v.StringBoolean(if_missing=False)
365 365 gravatar_url = v.UnicodeString(min=3)
366 366 clone_uri_tmpl = v.UnicodeString(min=3)
367 367
368 368 return _ApplicationVisualisationForm
369 369
370 370
371 371 def ApplicationUiSettingsForm():
372 372 class _ApplicationUiSettingsForm(formencode.Schema):
373 373 allow_extra_fields = True
374 374 filter_extra_fields = False
375 375 paths_root_path = All(
376 376 v.ValidPath(),
377 377 v.UnicodeString(strip=True, min=1, not_empty=True)
378 378 )
379 379 hooks_changegroup_update = v.StringBoolean(if_missing=False)
380 380 hooks_changegroup_repo_size = v.StringBoolean(if_missing=False)
381 381 hooks_changegroup_push_logger = v.StringBoolean(if_missing=False)
382 382 hooks_outgoing_pull_logger = v.StringBoolean(if_missing=False)
383 383
384 384 extensions_largefiles = v.StringBoolean(if_missing=False)
385 385 extensions_hgsubversion = v.StringBoolean(if_missing=False)
386 386 extensions_hggit = v.StringBoolean(if_missing=False)
387 387
388 388 return _ApplicationUiSettingsForm
389 389
390 390
391 391 def DefaultPermissionsForm(repo_perms_choices, group_perms_choices,
392 392 user_group_perms_choices, create_choices,
393 393 create_on_write_choices, repo_group_create_choices,
394 394 user_group_create_choices, fork_choices,
395 395 register_choices, extern_activate_choices):
396 396 class _DefaultPermissionsForm(formencode.Schema):
397 397 allow_extra_fields = True
398 398 filter_extra_fields = True
399 399 overwrite_default_repo = v.StringBoolean(if_missing=False)
400 400 overwrite_default_group = v.StringBoolean(if_missing=False)
401 401 overwrite_default_user_group = v.StringBoolean(if_missing=False)
402 402 anonymous = v.StringBoolean(if_missing=False)
403 403 default_repo_perm = v.OneOf(repo_perms_choices)
404 404 default_group_perm = v.OneOf(group_perms_choices)
405 405 default_user_group_perm = v.OneOf(user_group_perms_choices)
406 406
407 407 default_repo_create = v.OneOf(create_choices)
408 408 create_on_write = v.OneOf(create_on_write_choices)
409 409 default_user_group_create = v.OneOf(user_group_create_choices)
410 410 #default_repo_group_create = v.OneOf(repo_group_create_choices) #not impl. yet
411 411 default_fork = v.OneOf(fork_choices)
412 412
413 413 default_register = v.OneOf(register_choices)
414 414 default_extern_activate = v.OneOf(extern_activate_choices)
415 415 return _DefaultPermissionsForm
416 416
417 417
418 418 def CustomDefaultPermissionsForm():
419 419 class _CustomDefaultPermissionsForm(formencode.Schema):
420 420 filter_extra_fields = True
421 421 allow_extra_fields = True
422 422 inherit_default_permissions = v.StringBoolean(if_missing=False)
423 423
424 424 create_repo_perm = v.StringBoolean(if_missing=False)
425 425 create_user_group_perm = v.StringBoolean(if_missing=False)
426 426 #create_repo_group_perm Impl. later
427 427
428 428 fork_repo_perm = v.StringBoolean(if_missing=False)
429 429
430 430 return _CustomDefaultPermissionsForm
431 431
432 432
433 433 def DefaultsForm(edit=False, old_data=None, supported_backends=BACKENDS.keys()):
434 434 class _DefaultsForm(formencode.Schema):
435 435 allow_extra_fields = True
436 436 filter_extra_fields = True
437 437 default_repo_type = v.OneOf(supported_backends)
438 438 default_repo_private = v.StringBoolean(if_missing=False)
439 439 default_repo_enable_statistics = v.StringBoolean(if_missing=False)
440 440 default_repo_enable_downloads = v.StringBoolean(if_missing=False)
441 441 default_repo_enable_locking = v.StringBoolean(if_missing=False)
442 442
443 443 return _DefaultsForm
444 444
445 445
446 446 def AuthSettingsForm(current_active_modules):
447 447 class _AuthSettingsForm(formencode.Schema):
448 448 allow_extra_fields = True
449 449 filter_extra_fields = True
450 450 auth_plugins = All(v.ValidAuthPlugins(),
451 451 v.UniqueListFromString()(not_empty=True))
452 452
453 453 def __init__(self, *args, **kwargs):
454 454 # The auth plugins tell us what form validators they use
455 455 if current_active_modules:
456 456 import kallithea.lib.auth_modules
457 457 from kallithea.lib.auth_modules import LazyFormencode
458 458 for module in current_active_modules:
459 459 plugin = kallithea.lib.auth_modules.loadplugin(module)
460 460 plugin_name = plugin.name
461 461 for sv in plugin.plugin_settings():
462 462 newk = "auth_%s_%s" % (plugin_name, sv["name"])
463 463 # can be a LazyFormencode object from plugin settings
464 464 validator = sv["validator"]
465 465 if isinstance(validator, LazyFormencode):
466 466 validator = validator()
467 467 #init all lazy validators from formencode.All
468 468 if isinstance(validator, All):
469 469 init_validators = []
470 470 for validator in validator.validators:
471 471 if isinstance(validator, LazyFormencode):
472 472 validator = validator()
473 473 init_validators.append(validator)
474 474 validator.validators = init_validators
475 475
476 476 self.add_field(newk, validator)
477 477 formencode.Schema.__init__(self, *args, **kwargs)
478 478
479 479 return _AuthSettingsForm
480 480
481 481
482 482 def LdapSettingsForm(tls_reqcert_choices, search_scope_choices,
483 483 tls_kind_choices):
484 484 class _LdapSettingsForm(formencode.Schema):
485 485 allow_extra_fields = True
486 486 filter_extra_fields = True
487 487 #pre_validators = [LdapLibValidator]
488 488 ldap_active = v.StringBoolean(if_missing=False)
489 489 ldap_host = v.UnicodeString(strip=True,)
490 490 ldap_port = v.Number(strip=True,)
491 491 ldap_tls_kind = v.OneOf(tls_kind_choices)
492 492 ldap_tls_reqcert = v.OneOf(tls_reqcert_choices)
493 493 ldap_dn_user = v.UnicodeString(strip=True,)
494 494 ldap_dn_pass = v.UnicodeString(strip=True,)
495 495 ldap_base_dn = v.UnicodeString(strip=True,)
496 496 ldap_filter = v.UnicodeString(strip=True,)
497 497 ldap_search_scope = v.OneOf(search_scope_choices)
498 498 ldap_attr_login = v.AttrLoginValidator()(not_empty=True)
499 499 ldap_attr_firstname = v.UnicodeString(strip=True,)
500 500 ldap_attr_lastname = v.UnicodeString(strip=True,)
501 501 ldap_attr_email = v.UnicodeString(strip=True,)
502 502
503 503 return _LdapSettingsForm
504 504
505 505
506 506 def UserExtraEmailForm():
507 507 class _UserExtraEmailForm(formencode.Schema):
508 508 email = All(v.UniqSystemEmail(), v.Email(not_empty=True))
509 509 return _UserExtraEmailForm
510 510
511 511
512 512 def UserExtraIpForm():
513 513 class _UserExtraIpForm(formencode.Schema):
514 514 ip = v.ValidIp()(not_empty=True)
515 515 return _UserExtraIpForm
516 516
517 517
518 518 def PullRequestForm(repo_id):
519 519 class _PullRequestForm(formencode.Schema):
520 520 allow_extra_fields = True
521 521 filter_extra_fields = True
522 522
523 523 org_repo = v.UnicodeString(strip=True, required=True)
524 524 org_ref = v.UnicodeString(strip=True, required=True)
525 525 other_repo = v.UnicodeString(strip=True, required=True)
526 526 other_ref = v.UnicodeString(strip=True, required=True)
527 527
528 528 pullrequest_title = v.UnicodeString(strip=True, required=True)
529 529 pullrequest_desc = v.UnicodeString(strip=True, required=False)
530 530
531 531 return _PullRequestForm
532 532
533 533
534 534 def PullRequestPostForm():
535 535 class _PullRequestPostForm(formencode.Schema):
536 536 allow_extra_fields = True
537 537 filter_extra_fields = True
538 538
539 539 pullrequest_title = v.UnicodeString(strip=True, required=True)
540 540 pullrequest_desc = v.UnicodeString(strip=True, required=False)
541 541 org_review_members = v.Set()
542 542 review_members = v.Set()
543 543 updaterev = v.UnicodeString(strip=True, required=False, if_missing=None)
544 544 owner = All(v.UnicodeString(strip=True, required=True),
545 545 v.ValidRepoUser())
546 546
547 547 return _PullRequestPostForm
548 548
549 549
550 550 def GistForm(lifetime_options):
551 551 class _GistForm(formencode.Schema):
552 552 allow_extra_fields = True
553 553 filter_extra_fields = True
554 554
555 555 filename = All(v.BasePath()(),
556 556 v.UnicodeString(strip=True, required=False))
557 557 description = v.UnicodeString(required=False, if_missing=u'')
558 558 lifetime = v.OneOf(lifetime_options)
559 559 mimetype = v.UnicodeString(required=False, if_missing=None)
560 560 content = v.UnicodeString(required=True, not_empty=True)
561 561 public = v.UnicodeString(required=False, if_missing=u'')
562 562 private = v.UnicodeString(required=False, if_missing=u'')
563 563
564 564 return _GistForm
@@ -1,23 +1,23 b''
1 1 ## -*- coding: utf-8 -*-
2 2 <%inherit file="/base/base.html"/>
3 3 <%block name="title">
4 ${_('%s Repository group dashboard') % c.group.group_name}
4 ${_('Repository group %s') % c.group.group_name}
5 5 </%block>
6 6
7 7 <%def name="breadcrumbs()">
8 8 <span class="groups_breadcrumbs">
9 9 ${h.link_to(_('Home'),h.url('/'))}
10 10 %if c.group.parent_group:
11 11 &raquo; ${h.link_to(c.group.parent_group.name,h.url('repos_group_home',group_name=c.group.parent_group.group_name))}
12 12 %endif
13 13 &raquo; "${c.group.name}" ${_('with')}
14 14 </span>
15 15 </%def>
16 16
17 17 <%block name="header_menu">
18 18 ${self.menu('repositories')}
19 19 </%block>
20 20
21 21 <%def name="main()">
22 22 <%include file="/index_base.html" args="parent=self,group_name=c.group.group_name"/>
23 23 </%def>
@@ -1,115 +1,115 b''
1 1 ${h.form(url('admin_settings_visual'), method='post')}
2 2 <div class="form">
3 3
4 4 <div class="form-horizontal">
5 5
6 6 <div class="form-group">
7 7 <label>${_('General')}:</label>
8 8 <div class="checkboxes">
9 9 <div class="checkbox">
10 10 ${h.checkbox('repository_fields','True')}
11 11 <label for="repository_fields">${_('Use repository extra fields')}</label>
12 12 </div>
13 13 <span class="help-block">${_('Allows storing additional customized fields per repository.')}</span>
14 14
15 15 <div class="checkbox">
16 16 ${h.checkbox('show_version','True')}
17 17 <label for="show_version">${_('Show Kallithea version')}</label>
18 18 </div>
19 19 <span class="help-block">${_('Shows or hides a version number of Kallithea displayed in the footer.')}</span>
20 20
21 21 <div class="checkbox">
22 22 ${h.checkbox('use_gravatar','True')}
23 23 <label for="use_gravatar">${_('Use Gravatars in Kallithea')}</label>
24 24 </div>
25 25 </div>
26 26 <div class="input">
27 27 ${h.text('gravatar_url', size=80)}
28 28 <span class="help-block">${_('''Gravatar URL allows you to use another avatar server application.
29 29 The following variables of the URL will be replaced accordingly.
30 30 {scheme} 'http' or 'https' sent from running Kallithea server,
31 31 {email} user email,
32 32 {md5email} md5 hash of the user email (like at gravatar.com),
33 33 {size} size of the image that is expected from the server application,
34 34 {netloc} network location/server host of running Kallithea server''')}</span>
35 35 </div>
36 36 </div>
37 37
38 38 <div class="form-group">
39 39 <label>${_('Clone URL')}:</label>
40 40 <div class="input">
41 41 ${h.text('clone_uri_tmpl', size=80)}
42 42 <span class="help-block">${_('''Schema of clone URL construction eg. '{scheme}://{user}@{netloc}/{repo}'.
43 43 The following variables are available:
44 44 {scheme} 'http' or 'https' sent from running Kallithea server,
45 45 {user} current user username,
46 46 {netloc} network location/server host of running Kallithea server,
47 47 {repo} full repository name,
48 48 {repoid} ID of repository, can be used to construct clone-by-id''')}</span>
49 49 </div>
50 50 </div>
51 51
52 52 <div class="form-group">
53 <label class="control-label" for="dashboard_items">${_('Dashboard items')}:</label>
53 <label class="control-label" for="page_size">${_('Repository page size')}:</label>
54 54 <div class="input">
55 ${h.text('dashboard_items',size=5)}
56 <span class="help-block">${_('Number of items displayed in the main page dashboard before pagination is shown.')}</span>
55 ${h.text('page_size',size=5)}
56 <span class="help-block">${_('Number of items displayed in the repository pages before pagination is shown.')}</span>
57 57 </div>
58 58 </div>
59 59
60 60 <div class="form-group">
61 <label class="control-label" for="admin_grid_items">${_('Admin pages items')}:</label>
61 <label class="control-label" for="admin_grid_items">${_('Admin page size')}:</label>
62 62 <div class="input">
63 63 ${h.text('admin_grid_items',size=5)}
64 64 <span class="help-block">${_('Number of items displayed in the admin pages grids before pagination is shown.')}</span>
65 65 </div>
66 66 </div>
67 67
68 68 <div class="form-group">
69 69 <label>${_('Icons')}:</label>
70 70 <div class="checkboxes">
71 71 <div class="checkbox">
72 72 ${h.checkbox('show_public_icon','True')}
73 73 <label for="show_public_icon">${_('Show public repository icon on repositories')}</label>
74 74 </div>
75 75 <div class="checkbox">
76 76 ${h.checkbox('show_private_icon','True')}
77 77 <label for="show_private_icon">${_('Show private repository icon on repositories')}</label>
78 78 </div>
79 79 <span class="help-block">${_('Show public/private icons next to repository names.')}</span>
80 80 </div>
81 81 </div>
82 82
83 83 <div class="form-group">
84 84 <label>${_('Meta Tagging')}:</label>
85 85 <div class="checkboxes">
86 86 <div class="checkbox">
87 87 ${h.checkbox('stylify_metatags','True')}
88 88 <label for="stylify_metatags">${_('Stylify recognised meta tags:')}</label>
89 89 </div>
90 90 <div style="padding-left: 20px;">
91 91 <ul> <!-- Fix style here -->
92 92 <li>[featured] <span class="metatag" tag="featured">featured</span></li>
93 93 <li>[stale] <span class="metatag" tag="stale">stale</span></li>
94 94 <li>[dead] <span class="metatag" tag="dead">dead</span></li>
95 95 <li>[lang =&gt; lang] <span class="metatag" tag="lang" >lang</span></li>
96 96 <li>[license =&gt; License] <span class="metatag" tag="license"><a href="http://www.opensource.org/licenses/License" >License</a></span></li>
97 97 <li>[requires =&gt; Repo] <span class="metatag" tag="requires" >requires =&gt; <a href="#" >Repo</a></span></li>
98 98 <li>[recommends =&gt; Repo] <span class="metatag" tag="recommends" >recommends =&gt; <a href="#" >Repo</a></span></li>
99 99 <li>[see =&gt; URI] <span class="metatag" tag="see">see =&gt; <a href="#">URI</a> </span></li>
100 100 </ul>
101 101 </div>
102 102 <span class="help-block">${_('Parses meta tags from the repository description field and turns them into colored tags.')}</span>
103 103 </div>
104 104 </div>
105 105
106 106 <div class="form-group">
107 107 <div class="buttons">
108 108 ${h.submit('save',_('Save Settings'),class_="btn btn-default")}
109 109 ${h.reset('reset',_('Reset'),class_="btn btn-default")}
110 110 </div>
111 111 </div>
112 112
113 113 </div>
114 114 </div>
115 115 ${h.end_form()}
@@ -1,14 +1,14 b''
1 1 ## -*- coding: utf-8 -*-
2 2 <%inherit file="base/base.html"/>
3 3
4 4 <%block name="title">
5 ${_('Dashboard')}
5 ${_('Repositories')}
6 6 </%block>
7 7
8 8 <%block name="header_menu">
9 9 ${self.menu('repositories')}
10 10 </%block>
11 11
12 12 <%def name="main()">
13 13 <%include file="index_base.html" args="parent=self"/>
14 14 </%def>
General Comments 0
You need to be logged in to leave comments. Login now