##// END OF EJS Templates
Added application settings, are now customizable from database...
marcink -
r350:664a5b8c default
parent child Browse files
Show More
@@ -1,52 +1,51 b''
1 #!/usr/bin/env python
1 #!/usr/bin/env python
2 # encoding: utf-8
2 # encoding: utf-8
3 # admin controller for pylons
3 # admin controller for pylons
4 # Copyright (C) 2009-2010 Marcin Kuzminski <marcin@python-works.com>
4 # Copyright (C) 2009-2010 Marcin Kuzminski <marcin@python-works.com>
5
5
6 # This program is free software; you can redistribute it and/or
6 # This program is free software; you can redistribute it and/or
7 # modify it under the terms of the GNU General Public License
7 # modify it under the terms of the GNU General Public License
8 # as published by the Free Software Foundation; version 2
8 # as published by the Free Software Foundation; version 2
9 # of the License or (at your opinion) any later version of the license.
9 # of the License or (at your opinion) any later version of the license.
10 #
10 #
11 # This program is distributed in the hope that it will be useful,
11 # This program is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 # GNU General Public License for more details.
14 # GNU General Public License for more details.
15 #
15 #
16 # You should have received a copy of the GNU General Public License
16 # You should have received a copy of the GNU General Public License
17 # along with this program; if not, write to the Free Software
17 # along with this program; if not, write to the Free Software
18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
19 # MA 02110-1301, USA.
19 # MA 02110-1301, USA.
20 """
20 """
21 Created on April 7, 2010
21 Created on April 7, 2010
22 admin controller for pylons
22 admin controller for pylons
23 @author: marcink
23 @author: marcink
24 """
24 """
25 import logging
25 import logging
26 from pylons import request, response, session, tmpl_context as c
26 from pylons import request, response, session, tmpl_context as c
27 from pylons_app.lib.base import BaseController, render
27 from pylons_app.lib.base import BaseController, render
28 from pylons_app.model import meta
28 from pylons_app.model import meta
29 from pylons_app.model.db import UserLog
29 from pylons_app.model.db import UserLog
30 from webhelpers.paginate import Page
30 from webhelpers.paginate import Page
31 from pylons_app.lib.auth import LoginRequired, HasPermissionAllDecorator
31 from pylons_app.lib.auth import LoginRequired, HasPermissionAllDecorator
32
32
33 log = logging.getLogger(__name__)
33 log = logging.getLogger(__name__)
34
34
35 class AdminController(BaseController):
35 class AdminController(BaseController):
36
36
37 @LoginRequired()
37 @LoginRequired()
38 def __before__(self):
38 def __before__(self):
39 super(AdminController, self).__before__()
39 super(AdminController, self).__before__()
40
40
41 @HasPermissionAllDecorator('hg.admin')
41 @HasPermissionAllDecorator('hg.admin')
42 def index(self):
42 def index(self):
43 sa = meta.Session
43
44
44 users_log = self.sa.query(UserLog).order_by(UserLog.action_date.desc())
45 users_log = sa.query(UserLog).order_by(UserLog.action_date.desc())
46 p = int(request.params.get('page', 1))
45 p = int(request.params.get('page', 1))
47 c.users_log = Page(users_log, page=p, items_per_page=10)
46 c.users_log = Page(users_log, page=p, items_per_page=10)
48 c.log_data = render('admin/admin_log.html')
47 c.log_data = render('admin/admin_log.html')
49 if request.params.get('partial'):
48 if request.params.get('partial'):
50 return c.log_data
49 return c.log_data
51 return render('admin/admin.html')
50 return render('admin/admin.html')
52
51
@@ -1,109 +1,159 b''
1 #!/usr/bin/env python
1 #!/usr/bin/env python
2 # encoding: utf-8
2 # encoding: utf-8
3 # settings controller for pylons
3 # settings controller for pylons
4 # Copyright (C) 2009-2010 Marcin Kuzminski <marcin@python-works.com>
4 # Copyright (C) 2009-2010 Marcin Kuzminski <marcin@python-works.com>
5
5
6 # This program is free software; you can redistribute it and/or
6 # This program is free software; you can redistribute it and/or
7 # modify it under the terms of the GNU General Public License
7 # modify it under the terms of the GNU General Public License
8 # as published by the Free Software Foundation; version 2
8 # as published by the Free Software Foundation; version 2
9 # of the License or (at your opinion) any later version of the license.
9 # of the License or (at your opinion) any later version of the license.
10 #
10 #
11 # This program is distributed in the hope that it will be useful,
11 # This program is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 # GNU General Public License for more details.
14 # GNU General Public License for more details.
15 #
15 #
16 # You should have received a copy of the GNU General Public License
16 # You should have received a copy of the GNU General Public License
17 # along with this program; if not, write to the Free Software
17 # along with this program; if not, write to the Free Software
18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
19 # MA 02110-1301, USA.
19 # MA 02110-1301, USA.
20 """
20 """
21 Created on July 14, 2010
21 Created on July 14, 2010
22 settings controller for pylons
22 settings controller for pylons
23 @author: marcink
23 @author: marcink
24 """
24 """
25 from formencode import htmlfill
25 from formencode import htmlfill
26 from pylons import request, session, tmpl_context as c, url, app_globals as g
26 from pylons import request, session, tmpl_context as c, url, app_globals as g, \
27 config
27 from pylons.controllers.util import abort, redirect
28 from pylons.controllers.util import abort, redirect
28 from pylons.i18n.translation import _
29 from pylons.i18n.translation import _
29 from pylons_app.lib import helpers as h
30 from pylons_app.lib import helpers as h
30 from pylons_app.lib.auth import LoginRequired, HasPermissionAllDecorator
31 from pylons_app.lib.auth import LoginRequired, HasPermissionAllDecorator
31 from pylons_app.lib.base import BaseController, render
32 from pylons_app.lib.base import BaseController, render
32 from pylons_app.lib.utils import repo2db_mapper, invalidate_cache
33 from pylons_app.lib.utils import repo2db_mapper, invalidate_cache, \
33 from pylons_app.model.db import User, UserLog
34 set_hg_app_config
34 from pylons_app.model.forms import UserForm
35 from pylons_app.model.db import User, UserLog, HgAppSettings
36 from pylons_app.model.forms import UserForm, ApplicationSettingsForm
35 from pylons_app.model.hg_model import HgModel
37 from pylons_app.model.hg_model import HgModel
36 from pylons_app.model.user_model import UserModel
38 from pylons_app.model.user_model import UserModel
37 import formencode
39 import formencode
38 import logging
40 import logging
39
41 import traceback
42
40 log = logging.getLogger(__name__)
43 log = logging.getLogger(__name__)
41
44
42
45
43 class SettingsController(BaseController):
46 class SettingsController(BaseController):
44 """REST Controller styled on the Atom Publishing Protocol"""
47 """REST Controller styled on the Atom Publishing Protocol"""
45 # To properly map this controller, ensure your config/routing.py
48 # To properly map this controller, ensure your config/routing.py
46 # file has a resource setup:
49 # file has a resource setup:
47 # map.resource('setting', 'settings', controller='admin/settings',
50 # map.resource('setting', 'settings', controller='admin/settings',
48 # path_prefix='/admin', name_prefix='admin_')
51 # path_prefix='/admin', name_prefix='admin_')
49
52
50
53
51 @LoginRequired()
54 @LoginRequired()
52 #@HasPermissionAllDecorator('hg.admin')
55 #@HasPermissionAllDecorator('hg.admin')
53 def __before__(self):
56 def __before__(self):
54 c.admin_user = session.get('admin_user')
57 c.admin_user = session.get('admin_user')
55 c.admin_username = session.get('admin_username')
58 c.admin_username = session.get('admin_username')
56 super(SettingsController, self).__before__()
59 super(SettingsController, self).__before__()
57
60
58 def index(self, format='html'):
61 def index(self, format='html'):
59 """GET /admin/settings: All items in the collection"""
62 """GET /admin/settings: All items in the collection"""
60 # url('admin_settings')
63 # url('admin_settings')
61 return render('admin/settings/settings.html')
64
65 hgsettings = self.sa.query(HgAppSettings).scalar()
66 defaults = hgsettings.__dict__ if hgsettings else {}
67 return htmlfill.render(
68 render('admin/settings/settings.html'),
69 defaults=defaults,
70 encoding="UTF-8",
71 force_defaults=False
72 )
62
73
63 def create(self):
74 def create(self):
64 """POST /admin/settings: Create a new item"""
75 """POST /admin/settings: Create a new item"""
65 # url('admin_settings')
76 # url('admin_settings')
66
77
67 def new(self, format='html'):
78 def new(self, format='html'):
68 """GET /admin/settings/new: Form to create a new item"""
79 """GET /admin/settings/new: Form to create a new item"""
69 # url('admin_new_setting')
80 # url('admin_new_setting')
70
81
71 def update(self, id):
82 def update(self, id):
72 """PUT /admin/settings/id: Update an existing item"""
83 """PUT /admin/settings/id: Update an existing item"""
73 # Forms posted to this method should contain a hidden field:
84 # Forms posted to this method should contain a hidden field:
74 # <input type="hidden" name="_method" value="PUT" />
85 # <input type="hidden" name="_method" value="PUT" />
75 # Or using helpers:
86 # Or using helpers:
76 # h.form(url('admin_setting', id=ID),
87 # h.form(url('admin_setting', id=ID),
77 # method='put')
88 # method='put')
78 # url('admin_setting', id=ID)
89 # url('admin_setting', id=ID)
79 if id == 'mapping':
90 if id == 'mapping':
80 rm_obsolete = request.POST.get('destroy', False)
91 rm_obsolete = request.POST.get('destroy', False)
81 log.debug('Rescanning directories with destroy=%s', rm_obsolete)
92 log.debug('Rescanning directories with destroy=%s', rm_obsolete)
82
93
83 initial = HgModel.repo_scan(g.paths[0][0], g.paths[0][1], g.baseui)
94 initial = HgModel.repo_scan(g.paths[0][0], g.paths[0][1], g.baseui)
84 repo2db_mapper(initial, rm_obsolete)
95 repo2db_mapper(initial, rm_obsolete)
85 invalidate_cache('cached_repo_list')
96 invalidate_cache('cached_repo_list')
97
98 if id == 'global':
86
99
100 application_form = ApplicationSettingsForm()()
101 try:
102 form_result = application_form.to_python(dict(request.POST))
103 title = form_result['app_title']
104 realm = form_result['app_auth_realm']
105
106 try:
107 hgsettings = self.sa.query(HgAppSettings).get(1)
108 hgsettings.app_auth_realm = realm
109 hgsettings.app_title = title
110
111 self.sa.add(hgsettings)
112 self.sa.commit()
113 set_hg_app_config(config)
114 h.flash(_('Updated application settings'),
115 category='success')
116
117 except:
118 log.error(traceback.format_exc())
119 h.flash(_('error occured during chaning application settings'),
120 category='error')
121
122 self.sa.rollback()
123
124
125 except formencode.Invalid as errors:
126 c.form_errors = errors.error_dict
127 return htmlfill.render(
128 render('admin/settings/settings.html'),
129 defaults=errors.value,
130 encoding="UTF-8")
131
132
133
134
135
136
87
137
88 return redirect(url('admin_settings'))
138 return redirect(url('admin_settings'))
89
139
90
140
91
141
92
142
93
143
94 def delete(self, id):
144 def delete(self, id):
95 """DELETE /admin/settings/id: Delete an existing item"""
145 """DELETE /admin/settings/id: Delete an existing item"""
96 # Forms posted to this method should contain a hidden field:
146 # Forms posted to this method should contain a hidden field:
97 # <input type="hidden" name="_method" value="DELETE" />
147 # <input type="hidden" name="_method" value="DELETE" />
98 # Or using helpers:
148 # Or using helpers:
99 # h.form(url('admin_setting', id=ID),
149 # h.form(url('admin_setting', id=ID),
100 # method='delete')
150 # method='delete')
101 # url('admin_setting', id=ID)
151 # url('admin_setting', id=ID)
102
152
103 def show(self, id, format='html'):
153 def show(self, id, format='html'):
104 """GET /admin/settings/id: Show a specific item"""
154 """GET /admin/settings/id: Show a specific item"""
105 # url('admin_setting', id=ID)
155 # url('admin_setting', id=ID)
106
156
107 def edit(self, id, format='html'):
157 def edit(self, id, format='html'):
108 """GET /admin/settings/id/edit: Form to edit an existing item"""
158 """GET /admin/settings/id/edit: Form to edit an existing item"""
109 # url('admin_edit_setting', id=ID)
159 # url('admin_edit_setting', id=ID)
@@ -1,156 +1,155 b''
1 #!/usr/bin/env python
1 #!/usr/bin/env python
2 # encoding: utf-8
2 # encoding: utf-8
3 # users controller for pylons
3 # users controller for pylons
4 # Copyright (C) 2009-2010 Marcin Kuzminski <marcin@python-works.com>
4 # Copyright (C) 2009-2010 Marcin Kuzminski <marcin@python-works.com>
5
5
6 # This program is free software; you can redistribute it and/or
6 # This program is free software; you can redistribute it and/or
7 # modify it under the terms of the GNU General Public License
7 # modify it under the terms of the GNU General Public License
8 # as published by the Free Software Foundation; version 2
8 # as published by the Free Software Foundation; version 2
9 # of the License or (at your opinion) any later version of the license.
9 # of the License or (at your opinion) any later version of the license.
10 #
10 #
11 # This program is distributed in the hope that it will be useful,
11 # This program is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 # GNU General Public License for more details.
14 # GNU General Public License for more details.
15 #
15 #
16 # You should have received a copy of the GNU General Public License
16 # You should have received a copy of the GNU General Public License
17 # along with this program; if not, write to the Free Software
17 # along with this program; if not, write to the Free Software
18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
19 # MA 02110-1301, USA.
19 # MA 02110-1301, USA.
20 """
20 """
21 Created on April 4, 2010
21 Created on April 4, 2010
22 users controller for pylons
22 users controller for pylons
23 @author: marcink
23 @author: marcink
24 """
24 """
25 from formencode import htmlfill
25 from formencode import htmlfill
26 from pylons import request, session, tmpl_context as c, url
26 from pylons import request, session, tmpl_context as c, url
27 from pylons.controllers.util import abort, redirect
27 from pylons.controllers.util import abort, redirect
28 from pylons.i18n.translation import _
28 from pylons.i18n.translation import _
29 from pylons_app.lib import helpers as h
29 from pylons_app.lib import helpers as h
30 from pylons_app.lib.auth import LoginRequired, HasPermissionAllDecorator
30 from pylons_app.lib.auth import LoginRequired, HasPermissionAllDecorator
31 from pylons_app.lib.base import BaseController, render
31 from pylons_app.lib.base import BaseController, render
32 from pylons_app.model.db import User, UserLog
32 from pylons_app.model.db import User, UserLog
33 from pylons_app.model.forms import UserForm
33 from pylons_app.model.forms import UserForm
34 from pylons_app.model.user_model import UserModel, DefaultUserException
34 from pylons_app.model.user_model import UserModel, DefaultUserException
35 import formencode
35 import formencode
36 import logging
36 import logging
37
37
38 log = logging.getLogger(__name__)
38 log = logging.getLogger(__name__)
39
39
40 class UsersController(BaseController):
40 class UsersController(BaseController):
41 """REST Controller styled on the Atom Publishing Protocol"""
41 """REST Controller styled on the Atom Publishing Protocol"""
42 # To properly map this controller, ensure your config/routing.py
42 # To properly map this controller, ensure your config/routing.py
43 # file has a resource setup:
43 # file has a resource setup:
44 # map.resource('user', 'users')
44 # map.resource('user', 'users')
45
45
46 @LoginRequired()
46 @LoginRequired()
47 @HasPermissionAllDecorator('hg.admin')
47 @HasPermissionAllDecorator('hg.admin')
48 def __before__(self):
48 def __before__(self):
49 c.admin_user = session.get('admin_user')
49 c.admin_user = session.get('admin_user')
50 c.admin_username = session.get('admin_username')
50 c.admin_username = session.get('admin_username')
51 super(UsersController, self).__before__()
51 super(UsersController, self).__before__()
52
52
53
53
54 def index(self, format='html'):
54 def index(self, format='html'):
55 """GET /users: All items in the collection"""
55 """GET /users: All items in the collection"""
56 # url('users')
56 # url('users')
57
57
58 c.users_list = self.sa.query(User).all()
58 c.users_list = self.sa.query(User).all()
59 return render('admin/users/users.html')
59 return render('admin/users/users.html')
60
60
61 def create(self):
61 def create(self):
62 """POST /users: Create a new item"""
62 """POST /users: Create a new item"""
63 # url('users')
63 # url('users')
64
64
65 user_model = UserModel()
65 user_model = UserModel()
66 login_form = UserForm()()
66 login_form = UserForm()()
67 try:
67 try:
68 form_result = login_form.to_python(dict(request.POST))
68 form_result = login_form.to_python(dict(request.POST))
69 user_model.create(form_result)
69 user_model.create(form_result)
70 h.flash(_('created user %s') % form_result['username'],
70 h.flash(_('created user %s') % form_result['username'],
71 category='success')
71 category='success')
72 except formencode.Invalid as errors:
72 except formencode.Invalid as errors:
73 c.form_errors = errors.error_dict
73 c.form_errors = errors.error_dict
74 return htmlfill.render(
74 return htmlfill.render(
75 render('admin/users/user_add.html'),
75 render('admin/users/user_add.html'),
76 defaults=errors.value,
76 defaults=errors.value,
77 encoding="UTF-8")
77 encoding="UTF-8")
78 except Exception:
78 except Exception:
79
80 h.flash(_('error occured during creation of user') \
79 h.flash(_('error occured during creation of user') \
81 % request.POST.get('username'), category='error')
80 % request.POST.get('username'), category='error')
82 return redirect(url('users'))
81 return redirect(url('users'))
83
82
84 def new(self, format='html'):
83 def new(self, format='html'):
85 """GET /users/new: Form to create a new item"""
84 """GET /users/new: Form to create a new item"""
86 # url('new_user')
85 # url('new_user')
87 return render('admin/users/user_add.html')
86 return render('admin/users/user_add.html')
88
87
89 def update(self, id):
88 def update(self, id):
90 """PUT /users/id: Update an existing item"""
89 """PUT /users/id: Update an existing item"""
91 # Forms posted to this method should contain a hidden field:
90 # Forms posted to this method should contain a hidden field:
92 # <input type="hidden" name="_method" value="PUT" />
91 # <input type="hidden" name="_method" value="PUT" />
93 # Or using helpers:
92 # Or using helpers:
94 # h.form(url('user', id=ID),
93 # h.form(url('user', id=ID),
95 # method='put')
94 # method='put')
96 # url('user', id=ID)
95 # url('user', id=ID)
97 user_model = UserModel()
96 user_model = UserModel()
98 _form = UserForm(edit=True)()
97 _form = UserForm(edit=True)()
99 try:
98 try:
100 form_result = _form.to_python(dict(request.POST))
99 form_result = _form.to_python(dict(request.POST))
101 user_model.update(id, form_result)
100 user_model.update(id, form_result)
102 h.flash(_('User updated succesfully'), category='success')
101 h.flash(_('User updated succesfully'), category='success')
103
102
104 except formencode.Invalid as errors:
103 except formencode.Invalid as errors:
105 c.user = user_model.get_user(id)
104 c.user = user_model.get_user(id)
106 c.form_errors = errors.error_dict
105 c.form_errors = errors.error_dict
107 return htmlfill.render(
106 return htmlfill.render(
108 render('admin/users/user_edit.html'),
107 render('admin/users/user_edit.html'),
109 defaults=errors.value,
108 defaults=errors.value,
110 encoding="UTF-8")
109 encoding="UTF-8")
111 except Exception:
110 except Exception:
112 h.flash(_('error occured during update of user %s') \
111 h.flash(_('error occured during update of user %s') \
113 % form_result['username'], category='error')
112 % form_result['username'], category='error')
114
113
115 return redirect(url('users'))
114 return redirect(url('users'))
116
115
117 def delete(self, id):
116 def delete(self, id):
118 """DELETE /users/id: Delete an existing item"""
117 """DELETE /users/id: Delete an existing item"""
119 # Forms posted to this method should contain a hidden field:
118 # Forms posted to this method should contain a hidden field:
120 # <input type="hidden" name="_method" value="DELETE" />
119 # <input type="hidden" name="_method" value="DELETE" />
121 # Or using helpers:
120 # Or using helpers:
122 # h.form(url('user', id=ID),
121 # h.form(url('user', id=ID),
123 # method='delete')
122 # method='delete')
124 # url('user', id=ID)
123 # url('user', id=ID)
125 user_model = UserModel()
124 user_model = UserModel()
126 try:
125 try:
127 user_model.delete(id)
126 user_model.delete(id)
128 h.flash(_('sucessfully deleted user'), category='success')
127 h.flash(_('sucessfully deleted user'), category='success')
129 except DefaultUserException as e:
128 except DefaultUserException as e:
130 h.flash(str(e), category='warning')
129 h.flash(str(e), category='warning')
131 except Exception:
130 except Exception:
132 h.flash(_('An error occured during deletion of user'),
131 h.flash(_('An error occured during deletion of user'),
133 category='error')
132 category='error')
134 return redirect(url('users'))
133 return redirect(url('users'))
135
134
136 def show(self, id, format='html'):
135 def show(self, id, format='html'):
137 """GET /users/id: Show a specific item"""
136 """GET /users/id: Show a specific item"""
138 # url('user', id=ID)
137 # url('user', id=ID)
139
138
140
139
141 def edit(self, id, format='html'):
140 def edit(self, id, format='html'):
142 """GET /users/id/edit: Form to edit an existing item"""
141 """GET /users/id/edit: Form to edit an existing item"""
143 # url('edit_user', id=ID)
142 # url('edit_user', id=ID)
144 c.user = self.sa.query(User).get(id)
143 c.user = self.sa.query(User).get(id)
145 if c.user.username == 'default':
144 if c.user.username == 'default':
146 h.flash(_("You can't edit this user since it's"
145 h.flash(_("You can't edit this user since it's"
147 " crucial for entire application"), category='warning')
146 " crucial for entire application"), category='warning')
148 return redirect(url('users'))
147 return redirect(url('users'))
149
148
150 defaults = c.user.__dict__
149 defaults = c.user.__dict__
151 return htmlfill.render(
150 return htmlfill.render(
152 render('admin/users/user_edit.html'),
151 render('admin/users/user_edit.html'),
153 defaults=defaults,
152 defaults=defaults,
154 encoding="UTF-8",
153 encoding="UTF-8",
155 force_defaults=False
154 force_defaults=False
156 )
155 )
@@ -1,405 +1,413 b''
1 #!/usr/bin/env python
1 #!/usr/bin/env python
2 # encoding: utf-8
2 # encoding: utf-8
3 # authentication and permission libraries
3 # authentication and permission libraries
4 # Copyright (C) 2009-2010 Marcin Kuzminski <marcin@python-works.com>
4 # Copyright (C) 2009-2010 Marcin Kuzminski <marcin@python-works.com>
5
5
6 # This program is free software; you can redistribute it and/or
6 # This program is free software; you can redistribute it and/or
7 # modify it under the terms of the GNU General Public License
7 # modify it under the terms of the GNU General Public License
8 # as published by the Free Software Foundation; version 2
8 # as published by the Free Software Foundation; version 2
9 # of the License or (at your opinion) any later version of the license.
9 # of the License or (at your opinion) any later version of the license.
10 #
10 #
11 # This program is distributed in the hope that it will be useful,
11 # This program is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 # GNU General Public License for more details.
14 # GNU General Public License for more details.
15 #
15 #
16 # You should have received a copy of the GNU General Public License
16 # You should have received a copy of the GNU General Public License
17 # along with this program; if not, write to the Free Software
17 # along with this program; if not, write to the Free Software
18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
19 # MA 02110-1301, USA.
19 # MA 02110-1301, USA.
20 """
20 """
21 Created on April 4, 2010
21 Created on April 4, 2010
22
22
23 @author: marcink
23 @author: marcink
24 """
24 """
25 from beaker.cache import cache_region
25 from beaker.cache import cache_region
26 from functools import wraps
26 from functools import wraps
27 from pylons import config, session, url, request
27 from pylons import config, session, url, request
28 from pylons.controllers.util import abort, redirect
28 from pylons.controllers.util import abort, redirect
29 from pylons_app.lib.utils import get_repo_slug
29 from pylons_app.lib.utils import get_repo_slug
30 from pylons_app.model import meta
30 from pylons_app.model import meta
31 from pylons_app.model.db import User, Repo2Perm, Repository, Permission
31 from pylons_app.model.db import User, Repo2Perm, Repository, Permission
32 from sqlalchemy.exc import OperationalError
32 from sqlalchemy.exc import OperationalError
33 from sqlalchemy.orm.exc import NoResultFound, MultipleResultsFound
33 from sqlalchemy.orm.exc import NoResultFound, MultipleResultsFound
34 import crypt
34 import crypt
35 import logging
35 import logging
36
36
37 log = logging.getLogger(__name__)
37 log = logging.getLogger(__name__)
38
38
39 def get_crypt_password(password):
39 def get_crypt_password(password):
40 """
40 """
41 Cryptographic function used for password hashing
41 Cryptographic function used for password hashing
42 @param password: password to hash
42 @param password: password to hash
43 """
43 """
44 return crypt.crypt(password, '6a')
44 return crypt.crypt(password, '6a')
45
45
46
46
47 @cache_region('super_short_term', 'cached_user')
47 @cache_region('super_short_term', 'cached_user')
48 def get_user_cached(username):
48 def get_user_cached(username):
49 sa = meta.Session
49 sa = meta.Session
50 user = sa.query(User).filter(User.username == username).one()
50 try:
51 user = sa.query(User).filter(User.username == username).one()
52 finally:
53 meta.Session.remove()
51 return user
54 return user
52
55
53 def authfunc(environ, username, password):
56 def authfunc(environ, username, password):
54 password_crypt = get_crypt_password(password)
57 password_crypt = get_crypt_password(password)
55 try:
58 try:
56 user = get_user_cached(username)
59 user = get_user_cached(username)
57 except (NoResultFound, MultipleResultsFound, OperationalError) as e:
60 except (NoResultFound, MultipleResultsFound, OperationalError) as e:
58 log.error(e)
61 log.error(e)
59 user = None
62 user = None
60
63
61 if user:
64 if user:
62 if user.active:
65 if user.active:
63 if user.username == username and user.password == password_crypt:
66 if user.username == username and user.password == password_crypt:
64 log.info('user %s authenticated correctly', username)
67 log.info('user %s authenticated correctly', username)
65 return True
68 return True
66 else:
69 else:
67 log.error('user %s is disabled', username)
70 log.error('user %s is disabled', username)
68
71
69 return False
72 return False
70
73
71 class AuthUser(object):
74 class AuthUser(object):
72 """
75 """
73 A simple object that handles a mercurial username for authentication
76 A simple object that handles a mercurial username for authentication
74 """
77 """
75 def __init__(self):
78 def __init__(self):
76 self.username = 'None'
79 self.username = 'None'
77 self.user_id = None
80 self.user_id = None
78 self.is_authenticated = False
81 self.is_authenticated = False
79 self.is_admin = False
82 self.is_admin = False
80 self.permissions = {}
83 self.permissions = {}
81
84
82
85
83 def set_available_permissions(config):
86 def set_available_permissions(config):
84 """
87 """
85 This function will propagate pylons globals with all available defined
88 This function will propagate pylons globals with all available defined
86 permission given in db. We don't wannt to check each time from db for new
89 permission given in db. We don't wannt to check each time from db for new
87 permissions since adding a new permission also requires application restart
90 permissions since adding a new permission also requires application restart
88 ie. to decorate new views with the newly created permission
91 ie. to decorate new views with the newly created permission
89 @param config:
92 @param config:
90 """
93 """
91 log.info('getting information about all available permissions')
94 log.info('getting information about all available permissions')
92 sa = meta.Session
95 try:
93 all_perms = sa.query(Permission).all()
96 sa = meta.Session
97 all_perms = sa.query(Permission).all()
98 finally:
99 meta.Session.remove()
100
94 config['available_permissions'] = [x.permission_name for x in all_perms]
101 config['available_permissions'] = [x.permission_name for x in all_perms]
95
102
96 def set_base_path(config):
103 def set_base_path(config):
97 config['base_path'] = config['pylons.app_globals'].base_path
104 config['base_path'] = config['pylons.app_globals'].base_path
98
105
99 def fill_perms(user):
106 def fill_perms(user):
100 sa = meta.Session
107 sa = meta.Session
101 user.permissions['repositories'] = {}
108 user.permissions['repositories'] = {}
102
109
103 #first fetch default permissions
110 #first fetch default permissions
104 default_perms = sa.query(Repo2Perm, Repository, Permission)\
111 default_perms = sa.query(Repo2Perm, Repository, Permission)\
105 .join((Repository, Repo2Perm.repository == Repository.repo_name))\
112 .join((Repository, Repo2Perm.repository == Repository.repo_name))\
106 .join((Permission, Repo2Perm.permission_id == Permission.permission_id))\
113 .join((Permission, Repo2Perm.permission_id == Permission.permission_id))\
107 .filter(Repo2Perm.user_id == sa.query(User).filter(User.username ==
114 .filter(Repo2Perm.user_id == sa.query(User).filter(User.username ==
108 'default').one().user_id).all()
115 'default').one().user_id).all()
109
116
110 if user.is_admin:
117 if user.is_admin:
111 user.permissions['global'] = set(['hg.admin'])
118 user.permissions['global'] = set(['hg.admin'])
112 #admin have all rights full
119 #admin have all rights full
113 for perm in default_perms:
120 for perm in default_perms:
114 p = 'repository.admin'
121 p = 'repository.admin'
115 user.permissions['repositories'][perm.Repo2Perm.repository] = p
122 user.permissions['repositories'][perm.Repo2Perm.repository] = p
116
123
117 else:
124 else:
118 user.permissions['global'] = set()
125 user.permissions['global'] = set()
119 for perm in default_perms:
126 for perm in default_perms:
120 if perm.Repository.private:
127 if perm.Repository.private:
121 #disable defaults for private repos,
128 #disable defaults for private repos,
122 p = 'repository.none'
129 p = 'repository.none'
123 elif perm.Repository.user_id == user.user_id:
130 elif perm.Repository.user_id == user.user_id:
124 #set admin if owner
131 #set admin if owner
125 p = 'repository.admin'
132 p = 'repository.admin'
126 else:
133 else:
127 p = perm.Permission.permission_name
134 p = perm.Permission.permission_name
128
135
129 user.permissions['repositories'][perm.Repo2Perm.repository] = p
136 user.permissions['repositories'][perm.Repo2Perm.repository] = p
130
137
131
138
132 user_perms = sa.query(Repo2Perm, Permission, Repository)\
139 user_perms = sa.query(Repo2Perm, Permission, Repository)\
133 .join((Repository, Repo2Perm.repository == Repository.repo_name))\
140 .join((Repository, Repo2Perm.repository == Repository.repo_name))\
134 .join((Permission, Repo2Perm.permission_id == Permission.permission_id))\
141 .join((Permission, Repo2Perm.permission_id == Permission.permission_id))\
135 .filter(Repo2Perm.user_id == user.user_id).all()
142 .filter(Repo2Perm.user_id == user.user_id).all()
136 #overwrite userpermissions with defaults
143 #overwrite userpermissions with defaults
137 for perm in user_perms:
144 for perm in user_perms:
138 #set write if owner
145 #set write if owner
139 if perm.Repository.user_id == user.user_id:
146 if perm.Repository.user_id == user.user_id:
140 p = 'repository.write'
147 p = 'repository.write'
141 else:
148 else:
142 p = perm.Permission.permission_name
149 p = perm.Permission.permission_name
143 user.permissions['repositories'][perm.Repo2Perm.repository] = p
150 user.permissions['repositories'][perm.Repo2Perm.repository] = p
151 meta.Session.remove()
144 return user
152 return user
145
153
146 def get_user(session):
154 def get_user(session):
147 """
155 """
148 Gets user from session, and wraps permissions into user
156 Gets user from session, and wraps permissions into user
149 @param session:
157 @param session:
150 """
158 """
151 user = session.get('hg_app_user', AuthUser())
159 user = session.get('hg_app_user', AuthUser())
152
160
153 if user.is_authenticated:
161 if user.is_authenticated:
154 user = fill_perms(user)
162 user = fill_perms(user)
155
163
156 session['hg_app_user'] = user
164 session['hg_app_user'] = user
157 session.save()
165 session.save()
158 return user
166 return user
159
167
160 #===============================================================================
168 #===============================================================================
161 # CHECK DECORATORS
169 # CHECK DECORATORS
162 #===============================================================================
170 #===============================================================================
163 class LoginRequired(object):
171 class LoginRequired(object):
164 """
172 """
165 Must be logged in to execute this function else redirect to login page
173 Must be logged in to execute this function else redirect to login page
166 """
174 """
167
175
168 def __call__(self, func):
176 def __call__(self, func):
169 @wraps(func)
177 @wraps(func)
170 def _wrapper(*fargs, **fkwargs):
178 def _wrapper(*fargs, **fkwargs):
171 user = session.get('hg_app_user', AuthUser())
179 user = session.get('hg_app_user', AuthUser())
172 log.debug('Checking login required for user:%s', user.username)
180 log.debug('Checking login required for user:%s', user.username)
173 if user.is_authenticated:
181 if user.is_authenticated:
174 log.debug('user %s is authenticated', user.username)
182 log.debug('user %s is authenticated', user.username)
175 func(*fargs)
183 func(*fargs)
176 else:
184 else:
177 log.warn('user %s not authenticated', user.username)
185 log.warn('user %s not authenticated', user.username)
178 log.debug('redirecting to login page')
186 log.debug('redirecting to login page')
179 return redirect(url('login_home'))
187 return redirect(url('login_home'))
180
188
181 return _wrapper
189 return _wrapper
182
190
183 class PermsDecorator(object):
191 class PermsDecorator(object):
184 """
192 """
185 Base class for decorators
193 Base class for decorators
186 """
194 """
187
195
188 def __init__(self, *required_perms):
196 def __init__(self, *required_perms):
189 available_perms = config['available_permissions']
197 available_perms = config['available_permissions']
190 for perm in required_perms:
198 for perm in required_perms:
191 if perm not in available_perms:
199 if perm not in available_perms:
192 raise Exception("'%s' permission is not defined" % perm)
200 raise Exception("'%s' permission is not defined" % perm)
193 self.required_perms = set(required_perms)
201 self.required_perms = set(required_perms)
194 self.user_perms = None
202 self.user_perms = None
195
203
196 def __call__(self, func):
204 def __call__(self, func):
197 @wraps(func)
205 @wraps(func)
198 def _wrapper(*fargs, **fkwargs):
206 def _wrapper(*fargs, **fkwargs):
199 self.user_perms = session.get('hg_app_user', AuthUser()).permissions
207 self.user_perms = session.get('hg_app_user', AuthUser()).permissions
200 log.debug('checking %s permissions %s for %s',
208 log.debug('checking %s permissions %s for %s',
201 self.__class__.__name__, self.required_perms, func.__name__)
209 self.__class__.__name__, self.required_perms, func.__name__)
202
210
203 if self.check_permissions():
211 if self.check_permissions():
204 log.debug('Permission granted for %s', func.__name__)
212 log.debug('Permission granted for %s', func.__name__)
205 return func(*fargs)
213 return func(*fargs)
206
214
207 else:
215 else:
208 log.warning('Permission denied for %s', func.__name__)
216 log.warning('Permission denied for %s', func.__name__)
209 #redirect with forbidden ret code
217 #redirect with forbidden ret code
210 return abort(403)
218 return abort(403)
211 return _wrapper
219 return _wrapper
212
220
213
221
214 def check_permissions(self):
222 def check_permissions(self):
215 """
223 """
216 Dummy function for overriding
224 Dummy function for overriding
217 """
225 """
218 raise Exception('You have to write this function in child class')
226 raise Exception('You have to write this function in child class')
219
227
220 class HasPermissionAllDecorator(PermsDecorator):
228 class HasPermissionAllDecorator(PermsDecorator):
221 """
229 """
222 Checks for access permission for all given predicates. All of them have to
230 Checks for access permission for all given predicates. All of them have to
223 be meet in order to fulfill the request
231 be meet in order to fulfill the request
224 """
232 """
225
233
226 def check_permissions(self):
234 def check_permissions(self):
227 if self.required_perms.issubset(self.user_perms.get('global')):
235 if self.required_perms.issubset(self.user_perms.get('global')):
228 return True
236 return True
229 return False
237 return False
230
238
231
239
232 class HasPermissionAnyDecorator(PermsDecorator):
240 class HasPermissionAnyDecorator(PermsDecorator):
233 """
241 """
234 Checks for access permission for any of given predicates. In order to
242 Checks for access permission for any of given predicates. In order to
235 fulfill the request any of predicates must be meet
243 fulfill the request any of predicates must be meet
236 """
244 """
237
245
238 def check_permissions(self):
246 def check_permissions(self):
239 if self.required_perms.intersection(self.user_perms.get('global')):
247 if self.required_perms.intersection(self.user_perms.get('global')):
240 return True
248 return True
241 return False
249 return False
242
250
243 class HasRepoPermissionAllDecorator(PermsDecorator):
251 class HasRepoPermissionAllDecorator(PermsDecorator):
244 """
252 """
245 Checks for access permission for all given predicates for specific
253 Checks for access permission for all given predicates for specific
246 repository. All of them have to be meet in order to fulfill the request
254 repository. All of them have to be meet in order to fulfill the request
247 """
255 """
248
256
249 def check_permissions(self):
257 def check_permissions(self):
250 repo_name = get_repo_slug(request)
258 repo_name = get_repo_slug(request)
251 try:
259 try:
252 user_perms = set([self.user_perms['repositories'][repo_name]])
260 user_perms = set([self.user_perms['repositories'][repo_name]])
253 except KeyError:
261 except KeyError:
254 return False
262 return False
255 if self.required_perms.issubset(user_perms):
263 if self.required_perms.issubset(user_perms):
256 return True
264 return True
257 return False
265 return False
258
266
259
267
260 class HasRepoPermissionAnyDecorator(PermsDecorator):
268 class HasRepoPermissionAnyDecorator(PermsDecorator):
261 """
269 """
262 Checks for access permission for any of given predicates for specific
270 Checks for access permission for any of given predicates for specific
263 repository. In order to fulfill the request any of predicates must be meet
271 repository. In order to fulfill the request any of predicates must be meet
264 """
272 """
265
273
266 def check_permissions(self):
274 def check_permissions(self):
267 repo_name = get_repo_slug(request)
275 repo_name = get_repo_slug(request)
268
276
269 try:
277 try:
270 user_perms = set([self.user_perms['repositories'][repo_name]])
278 user_perms = set([self.user_perms['repositories'][repo_name]])
271 except KeyError:
279 except KeyError:
272 return False
280 return False
273 if self.required_perms.intersection(user_perms):
281 if self.required_perms.intersection(user_perms):
274 return True
282 return True
275 return False
283 return False
276 #===============================================================================
284 #===============================================================================
277 # CHECK FUNCTIONS
285 # CHECK FUNCTIONS
278 #===============================================================================
286 #===============================================================================
279
287
280 class PermsFunction(object):
288 class PermsFunction(object):
281 """
289 """
282 Base function for other check functions
290 Base function for other check functions
283 """
291 """
284
292
285 def __init__(self, *perms):
293 def __init__(self, *perms):
286 available_perms = config['available_permissions']
294 available_perms = config['available_permissions']
287
295
288 for perm in perms:
296 for perm in perms:
289 if perm not in available_perms:
297 if perm not in available_perms:
290 raise Exception("'%s' permission in not defined" % perm)
298 raise Exception("'%s' permission in not defined" % perm)
291 self.required_perms = set(perms)
299 self.required_perms = set(perms)
292 self.user_perms = None
300 self.user_perms = None
293 self.granted_for = ''
301 self.granted_for = ''
294 self.repo_name = None
302 self.repo_name = None
295
303
296 def __call__(self, check_Location=''):
304 def __call__(self, check_Location=''):
297 user = session.get('hg_app_user', False)
305 user = session.get('hg_app_user', False)
298 if not user:
306 if not user:
299 return False
307 return False
300 self.user_perms = user.permissions
308 self.user_perms = user.permissions
301 self.granted_for = user.username
309 self.granted_for = user.username
302 log.debug('checking %s %s', self.__class__.__name__, self.required_perms)
310 log.debug('checking %s %s', self.__class__.__name__, self.required_perms)
303
311
304 if self.check_permissions():
312 if self.check_permissions():
305 log.debug('Permission granted for %s @%s', self.granted_for,
313 log.debug('Permission granted for %s @%s', self.granted_for,
306 check_Location)
314 check_Location)
307 return True
315 return True
308
316
309 else:
317 else:
310 log.warning('Permission denied for %s @%s', self.granted_for,
318 log.warning('Permission denied for %s @%s', self.granted_for,
311 check_Location)
319 check_Location)
312 return False
320 return False
313
321
314 def check_permissions(self):
322 def check_permissions(self):
315 """
323 """
316 Dummy function for overriding
324 Dummy function for overriding
317 """
325 """
318 raise Exception('You have to write this function in child class')
326 raise Exception('You have to write this function in child class')
319
327
320 class HasPermissionAll(PermsFunction):
328 class HasPermissionAll(PermsFunction):
321 def check_permissions(self):
329 def check_permissions(self):
322 if self.required_perms.issubset(self.user_perms.get('global')):
330 if self.required_perms.issubset(self.user_perms.get('global')):
323 return True
331 return True
324 return False
332 return False
325
333
326 class HasPermissionAny(PermsFunction):
334 class HasPermissionAny(PermsFunction):
327 def check_permissions(self):
335 def check_permissions(self):
328 if self.required_perms.intersection(self.user_perms.get('global')):
336 if self.required_perms.intersection(self.user_perms.get('global')):
329 return True
337 return True
330 return False
338 return False
331
339
332 class HasRepoPermissionAll(PermsFunction):
340 class HasRepoPermissionAll(PermsFunction):
333
341
334 def __call__(self, repo_name=None, check_Location=''):
342 def __call__(self, repo_name=None, check_Location=''):
335 self.repo_name = repo_name
343 self.repo_name = repo_name
336 return super(HasRepoPermissionAll, self).__call__(check_Location)
344 return super(HasRepoPermissionAll, self).__call__(check_Location)
337
345
338 def check_permissions(self):
346 def check_permissions(self):
339 if not self.repo_name:
347 if not self.repo_name:
340 self.repo_name = get_repo_slug(request)
348 self.repo_name = get_repo_slug(request)
341
349
342 try:
350 try:
343 self.user_perms = set([self.user_perms['repositories']\
351 self.user_perms = set([self.user_perms['repositories']\
344 [self.repo_name]])
352 [self.repo_name]])
345 except KeyError:
353 except KeyError:
346 return False
354 return False
347 self.granted_for = self.repo_name
355 self.granted_for = self.repo_name
348 if self.required_perms.issubset(self.user_perms):
356 if self.required_perms.issubset(self.user_perms):
349 return True
357 return True
350 return False
358 return False
351
359
352 class HasRepoPermissionAny(PermsFunction):
360 class HasRepoPermissionAny(PermsFunction):
353
361
354
362
355 def __call__(self, repo_name=None, check_Location=''):
363 def __call__(self, repo_name=None, check_Location=''):
356 self.repo_name = repo_name
364 self.repo_name = repo_name
357 return super(HasRepoPermissionAny, self).__call__(check_Location)
365 return super(HasRepoPermissionAny, self).__call__(check_Location)
358
366
359 def check_permissions(self):
367 def check_permissions(self):
360 if not self.repo_name:
368 if not self.repo_name:
361 self.repo_name = get_repo_slug(request)
369 self.repo_name = get_repo_slug(request)
362
370
363 try:
371 try:
364 self.user_perms = set([self.user_perms['repositories']\
372 self.user_perms = set([self.user_perms['repositories']\
365 [self.repo_name]])
373 [self.repo_name]])
366 except KeyError:
374 except KeyError:
367 return False
375 return False
368 self.granted_for = self.repo_name
376 self.granted_for = self.repo_name
369 if self.required_perms.intersection(self.user_perms):
377 if self.required_perms.intersection(self.user_perms):
370 return True
378 return True
371 return False
379 return False
372
380
373 #===============================================================================
381 #===============================================================================
374 # SPECIAL VERSION TO HANDLE MIDDLEWARE AUTH
382 # SPECIAL VERSION TO HANDLE MIDDLEWARE AUTH
375 #===============================================================================
383 #===============================================================================
376
384
377 class HasPermissionAnyMiddleware(object):
385 class HasPermissionAnyMiddleware(object):
378 def __init__(self, *perms):
386 def __init__(self, *perms):
379 self.required_perms = set(perms)
387 self.required_perms = set(perms)
380
388
381 def __call__(self, user, repo_name):
389 def __call__(self, user, repo_name):
382 usr = AuthUser()
390 usr = AuthUser()
383 usr.user_id = user.user_id
391 usr.user_id = user.user_id
384 usr.username = user.username
392 usr.username = user.username
385 usr.is_admin = user.admin
393 usr.is_admin = user.admin
386
394
387 try:
395 try:
388 self.user_perms = set([fill_perms(usr)\
396 self.user_perms = set([fill_perms(usr)\
389 .permissions['repositories'][repo_name]])
397 .permissions['repositories'][repo_name]])
390 except:
398 except:
391 self.user_perms = set()
399 self.user_perms = set()
392 self.granted_for = ''
400 self.granted_for = ''
393 self.username = user.username
401 self.username = user.username
394 self.repo_name = repo_name
402 self.repo_name = repo_name
395 return self.check_permissions()
403 return self.check_permissions()
396
404
397 def check_permissions(self):
405 def check_permissions(self):
398 log.debug('checking mercurial protocol '
406 log.debug('checking mercurial protocol '
399 'permissions for user:%s repository:%s',
407 'permissions for user:%s repository:%s',
400 self.username, self.repo_name)
408 self.username, self.repo_name)
401 if self.required_perms.intersection(self.user_perms):
409 if self.required_perms.intersection(self.user_perms):
402 log.debug('permission granted')
410 log.debug('permission granted')
403 return True
411 return True
404 log.debug('permission denied')
412 log.debug('permission denied')
405 return False
413 return False
@@ -1,193 +1,193 b''
1 #!/usr/bin/env python
1 #!/usr/bin/env python
2 # encoding: utf-8
2 # encoding: utf-8
3 # database managment for hg app
3 # database managment for hg app
4 # Copyright (C) 2009-2010 Marcin Kuzminski <marcin@python-works.com>
4 # Copyright (C) 2009-2010 Marcin Kuzminski <marcin@python-works.com>
5
5
6 # This program is free software; you can redistribute it and/or
6 # This program is free software; you can redistribute it and/or
7 # modify it under the terms of the GNU General Public License
7 # modify it under the terms of the GNU General Public License
8 # as published by the Free Software Foundation; version 2
8 # as published by the Free Software Foundation; version 2
9 # of the License or (at your opinion) any later version of the license.
9 # of the License or (at your opinion) any later version of the license.
10 #
10 #
11 # This program is distributed in the hope that it will be useful,
11 # This program is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 # GNU General Public License for more details.
14 # GNU General Public License for more details.
15 #
15 #
16 # You should have received a copy of the GNU General Public License
16 # You should have received a copy of the GNU General Public License
17 # along with this program; if not, write to the Free Software
17 # along with this program; if not, write to the Free Software
18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
19 # MA 02110-1301, USA.
19 # MA 02110-1301, USA.
20
20
21 """
21 """
22 Created on April 10, 2010
22 Created on April 10, 2010
23 database managment and creation for hg app
23 database managment and creation for hg app
24 @author: marcink
24 @author: marcink
25 """
25 """
26
26
27 from os.path import dirname as dn, join as jn
27 from os.path import dirname as dn, join as jn
28 import os
28 import os
29 import sys
29 import sys
30 import uuid
30 import uuid
31 ROOT = dn(dn(dn(os.path.realpath(__file__))))
31 ROOT = dn(dn(dn(os.path.realpath(__file__))))
32 sys.path.append(ROOT)
32 sys.path.append(ROOT)
33
33
34 from pylons_app.lib.auth import get_crypt_password
34 from pylons_app.lib.auth import get_crypt_password
35 from pylons_app.model import init_model
35 from pylons_app.model import init_model
36 from pylons_app.model.db import User, Permission, HgAppUi, HgAppSettings
36 from pylons_app.model.db import User, Permission, HgAppUi, HgAppSettings
37 from pylons_app.model.meta import Session, Base
37 from pylons_app.model import meta
38 from sqlalchemy.engine import create_engine
38 from sqlalchemy.engine import create_engine
39 import logging
39 import logging
40
40
41 log = logging.getLogger('db manage')
41 log = logging.getLogger('db manage')
42 log.setLevel(logging.DEBUG)
42 log.setLevel(logging.DEBUG)
43 console_handler = logging.StreamHandler()
43 console_handler = logging.StreamHandler()
44 console_handler.setFormatter(logging.Formatter("%(asctime)s.%(msecs)03d"
44 console_handler.setFormatter(logging.Formatter("%(asctime)s.%(msecs)03d"
45 " %(levelname)-5.5s [%(name)s] %(message)s"))
45 " %(levelname)-5.5s [%(name)s] %(message)s"))
46 log.addHandler(console_handler)
46 log.addHandler(console_handler)
47
47
48 class DbManage(object):
48 class DbManage(object):
49 def __init__(self, log_sql):
49 def __init__(self, log_sql):
50 self.dbname = 'hg_app.db'
50 self.dbname = 'hg_app.db'
51 dburi = 'sqlite:////%s' % jn(ROOT, self.dbname)
51 dburi = 'sqlite:////%s' % jn(ROOT, self.dbname)
52 engine = create_engine(dburi, echo=log_sql)
52 engine = create_engine(dburi, echo=log_sql)
53 init_model(engine)
53 init_model(engine)
54 self.sa = Session()
54 self.sa = meta.Session
55 self.db_exists = False
55 self.db_exists = False
56
56
57 def check_for_db(self, override):
57 def check_for_db(self, override):
58 log.info('checking for exisiting db')
58 log.info('checking for exisiting db')
59 if os.path.isfile(jn(ROOT, self.dbname)):
59 if os.path.isfile(jn(ROOT, self.dbname)):
60 self.db_exists = True
60 self.db_exists = True
61 log.info('database exisist')
61 log.info('database exisist')
62 if not override:
62 if not override:
63 raise Exception('database already exists')
63 raise Exception('database already exists')
64
64
65 def create_tables(self, override=False):
65 def create_tables(self, override=False):
66 """
66 """
67 Create a auth database
67 Create a auth database
68 """
68 """
69 self.check_for_db(override)
69 self.check_for_db(override)
70 if override:
70 if override:
71 log.info("database exisist and it's going to be destroyed")
71 log.info("database exisist and it's going to be destroyed")
72 if self.db_exists:
72 if self.db_exists:
73 os.remove(jn(ROOT, self.dbname))
73 os.remove(jn(ROOT, self.dbname))
74 Base.metadata.create_all(checkfirst=override)
74 meta.Base.metadata.create_all(checkfirst=override)
75 log.info('Created tables for %s', self.dbname)
75 log.info('Created tables for %s', self.dbname)
76
76
77 def admin_prompt(self):
77 def admin_prompt(self):
78 import getpass
78 import getpass
79 username = raw_input('Specify admin username:')
79 username = raw_input('Specify admin username:')
80 password = getpass.getpass('Specify admin password:')
80 password = getpass.getpass('Specify admin password:')
81 self.create_user(username, password, True)
81 self.create_user(username, password, True)
82
82
83 def config_prompt(self):
83 def config_prompt(self):
84 log.info('Setting up repositories config')
84 log.info('Setting up repositories config')
85
85
86
86
87 path = raw_input('Specify valid full path to your repositories'
87 path = raw_input('Specify valid full path to your repositories'
88 ' you can change this later application settings:')
88 ' you can change this later application settings:')
89
89
90 if not os.path.isdir(path):
90 if not os.path.isdir(path):
91 log.error('You entered wrong path')
91 log.error('You entered wrong path')
92 sys.exit()
92 sys.exit()
93
93
94 hooks = HgAppUi()
94 hooks = HgAppUi()
95 hooks.ui_section = 'hooks'
95 hooks.ui_section = 'hooks'
96 hooks.ui_key = 'changegroup'
96 hooks.ui_key = 'changegroup'
97 hooks.ui_value = 'hg update >&2'
97 hooks.ui_value = 'hg update >&2'
98
98
99 web1 = HgAppUi()
99 web1 = HgAppUi()
100 web1.ui_section = 'web'
100 web1.ui_section = 'web'
101 web1.ui_key = 'push_ssl'
101 web1.ui_key = 'push_ssl'
102 web1.ui_value = 'false'
102 web1.ui_value = 'false'
103
103
104 web2 = HgAppUi()
104 web2 = HgAppUi()
105 web2.ui_section = 'web'
105 web2.ui_section = 'web'
106 web2.ui_key = 'allow_archive'
106 web2.ui_key = 'allow_archive'
107 web2.ui_value = 'gz zip bz2'
107 web2.ui_value = 'gz zip bz2'
108
108
109 web3 = HgAppUi()
109 web3 = HgAppUi()
110 web3.ui_section = 'web'
110 web3.ui_section = 'web'
111 web3.ui_key = 'allow_push'
111 web3.ui_key = 'allow_push'
112 web3.ui_value = '*'
112 web3.ui_value = '*'
113
113
114 web4 = HgAppUi()
114 web4 = HgAppUi()
115 web4.ui_section = 'web'
115 web4.ui_section = 'web'
116 web4.ui_key = 'baseurl'
116 web4.ui_key = 'baseurl'
117 web4.ui_value = '/'
117 web4.ui_value = '/'
118
118
119 paths = HgAppUi()
119 paths = HgAppUi()
120 paths.ui_section = 'paths'
120 paths.ui_section = 'paths'
121 paths.ui_key = '/'
121 paths.ui_key = '/'
122 paths.ui_value = os.path.join(path, '*')
122 paths.ui_value = os.path.join(path, '*')
123
123
124
124
125 hgsettings = HgAppSettings()
125 hgsettings = HgAppSettings()
126 hgsettings.app_auth_realm = 'hg-app authentication'
126 hgsettings.app_auth_realm = 'hg-app authentication'
127 hgsettings.app_title = 'hg-app'
127 hgsettings.app_title = 'hg-app'
128
128
129 try:
129 try:
130 self.sa.add(hooks)
130 self.sa.add(hooks)
131 self.sa.add(web1)
131 self.sa.add(web1)
132 self.sa.add(web2)
132 self.sa.add(web2)
133 self.sa.add(web3)
133 self.sa.add(web3)
134 self.sa.add(web4)
134 self.sa.add(web4)
135 self.sa.add(paths)
135 self.sa.add(paths)
136 self.sa.add(hgsettings)
136 self.sa.add(hgsettings)
137 self.sa.commit()
137 self.sa.commit()
138 except:
138 except:
139 self.sa.rollback()
139 self.sa.rollback()
140 raise
140 raise
141 log.info('created ui config')
141 log.info('created ui config')
142
142
143 def create_user(self, username, password, admin=False):
143 def create_user(self, username, password, admin=False):
144
144
145 log.info('creating default user')
145 log.info('creating default user')
146 #create default user for handling default permissions.
146 #create default user for handling default permissions.
147 def_user = User()
147 def_user = User()
148 def_user.username = 'default'
148 def_user.username = 'default'
149 def_user.password = get_crypt_password(str(uuid.uuid1())[:8])
149 def_user.password = get_crypt_password(str(uuid.uuid1())[:8])
150 def_user.name = 'default'
150 def_user.name = 'default'
151 def_user.lastname = 'default'
151 def_user.lastname = 'default'
152 def_user.email = 'default@default.com'
152 def_user.email = 'default@default.com'
153 def_user.admin = False
153 def_user.admin = False
154 def_user.active = False
154 def_user.active = False
155
155
156 log.info('creating administrator user %s', username)
156 log.info('creating administrator user %s', username)
157 new_user = User()
157 new_user = User()
158 new_user.username = username
158 new_user.username = username
159 new_user.password = get_crypt_password(password)
159 new_user.password = get_crypt_password(password)
160 new_user.name = 'Hg'
160 new_user.name = 'Hg'
161 new_user.lastname = 'Admin'
161 new_user.lastname = 'Admin'
162 new_user.email = 'admin@localhost'
162 new_user.email = 'admin@localhost'
163 new_user.admin = admin
163 new_user.admin = admin
164 new_user.active = True
164 new_user.active = True
165
165
166 try:
166 try:
167 self.sa.add(def_user)
167 self.sa.add(def_user)
168 self.sa.add(new_user)
168 self.sa.add(new_user)
169 self.sa.commit()
169 self.sa.commit()
170 except:
170 except:
171 self.sa.rollback()
171 self.sa.rollback()
172 raise
172 raise
173
173
174 def create_permissions(self):
174 def create_permissions(self):
175 #module.(access|create|change|delete)_[name]
175 #module.(access|create|change|delete)_[name]
176 #module.(read|write|owner)
176 #module.(read|write|owner)
177 perms = [('repository.none', 'Repository no access'),
177 perms = [('repository.none', 'Repository no access'),
178 ('repository.read', 'Repository read access'),
178 ('repository.read', 'Repository read access'),
179 ('repository.write', 'Repository write access'),
179 ('repository.write', 'Repository write access'),
180 ('repository.admin', 'Repository admin access'),
180 ('repository.admin', 'Repository admin access'),
181 ('hg.admin', 'Hg Administrator'),
181 ('hg.admin', 'Hg Administrator'),
182 ]
182 ]
183
183
184 for p in perms:
184 for p in perms:
185 new_perm = Permission()
185 new_perm = Permission()
186 new_perm.permission_name = p[0]
186 new_perm.permission_name = p[0]
187 new_perm.permission_longname = p[1]
187 new_perm.permission_longname = p[1]
188 try:
188 try:
189 self.sa.add(new_perm)
189 self.sa.add(new_perm)
190 self.sa.commit()
190 self.sa.commit()
191 except:
191 except:
192 self.sa.rollback()
192 self.sa.rollback()
193 raise
193 raise
@@ -1,231 +1,233 b''
1 #!/usr/bin/env python
1 #!/usr/bin/env python
2 # encoding: utf-8
2 # encoding: utf-8
3 # middleware to handle mercurial api calls
3 # middleware to handle mercurial api calls
4 # Copyright (C) 2009-2010 Marcin Kuzminski <marcin@python-works.com>
4 # Copyright (C) 2009-2010 Marcin Kuzminski <marcin@python-works.com>
5
5
6 # This program is free software; you can redistribute it and/or
6 # This program is free software; you can redistribute it and/or
7 # modify it under the terms of the GNU General Public License
7 # modify it under the terms of the GNU General Public License
8 # as published by the Free Software Foundation; version 2
8 # as published by the Free Software Foundation; version 2
9 # of the License or (at your opinion) any later version of the license.
9 # of the License or (at your opinion) any later version of the license.
10 #
10 #
11 # This program is distributed in the hope that it will be useful,
11 # This program is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 # GNU General Public License for more details.
14 # GNU General Public License for more details.
15 #
15 #
16 # You should have received a copy of the GNU General Public License
16 # You should have received a copy of the GNU General Public License
17 # along with this program; if not, write to the Free Software
17 # along with this program; if not, write to the Free Software
18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
19 # MA 02110-1301, USA.
19 # MA 02110-1301, USA.
20
20
21 """
21 """
22 Created on 2010-04-28
22 Created on 2010-04-28
23
23
24 @author: marcink
24 @author: marcink
25 SimpleHG middleware for handling mercurial protocol request (push/clone etc.)
25 SimpleHG middleware for handling mercurial protocol request (push/clone etc.)
26 It's implemented with basic auth function
26 It's implemented with basic auth function
27 """
27 """
28 from datetime import datetime
28 from datetime import datetime
29 from itertools import chain
29 from itertools import chain
30 from mercurial.error import RepoError
30 from mercurial.error import RepoError
31 from mercurial.hgweb import hgweb
31 from mercurial.hgweb import hgweb
32 from mercurial.hgweb.request import wsgiapplication
32 from mercurial.hgweb.request import wsgiapplication
33 from paste.auth.basic import AuthBasicAuthenticator
33 from paste.auth.basic import AuthBasicAuthenticator
34 from paste.httpheaders import REMOTE_USER, AUTH_TYPE
34 from paste.httpheaders import REMOTE_USER, AUTH_TYPE
35 from pylons_app.lib.auth import authfunc, HasPermissionAnyMiddleware, \
35 from pylons_app.lib.auth import authfunc, HasPermissionAnyMiddleware, \
36 get_user_cached
36 get_user_cached
37 from pylons_app.lib.utils import is_mercurial, make_ui, invalidate_cache, \
37 from pylons_app.lib.utils import is_mercurial, make_ui, invalidate_cache, \
38 check_repo_fast
38 check_repo_fast
39 from pylons_app.model import meta
39 from pylons_app.model import meta
40 from pylons_app.model.db import UserLog, User
40 from pylons_app.model.db import UserLog, User
41 from webob.exc import HTTPNotFound, HTTPForbidden, HTTPInternalServerError
41 from webob.exc import HTTPNotFound, HTTPForbidden, HTTPInternalServerError
42 import logging
42 import logging
43 import os
43 import os
44 import pylons_app.lib.helpers as h
44 import pylons_app.lib.helpers as h
45 import traceback
45 import traceback
46
46
47 log = logging.getLogger(__name__)
47 log = logging.getLogger(__name__)
48
48
49 class SimpleHg(object):
49 class SimpleHg(object):
50
50
51 def __init__(self, application, config):
51 def __init__(self, application, config):
52 self.application = application
52 self.application = application
53 self.config = config
53 self.config = config
54 #authenticate this mercurial request using
54 #authenticate this mercurial request using
55 realm = self.config['hg_app_auth_realm']
55 self.authenticate = AuthBasicAuthenticator('', authfunc)
56 self.authenticate = AuthBasicAuthenticator(realm, authfunc)
57
56
58 def __call__(self, environ, start_response):
57 def __call__(self, environ, start_response):
59 if not is_mercurial(environ):
58 if not is_mercurial(environ):
60 return self.application(environ, start_response)
59 return self.application(environ, start_response)
61
60
62 #===================================================================
61 #===================================================================
63 # AUTHENTICATE THIS MERCURIAL REQUEST
62 # AUTHENTICATE THIS MERCURIAL REQUEST
64 #===================================================================
63 #===================================================================
65 username = REMOTE_USER(environ)
64 username = REMOTE_USER(environ)
66 if not username:
65 if not username:
66 self.authenticate.realm = self.config['hg_app_auth_realm']
67 result = self.authenticate(environ)
67 result = self.authenticate(environ)
68 if isinstance(result, str):
68 if isinstance(result, str):
69 AUTH_TYPE.update(environ, 'basic')
69 AUTH_TYPE.update(environ, 'basic')
70 REMOTE_USER.update(environ, result)
70 REMOTE_USER.update(environ, result)
71 else:
71 else:
72 return result.wsgi_application(environ, start_response)
72 return result.wsgi_application(environ, start_response)
73
73
74 try:
74 try:
75 repo_name = '/'.join(environ['PATH_INFO'].split('/')[1:])
75 repo_name = '/'.join(environ['PATH_INFO'].split('/')[1:])
76 except:
76 except:
77 log.error(traceback.format_exc())
77 log.error(traceback.format_exc())
78 return HTTPInternalServerError()(environ, start_response)
78 return HTTPInternalServerError()(environ, start_response)
79
79
80 #===================================================================
80 #===================================================================
81 # CHECK PERMISSIONS FOR THIS REQUEST
81 # CHECK PERMISSIONS FOR THIS REQUEST
82 #===================================================================
82 #===================================================================
83 action = self.__get_action(environ)
83 action = self.__get_action(environ)
84 if action:
84 if action:
85 username = self.__get_environ_user(environ)
85 username = self.__get_environ_user(environ)
86 try:
86 try:
87 user = self.__get_user(username)
87 user = self.__get_user(username)
88 except:
88 except:
89 log.error(traceback.format_exc())
89 log.error(traceback.format_exc())
90 return HTTPInternalServerError()(environ, start_response)
90 return HTTPInternalServerError()(environ, start_response)
91 #check permissions for this repository
91 #check permissions for this repository
92 if action == 'pull':
92 if action == 'pull':
93 if not HasPermissionAnyMiddleware('repository.read',
93 if not HasPermissionAnyMiddleware('repository.read',
94 'repository.write',
94 'repository.write',
95 'repository.admin')\
95 'repository.admin')\
96 (user, repo_name):
96 (user, repo_name):
97 return HTTPForbidden()(environ, start_response)
97 return HTTPForbidden()(environ, start_response)
98 if action == 'push':
98 if action == 'push':
99 if not HasPermissionAnyMiddleware('repository.write',
99 if not HasPermissionAnyMiddleware('repository.write',
100 'repository.admin')\
100 'repository.admin')\
101 (user, repo_name):
101 (user, repo_name):
102 return HTTPForbidden()(environ, start_response)
102 return HTTPForbidden()(environ, start_response)
103
103
104 #log action
104 #log action
105 proxy_key = 'HTTP_X_REAL_IP'
105 proxy_key = 'HTTP_X_REAL_IP'
106 def_key = 'REMOTE_ADDR'
106 def_key = 'REMOTE_ADDR'
107 ipaddr = environ.get(proxy_key, environ.get(def_key, '0.0.0.0'))
107 ipaddr = environ.get(proxy_key, environ.get(def_key, '0.0.0.0'))
108 self.__log_user_action(user, action, repo_name, ipaddr)
108 self.__log_user_action(user, action, repo_name, ipaddr)
109
109
110 #===================================================================
110 #===================================================================
111 # MERCURIAL REQUEST HANDLING
111 # MERCURIAL REQUEST HANDLING
112 #===================================================================
112 #===================================================================
113 environ['PATH_INFO'] = '/'#since we wrap into hgweb, reset the path
113 environ['PATH_INFO'] = '/'#since we wrap into hgweb, reset the path
114 self.baseui = make_ui('db')
114 self.baseui = make_ui('db')
115 self.basepath = self.config['base_path']
115 self.basepath = self.config['base_path']
116 self.repo_path = os.path.join(self.basepath, repo_name)
116 self.repo_path = os.path.join(self.basepath, repo_name)
117
117
118 #quick check if that dir exists...
118 #quick check if that dir exists...
119 if check_repo_fast(repo_name, self.basepath):
119 if check_repo_fast(repo_name, self.basepath):
120 return HTTPNotFound()(environ, start_response)
120 return HTTPNotFound()(environ, start_response)
121 try:
121 try:
122 app = wsgiapplication(self.__make_app)
122 app = wsgiapplication(self.__make_app)
123 except RepoError as e:
123 except RepoError as e:
124 if str(e).find('not found') != -1:
124 if str(e).find('not found') != -1:
125 return HTTPNotFound()(environ, start_response)
125 return HTTPNotFound()(environ, start_response)
126 except Exception:
126 except Exception:
127 log.error(traceback.format_exc())
127 log.error(traceback.format_exc())
128 return HTTPInternalServerError()(environ, start_response)
128 return HTTPInternalServerError()(environ, start_response)
129
129
130 #invalidate cache on push
130 #invalidate cache on push
131 if action == 'push':
131 if action == 'push':
132 self.__invalidate_cache(repo_name)
132 self.__invalidate_cache(repo_name)
133 messages = []
133 messages = []
134 messages.append('thank you for using hg-app')
134 messages.append('thank you for using hg-app')
135
135
136 return self.msg_wrapper(app, environ, start_response, messages)
136 return self.msg_wrapper(app, environ, start_response, messages)
137 else:
137 else:
138 return app(environ, start_response)
138 return app(environ, start_response)
139
139
140
140
141 def msg_wrapper(self, app, environ, start_response, messages=[]):
141 def msg_wrapper(self, app, environ, start_response, messages=[]):
142 """
142 """
143 Wrapper for custom messages that come out of mercurial respond messages
143 Wrapper for custom messages that come out of mercurial respond messages
144 is a list of messages that the user will see at the end of response
144 is a list of messages that the user will see at the end of response
145 from merurial protocol actions that involves remote answers
145 from merurial protocol actions that involves remote answers
146 @param app:
146 @param app:
147 @param environ:
147 @param environ:
148 @param start_response:
148 @param start_response:
149 """
149 """
150 def custom_messages(msg_list):
150 def custom_messages(msg_list):
151 for msg in msg_list:
151 for msg in msg_list:
152 yield msg + '\n'
152 yield msg + '\n'
153 org_response = app(environ, start_response)
153 org_response = app(environ, start_response)
154 return chain(org_response, custom_messages(messages))
154 return chain(org_response, custom_messages(messages))
155
155
156 def __make_app(self):
156 def __make_app(self):
157 hgserve = hgweb(str(self.repo_path), baseui=self.baseui)
157 hgserve = hgweb(str(self.repo_path), baseui=self.baseui)
158 return self.__load_web_settings(hgserve)
158 return self.__load_web_settings(hgserve)
159
159
160 def __get_environ_user(self, environ):
160 def __get_environ_user(self, environ):
161 return environ.get('REMOTE_USER')
161 return environ.get('REMOTE_USER')
162
162
163 def __get_user(self, username):
163 def __get_user(self, username):
164 return get_user_cached(username)
164 return get_user_cached(username)
165
165
166
166
167
167
168 def __get_size(self, repo_path, content_size):
168 def __get_size(self, repo_path, content_size):
169 size = int(content_size)
169 size = int(content_size)
170 for path, dirs, files in os.walk(repo_path):
170 for path, dirs, files in os.walk(repo_path):
171 if path.find('.hg') == -1:
171 if path.find('.hg') == -1:
172 for f in files:
172 for f in files:
173 size += os.path.getsize(os.path.join(path, f))
173 size += os.path.getsize(os.path.join(path, f))
174 return size
174 return size
175 return h.format_byte_size(size)
175 return h.format_byte_size(size)
176
176
177 def __get_action(self, environ):
177 def __get_action(self, environ):
178 """
178 """
179 Maps mercurial request commands into a pull or push command.
179 Maps mercurial request commands into a pull or push command.
180 @param environ:
180 @param environ:
181 """
181 """
182 mapping = {'changegroup': 'pull',
182 mapping = {'changegroup': 'pull',
183 'changegroupsubset': 'pull',
183 'changegroupsubset': 'pull',
184 'stream_out': 'pull',
184 'stream_out': 'pull',
185 'listkeys': 'pull',
185 'listkeys': 'pull',
186 'unbundle': 'push',
186 'unbundle': 'push',
187 'pushkey': 'push', }
187 'pushkey': 'push', }
188
188
189 for qry in environ['QUERY_STRING'].split('&'):
189 for qry in environ['QUERY_STRING'].split('&'):
190 if qry.startswith('cmd'):
190 if qry.startswith('cmd'):
191 cmd = qry.split('=')[-1]
191 cmd = qry.split('=')[-1]
192 if mapping.has_key(cmd):
192 if mapping.has_key(cmd):
193 return mapping[cmd]
193 return mapping[cmd]
194
194
195 def __log_user_action(self, user, action, repo, ipaddr):
195 def __log_user_action(self, user, action, repo, ipaddr):
196 sa = meta.Session
196 sa = meta.Session
197 try:
197 try:
198 user_log = UserLog()
198 user_log = UserLog()
199 user_log.user_id = user.user_id
199 user_log.user_id = user.user_id
200 user_log.action = action
200 user_log.action = action
201 user_log.repository = repo.replace('/', '')
201 user_log.repository = repo.replace('/', '')
202 user_log.action_date = datetime.now()
202 user_log.action_date = datetime.now()
203 user_log.user_ip = ipaddr
203 user_log.user_ip = ipaddr
204 sa.add(user_log)
204 sa.add(user_log)
205 sa.commit()
205 sa.commit()
206 log.info('Adding user %s, action %s on %s',
206 log.info('Adding user %s, action %s on %s',
207 user.username, action, repo)
207 user.username, action, repo)
208 except Exception as e:
208 except Exception as e:
209 sa.rollback()
209 sa.rollback()
210 log.error('could not log user action:%s', str(e))
210 log.error('could not log user action:%s', str(e))
211
211 finally:
212 meta.Session.remove()
213
212 def __invalidate_cache(self, repo_name):
214 def __invalidate_cache(self, repo_name):
213 """we know that some change was made to repositories and we should
215 """we know that some change was made to repositories and we should
214 invalidate the cache to see the changes right away but only for
216 invalidate the cache to see the changes right away but only for
215 push requests"""
217 push requests"""
216 invalidate_cache('cached_repo_list')
218 invalidate_cache('cached_repo_list')
217 invalidate_cache('full_changelog', repo_name)
219 invalidate_cache('full_changelog', repo_name)
218
220
219
221
220 def __load_web_settings(self, hgserve):
222 def __load_web_settings(self, hgserve):
221 #set the global ui for hgserve
223 #set the global ui for hgserve
222 hgserve.repo.ui = self.baseui
224 hgserve.repo.ui = self.baseui
223
225
224 hgrc = os.path.join(self.repo_path, '.hg', 'hgrc')
226 hgrc = os.path.join(self.repo_path, '.hg', 'hgrc')
225 repoui = make_ui('file', hgrc, False)
227 repoui = make_ui('file', hgrc, False)
226
228
227 if repoui:
229 if repoui:
228 #set the repository based config
230 #set the repository based config
229 hgserve.repo.ui = repoui
231 hgserve.repo.ui = repoui
230
232
231 return hgserve
233 return hgserve
@@ -1,210 +1,221 b''
1 #!/usr/bin/env python
1 #!/usr/bin/env python
2 # encoding: utf-8
2 # encoding: utf-8
3 # Utilities for hg app
3 # Utilities for hg app
4 # Copyright (C) 2009-2010 Marcin Kuzminski <marcin@python-works.com>
4 # Copyright (C) 2009-2010 Marcin Kuzminski <marcin@python-works.com>
5 # This program is free software; you can redistribute it and/or
5 # This program is free software; you can redistribute it and/or
6 # modify it under the terms of the GNU General Public License
6 # modify it under the terms of the GNU General Public License
7 # as published by the Free Software Foundation; version 2
7 # as published by the Free Software Foundation; version 2
8 # of the License or (at your opinion) any later version of the license.
8 # of the License or (at your opinion) any later version of the license.
9 #
9 #
10 # This program is distributed in the hope that it will be useful,
10 # This program is distributed in the hope that it will be useful,
11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 # GNU General Public License for more details.
13 # GNU General Public License for more details.
14 #
14 #
15 # You should have received a copy of the GNU General Public License
15 # You should have received a copy of the GNU General Public License
16 # along with this program; if not, write to the Free Software
16 # along with this program; if not, write to the Free Software
17 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
17 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
18 # MA 02110-1301, USA.
18 # MA 02110-1301, USA.
19 from beaker.cache import cache_region
19 from beaker.cache import cache_region
20
20
21 """
21 """
22 Created on April 18, 2010
22 Created on April 18, 2010
23 Utilities for hg app
23 Utilities for hg app
24 @author: marcink
24 @author: marcink
25 """
25 """
26
26
27 import os
27 import os
28 import logging
28 import logging
29 from mercurial import ui, config, hg
29 from mercurial import ui, config, hg
30 from mercurial.error import RepoError
30 from mercurial.error import RepoError
31 from pylons_app.model.db import Repository, User, HgAppUi, HgAppSettings
31 from pylons_app.model.db import Repository, User, HgAppUi, HgAppSettings
32 from pylons_app.model.meta import Session
32 from pylons_app.model import meta
33 log = logging.getLogger(__name__)
33 log = logging.getLogger(__name__)
34
34
35
35
36 def get_repo_slug(request):
36 def get_repo_slug(request):
37 return request.environ['pylons.routes_dict'].get('repo_name')
37 return request.environ['pylons.routes_dict'].get('repo_name')
38
38
39 def is_mercurial(environ):
39 def is_mercurial(environ):
40 """
40 """
41 Returns True if request's target is mercurial server - header
41 Returns True if request's target is mercurial server - header
42 ``HTTP_ACCEPT`` of such request would start with ``application/mercurial``.
42 ``HTTP_ACCEPT`` of such request would start with ``application/mercurial``.
43 """
43 """
44 http_accept = environ.get('HTTP_ACCEPT')
44 http_accept = environ.get('HTTP_ACCEPT')
45 if http_accept and http_accept.startswith('application/mercurial'):
45 if http_accept and http_accept.startswith('application/mercurial'):
46 return True
46 return True
47 return False
47 return False
48
48
49 def check_repo_dir(paths):
49 def check_repo_dir(paths):
50 repos_path = paths[0][1].split('/')
50 repos_path = paths[0][1].split('/')
51 if repos_path[-1] in ['*', '**']:
51 if repos_path[-1] in ['*', '**']:
52 repos_path = repos_path[:-1]
52 repos_path = repos_path[:-1]
53 if repos_path[0] != '/':
53 if repos_path[0] != '/':
54 repos_path[0] = '/'
54 repos_path[0] = '/'
55 if not os.path.isdir(os.path.join(*repos_path)):
55 if not os.path.isdir(os.path.join(*repos_path)):
56 raise Exception('Not a valid repository in %s' % paths[0][1])
56 raise Exception('Not a valid repository in %s' % paths[0][1])
57
57
58 def check_repo_fast(repo_name, base_path):
58 def check_repo_fast(repo_name, base_path):
59 if os.path.isdir(os.path.join(base_path, repo_name)):return False
59 if os.path.isdir(os.path.join(base_path, repo_name)):return False
60 return True
60 return True
61
61
62 def check_repo(repo_name, base_path, verify=True):
62 def check_repo(repo_name, base_path, verify=True):
63
63
64 repo_path = os.path.join(base_path, repo_name)
64 repo_path = os.path.join(base_path, repo_name)
65
65
66 try:
66 try:
67 if not check_repo_fast(repo_name, base_path):
67 if not check_repo_fast(repo_name, base_path):
68 return False
68 return False
69 r = hg.repository(ui.ui(), repo_path)
69 r = hg.repository(ui.ui(), repo_path)
70 if verify:
70 if verify:
71 hg.verify(r)
71 hg.verify(r)
72 #here we hnow that repo exists it was verified
72 #here we hnow that repo exists it was verified
73 log.info('%s repo is already created', repo_name)
73 log.info('%s repo is already created', repo_name)
74 return False
74 return False
75 except RepoError:
75 except RepoError:
76 #it means that there is no valid repo there...
76 #it means that there is no valid repo there...
77 log.info('%s repo is free for creation', repo_name)
77 log.info('%s repo is free for creation', repo_name)
78 return True
78 return True
79
79
80
80
81 @cache_region('super_short_term', 'cached_hg_ui')
81 @cache_region('super_short_term', 'cached_hg_ui')
82 def get_hg_ui_cached():
82 def get_hg_ui_cached():
83 sa = Session()
83 try:
84 return sa.query(HgAppUi).all()
84 sa = meta.Session
85 ret = sa.query(HgAppUi).all()
86 finally:
87 meta.Session.remove()
88 return ret
89
85
90
86 def get_hg_settings():
91 def get_hg_settings():
87 sa = Session()
92 try:
88 ret = sa.query(HgAppSettings).scalar()
93 sa = meta.Session
94 ret = sa.query(HgAppSettings).scalar()
95 finally:
96 meta.Session.remove()
97
89 if not ret:
98 if not ret:
90 raise Exception('Could not get application settings !')
99 raise Exception('Could not get application settings !')
91 return ret
100 return ret
92
101
93 def make_ui(read_from='file', path=None, checkpaths=True):
102 def make_ui(read_from='file', path=None, checkpaths=True):
94 """
103 """
95 A function that will read python rc files or database
104 A function that will read python rc files or database
96 and make an mercurial ui object from read options
105 and make an mercurial ui object from read options
97
106
98 @param path: path to mercurial config file
107 @param path: path to mercurial config file
99 @param checkpaths: check the path
108 @param checkpaths: check the path
100 @param read_from: read from 'file' or 'db'
109 @param read_from: read from 'file' or 'db'
101 """
110 """
102 #propagated from mercurial documentation
111 #propagated from mercurial documentation
103 sections = ['alias', 'auth',
112 sections = ['alias', 'auth',
104 'decode/encode', 'defaults',
113 'decode/encode', 'defaults',
105 'diff', 'email',
114 'diff', 'email',
106 'extensions', 'format',
115 'extensions', 'format',
107 'merge-patterns', 'merge-tools',
116 'merge-patterns', 'merge-tools',
108 'hooks', 'http_proxy',
117 'hooks', 'http_proxy',
109 'smtp', 'patch',
118 'smtp', 'patch',
110 'paths', 'profiling',
119 'paths', 'profiling',
111 'server', 'trusted',
120 'server', 'trusted',
112 'ui', 'web', ]
121 'ui', 'web', ]
113 baseui = ui.ui()
122 baseui = ui.ui()
114
123
115
124
116 if read_from == 'file':
125 if read_from == 'file':
117 if not os.path.isfile(path):
126 if not os.path.isfile(path):
118 log.warning('Unable to read config file %s' % path)
127 log.warning('Unable to read config file %s' % path)
119 return False
128 return False
120
129
121 cfg = config.config()
130 cfg = config.config()
122 cfg.read(path)
131 cfg.read(path)
123 for section in sections:
132 for section in sections:
124 for k, v in cfg.items(section):
133 for k, v in cfg.items(section):
125 baseui.setconfig(section, k, v)
134 baseui.setconfig(section, k, v)
126 if checkpaths:check_repo_dir(cfg.items('paths'))
135 if checkpaths:check_repo_dir(cfg.items('paths'))
127
136
128
137
129 elif read_from == 'db':
138 elif read_from == 'db':
130 hg_ui = get_hg_ui_cached()
139 hg_ui = get_hg_ui_cached()
131 for ui_ in hg_ui:
140 for ui_ in hg_ui:
132 baseui.setconfig(ui_.ui_section, ui_.ui_key, ui_.ui_value)
141 baseui.setconfig(ui_.ui_section, ui_.ui_key, ui_.ui_value)
133
142
134
143
135 return baseui
144 return baseui
136
145
137
146
138 def set_hg_app_config(config):
147 def set_hg_app_config(config):
139 hgsettings = get_hg_settings()
148 hgsettings = get_hg_settings()
140 config['hg_app_auth_realm'] = hgsettings.app_auth_realm
149 config['hg_app_auth_realm'] = hgsettings.app_auth_realm
141 config['hg_app_name'] = hgsettings.app_title
150 config['hg_app_name'] = hgsettings.app_title
142
151
143 def invalidate_cache(name, *args):
152 def invalidate_cache(name, *args):
144 """Invalidates given name cache"""
153 """Invalidates given name cache"""
145
154
146 from beaker.cache import region_invalidate
155 from beaker.cache import region_invalidate
147 log.info('INVALIDATING CACHE FOR %s', name)
156 log.info('INVALIDATING CACHE FOR %s', name)
148
157
149 """propagate our arguments to make sure invalidation works. First
158 """propagate our arguments to make sure invalidation works. First
150 argument has to be the name of cached func name give to cache decorator
159 argument has to be the name of cached func name give to cache decorator
151 without that the invalidation would not work"""
160 without that the invalidation would not work"""
152 tmp = [name]
161 tmp = [name]
153 tmp.extend(args)
162 tmp.extend(args)
154 args = tuple(tmp)
163 args = tuple(tmp)
155
164
156 if name == 'cached_repo_list':
165 if name == 'cached_repo_list':
157 from pylons_app.model.hg_model import _get_repos_cached
166 from pylons_app.model.hg_model import _get_repos_cached
158 region_invalidate(_get_repos_cached, None, *args)
167 region_invalidate(_get_repos_cached, None, *args)
159
168
160 if name == 'full_changelog':
169 if name == 'full_changelog':
161 from pylons_app.model.hg_model import _full_changelog_cached
170 from pylons_app.model.hg_model import _full_changelog_cached
162 region_invalidate(_full_changelog_cached, None, *args)
171 region_invalidate(_full_changelog_cached, None, *args)
163
172
164 from vcs.backends.base import BaseChangeset
173 from vcs.backends.base import BaseChangeset
165 from vcs.utils.lazy import LazyProperty
174 from vcs.utils.lazy import LazyProperty
166 class EmptyChangeset(BaseChangeset):
175 class EmptyChangeset(BaseChangeset):
167
176
168 revision = -1
177 revision = -1
169 message = ''
178 message = ''
170
179
171 @LazyProperty
180 @LazyProperty
172 def raw_id(self):
181 def raw_id(self):
173 """
182 """
174 Returns raw string identifing this changeset, useful for web
183 Returns raw string identifing this changeset, useful for web
175 representation.
184 representation.
176 """
185 """
177 return '0' * 12
186 return '0' * 12
178
187
179
188
180 def repo2db_mapper(initial_repo_list, remove_obsolete=False):
189 def repo2db_mapper(initial_repo_list, remove_obsolete=False):
181 """
190 """
182 maps all found repositories into db
191 maps all found repositories into db
183 """
192 """
184 from pylons_app.model.repo_model import RepoModel
193 from pylons_app.model.repo_model import RepoModel
185
194
186 sa = Session()
195 sa = meta.Session
187 user = sa.query(User).filter(User.admin == True).first()
196 user = sa.query(User).filter(User.admin == True).first()
188
197
189 rm = RepoModel()
198 rm = RepoModel()
190
199
191 for name, repo in initial_repo_list.items():
200 for name, repo in initial_repo_list.items():
192 if not sa.query(Repository).get(name):
201 if not sa.query(Repository).get(name):
193 log.info('repository %s not found creating default', name)
202 log.info('repository %s not found creating default', name)
194
203
195 form_data = {
204 form_data = {
196 'repo_name':name,
205 'repo_name':name,
197 'description':repo.description if repo.description != 'unknown' else \
206 'description':repo.description if repo.description != 'unknown' else \
198 'auto description for %s' % name,
207 'auto description for %s' % name,
199 'private':False
208 'private':False
200 }
209 }
201 rm.create(form_data, user, just_db=True)
210 rm.create(form_data, user, just_db=True)
202
211
203
212
204 if remove_obsolete:
213 if remove_obsolete:
205 #remove from database those repositories that are not in the filesystem
214 #remove from database those repositories that are not in the filesystem
206 for repo in sa.query(Repository).all():
215 for repo in sa.query(Repository).all():
207 if repo.repo_name not in initial_repo_list.keys():
216 if repo.repo_name not in initial_repo_list.keys():
208 sa.delete(repo)
217 sa.delete(repo)
209 sa.commit()
218 sa.commit()
210
219
220
221 meta.Session.remove()
@@ -1,271 +1,280 b''
1 """ this is forms validation classes
1 """ this is forms validation classes
2 http://formencode.org/module-formencode.validators.html
2 http://formencode.org/module-formencode.validators.html
3 for list off all availible validators
3 for list off all availible validators
4
4
5 we can create our own validators
5 we can create our own validators
6
6
7 The table below outlines the options which can be used in a schema in addition to the validators themselves
7 The table below outlines the options which can be used in a schema in addition to the validators themselves
8 pre_validators [] These validators will be applied before the schema
8 pre_validators [] These validators will be applied before the schema
9 chained_validators [] These validators will be applied after the schema
9 chained_validators [] These validators will be applied after the schema
10 allow_extra_fields False If True, then it is not an error when keys that aren't associated with a validator are present
10 allow_extra_fields False If True, then it is not an error when keys that aren't associated with a validator are present
11 filter_extra_fields False If True, then keys that aren't associated with a validator are removed
11 filter_extra_fields False If True, then keys that aren't associated with a validator are removed
12 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.
12 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.
13 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
13 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
14
14
15
15
16 <name> = formencode.validators.<name of validator>
16 <name> = formencode.validators.<name of validator>
17 <name> must equal form name
17 <name> must equal form name
18 list=[1,2,3,4,5]
18 list=[1,2,3,4,5]
19 for SELECT use formencode.All(OneOf(list), Int())
19 for SELECT use formencode.All(OneOf(list), Int())
20
20
21 """
21 """
22 from formencode import All
22 from formencode import All
23 from formencode.validators import UnicodeString, OneOf, Int, Number, Regex, \
23 from formencode.validators import UnicodeString, OneOf, Int, Number, Regex, \
24 Email, Bool, StringBoolean
24 Email, Bool, StringBoolean
25 from pylons import session
25 from pylons import session
26 from pylons.i18n.translation import _
26 from pylons.i18n.translation import _
27 from pylons_app.lib.auth import get_crypt_password
27 from pylons_app.lib.auth import get_crypt_password
28 import pylons_app.lib.helpers as h
28 import pylons_app.lib.helpers as h
29 from pylons_app.model import meta
29 from pylons_app.model import meta
30 from pylons_app.model.db import User, Repository
30 from pylons_app.model.db import User, Repository
31 from sqlalchemy.exc import OperationalError
31 from sqlalchemy.exc import OperationalError
32 from sqlalchemy.orm.exc import NoResultFound, MultipleResultsFound
32 from sqlalchemy.orm.exc import NoResultFound, MultipleResultsFound
33 from webhelpers.pylonslib.secure_form import authentication_token
33 from webhelpers.pylonslib.secure_form import authentication_token
34 import datetime
34 import datetime
35 import formencode
35 import formencode
36 import logging
36 import logging
37 log = logging.getLogger(__name__)
37 log = logging.getLogger(__name__)
38
38
39
39
40 #this is needed to translate the messages using _() in validators
40 #this is needed to translate the messages using _() in validators
41 class State_obj(object):
41 class State_obj(object):
42 _ = staticmethod(_)
42 _ = staticmethod(_)
43
43
44 #===============================================================================
44 #===============================================================================
45 # VALIDATORS
45 # VALIDATORS
46 #===============================================================================
46 #===============================================================================
47 class ValidAuthToken(formencode.validators.FancyValidator):
47 class ValidAuthToken(formencode.validators.FancyValidator):
48 messages = {'invalid_token':_('Token mismatch')}
48 messages = {'invalid_token':_('Token mismatch')}
49
49
50 def validate_python(self, value, state):
50 def validate_python(self, value, state):
51
51
52 if value != authentication_token():
52 if value != authentication_token():
53 raise formencode.Invalid(self.message('invalid_token', state,
53 raise formencode.Invalid(self.message('invalid_token', state,
54 search_number=value), value, state)
54 search_number=value), value, state)
55 class ValidUsername(formencode.validators.FancyValidator):
55 class ValidUsername(formencode.validators.FancyValidator):
56
56
57 def validate_python(self, value, state):
57 def validate_python(self, value, state):
58 if value in ['default', 'new_user']:
58 if value in ['default', 'new_user']:
59 raise formencode.Invalid(_('Invalid username'), value, state)
59 raise formencode.Invalid(_('Invalid username'), value, state)
60
60
61 class ValidPassword(formencode.validators.FancyValidator):
61 class ValidPassword(formencode.validators.FancyValidator):
62
62
63 def to_python(self, value, state):
63 def to_python(self, value, state):
64 if value:
64 if value:
65 return get_crypt_password(value)
65 return get_crypt_password(value)
66
66
67 class ValidAuth(formencode.validators.FancyValidator):
67 class ValidAuth(formencode.validators.FancyValidator):
68 messages = {
68 messages = {
69 'invalid_password':_('invalid password'),
69 'invalid_password':_('invalid password'),
70 'invalid_login':_('invalid user name'),
70 'invalid_login':_('invalid user name'),
71 'disabled_account':_('Your acccount is disabled')
71 'disabled_account':_('Your acccount is disabled')
72
72
73 }
73 }
74 #error mapping
74 #error mapping
75 e_dict = {'username':messages['invalid_login'],
75 e_dict = {'username':messages['invalid_login'],
76 'password':messages['invalid_password']}
76 'password':messages['invalid_password']}
77 e_dict_disable = {'username':messages['disabled_account']}
77 e_dict_disable = {'username':messages['disabled_account']}
78
78
79 def validate_python(self, value, state):
79 def validate_python(self, value, state):
80 sa = meta.Session
80 sa = meta.Session
81 crypted_passwd = get_crypt_password(value['password'])
81 crypted_passwd = get_crypt_password(value['password'])
82 username = value['username']
82 username = value['username']
83 try:
83 try:
84 user = sa.query(User).filter(User.username == username).one()
84 user = sa.query(User).filter(User.username == username).one()
85 except (NoResultFound, MultipleResultsFound, OperationalError) as e:
85 except (NoResultFound, MultipleResultsFound, OperationalError) as e:
86 log.error(e)
86 log.error(e)
87 user = None
87 user = None
88 raise formencode.Invalid(self.message('invalid_password',
88 raise formencode.Invalid(self.message('invalid_password',
89 state=State_obj), value, state,
89 state=State_obj), value, state,
90 error_dict=self.e_dict)
90 error_dict=self.e_dict)
91 if user:
91 if user:
92 if user.active:
92 if user.active:
93 if user.username == username and user.password == crypted_passwd:
93 if user.username == username and user.password == crypted_passwd:
94 from pylons_app.lib.auth import AuthUser
94 from pylons_app.lib.auth import AuthUser
95 auth_user = AuthUser()
95 auth_user = AuthUser()
96 auth_user.username = username
96 auth_user.username = username
97 auth_user.is_authenticated = True
97 auth_user.is_authenticated = True
98 auth_user.is_admin = user.admin
98 auth_user.is_admin = user.admin
99 auth_user.user_id = user.user_id
99 auth_user.user_id = user.user_id
100 session['hg_app_user'] = auth_user
100 session['hg_app_user'] = auth_user
101 session.save()
101 session.save()
102 log.info('user %s is now authenticated', username)
102 log.info('user %s is now authenticated', username)
103
103
104 try:
104 try:
105 user.last_login = datetime.datetime.now()
105 user.last_login = datetime.datetime.now()
106 sa.add(user)
106 sa.add(user)
107 sa.commit()
107 sa.commit()
108 except (OperationalError) as e:
108 except (OperationalError) as e:
109 log.error(e)
109 log.error(e)
110 sa.rollback()
110 sa.rollback()
111
111
112 return value
112 return value
113 else:
113 else:
114 log.warning('user %s not authenticated', username)
114 log.warning('user %s not authenticated', username)
115 raise formencode.Invalid(self.message('invalid_password',
115 raise formencode.Invalid(self.message('invalid_password',
116 state=State_obj), value, state,
116 state=State_obj), value, state,
117 error_dict=self.e_dict)
117 error_dict=self.e_dict)
118 else:
118 else:
119 log.warning('user %s is disabled', username)
119 log.warning('user %s is disabled', username)
120 raise formencode.Invalid(self.message('disabled_account',
120 raise formencode.Invalid(self.message('disabled_account',
121 state=State_obj),
121 state=State_obj),
122 value, state,
122 value, state,
123 error_dict=self.e_dict_disable)
123 error_dict=self.e_dict_disable)
124
124
125
125
126 class ValidRepoUser(formencode.validators.FancyValidator):
126 class ValidRepoUser(formencode.validators.FancyValidator):
127
127
128 def to_python(self, value, state):
128 def to_python(self, value, state):
129 sa = meta.Session
129 sa = meta.Session
130 try:
130 try:
131 self.user_db = sa.query(User)\
131 self.user_db = sa.query(User)\
132 .filter(User.active == True)\
132 .filter(User.active == True)\
133 .filter(User.username == value).one()
133 .filter(User.username == value).one()
134 except Exception:
134 except Exception:
135 raise formencode.Invalid(_('This username is not valid'),
135 raise formencode.Invalid(_('This username is not valid'),
136 value, state)
136 value, state)
137 return self.user_db.user_id
137 return self.user_db.user_id
138
138
139 def ValidRepoName(edit=False):
139 def ValidRepoName(edit=False):
140 class _ValidRepoName(formencode.validators.FancyValidator):
140 class _ValidRepoName(formencode.validators.FancyValidator):
141
141
142 def to_python(self, value, state):
142 def to_python(self, value, state):
143 slug = h.repo_name_slug(value)
143 slug = h.repo_name_slug(value)
144 if slug in ['_admin']:
144 if slug in ['_admin']:
145 raise formencode.Invalid(_('This repository name is disallowed'),
145 raise formencode.Invalid(_('This repository name is disallowed'),
146 value, state)
146 value, state)
147 sa = meta.Session
147 sa = meta.Session
148 if sa.query(Repository).get(slug) and not edit:
148 if sa.query(Repository).get(slug) and not edit:
149 raise formencode.Invalid(_('This repository already exists'),
149 raise formencode.Invalid(_('This repository already exists'),
150 value, state)
150 value, state)
151
151
152 return slug
152 return slug
153 return _ValidRepoName
153 return _ValidRepoName
154
154
155 class ValidPerms(formencode.validators.FancyValidator):
155 class ValidPerms(formencode.validators.FancyValidator):
156 messages = {'perm_new_user_name':_('This username is not valid')}
156 messages = {'perm_new_user_name':_('This username is not valid')}
157
157
158 def to_python(self, value, state):
158 def to_python(self, value, state):
159 perms_update = []
159 perms_update = []
160 perms_new = []
160 perms_new = []
161 #build a list of permission to update and new permission to create
161 #build a list of permission to update and new permission to create
162 for k, v in value.items():
162 for k, v in value.items():
163 if k.startswith('perm_'):
163 if k.startswith('perm_'):
164 if k.startswith('perm_new_user'):
164 if k.startswith('perm_new_user'):
165 new_perm = value.get('perm_new_user', False)
165 new_perm = value.get('perm_new_user', False)
166 new_user = value.get('perm_new_user_name', False)
166 new_user = value.get('perm_new_user_name', False)
167 if new_user and new_perm:
167 if new_user and new_perm:
168 if (new_user, new_perm) not in perms_new:
168 if (new_user, new_perm) not in perms_new:
169 perms_new.append((new_user, new_perm))
169 perms_new.append((new_user, new_perm))
170 else:
170 else:
171 usr = k[5:]
171 usr = k[5:]
172 if usr == 'default':
172 if usr == 'default':
173 if value['private']:
173 if value['private']:
174 #set none for default when updating to private repo
174 #set none for default when updating to private repo
175 v = 'repository.none'
175 v = 'repository.none'
176 perms_update.append((usr, v))
176 perms_update.append((usr, v))
177 value['perms_updates'] = perms_update
177 value['perms_updates'] = perms_update
178 value['perms_new'] = perms_new
178 value['perms_new'] = perms_new
179 sa = meta.Session
179 sa = meta.Session
180 for k, v in perms_new:
180 for k, v in perms_new:
181 try:
181 try:
182 self.user_db = sa.query(User)\
182 self.user_db = sa.query(User)\
183 .filter(User.active == True)\
183 .filter(User.active == True)\
184 .filter(User.username == k).one()
184 .filter(User.username == k).one()
185 except Exception:
185 except Exception:
186 msg = self.message('perm_new_user_name',
186 msg = self.message('perm_new_user_name',
187 state=State_obj)
187 state=State_obj)
188 raise formencode.Invalid(msg, value, state, error_dict={'perm_new_user_name':msg})
188 raise formencode.Invalid(msg, value, state, error_dict={'perm_new_user_name':msg})
189 return value
189 return value
190
190
191 class ValidSettings(formencode.validators.FancyValidator):
191 class ValidSettings(formencode.validators.FancyValidator):
192
192
193 def to_python(self, value, state):
193 def to_python(self, value, state):
194 #settings form can't edit user
194 #settings form can't edit user
195 if value.has_key('user'):
195 if value.has_key('user'):
196 del['value']['user']
196 del['value']['user']
197
197
198 return value
198 return value
199 #===============================================================================
199 #===============================================================================
200 # FORMS
200 # FORMS
201 #===============================================================================
201 #===============================================================================
202 class LoginForm(formencode.Schema):
202 class LoginForm(formencode.Schema):
203 allow_extra_fields = True
203 allow_extra_fields = True
204 filter_extra_fields = True
204 filter_extra_fields = True
205 username = UnicodeString(
205 username = UnicodeString(
206 strip=True,
206 strip=True,
207 min=3,
207 min=3,
208 not_empty=True,
208 not_empty=True,
209 messages={
209 messages={
210 'empty':_('Please enter a login'),
210 'empty':_('Please enter a login'),
211 'tooShort':_('Enter a value %(min)i characters long or more')}
211 'tooShort':_('Enter a value %(min)i characters long or more')}
212 )
212 )
213
213
214 password = UnicodeString(
214 password = UnicodeString(
215 strip=True,
215 strip=True,
216 min=3,
216 min=3,
217 not_empty=True,
217 not_empty=True,
218 messages={
218 messages={
219 'empty':_('Please enter a password'),
219 'empty':_('Please enter a password'),
220 'tooShort':_('Enter a value %(min)i characters long or more')}
220 'tooShort':_('Enter a value %(min)i characters long or more')}
221 )
221 )
222
222
223
223
224 #chained validators have access to all data
224 #chained validators have access to all data
225 chained_validators = [ValidAuth]
225 chained_validators = [ValidAuth]
226
226
227 def UserForm(edit=False):
227 def UserForm(edit=False):
228 class _UserForm(formencode.Schema):
228 class _UserForm(formencode.Schema):
229 allow_extra_fields = True
229 allow_extra_fields = True
230 filter_extra_fields = True
230 filter_extra_fields = True
231 username = All(UnicodeString(strip=True, min=3, not_empty=True), ValidUsername)
231 username = All(UnicodeString(strip=True, min=3, not_empty=True), ValidUsername)
232 if edit:
232 if edit:
233 new_password = All(UnicodeString(strip=True, min=3, not_empty=False), ValidPassword)
233 new_password = All(UnicodeString(strip=True, min=3, not_empty=False), ValidPassword)
234 admin = StringBoolean(if_missing=False)
234 admin = StringBoolean(if_missing=False)
235 else:
235 else:
236 password = All(UnicodeString(strip=True, min=3, not_empty=False), ValidPassword)
236 password = All(UnicodeString(strip=True, min=3, not_empty=False), ValidPassword)
237 active = StringBoolean(if_missing=False)
237 active = StringBoolean(if_missing=False)
238 name = UnicodeString(strip=True, min=3, not_empty=True)
238 name = UnicodeString(strip=True, min=3, not_empty=True)
239 lastname = UnicodeString(strip=True, min=3, not_empty=True)
239 lastname = UnicodeString(strip=True, min=3, not_empty=True)
240 email = Email(not_empty=True)
240 email = Email(not_empty=True)
241
241
242 return _UserForm
242 return _UserForm
243
243
244 def RepoForm(edit=False):
244 def RepoForm(edit=False):
245 class _RepoForm(formencode.Schema):
245 class _RepoForm(formencode.Schema):
246 allow_extra_fields = True
246 allow_extra_fields = True
247 filter_extra_fields = False
247 filter_extra_fields = False
248 repo_name = All(UnicodeString(strip=True, min=1, not_empty=True), ValidRepoName(edit))
248 repo_name = All(UnicodeString(strip=True, min=1, not_empty=True), ValidRepoName(edit))
249 description = UnicodeString(strip=True, min=3, not_empty=True)
249 description = UnicodeString(strip=True, min=3, not_empty=True)
250 private = StringBoolean(if_missing=False)
250 private = StringBoolean(if_missing=False)
251
251
252 if edit:
252 if edit:
253 user = All(Int(not_empty=True), ValidRepoUser)
253 user = All(Int(not_empty=True), ValidRepoUser)
254
254
255 chained_validators = [ValidPerms]
255 chained_validators = [ValidPerms]
256 return _RepoForm
256 return _RepoForm
257
257
258 def RepoSettingsForm(edit=False):
258 def RepoSettingsForm(edit=False):
259 class _RepoForm(formencode.Schema):
259 class _RepoForm(formencode.Schema):
260 allow_extra_fields = True
260 allow_extra_fields = True
261 filter_extra_fields = False
261 filter_extra_fields = False
262 repo_name = All(UnicodeString(strip=True, min=1, not_empty=True), ValidRepoName(edit))
262 repo_name = All(UnicodeString(strip=True, min=1, not_empty=True), ValidRepoName(edit))
263 description = UnicodeString(strip=True, min=3, not_empty=True)
263 description = UnicodeString(strip=True, min=3, not_empty=True)
264 private = StringBoolean(if_missing=False)
264 private = StringBoolean(if_missing=False)
265
265
266 chained_validators = [ValidPerms, ValidSettings]
266 chained_validators = [ValidPerms, ValidSettings]
267 return _RepoForm
267 return _RepoForm
268
268
269
269
270 def ApplicationSettingsForm():
271 class _ApplicationSettingsForm(formencode.Schema):
272 allow_extra_fields = True
273 filter_extra_fields = False
274 app_title = UnicodeString(strip=True, min=3, not_empty=True)
275 app_auth_realm = UnicodeString(strip=True, min=3, not_empty=True)
276
277 return _ApplicationSettingsForm
278
270
279
271
280
@@ -1,158 +1,159 b''
1 #!/usr/bin/env python
1 #!/usr/bin/env python
2 # encoding: utf-8
2 # encoding: utf-8
3 # Model for hg app
3 # Model for hg app
4 # Copyright (C) 2009-2010 Marcin Kuzminski <marcin@python-works.com>
4 # Copyright (C) 2009-2010 Marcin Kuzminski <marcin@python-works.com>
5
5
6 # This program is free software; you can redistribute it and/or
6 # This program is free software; you can redistribute it and/or
7 # modify it under the terms of the GNU General Public License
7 # modify it under the terms of the GNU General Public License
8 # as published by the Free Software Foundation; version 2
8 # as published by the Free Software Foundation; version 2
9 # of the License or (at your opinion) any later version of the license.
9 # of the License or (at your opinion) any later version of the license.
10 #
10 #
11 # This program is distributed in the hope that it will be useful,
11 # This program is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 # GNU General Public License for more details.
14 # GNU General Public License for more details.
15 #
15 #
16 # You should have received a copy of the GNU General Public License
16 # You should have received a copy of the GNU General Public License
17 # along with this program; if not, write to the Free Software
17 # along with this program; if not, write to the Free Software
18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
19 # MA 02110-1301, USA.
19 # MA 02110-1301, USA.
20
20
21 """
21 """
22 Created on April 9, 2010
22 Created on April 9, 2010
23 Model for hg app
23 Model for hg app
24 @author: marcink
24 @author: marcink
25 """
25 """
26
26
27 from beaker.cache import cache_region
27 from beaker.cache import cache_region
28 from mercurial import ui
28 from mercurial import ui
29 from mercurial.hgweb.hgwebdir_mod import findrepos
29 from mercurial.hgweb.hgwebdir_mod import findrepos
30 from vcs.exceptions import RepositoryError, VCSError
30 from vcs.exceptions import RepositoryError, VCSError
31 from pylons_app.model.meta import Session
31 from pylons_app.model import meta
32 from pylons_app.model.db import Repository
32 from pylons_app.model.db import Repository
33 from sqlalchemy.orm import joinedload
33 from sqlalchemy.orm import joinedload
34 import logging
34 import logging
35 import os
35 import os
36 import sys
36 import sys
37 log = logging.getLogger(__name__)
37 log = logging.getLogger(__name__)
38
38
39 try:
39 try:
40 from vcs.backends.hg import MercurialRepository
40 from vcs.backends.hg import MercurialRepository
41 except ImportError:
41 except ImportError:
42 sys.stderr.write('You have to import vcs module')
42 sys.stderr.write('You have to import vcs module')
43 raise Exception('Unable to import vcs')
43 raise Exception('Unable to import vcs')
44
44
45 def _get_repos_cached_initial(app_globals):
45 def _get_repos_cached_initial(app_globals):
46 """
46 """
47 return cached dict with repos
47 return cached dict with repos
48 """
48 """
49 g = app_globals
49 g = app_globals
50 return HgModel.repo_scan(g.paths[0][0], g.paths[0][1], g.baseui)
50 return HgModel.repo_scan(g.paths[0][0], g.paths[0][1], g.baseui)
51
51
52 @cache_region('long_term', 'cached_repo_list')
52 @cache_region('long_term', 'cached_repo_list')
53 def _get_repos_cached():
53 def _get_repos_cached():
54 """
54 """
55 return cached dict with repos
55 return cached dict with repos
56 """
56 """
57 log.info('getting all repositories list')
57 log.info('getting all repositories list')
58 from pylons import app_globals as g
58 from pylons import app_globals as g
59 return HgModel.repo_scan(g.paths[0][0], g.paths[0][1], g.baseui)
59 return HgModel.repo_scan(g.paths[0][0], g.paths[0][1], g.baseui)
60
60
61 @cache_region('long_term', 'full_changelog')
61 @cache_region('long_term', 'full_changelog')
62 def _full_changelog_cached(repo_name):
62 def _full_changelog_cached(repo_name):
63 log.info('getting full changelog for %s', repo_name)
63 log.info('getting full changelog for %s', repo_name)
64 return list(reversed(list(HgModel().get_repo(repo_name))))
64 return list(reversed(list(HgModel().get_repo(repo_name))))
65
65
66 class HgModel(object):
66 class HgModel(object):
67 """
67 """
68 Mercurial Model
68 Mercurial Model
69 """
69 """
70
70
71 def __init__(self):
71 def __init__(self):
72 """
72 """
73 Constructor
73 Constructor
74 """
74 """
75
75
76 @staticmethod
76 @staticmethod
77 def repo_scan(repos_prefix, repos_path, baseui):
77 def repo_scan(repos_prefix, repos_path, baseui):
78 """
78 """
79 Listing of repositories in given path. This path should not be a
79 Listing of repositories in given path. This path should not be a
80 repository itself. Return a dictionary of repository objects
80 repository itself. Return a dictionary of repository objects
81 :param repos_path: path to directory it could take syntax with
81 :param repos_path: path to directory it could take syntax with
82 * or ** for deep recursive displaying repositories
82 * or ** for deep recursive displaying repositories
83 """
83 """
84 sa = Session()
84 sa = meta.Session()
85 def check_repo_dir(path):
85 def check_repo_dir(path):
86 """
86 """
87 Checks the repository
87 Checks the repository
88 :param path:
88 :param path:
89 """
89 """
90 repos_path = path.split('/')
90 repos_path = path.split('/')
91 if repos_path[-1] in ['*', '**']:
91 if repos_path[-1] in ['*', '**']:
92 repos_path = repos_path[:-1]
92 repos_path = repos_path[:-1]
93 if repos_path[0] != '/':
93 if repos_path[0] != '/':
94 repos_path[0] = '/'
94 repos_path[0] = '/'
95 if not os.path.isdir(os.path.join(*repos_path)):
95 if not os.path.isdir(os.path.join(*repos_path)):
96 raise RepositoryError('Not a valid repository in %s' % path[0][1])
96 raise RepositoryError('Not a valid repository in %s' % path[0][1])
97 if not repos_path.endswith('*'):
97 if not repos_path.endswith('*'):
98 raise VCSError('You need to specify * or ** at the end of path '
98 raise VCSError('You need to specify * or ** at the end of path '
99 'for recursive scanning')
99 'for recursive scanning')
100
100
101 check_repo_dir(repos_path)
101 check_repo_dir(repos_path)
102 log.info('scanning for repositories in %s', repos_path)
102 log.info('scanning for repositories in %s', repos_path)
103 repos = findrepos([(repos_prefix, repos_path)])
103 repos = findrepos([(repos_prefix, repos_path)])
104 if not isinstance(baseui, ui.ui):
104 if not isinstance(baseui, ui.ui):
105 baseui = ui.ui()
105 baseui = ui.ui()
106
106
107 repos_list = {}
107 repos_list = {}
108 for name, path in repos:
108 for name, path in repos:
109 try:
109 try:
110 #name = name.split('/')[-1]
110 #name = name.split('/')[-1]
111 if repos_list.has_key(name):
111 if repos_list.has_key(name):
112 raise RepositoryError('Duplicate repository name %s found in'
112 raise RepositoryError('Duplicate repository name %s found in'
113 ' %s' % (name, path))
113 ' %s' % (name, path))
114 else:
114 else:
115
115
116 repos_list[name] = MercurialRepository(path, baseui=baseui)
116 repos_list[name] = MercurialRepository(path, baseui=baseui)
117 repos_list[name].name = name
117 repos_list[name].name = name
118 dbrepo = sa.query(Repository).get(name)
118 dbrepo = sa.query(Repository).get(name)
119 if dbrepo:
119 if dbrepo:
120 repos_list[name].dbrepo = dbrepo
120 repos_list[name].dbrepo = dbrepo
121 repos_list[name].description = dbrepo.description
121 repos_list[name].description = dbrepo.description
122 repos_list[name].contact = dbrepo.user.full_contact
122 repos_list[name].contact = dbrepo.user.full_contact
123 except OSError:
123 except OSError:
124 continue
124 continue
125 meta.Session.remove()
125 return repos_list
126 return repos_list
126
127
127 def get_repos(self):
128 def get_repos(self):
128 for name, repo in _get_repos_cached().items():
129 for name, repo in _get_repos_cached().items():
129 if repo._get_hidden():
130 if repo._get_hidden():
130 #skip hidden web repository
131 #skip hidden web repository
131 continue
132 continue
132
133
133 last_change = repo.last_change
134 last_change = repo.last_change
134 try:
135 try:
135 tip = repo.get_changeset('tip')
136 tip = repo.get_changeset('tip')
136 except RepositoryError:
137 except RepositoryError:
137 from pylons_app.lib.utils import EmptyChangeset
138 from pylons_app.lib.utils import EmptyChangeset
138 tip = EmptyChangeset()
139 tip = EmptyChangeset()
139
140
140 tmp_d = {}
141 tmp_d = {}
141 tmp_d['name'] = repo.name
142 tmp_d['name'] = repo.name
142 tmp_d['name_sort'] = tmp_d['name'].lower()
143 tmp_d['name_sort'] = tmp_d['name'].lower()
143 tmp_d['description'] = repo.description
144 tmp_d['description'] = repo.description
144 tmp_d['description_sort'] = tmp_d['description']
145 tmp_d['description_sort'] = tmp_d['description']
145 tmp_d['last_change'] = last_change
146 tmp_d['last_change'] = last_change
146 tmp_d['last_change_sort'] = last_change[1] - last_change[0]
147 tmp_d['last_change_sort'] = last_change[1] - last_change[0]
147 tmp_d['tip'] = tip.raw_id
148 tmp_d['tip'] = tip.raw_id
148 tmp_d['tip_sort'] = tip.revision
149 tmp_d['tip_sort'] = tip.revision
149 tmp_d['rev'] = tip.revision
150 tmp_d['rev'] = tip.revision
150 tmp_d['contact'] = repo.contact
151 tmp_d['contact'] = repo.contact
151 tmp_d['contact_sort'] = tmp_d['contact']
152 tmp_d['contact_sort'] = tmp_d['contact']
152 tmp_d['repo_archives'] = list(repo._get_archives())
153 tmp_d['repo_archives'] = list(repo._get_archives())
153 tmp_d['last_msg'] = tip.message
154 tmp_d['last_msg'] = tip.message
154 tmp_d['repo'] = repo
155 tmp_d['repo'] = repo
155 yield tmp_d
156 yield tmp_d
156
157
157 def get_repo(self, repo_name):
158 def get_repo(self, repo_name):
158 return _get_repos_cached()[repo_name]
159 return _get_repos_cached()[repo_name]
@@ -1,53 +1,53 b''
1 ## -*- coding: utf-8 -*-
1 ## -*- coding: utf-8 -*-
2 <%inherit file="/base/base.html"/>
2 <%inherit file="/base/base.html"/>
3
3
4 <%def name="title()">
4 <%def name="title()">
5 ${_('Settings administration')}
5 ${_('Settings administration')}
6 </%def>
6 </%def>
7 <%def name="breadcrumbs()">
7 <%def name="breadcrumbs()">
8 ${h.link_to(u'Admin',h.url('admin_home'))}
8 ${h.link_to(u'Admin',h.url('admin_home'))}
9 /
9 /
10 ${_('Settings')}
10 ${_('Settings')}
11 </%def>
11 </%def>
12 <%def name="page_nav()">
12 <%def name="page_nav()">
13 ${self.menu('admin')}
13 ${self.menu('admin')}
14 ${self.submenu('settings')}
14 ${self.submenu('settings')}
15 </%def>
15 </%def>
16 <%def name="main()">
16 <%def name="main()">
17 <div>
17 <div>
18 <h2>${_('Settings administration')}</h2>
18 <h2>${_('Settings administration')}</h2>
19
19
20 ${h.form(url('admin_setting', id='mapping'),method='put')}
20 ${h.form(url('admin_setting', id='mapping'),method='put')}
21 <table class="table_disp">
21 <table class="table_disp">
22 <tr class="header">
22 <tr class="header">
23 <td colspan="2">${_('Remap andv rescan repositories')}</td>
23 <td colspan="2">${_('Remap and rescan repositories')}</td>
24 </tr>
24 </tr>
25 <tr align="right">
25 <tr align="right">
26 <td><span class="tooltip" tooltip_title="${h.tooltip(_('In case a repository was deleted from filesystem and there are leftovers in the database check this option to scan obsolete data in database and remove it.'))}">
26 <td><span class="tooltip" tooltip_title="${h.tooltip(_('In case a repository was deleted from filesystem and there are leftovers in the database check this option to scan obsolete data in database and remove it.'))}">
27 ${_('destroy old data')}</span> ${h.checkbox('destroy',True)}</td>
27 ${_('destroy old data')}</span> ${h.checkbox('destroy',True)}</td>
28 <td>${h.submit('rescan','rescan repositories')}</td>
28 <td>${h.submit('rescan','rescan repositories')}</td>
29 </table>
29 </table>
30 ${h.end_form()}
30 ${h.end_form()}
31 <br/>
31 <br/>
32 ${h.form(url('admin_setting', id='global'),method='put')}
32 ${h.form(url('admin_setting', id='global'),method='put')}
33 <table class="table_disp">
33 <table class="table_disp">
34 <tr class="header">
34 <tr class="header">
35 <td colspan="3">${_('Global application settings')}</td>
35 <td colspan="3">${_('Global application settings')}</td>
36 </tr>
36 </tr>
37 <tr>
37 <tr>
38 <td>${_('Application name')}</td>
38 <td>${_('Application name')}</td>
39 <td>${h.text('app_title')}</td>
39 <td>${h.text('app_title',size=30)}${self.get_form_error('app_title')}</td>
40 </tr>
40 </tr>
41 <tr>
41 <tr>
42 <td>${_('Realm text')}</td>
42 <td>${_('Realm text')}</td>
43 <td>${h.text('app_auth_realm')}</td>
43 <td>${h.text('app_auth_realm',size=30)}${self.get_form_error('app_auth_realm')}</td>
44 </tr>
44 </tr>
45 <tr>
45 <tr>
46 <td></td>
46 <td></td>
47 <td>${h.submit('save','save settings')}</td>
47 <td>${h.submit('save','save settings')}</td>
48 </tr>
48 </tr>
49 </table>
49 </table>
50 ${h.end_form()}
50 ${h.end_form()}
51
51
52 </div>
52 </div>
53 </%def>
53 </%def>
@@ -1,62 +1,62 b''
1 ## -*- coding: utf-8 -*-
1 ## -*- coding: utf-8 -*-
2 <%inherit file="base/base.html"/>
2 <%inherit file="base/base.html"/>
3 <%def name="title()">
3 <%def name="title()">
4 ${c.repos_prefix} Mercurial Repositories
4 ${c.repos_prefix}
5 </%def>
5 </%def>
6 <%def name="breadcrumbs()">
6 <%def name="breadcrumbs()">
7 ${c.repos_prefix} Mercurial Repositories
7 ${c.repos_prefix}
8 </%def>
8 </%def>
9 <%def name="page_nav()">
9 <%def name="page_nav()">
10 ${self.menu('home')}
10 ${self.menu('home')}
11 </%def>
11 </%def>
12 <%def name="main()">
12 <%def name="main()">
13 <%def name="get_sort(name)">
13 <%def name="get_sort(name)">
14 <%name_slug = name.lower().replace(' ','_') %>
14 <%name_slug = name.lower().replace(' ','_') %>
15 %if name_slug == c.cs_slug:
15 %if name_slug == c.cs_slug:
16 <span style="font-weight: bold;text-decoration: underline;">${name}</span>
16 <span style="font-weight: bold;text-decoration: underline;">${name}</span>
17 %else:
17 %else:
18 <span style="font-weight: bold">${name}</span>
18 <span style="font-weight: bold">${name}</span>
19 %endif
19 %endif
20 <a style="color:#FFF" href="?sort=${name_slug}">&darr;</a>
20 <a style="color:#FFF" href="?sort=${name_slug}">&darr;</a>
21 <a style="color:#FFF" href="?sort=-${name_slug}">&uarr;</a>
21 <a style="color:#FFF" href="?sort=-${name_slug}">&uarr;</a>
22 </%def>
22 </%def>
23 <table class="table_disp">
23 <table class="table_disp">
24 <tr class="header">
24 <tr class="header">
25 <td>${get_sort(_('Name'))}</td>
25 <td>${get_sort(_('Name'))}</td>
26 <td>${get_sort(_('Description'))}</td>
26 <td>${get_sort(_('Description'))}</td>
27 <td>${get_sort(_('Last change'))}</td>
27 <td>${get_sort(_('Last change'))}</td>
28 <td>${get_sort(_('Tip'))}</td>
28 <td>${get_sort(_('Tip'))}</td>
29 <td>${get_sort(_('Contact'))}</td>
29 <td>${get_sort(_('Contact'))}</td>
30 <td>${_('RSS')}</td>
30 <td>${_('RSS')}</td>
31 <td>${_('Atom')}</td>
31 <td>${_('Atom')}</td>
32 </tr>
32 </tr>
33 %for cnt,repo in enumerate(c.repos_list):
33 %for cnt,repo in enumerate(c.repos_list):
34 %if h.HasRepoPermissionAny('repository.write','repository.read','repository.admin')(repo['name'],'main page check'):
34 %if h.HasRepoPermissionAny('repository.write','repository.read','repository.admin')(repo['name'],'main page check'):
35
35
36 <tr class="parity${cnt%2}">
36 <tr class="parity${cnt%2}">
37 <td>
37 <td>
38 %if repo['repo'].dbrepo.private:
38 %if repo['repo'].dbrepo.private:
39 <img alt="${_('private')}" src="/images/icons/lock.png">
39 <img alt="${_('private')}" src="/images/icons/lock.png">
40 %else:
40 %else:
41 <img alt="${_('public')}" src="/images/icons/lock_open.png">
41 <img alt="${_('public')}" src="/images/icons/lock_open.png">
42 %endif
42 %endif
43 ${h.link_to(repo['name'],
43 ${h.link_to(repo['name'],
44 h.url('summary_home',repo_name=repo['name']))}</td>
44 h.url('summary_home',repo_name=repo['name']))}</td>
45 <td title="${repo['description']}">${h.truncate(repo['description'],60)}</td>
45 <td title="${repo['description']}">${h.truncate(repo['description'],60)}</td>
46 <td>${h.age(repo['last_change'])}</td>
46 <td>${h.age(repo['last_change'])}</td>
47 <td>${h.link_to_if(repo['rev']>=0,'r%s:%s' % (repo['rev'],repo['tip']),
47 <td>${h.link_to_if(repo['rev']>=0,'r%s:%s' % (repo['rev'],repo['tip']),
48 h.url('changeset_home',repo_name=repo['name'],revision=repo['tip']),
48 h.url('changeset_home',repo_name=repo['name'],revision=repo['tip']),
49 class_="tooltip",
49 class_="tooltip",
50 tooltip_title=h.tooltip(repo['last_msg']))}</td>
50 tooltip_title=h.tooltip(repo['last_msg']))}</td>
51 <td title="${repo['contact']}">${h.person(repo['contact'])}</td>
51 <td title="${repo['contact']}">${h.person(repo['contact'])}</td>
52 <td>
52 <td>
53 <a title="${_('Subscribe to %s rss feed')%repo['name']}" class="rss_logo" href="${h.url('rss_feed_home',repo_name=repo['name'])}"></a>
53 <a title="${_('Subscribe to %s rss feed')%repo['name']}" class="rss_logo" href="${h.url('rss_feed_home',repo_name=repo['name'])}"></a>
54 </td>
54 </td>
55 <td>
55 <td>
56 <a title="${_('Subscribe to %s atom feed')%repo['name']}" class="atom_logo" href="${h.url('atom_feed_home',repo_name=repo['name'])}"></a>
56 <a title="${_('Subscribe to %s atom feed')%repo['name']}" class="atom_logo" href="${h.url('atom_feed_home',repo_name=repo['name'])}"></a>
57 </td>
57 </td>
58 </tr>
58 </tr>
59 %endif
59 %endif
60 %endfor
60 %endfor
61 </table>
61 </table>
62 </%def>
62 </%def>
@@ -1,38 +1,38 b''
1 ## -*- coding: utf-8 -*-
1 ## -*- coding: utf-8 -*-
2 <%inherit file="base/base.html"/>
2 <%inherit file="base/base.html"/>
3 <%def name="title()">
3 <%def name="title()">
4 ${c.repos_prefix} Mercurial Repositories
4 ${c.repos_prefix}
5 </%def>
5 </%def>
6 <%def name="breadcrumbs()">
6 <%def name="breadcrumbs()">
7 ${c.repos_prefix} Mercurial Repositories
7 ${c.repos_prefix}
8 </%def>
8 </%def>
9 <%def name="page_nav()">
9 <%def name="page_nav()">
10 &nbsp;
10 &nbsp;
11 </div>
11 </div>
12 </%def>
12 </%def>
13 <%def name="main()">
13 <%def name="main()">
14 <div>
14 <div>
15 <br />
15 <br />
16 <h2>${_('Login to hg app')}</h2>
16 <h2>${_('Login to hg app')}</h2>
17 ${h.form(h.url.current())}
17 ${h.form(h.url.current())}
18 <table>
18 <table>
19 <tr>
19 <tr>
20 <td>${_('Username')}</td>
20 <td>${_('Username')}</td>
21 <td>${h.text('username')}</td>
21 <td>${h.text('username')}</td>
22 <td>${self.get_form_error('username')}</td>
22 <td>${self.get_form_error('username')}</td>
23 </tr>
23 </tr>
24 <tr>
24 <tr>
25 <td>${_('Password')}</td>
25 <td>${_('Password')}</td>
26 <td>${h.password('password')}</td>
26 <td>${h.password('password')}</td>
27 <td>${self.get_form_error('password')}</td>
27 <td>${self.get_form_error('password')}</td>
28 </tr>
28 </tr>
29 <tr>
29 <tr>
30 <td></td>
30 <td></td>
31 <td>${h.submit('login','login')}</td>
31 <td>${h.submit('login','login')}</td>
32 </tr>
32 </tr>
33 </table>
33 </table>
34 ${h.end_form()}
34 ${h.end_form()}
35 </div>
35 </div>
36 </%def>
36 </%def>
37
37
38
38
General Comments 0
You need to be logged in to leave comments. Login now