##// END OF EJS Templates
controllers: consistently use formfill.render with force_defaults=False...
Mads Kiilerich -
r4941:c04c2734 default
parent child Browse files
Show More
@@ -1,140 +1,139 b''
1 1 # -*- coding: utf-8 -*-
2 2 # This program is free software: you can redistribute it and/or modify
3 3 # it under the terms of the GNU General Public License as published by
4 4 # the Free Software Foundation, either version 3 of the License, or
5 5 # (at your option) any later version.
6 6 #
7 7 # This program is distributed in the hope that it will be useful,
8 8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 10 # GNU General Public License for more details.
11 11 #
12 12 # You should have received a copy of the GNU General Public License
13 13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
14 14 """
15 15 kallithea.controllers.admin.auth_settings
16 16 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
17 17
18 18 pluggable authentication controller for Kallithea
19 19
20 20 This file was forked by the Kallithea project in July 2014.
21 21 Original author and date, and relevant copyright and licensing information is below:
22 22 :created_on: Nov 26, 2010
23 23 :author: akesterson
24 24 """
25 25
26 26 import pprint
27 27 import logging
28 28 import formencode.htmlfill
29 29 import traceback
30 30
31 31 from pylons import request, tmpl_context as c, url
32 32 from pylons.controllers.util import redirect
33 33 from pylons.i18n.translation import _
34 34
35 35 from kallithea.lib import helpers as h
36 36 from kallithea.lib.compat import formatted_json
37 37 from kallithea.lib.base import BaseController, render
38 38 from kallithea.lib.auth import LoginRequired, HasPermissionAllDecorator
39 39 from kallithea.lib import auth_modules
40 40 from kallithea.model.forms import AuthSettingsForm
41 41 from kallithea.model.db import Setting
42 42 from kallithea.model.meta import Session
43 43
44 44 log = logging.getLogger(__name__)
45 45
46 46
47 47 class AuthSettingsController(BaseController):
48 48
49 49 @LoginRequired()
50 50 @HasPermissionAllDecorator('hg.admin')
51 51 def __before__(self):
52 52 super(AuthSettingsController, self).__before__()
53 53
54 54 def __load_defaults(self):
55 55 c.available_plugins = [
56 56 'kallithea.lib.auth_modules.auth_internal',
57 57 'kallithea.lib.auth_modules.auth_container',
58 58 'kallithea.lib.auth_modules.auth_ldap',
59 59 'kallithea.lib.auth_modules.auth_crowd',
60 60 'kallithea.lib.auth_modules.auth_pam'
61 61 ]
62 62 c.enabled_plugins = Setting.get_auth_plugins()
63 63
64 64 def index(self, defaults=None, errors=None, prefix_error=False):
65 65 self.__load_defaults()
66 66 _defaults = {}
67 67 # default plugins loaded
68 68 formglobals = {
69 69 "auth_plugins": ["kallithea.lib.auth_modules.auth_internal"]
70 70 }
71 71 formglobals.update(Setting.get_auth_settings())
72 72 formglobals["plugin_settings"] = {}
73 73 formglobals["auth_plugins_shortnames"] = {}
74 74 _defaults["auth_plugins"] = formglobals["auth_plugins"]
75 75
76 76 for module in formglobals["auth_plugins"]:
77 77 plugin = auth_modules.loadplugin(module)
78 78 plugin_name = plugin.name
79 79 formglobals["auth_plugins_shortnames"][module] = plugin_name
80 80 formglobals["plugin_settings"][module] = plugin.plugin_settings()
81 81 for v in formglobals["plugin_settings"][module]:
82 82 fullname = ("auth_" + plugin_name + "_" + v["name"])
83 83 if "default" in v:
84 84 _defaults[fullname] = v["default"]
85 85 # Current values will be the default on the form, if there are any
86 86 setting = Setting.get_by_name(fullname)
87 87 if setting:
88 88 _defaults[fullname] = setting.app_settings_value
89 89 # we want to show , separated list of enabled plugins
90 90 _defaults['auth_plugins'] = ','.join(_defaults['auth_plugins'])
91 91 if defaults:
92 92 _defaults.update(defaults)
93 93
94 94 formglobals["defaults"] = _defaults
95 95 # set template context variables
96 96 for k, v in formglobals.iteritems():
97 97 setattr(c, k, v)
98 98
99 99 log.debug(pprint.pformat(formglobals, indent=4))
100 100 log.debug(formatted_json(defaults))
101 101 return formencode.htmlfill.render(
102 102 render('admin/auth/auth_settings.html'),
103 103 defaults=_defaults,
104 104 errors=errors,
105 105 prefix_error=prefix_error,
106 106 encoding="UTF-8",
107 force_defaults=True,
108 )
107 force_defaults=False)
109 108
110 109 def auth_settings(self):
111 110 """POST create and store auth settings"""
112 111 self.__load_defaults()
113 112 _form = AuthSettingsForm(c.enabled_plugins)()
114 113 log.debug("POST Result: %s" % formatted_json(dict(request.POST)))
115 114
116 115 try:
117 116 form_result = _form.to_python(dict(request.POST))
118 117 for k, v in form_result.items():
119 118 if k == 'auth_plugins':
120 119 # we want to store it comma separated inside our settings
121 120 v = ','.join(v)
122 121 log.debug("%s = %s" % (k, str(v)))
123 122 setting = Setting.create_or_update(k, v)
124 123 Session().add(setting)
125 124 Session().commit()
126 125 h.flash(_('Auth settings updated successfully'),
127 126 category='success')
128 127 except formencode.Invalid, errors:
129 128 log.error(traceback.format_exc())
130 129 e = errors.error_dict or {}
131 130 return self.index(
132 131 defaults=errors.value,
133 132 errors=e,
134 133 prefix_error=False)
135 134 except Exception:
136 135 log.error(traceback.format_exc())
137 136 h.flash(_('error occurred during update of auth settings'),
138 137 category='error')
139 138
140 139 return redirect(url('auth_home'))
@@ -1,131 +1,132 b''
1 1 # -*- coding: utf-8 -*-
2 2 # This program is free software: you can redistribute it and/or modify
3 3 # it under the terms of the GNU General Public License as published by
4 4 # the Free Software Foundation, either version 3 of the License, or
5 5 # (at your option) any later version.
6 6 #
7 7 # This program is distributed in the hope that it will be useful,
8 8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 10 # GNU General Public License for more details.
11 11 #
12 12 # You should have received a copy of the GNU General Public License
13 13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
14 14 """
15 15 kallithea.controllers.admin.defaults
16 16 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
17 17
18 18 default settings controller for Kallithea
19 19
20 20 This file was forked by the Kallithea project in July 2014.
21 21 Original author and date, and relevant copyright and licensing information is below:
22 22 :created_on: Apr 27, 2010
23 23 :author: marcink
24 24 :copyright: (c) 2013 RhodeCode GmbH, and others.
25 25 :license: GPLv3, see LICENSE.md for more details.
26 26 """
27 27
28 28 import logging
29 29 import traceback
30 30 import formencode
31 31 from formencode import htmlfill
32 32
33 33 from pylons import request, tmpl_context as c, url
34 34 from pylons.controllers.util import redirect
35 35 from pylons.i18n.translation import _
36 36
37 37 from kallithea.lib import helpers as h
38 38 from kallithea.lib.auth import LoginRequired, HasPermissionAllDecorator
39 39 from kallithea.lib.base import BaseController, render
40 40 from kallithea.model.forms import DefaultsForm
41 41 from kallithea.model.meta import Session
42 42 from kallithea import BACKENDS
43 43 from kallithea.model.db import Setting
44 44
45 45 log = logging.getLogger(__name__)
46 46
47 47
48 48 class DefaultsController(BaseController):
49 49 """REST Controller styled on the Atom Publishing Protocol"""
50 50 # To properly map this controller, ensure your config/routing.py
51 51 # file has a resource setup:
52 52 # map.resource('default', 'defaults')
53 53
54 54 @LoginRequired()
55 55 @HasPermissionAllDecorator('hg.admin')
56 56 def __before__(self):
57 57 super(DefaultsController, self).__before__()
58 58
59 59 def index(self, format='html'):
60 60 """GET /defaults: All items in the collection"""
61 61 # url('defaults')
62 62 c.backends = BACKENDS.keys()
63 63 defaults = Setting.get_default_repo_settings()
64 64
65 65 return htmlfill.render(
66 66 render('admin/defaults/defaults.html'),
67 67 defaults=defaults,
68 68 encoding="UTF-8",
69 69 force_defaults=False
70 70 )
71 71
72 72 def create(self):
73 73 """POST /defaults: Create a new item"""
74 74 # url('defaults')
75 75
76 76 def new(self, format='html'):
77 77 """GET /defaults/new: Form to create a new item"""
78 78 # url('new_default')
79 79
80 80 def update(self, id):
81 81 """PUT /defaults/id: Update an existing item"""
82 82 # Forms posted to this method should contain a hidden field:
83 83 # <input type="hidden" name="_method" value="PUT" />
84 84 # Or using helpers:
85 85 # h.form(url('default', id=ID),
86 86 # method='put')
87 87 # url('default', id=ID)
88 88
89 89 _form = DefaultsForm()()
90 90
91 91 try:
92 92 form_result = _form.to_python(dict(request.POST))
93 93 for k, v in form_result.iteritems():
94 94 setting = Setting.create_or_update(k, v)
95 95 Session().add(setting)
96 96 Session().commit()
97 97 h.flash(_('Default settings updated successfully'),
98 98 category='success')
99 99
100 100 except formencode.Invalid, errors:
101 101 defaults = errors.value
102 102
103 103 return htmlfill.render(
104 104 render('admin/defaults/defaults.html'),
105 105 defaults=defaults,
106 106 errors=errors.error_dict or {},
107 107 prefix_error=False,
108 encoding="UTF-8")
108 encoding="UTF-8",
109 force_defaults=False)
109 110 except Exception:
110 111 log.error(traceback.format_exc())
111 112 h.flash(_('Error occurred during update of defaults'),
112 113 category='error')
113 114
114 115 return redirect(url('defaults'))
115 116
116 117 def delete(self, id):
117 118 """DELETE /defaults/id: Delete an existing item"""
118 119 # Forms posted to this method should contain a hidden field:
119 120 # <input type="hidden" name="_method" value="DELETE" />
120 121 # Or using helpers:
121 122 # h.form(url('default', id=ID),
122 123 # method='delete')
123 124 # url('default', id=ID)
124 125
125 126 def show(self, id, format='html'):
126 127 """GET /defaults/id: Show a specific item"""
127 128 # url('default', id=ID)
128 129
129 130 def edit(self, id, format='html'):
130 131 """GET /defaults/id/edit: Form to edit an existing item"""
131 132 # url('edit_default', id=ID)
@@ -1,293 +1,293 b''
1 1 # -*- coding: utf-8 -*-
2 2 # This program is free software: you can redistribute it and/or modify
3 3 # it under the terms of the GNU General Public License as published by
4 4 # the Free Software Foundation, either version 3 of the License, or
5 5 # (at your option) any later version.
6 6 #
7 7 # This program is distributed in the hope that it will be useful,
8 8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 10 # GNU General Public License for more details.
11 11 #
12 12 # You should have received a copy of the GNU General Public License
13 13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
14 14 """
15 15 kallithea.controllers.admin.gist
16 16 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
17 17
18 18 gist controller for Kallithea
19 19
20 20 This file was forked by the Kallithea project in July 2014.
21 21 Original author and date, and relevant copyright and licensing information is below:
22 22 :created_on: May 9, 2013
23 23 :author: marcink
24 24 :copyright: (c) 2013 RhodeCode GmbH, and others.
25 25 :license: GPLv3, see LICENSE.md for more details.
26 26 """
27 27
28 28 import time
29 29 import logging
30 30 import traceback
31 31 import formencode.htmlfill
32 32
33 33 from pylons import request, response, tmpl_context as c, url
34 34 from pylons.controllers.util import redirect
35 35 from pylons.i18n.translation import _
36 36
37 37 from kallithea.model.forms import GistForm
38 38 from kallithea.model.gist import GistModel
39 39 from kallithea.model.meta import Session
40 40 from kallithea.model.db import Gist, User
41 41 from kallithea.lib import helpers as h
42 42 from kallithea.lib.base import BaseController, render
43 43 from kallithea.lib.auth import LoginRequired, NotAnonymous
44 44 from kallithea.lib.utils import jsonify
45 45 from kallithea.lib.utils2 import safe_int, time_to_datetime
46 46 from kallithea.lib.helpers import Page
47 47 from webob.exc import HTTPNotFound, HTTPForbidden
48 48 from sqlalchemy.sql.expression import or_
49 49 from kallithea.lib.vcs.exceptions import VCSError, NodeNotChangedError
50 50
51 51 log = logging.getLogger(__name__)
52 52
53 53
54 54 class GistsController(BaseController):
55 55 """REST Controller styled on the Atom Publishing Protocol"""
56 56
57 57 def __load_defaults(self, extra_values=None):
58 58 c.lifetime_values = [
59 59 (str(-1), _('forever')),
60 60 (str(5), _('5 minutes')),
61 61 (str(60), _('1 hour')),
62 62 (str(60 * 24), _('1 day')),
63 63 (str(60 * 24 * 30), _('1 month')),
64 64 ]
65 65 if extra_values:
66 66 c.lifetime_values.append(extra_values)
67 67 c.lifetime_options = [(c.lifetime_values, _("Lifetime"))]
68 68
69 69 @LoginRequired()
70 70 def index(self):
71 71 """GET /admin/gists: All items in the collection"""
72 72 # url('gists')
73 73 not_default_user = c.authuser.username != User.DEFAULT_USER
74 74 c.show_private = request.GET.get('private') and not_default_user
75 75 c.show_public = request.GET.get('public') and not_default_user
76 76
77 77 gists = Gist().query()\
78 78 .filter(or_(Gist.gist_expires == -1, Gist.gist_expires >= time.time()))\
79 79 .order_by(Gist.created_on.desc())
80 80
81 81 # MY private
82 82 if c.show_private and not c.show_public:
83 83 gists = gists.filter(Gist.gist_type == Gist.GIST_PRIVATE)\
84 84 .filter(Gist.gist_owner == c.authuser.user_id)
85 85 # MY public
86 86 elif c.show_public and not c.show_private:
87 87 gists = gists.filter(Gist.gist_type == Gist.GIST_PUBLIC)\
88 88 .filter(Gist.gist_owner == c.authuser.user_id)
89 89
90 90 # MY public+private
91 91 elif c.show_private and c.show_public:
92 92 gists = gists.filter(or_(Gist.gist_type == Gist.GIST_PUBLIC,
93 93 Gist.gist_type == Gist.GIST_PRIVATE))\
94 94 .filter(Gist.gist_owner == c.authuser.user_id)
95 95
96 96 # default show ALL public gists
97 97 if not c.show_public and not c.show_private:
98 98 gists = gists.filter(Gist.gist_type == Gist.GIST_PUBLIC)
99 99
100 100 c.gists = gists
101 101 p = safe_int(request.GET.get('page', 1), 1)
102 102 c.gists_pager = Page(c.gists, page=p, items_per_page=10)
103 103 return render('admin/gists/index.html')
104 104
105 105 @LoginRequired()
106 106 @NotAnonymous()
107 107 def create(self):
108 108 """POST /admin/gists: Create a new item"""
109 109 # url('gists')
110 110 self.__load_defaults()
111 111 gist_form = GistForm([x[0] for x in c.lifetime_values])()
112 112 try:
113 113 form_result = gist_form.to_python(dict(request.POST))
114 114 #TODO: multiple files support, from the form
115 115 filename = form_result['filename'] or Gist.DEFAULT_FILENAME
116 116 nodes = {
117 117 filename: {
118 118 'content': form_result['content'],
119 119 'lexer': form_result['mimetype'] # None is autodetect
120 120 }
121 121 }
122 122 _public = form_result['public']
123 123 gist_type = Gist.GIST_PUBLIC if _public else Gist.GIST_PRIVATE
124 124 gist = GistModel().create(
125 125 description=form_result['description'],
126 126 owner=c.authuser.user_id,
127 127 gist_mapping=nodes,
128 128 gist_type=gist_type,
129 129 lifetime=form_result['lifetime']
130 130 )
131 131 Session().commit()
132 132 new_gist_id = gist.gist_access_id
133 133 except formencode.Invalid, errors:
134 134 defaults = errors.value
135 135
136 136 return formencode.htmlfill.render(
137 137 render('admin/gists/new.html'),
138 138 defaults=defaults,
139 139 errors=errors.error_dict or {},
140 140 prefix_error=False,
141 encoding="UTF-8"
142 )
141 encoding="UTF-8",
142 force_defaults=False)
143 143
144 144 except Exception, e:
145 145 log.error(traceback.format_exc())
146 146 h.flash(_('Error occurred during gist creation'), category='error')
147 147 return redirect(url('new_gist'))
148 148 return redirect(url('gist', gist_id=new_gist_id))
149 149
150 150 @LoginRequired()
151 151 @NotAnonymous()
152 152 def new(self, format='html'):
153 153 """GET /admin/gists/new: Form to create a new item"""
154 154 # url('new_gist')
155 155 self.__load_defaults()
156 156 return render('admin/gists/new.html')
157 157
158 158 @LoginRequired()
159 159 @NotAnonymous()
160 160 def update(self, gist_id):
161 161 """PUT /admin/gists/gist_id: Update an existing item"""
162 162 # Forms posted to this method should contain a hidden field:
163 163 # <input type="hidden" name="_method" value="PUT" />
164 164 # Or using helpers:
165 165 # h.form(url('gist', gist_id=ID),
166 166 # method='put')
167 167 # url('gist', gist_id=ID)
168 168
169 169 @LoginRequired()
170 170 @NotAnonymous()
171 171 def delete(self, gist_id):
172 172 """DELETE /admin/gists/gist_id: Delete an existing item"""
173 173 # Forms posted to this method should contain a hidden field:
174 174 # <input type="hidden" name="_method" value="DELETE" />
175 175 # Or using helpers:
176 176 # h.form(url('gist', gist_id=ID),
177 177 # method='delete')
178 178 # url('gist', gist_id=ID)
179 179 gist = GistModel().get_gist(gist_id)
180 180 owner = gist.gist_owner == c.authuser.user_id
181 181 if h.HasPermissionAny('hg.admin')() or owner:
182 182 GistModel().delete(gist)
183 183 Session().commit()
184 184 h.flash(_('Deleted gist %s') % gist.gist_access_id, category='success')
185 185 else:
186 186 raise HTTPForbidden()
187 187
188 188 return redirect(url('gists'))
189 189
190 190 @LoginRequired()
191 191 def show(self, gist_id, revision='tip', format='html', f_path=None):
192 192 """GET /admin/gists/gist_id: Show a specific item"""
193 193 # url('gist', gist_id=ID)
194 194 c.gist = Gist.get_or_404(gist_id)
195 195
196 196 #check if this gist is not expired
197 197 if c.gist.gist_expires != -1:
198 198 if time.time() > c.gist.gist_expires:
199 199 log.error('Gist expired at %s' %
200 200 (time_to_datetime(c.gist.gist_expires)))
201 201 raise HTTPNotFound()
202 202 try:
203 203 c.file_changeset, c.files = GistModel().get_gist_files(gist_id,
204 204 revision=revision)
205 205 except VCSError:
206 206 log.error(traceback.format_exc())
207 207 raise HTTPNotFound()
208 208 if format == 'raw':
209 209 content = '\n\n'.join([f.content for f in c.files if (f_path is None or f.path == f_path)])
210 210 response.content_type = 'text/plain'
211 211 return content
212 212 return render('admin/gists/show.html')
213 213
214 214 @LoginRequired()
215 215 @NotAnonymous()
216 216 def edit(self, gist_id, format='html'):
217 217 """GET /admin/gists/gist_id/edit: Form to edit an existing item"""
218 218 # url('edit_gist', gist_id=ID)
219 219 c.gist = Gist.get_or_404(gist_id)
220 220
221 221 #check if this gist is not expired
222 222 if c.gist.gist_expires != -1:
223 223 if time.time() > c.gist.gist_expires:
224 224 log.error('Gist expired at %s' %
225 225 (time_to_datetime(c.gist.gist_expires)))
226 226 raise HTTPNotFound()
227 227 try:
228 228 c.file_changeset, c.files = GistModel().get_gist_files(gist_id)
229 229 except VCSError:
230 230 log.error(traceback.format_exc())
231 231 raise HTTPNotFound()
232 232
233 233 self.__load_defaults(extra_values=('0', _('unmodified')))
234 234 rendered = render('admin/gists/edit.html')
235 235
236 236 if request.POST:
237 237 rpost = request.POST
238 238 nodes = {}
239 239 for org_filename, filename, mimetype, content in zip(
240 240 rpost.getall('org_files'),
241 241 rpost.getall('files'),
242 242 rpost.getall('mimetypes'),
243 243 rpost.getall('contents')):
244 244
245 245 nodes[org_filename] = {
246 246 'org_filename': org_filename,
247 247 'filename': filename,
248 248 'content': content,
249 249 'lexer': mimetype,
250 250 }
251 251 try:
252 252 GistModel().update(
253 253 gist=c.gist,
254 254 description=rpost['description'],
255 255 owner=c.gist.owner,
256 256 gist_mapping=nodes,
257 257 gist_type=c.gist.gist_type,
258 258 lifetime=rpost['lifetime']
259 259 )
260 260
261 261 Session().commit()
262 262 h.flash(_('Successfully updated gist content'), category='success')
263 263 except NodeNotChangedError:
264 264 # raised if nothing was changed in repo itself. We anyway then
265 265 # store only DB stuff for gist
266 266 Session().commit()
267 267 h.flash(_('Successfully updated gist data'), category='success')
268 268 except Exception:
269 269 log.error(traceback.format_exc())
270 270 h.flash(_('Error occurred during update of gist %s') % gist_id,
271 271 category='error')
272 272
273 273 return redirect(url('gist', gist_id=gist_id))
274 274
275 275 return rendered
276 276
277 277 @LoginRequired()
278 278 @NotAnonymous()
279 279 @jsonify
280 280 def check_revision(self, gist_id):
281 281 c.gist = Gist.get_or_404(gist_id)
282 282 last_rev = c.gist.scm_instance.get_changeset()
283 283 success = True
284 284 revision = request.POST.get('revision')
285 285
286 286 ##TODO: maybe move this to model ?
287 287 if revision != last_rev.raw_id:
288 288 log.error('Last revision %s is different than submitted %s'
289 289 % (revision, last_rev))
290 290 # our gist has newer version than we
291 291 success = False
292 292
293 293 return {'success': success}
@@ -1,271 +1,272 b''
1 1 # -*- coding: utf-8 -*-
2 2 # This program is free software: you can redistribute it and/or modify
3 3 # it under the terms of the GNU General Public License as published by
4 4 # the Free Software Foundation, either version 3 of the License, or
5 5 # (at your option) any later version.
6 6 #
7 7 # This program is distributed in the hope that it will be useful,
8 8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 10 # GNU General Public License for more details.
11 11 #
12 12 # You should have received a copy of the GNU General Public License
13 13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
14 14 """
15 15 kallithea.controllers.admin.my_account
16 16 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
17 17
18 18 my account controller for Kallithea admin
19 19
20 20 This file was forked by the Kallithea project in July 2014.
21 21 Original author and date, and relevant copyright and licensing information is below:
22 22 :created_on: August 20, 2013
23 23 :author: marcink
24 24 :copyright: (c) 2013 RhodeCode GmbH, and others.
25 25 :license: GPLv3, see LICENSE.md for more details.
26 26 """
27 27
28 28 import logging
29 29 import traceback
30 30 import formencode
31 31
32 32 from sqlalchemy import func
33 33 from formencode import htmlfill
34 34 from pylons import request, tmpl_context as c, url
35 35 from pylons.controllers.util import redirect
36 36 from pylons.i18n.translation import _
37 37
38 38 from kallithea import EXTERN_TYPE_INTERNAL
39 39 from kallithea.lib import helpers as h
40 40 from kallithea.lib.auth import LoginRequired, NotAnonymous, AuthUser
41 41 from kallithea.lib.base import BaseController, render
42 42 from kallithea.lib.utils2 import generate_api_key, safe_int
43 43 from kallithea.lib.compat import json
44 44 from kallithea.model.db import Repository, \
45 45 UserEmailMap, UserApiKeys, User, UserFollowing
46 46 from kallithea.model.forms import UserForm, PasswordChangeForm
47 47 from kallithea.model.user import UserModel
48 48 from kallithea.model.repo import RepoModel
49 49 from kallithea.model.api_key import ApiKeyModel
50 50 from kallithea.model.meta import Session
51 51
52 52 log = logging.getLogger(__name__)
53 53
54 54
55 55 class MyAccountController(BaseController):
56 56 """REST Controller styled on the Atom Publishing Protocol"""
57 57 # To properly map this controller, ensure your config/routing.py
58 58 # file has a resource setup:
59 59 # map.resource('setting', 'settings', controller='admin/settings',
60 60 # path_prefix='/admin', name_prefix='admin_')
61 61
62 62 @LoginRequired()
63 63 @NotAnonymous()
64 64 def __before__(self):
65 65 super(MyAccountController, self).__before__()
66 66
67 67 def __load_data(self):
68 68 c.user = User.get(self.authuser.user_id)
69 69 if c.user.username == User.DEFAULT_USER:
70 70 h.flash(_("You can't edit this user since it's"
71 71 " crucial for entire application"), category='warning')
72 72 return redirect(url('users'))
73 73 c.EXTERN_TYPE_INTERNAL = EXTERN_TYPE_INTERNAL
74 74
75 75 def _load_my_repos_data(self, watched=False):
76 76 if watched:
77 77 admin = False
78 78 repos_list = [x.follows_repository for x in
79 79 Session().query(UserFollowing).filter(
80 80 UserFollowing.user_id ==
81 81 self.authuser.user_id).all()]
82 82 else:
83 83 admin = True
84 84 repos_list = Session().query(Repository)\
85 85 .filter(Repository.user_id ==
86 86 self.authuser.user_id)\
87 87 .order_by(func.lower(Repository.repo_name)).all()
88 88
89 89 repos_data = RepoModel().get_repos_as_dict(repos_list=repos_list,
90 90 admin=admin)
91 91 #json used to render the grid
92 92 return json.dumps(repos_data)
93 93
94 94 def my_account(self):
95 95 """
96 96 GET /_admin/my_account Displays info about my account
97 97 """
98 98 # url('my_account')
99 99 c.active = 'profile'
100 100 self.__load_data()
101 101 c.perm_user = AuthUser(user_id=self.authuser.user_id,
102 102 ip_addr=self.ip_addr)
103 103 c.extern_type = c.user.extern_type
104 104 c.extern_name = c.user.extern_name
105 105
106 106 defaults = c.user.get_dict()
107 107 update = False
108 108 if request.POST:
109 109 _form = UserForm(edit=True,
110 110 old_data={'user_id': self.authuser.user_id,
111 111 'email': self.authuser.email})()
112 112 form_result = {}
113 113 try:
114 114 post_data = dict(request.POST)
115 115 post_data['new_password'] = ''
116 116 post_data['password_confirmation'] = ''
117 117 form_result = _form.to_python(post_data)
118 118 # skip updating those attrs for my account
119 119 skip_attrs = ['admin', 'active', 'extern_type', 'extern_name',
120 120 'new_password', 'password_confirmation']
121 121 #TODO: plugin should define if username can be updated
122 122 if c.extern_type != EXTERN_TYPE_INTERNAL:
123 123 # forbid updating username for external accounts
124 124 skip_attrs.append('username')
125 125
126 126 UserModel().update(self.authuser.user_id, form_result,
127 127 skip_attrs=skip_attrs)
128 128 h.flash(_('Your account was updated successfully'),
129 129 category='success')
130 130 Session().commit()
131 131 update = True
132 132
133 133 except formencode.Invalid, errors:
134 134 return htmlfill.render(
135 135 render('admin/my_account/my_account.html'),
136 136 defaults=errors.value,
137 137 errors=errors.error_dict or {},
138 138 prefix_error=False,
139 encoding="UTF-8")
139 encoding="UTF-8",
140 force_defaults=False)
140 141 except Exception:
141 142 log.error(traceback.format_exc())
142 143 h.flash(_('Error occurred during update of user %s') \
143 144 % form_result.get('username'), category='error')
144 145 if update:
145 146 return redirect('my_account')
146 147 return htmlfill.render(
147 148 render('admin/my_account/my_account.html'),
148 149 defaults=defaults,
149 150 encoding="UTF-8",
150 force_defaults=False
151 )
151 force_defaults=False)
152 152
153 153 def my_account_password(self):
154 154 c.active = 'password'
155 155 self.__load_data()
156 156 if request.POST:
157 157 _form = PasswordChangeForm(self.authuser.username)()
158 158 try:
159 159 form_result = _form.to_python(request.POST)
160 160 UserModel().update(self.authuser.user_id, form_result)
161 161 Session().commit()
162 162 h.flash(_("Successfully updated password"), category='success')
163 163 except formencode.Invalid as errors:
164 164 return htmlfill.render(
165 165 render('admin/my_account/my_account.html'),
166 166 defaults=errors.value,
167 167 errors=errors.error_dict or {},
168 168 prefix_error=False,
169 encoding="UTF-8")
169 encoding="UTF-8",
170 force_defaults=False)
170 171 except Exception:
171 172 log.error(traceback.format_exc())
172 173 h.flash(_('Error occurred during update of user password'),
173 174 category='error')
174 175 return render('admin/my_account/my_account.html')
175 176
176 177 def my_account_repos(self):
177 178 c.active = 'repos'
178 179 self.__load_data()
179 180
180 181 #json used to render the grid
181 182 c.data = self._load_my_repos_data()
182 183 return render('admin/my_account/my_account.html')
183 184
184 185 def my_account_watched(self):
185 186 c.active = 'watched'
186 187 self.__load_data()
187 188
188 189 #json used to render the grid
189 190 c.data = self._load_my_repos_data(watched=True)
190 191 return render('admin/my_account/my_account.html')
191 192
192 193 def my_account_perms(self):
193 194 c.active = 'perms'
194 195 self.__load_data()
195 196 c.perm_user = AuthUser(user_id=self.authuser.user_id,
196 197 ip_addr=self.ip_addr)
197 198
198 199 return render('admin/my_account/my_account.html')
199 200
200 201 def my_account_emails(self):
201 202 c.active = 'emails'
202 203 self.__load_data()
203 204
204 205 c.user_email_map = UserEmailMap.query()\
205 206 .filter(UserEmailMap.user == c.user).all()
206 207 return render('admin/my_account/my_account.html')
207 208
208 209 def my_account_emails_add(self):
209 210 email = request.POST.get('new_email')
210 211
211 212 try:
212 213 UserModel().add_extra_email(self.authuser.user_id, email)
213 214 Session().commit()
214 215 h.flash(_("Added email %s to user") % email, category='success')
215 216 except formencode.Invalid, error:
216 217 msg = error.error_dict['email']
217 218 h.flash(msg, category='error')
218 219 except Exception:
219 220 log.error(traceback.format_exc())
220 221 h.flash(_('An error occurred during email saving'),
221 222 category='error')
222 223 return redirect(url('my_account_emails'))
223 224
224 225 def my_account_emails_delete(self):
225 226 email_id = request.POST.get('del_email_id')
226 227 user_model = UserModel()
227 228 user_model.delete_extra_email(self.authuser.user_id, email_id)
228 229 Session().commit()
229 230 h.flash(_("Removed email from user"), category='success')
230 231 return redirect(url('my_account_emails'))
231 232
232 233 def my_account_api_keys(self):
233 234 c.active = 'api_keys'
234 235 self.__load_data()
235 236 show_expired = True
236 237 c.lifetime_values = [
237 238 (str(-1), _('forever')),
238 239 (str(5), _('5 minutes')),
239 240 (str(60), _('1 hour')),
240 241 (str(60 * 24), _('1 day')),
241 242 (str(60 * 24 * 30), _('1 month')),
242 243 ]
243 244 c.lifetime_options = [(c.lifetime_values, _("Lifetime"))]
244 245 c.user_api_keys = ApiKeyModel().get_api_keys(self.authuser.user_id,
245 246 show_expired=show_expired)
246 247 return render('admin/my_account/my_account.html')
247 248
248 249 def my_account_api_keys_add(self):
249 250 lifetime = safe_int(request.POST.get('lifetime'), -1)
250 251 description = request.POST.get('description')
251 252 ApiKeyModel().create(self.authuser.user_id, description, lifetime)
252 253 Session().commit()
253 254 h.flash(_("Api key successfully created"), category='success')
254 255 return redirect(url('my_account_api_keys'))
255 256
256 257 def my_account_api_keys_delete(self):
257 258 api_key = request.POST.get('del_api_key')
258 259 user_id = self.authuser.user_id
259 260 if request.POST.get('del_api_key_builtin'):
260 261 user = User.get(user_id)
261 262 if user:
262 263 user.api_key = generate_api_key(user.username)
263 264 Session().add(user)
264 265 Session().commit()
265 266 h.flash(_("Api key successfully reset"), category='success')
266 267 elif api_key:
267 268 ApiKeyModel().delete(api_key, self.authuser.user_id)
268 269 Session().commit()
269 270 h.flash(_("Api key successfully deleted"), category='success')
270 271
271 272 return redirect(url('my_account_api_keys'))
@@ -1,196 +1,197 b''
1 1 # -*- coding: utf-8 -*-
2 2 # This program is free software: you can redistribute it and/or modify
3 3 # it under the terms of the GNU General Public License as published by
4 4 # the Free Software Foundation, either version 3 of the License, or
5 5 # (at your option) any later version.
6 6 #
7 7 # This program is distributed in the hope that it will be useful,
8 8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 10 # GNU General Public License for more details.
11 11 #
12 12 # You should have received a copy of the GNU General Public License
13 13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
14 14 """
15 15 kallithea.controllers.admin.permissions
16 16 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
17 17
18 18 permissions controller for Kallithea
19 19
20 20 This file was forked by the Kallithea project in July 2014.
21 21 Original author and date, and relevant copyright and licensing information is below:
22 22 :created_on: Apr 27, 2010
23 23 :author: marcink
24 24 :copyright: (c) 2013 RhodeCode GmbH, and others.
25 25 :license: GPLv3, see LICENSE.md for more details.
26 26 """
27 27
28 28
29 29 import logging
30 30 import traceback
31 31 import formencode
32 32 from formencode import htmlfill
33 33
34 34 from pylons import request, tmpl_context as c, url
35 35 from pylons.controllers.util import redirect
36 36 from pylons.i18n.translation import _
37 37
38 38 from kallithea.lib import helpers as h
39 39 from kallithea.lib.auth import LoginRequired, HasPermissionAllDecorator,\
40 40 AuthUser
41 41 from kallithea.lib.base import BaseController, render
42 42 from kallithea.model.forms import DefaultPermissionsForm
43 43 from kallithea.model.permission import PermissionModel
44 44 from kallithea.model.db import User, UserIpMap
45 45 from kallithea.model.meta import Session
46 46
47 47 log = logging.getLogger(__name__)
48 48
49 49
50 50 class PermissionsController(BaseController):
51 51 """REST Controller styled on the Atom Publishing Protocol"""
52 52 # To properly map this controller, ensure your config/routing.py
53 53 # file has a resource setup:
54 54 # map.resource('permission', 'permissions')
55 55
56 56 @LoginRequired()
57 57 @HasPermissionAllDecorator('hg.admin')
58 58 def __before__(self):
59 59 super(PermissionsController, self).__before__()
60 60
61 61 def __load_data(self):
62 62 c.repo_perms_choices = [('repository.none', _('None'),),
63 63 ('repository.read', _('Read'),),
64 64 ('repository.write', _('Write'),),
65 65 ('repository.admin', _('Admin'),)]
66 66 c.group_perms_choices = [('group.none', _('None'),),
67 67 ('group.read', _('Read'),),
68 68 ('group.write', _('Write'),),
69 69 ('group.admin', _('Admin'),)]
70 70 c.user_group_perms_choices = [('usergroup.none', _('None'),),
71 71 ('usergroup.read', _('Read'),),
72 72 ('usergroup.write', _('Write'),),
73 73 ('usergroup.admin', _('Admin'),)]
74 74 c.register_choices = [
75 75 ('hg.register.none',
76 76 _('Disabled')),
77 77 ('hg.register.manual_activate',
78 78 _('Allowed with manual account activation')),
79 79 ('hg.register.auto_activate',
80 80 _('Allowed with automatic account activation')), ]
81 81
82 82 c.extern_activate_choices = [
83 83 ('hg.extern_activate.manual', _('Manual activation of external account')),
84 84 ('hg.extern_activate.auto', _('Automatic activation of external account')),
85 85 ]
86 86
87 87 c.repo_create_choices = [('hg.create.none', _('Disabled')),
88 88 ('hg.create.repository', _('Enabled'))]
89 89
90 90 c.repo_create_on_write_choices = [
91 91 ('hg.create.write_on_repogroup.true', _('Enabled')),
92 92 ('hg.create.write_on_repogroup.false', _('Disabled')),
93 93 ]
94 94
95 95 c.user_group_create_choices = [('hg.usergroup.create.false', _('Disabled')),
96 96 ('hg.usergroup.create.true', _('Enabled'))]
97 97
98 98 c.repo_group_create_choices = [('hg.repogroup.create.false', _('Disabled')),
99 99 ('hg.repogroup.create.true', _('Enabled'))]
100 100
101 101 c.fork_choices = [('hg.fork.none', _('Disabled')),
102 102 ('hg.fork.repository', _('Enabled'))]
103 103
104 104 def permission_globals(self):
105 105 c.active = 'globals'
106 106 self.__load_data()
107 107 if request.POST:
108 108 _form = DefaultPermissionsForm(
109 109 [x[0] for x in c.repo_perms_choices],
110 110 [x[0] for x in c.group_perms_choices],
111 111 [x[0] for x in c.user_group_perms_choices],
112 112 [x[0] for x in c.repo_create_choices],
113 113 [x[0] for x in c.repo_create_on_write_choices],
114 114 [x[0] for x in c.repo_group_create_choices],
115 115 [x[0] for x in c.user_group_create_choices],
116 116 [x[0] for x in c.fork_choices],
117 117 [x[0] for x in c.register_choices],
118 118 [x[0] for x in c.extern_activate_choices])()
119 119
120 120 try:
121 121 form_result = _form.to_python(dict(request.POST))
122 122 form_result.update({'perm_user_name': 'default'})
123 123 PermissionModel().update(form_result)
124 124 Session().commit()
125 125 h.flash(_('Global permissions updated successfully'),
126 126 category='success')
127 127
128 128 except formencode.Invalid, errors:
129 129 defaults = errors.value
130 130
131 131 return htmlfill.render(
132 132 render('admin/permissions/permissions.html'),
133 133 defaults=defaults,
134 134 errors=errors.error_dict or {},
135 135 prefix_error=False,
136 encoding="UTF-8")
136 encoding="UTF-8",
137 force_defaults=False)
137 138 except Exception:
138 139 log.error(traceback.format_exc())
139 140 h.flash(_('Error occurred during update of permissions'),
140 141 category='error')
141 142
142 143 return redirect(url('admin_permissions'))
143 144
144 145 c.user = User.get_default_user()
145 146 defaults = {'anonymous': c.user.active}
146 147
147 148 for p in c.user.user_perms:
148 149 if p.permission.permission_name.startswith('repository.'):
149 150 defaults['default_repo_perm'] = p.permission.permission_name
150 151
151 152 if p.permission.permission_name.startswith('group.'):
152 153 defaults['default_group_perm'] = p.permission.permission_name
153 154
154 155 if p.permission.permission_name.startswith('usergroup.'):
155 156 defaults['default_user_group_perm'] = p.permission.permission_name
156 157
157 158 if p.permission.permission_name.startswith('hg.create.write_on_repogroup'):
158 159 defaults['create_on_write'] = p.permission.permission_name
159 160
160 161 elif p.permission.permission_name.startswith('hg.create.'):
161 162 defaults['default_repo_create'] = p.permission.permission_name
162 163
163 164 if p.permission.permission_name.startswith('hg.repogroup.'):
164 165 defaults['default_repo_group_create'] = p.permission.permission_name
165 166
166 167 if p.permission.permission_name.startswith('hg.usergroup.'):
167 168 defaults['default_user_group_create'] = p.permission.permission_name
168 169
169 170 if p.permission.permission_name.startswith('hg.register.'):
170 171 defaults['default_register'] = p.permission.permission_name
171 172
172 173 if p.permission.permission_name.startswith('hg.extern_activate.'):
173 174 defaults['default_extern_activate'] = p.permission.permission_name
174 175
175 176 if p.permission.permission_name.startswith('hg.fork.'):
176 177 defaults['default_fork'] = p.permission.permission_name
177 178
178 179 return htmlfill.render(
179 180 render('admin/permissions/permissions.html'),
180 181 defaults=defaults,
181 182 encoding="UTF-8",
182 183 force_defaults=False)
183 184
184 185 def permission_ips(self):
185 186 c.active = 'ips'
186 187 c.user = User.get_default_user()
187 188 c.user_ip_map = UserIpMap.query()\
188 189 .filter(UserIpMap.user == c.user).all()
189 190
190 191 return render('admin/permissions/permissions.html')
191 192
192 193 def permission_perms(self):
193 194 c.active = 'perms'
194 195 c.user = User.get_default_user()
195 196 c.perm_user = c.user.AuthUser
196 197 return render('admin/permissions/permissions.html')
@@ -1,472 +1,474 b''
1 1 # -*- coding: utf-8 -*-
2 2 # This program is free software: you can redistribute it and/or modify
3 3 # it under the terms of the GNU General Public License as published by
4 4 # the Free Software Foundation, either version 3 of the License, or
5 5 # (at your option) any later version.
6 6 #
7 7 # This program is distributed in the hope that it will be useful,
8 8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 10 # GNU General Public License for more details.
11 11 #
12 12 # You should have received a copy of the GNU General Public License
13 13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
14 14 """
15 15 kallithea.controllers.admin.repo_groups
16 16 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
17 17
18 18 Repository groups controller for Kallithea
19 19
20 20 This file was forked by the Kallithea project in July 2014.
21 21 Original author and date, and relevant copyright and licensing information is below:
22 22 :created_on: Mar 23, 2010
23 23 :author: marcink
24 24 :copyright: (c) 2013 RhodeCode GmbH, and others.
25 25 :license: GPLv3, see LICENSE.md for more details.
26 26 """
27 27
28 28 import logging
29 29 import traceback
30 30 import formencode
31 31 import itertools
32 32
33 33 from formencode import htmlfill
34 34
35 35 from pylons import request, tmpl_context as c, url
36 36 from pylons.controllers.util import abort, redirect
37 37 from pylons.i18n.translation import _, ungettext
38 38
39 39 import kallithea
40 40 from kallithea.lib import helpers as h
41 41 from kallithea.lib.compat import json
42 42 from kallithea.lib.auth import LoginRequired, HasPermissionAnyDecorator,\
43 43 HasRepoGroupPermissionAnyDecorator, HasRepoGroupPermissionAll,\
44 44 HasPermissionAll
45 45 from kallithea.lib.base import BaseController, render
46 46 from kallithea.model.db import RepoGroup, Repository
47 47 from kallithea.model.scm import RepoGroupList
48 48 from kallithea.model.repo_group import RepoGroupModel
49 49 from kallithea.model.forms import RepoGroupForm, RepoGroupPermsForm
50 50 from kallithea.model.meta import Session
51 51 from kallithea.model.repo import RepoModel
52 52 from webob.exc import HTTPInternalServerError, HTTPNotFound
53 53 from kallithea.lib.utils2 import safe_int
54 54 from sqlalchemy.sql.expression import func
55 55
56 56
57 57 log = logging.getLogger(__name__)
58 58
59 59
60 60 class RepoGroupsController(BaseController):
61 61 """REST Controller styled on the Atom Publishing Protocol"""
62 62
63 63 @LoginRequired()
64 64 def __before__(self):
65 65 super(RepoGroupsController, self).__before__()
66 66
67 67 def __load_defaults(self, allow_empty_group=False, exclude_group_ids=[]):
68 68 if HasPermissionAll('hg.admin')('group edit'):
69 69 #we're global admin, we're ok and we can create TOP level groups
70 70 allow_empty_group = True
71 71
72 72 #override the choices for this form, we need to filter choices
73 73 #and display only those we have ADMIN right
74 74 groups_with_admin_rights = RepoGroupList(RepoGroup.query().all(),
75 75 perm_set=['group.admin'])
76 76 c.repo_groups = RepoGroup.groups_choices(groups=groups_with_admin_rights,
77 77 show_empty_group=allow_empty_group)
78 78 # exclude filtered ids
79 79 c.repo_groups = filter(lambda x: x[0] not in exclude_group_ids,
80 80 c.repo_groups)
81 81 c.repo_groups_choices = map(lambda k: unicode(k[0]), c.repo_groups)
82 82 repo_model = RepoModel()
83 83 c.users_array = repo_model.get_users_js()
84 84 c.user_groups_array = repo_model.get_user_groups_js()
85 85
86 86 def __load_data(self, group_id):
87 87 """
88 88 Load defaults settings for edit, and update
89 89
90 90 :param group_id:
91 91 """
92 92 repo_group = RepoGroup.get_or_404(group_id)
93 93 data = repo_group.get_dict()
94 94 data['group_name'] = repo_group.name
95 95
96 96 # fill repository group users
97 97 for p in repo_group.repo_group_to_perm:
98 98 data.update({'u_perm_%s' % p.user.username:
99 99 p.permission.permission_name})
100 100
101 101 # fill repository group groups
102 102 for p in repo_group.users_group_to_perm:
103 103 data.update({'g_perm_%s' % p.users_group.users_group_name:
104 104 p.permission.permission_name})
105 105
106 106 return data
107 107
108 108 def _revoke_perms_on_yourself(self, form_result):
109 109 _up = filter(lambda u: c.authuser.username == u[0],
110 110 form_result['perms_updates'])
111 111 _new = filter(lambda u: c.authuser.username == u[0],
112 112 form_result['perms_new'])
113 113 if _new and _new[0][1] != 'group.admin' or _up and _up[0][1] != 'group.admin':
114 114 return True
115 115 return False
116 116
117 117 def index(self, format='html'):
118 118 """GET /repo_groups: All items in the collection"""
119 119 # url('repos_groups')
120 120 _list = RepoGroup.query()\
121 121 .order_by(func.lower(RepoGroup.group_name))\
122 122 .all()
123 123 group_iter = RepoGroupList(_list, perm_set=['group.admin'])
124 124 repo_groups_data = []
125 125 total_records = len(group_iter)
126 126 _tmpl_lookup = kallithea.CONFIG['pylons.app_globals'].mako_lookup
127 127 template = _tmpl_lookup.get_template('data_table/_dt_elements.html')
128 128
129 129 repo_group_name = lambda repo_group_name, children_groups: (
130 130 template.get_def("repo_group_name")
131 131 .render(repo_group_name, children_groups, _=_, h=h, c=c)
132 132 )
133 133 repo_group_actions = lambda repo_group_id, repo_group_name, gr_count: (
134 134 template.get_def("repo_group_actions")
135 135 .render(repo_group_id, repo_group_name, gr_count, _=_, h=h, c=c,
136 136 ungettext=ungettext)
137 137 )
138 138
139 139 for repo_gr in group_iter:
140 140 children_groups = map(h.safe_unicode,
141 141 itertools.chain((g.name for g in repo_gr.parents),
142 142 (x.name for x in [repo_gr])))
143 143 repo_count = repo_gr.repositories.count()
144 144 repo_groups_data.append({
145 145 "raw_name": repo_gr.group_name,
146 146 "group_name": repo_group_name(repo_gr.group_name, children_groups),
147 147 "desc": repo_gr.group_description,
148 148 "repos": repo_count,
149 149 "owner": h.person(repo_gr.user),
150 150 "action": repo_group_actions(repo_gr.group_id, repo_gr.group_name,
151 151 repo_count)
152 152 })
153 153
154 154 c.data = json.dumps({
155 155 "totalRecords": total_records,
156 156 "startIndex": 0,
157 157 "sort": None,
158 158 "dir": "asc",
159 159 "records": repo_groups_data
160 160 })
161 161
162 162 return render('admin/repo_groups/repo_groups.html')
163 163
164 164 def create(self):
165 165 """POST /repo_groups: Create a new item"""
166 166 # url('repos_groups')
167 167
168 168 self.__load_defaults()
169 169
170 170 # permissions for can create group based on parent_id are checked
171 171 # here in the Form
172 172 repo_group_form = RepoGroupForm(available_groups=
173 173 map(lambda k: unicode(k[0]), c.repo_groups))()
174 174 try:
175 175 form_result = repo_group_form.to_python(dict(request.POST))
176 176 RepoGroupModel().create(
177 177 group_name=form_result['group_name'],
178 178 group_description=form_result['group_description'],
179 179 parent=form_result['group_parent_id'],
180 180 owner=self.authuser.user_id,
181 181 copy_permissions=form_result['group_copy_permissions']
182 182 )
183 183 Session().commit()
184 184 h.flash(_('Created repository group %s') \
185 185 % form_result['group_name'], category='success')
186 186 #TODO: in futureaction_logger(, '', '', '', self.sa)
187 187 except formencode.Invalid, errors:
188 188 return htmlfill.render(
189 189 render('admin/repo_groups/repo_group_add.html'),
190 190 defaults=errors.value,
191 191 errors=errors.error_dict or {},
192 192 prefix_error=False,
193 encoding="UTF-8")
193 encoding="UTF-8",
194 force_defaults=False)
194 195 except Exception:
195 196 log.error(traceback.format_exc())
196 197 h.flash(_('Error occurred during creation of repository group %s') \
197 198 % request.POST.get('group_name'), category='error')
198 199 parent_group_id = form_result['group_parent_id']
199 200 #TODO: maybe we should get back to the main view, not the admin one
200 201 return redirect(url('repos_groups', parent_group=parent_group_id))
201 202
202 203 def new(self):
203 204 """GET /repo_groups/new: Form to create a new item"""
204 205 # url('new_repos_group')
205 206 if HasPermissionAll('hg.admin')('group create'):
206 207 #we're global admin, we're ok and we can create TOP level groups
207 208 pass
208 209 else:
209 210 # we pass in parent group into creation form, thus we know
210 211 # what would be the group, we can check perms here !
211 212 group_id = safe_int(request.GET.get('parent_group'))
212 213 group = RepoGroup.get(group_id) if group_id else None
213 214 group_name = group.group_name if group else None
214 215 if HasRepoGroupPermissionAll('group.admin')(group_name, 'group create'):
215 216 pass
216 217 else:
217 218 return abort(403)
218 219
219 220 self.__load_defaults()
220 221 return render('admin/repo_groups/repo_group_add.html')
221 222
222 223 @HasRepoGroupPermissionAnyDecorator('group.admin')
223 224 def update(self, group_name):
224 225 """PUT /repo_groups/group_name: Update an existing item"""
225 226 # Forms posted to this method should contain a hidden field:
226 227 # <input type="hidden" name="_method" value="PUT" />
227 228 # Or using helpers:
228 229 # h.form(url('repos_group', group_name=GROUP_NAME),
229 230 # method='put')
230 231 # url('repos_group', group_name=GROUP_NAME)
231 232
232 233 c.repo_group = RepoGroupModel()._get_repo_group(group_name)
233 234 if HasPermissionAll('hg.admin')('group edit'):
234 235 #we're global admin, we're ok and we can create TOP level groups
235 236 allow_empty_group = True
236 237 elif not c.repo_group.parent_group:
237 238 allow_empty_group = True
238 239 else:
239 240 allow_empty_group = False
240 241 self.__load_defaults(allow_empty_group=allow_empty_group,
241 242 exclude_group_ids=[c.repo_group.group_id])
242 243
243 244 repo_group_form = RepoGroupForm(
244 245 edit=True,
245 246 old_data=c.repo_group.get_dict(),
246 247 available_groups=c.repo_groups_choices,
247 248 can_create_in_root=allow_empty_group,
248 249 )()
249 250 try:
250 251 form_result = repo_group_form.to_python(dict(request.POST))
251 252
252 253 new_gr = RepoGroupModel().update(group_name, form_result)
253 254 Session().commit()
254 255 h.flash(_('Updated repository group %s') \
255 256 % form_result['group_name'], category='success')
256 257 # we now have new name !
257 258 group_name = new_gr.group_name
258 259 #TODO: in future action_logger(, '', '', '', self.sa)
259 260 except formencode.Invalid, errors:
260 261
261 262 return htmlfill.render(
262 263 render('admin/repo_groups/repo_group_edit.html'),
263 264 defaults=errors.value,
264 265 errors=errors.error_dict or {},
265 266 prefix_error=False,
266 encoding="UTF-8")
267 encoding="UTF-8",
268 force_defaults=False)
267 269 except Exception:
268 270 log.error(traceback.format_exc())
269 271 h.flash(_('Error occurred during update of repository group %s') \
270 272 % request.POST.get('group_name'), category='error')
271 273
272 274 return redirect(url('edit_repo_group', group_name=group_name))
273 275
274 276 @HasRepoGroupPermissionAnyDecorator('group.admin')
275 277 def delete(self, group_name):
276 278 """DELETE /repo_groups/group_name: Delete an existing item"""
277 279 # Forms posted to this method should contain a hidden field:
278 280 # <input type="hidden" name="_method" value="DELETE" />
279 281 # Or using helpers:
280 282 # h.form(url('repos_group', group_name=GROUP_NAME),
281 283 # method='delete')
282 284 # url('repos_group', group_name=GROUP_NAME)
283 285
284 286 gr = c.repo_group = RepoGroupModel()._get_repo_group(group_name)
285 287 repos = gr.repositories.all()
286 288 if repos:
287 289 h.flash(_('This group contains %s repositories and cannot be '
288 290 'deleted') % len(repos), category='warning')
289 291 return redirect(url('repos_groups'))
290 292
291 293 children = gr.children.all()
292 294 if children:
293 295 h.flash(_('This group contains %s subgroups and cannot be deleted'
294 296 % (len(children))), category='warning')
295 297 return redirect(url('repos_groups'))
296 298
297 299 try:
298 300 RepoGroupModel().delete(group_name)
299 301 Session().commit()
300 302 h.flash(_('Removed repository group %s') % group_name,
301 303 category='success')
302 304 #TODO: in future action_logger(, '', '', '', self.sa)
303 305 except Exception:
304 306 log.error(traceback.format_exc())
305 307 h.flash(_('Error occurred during deletion of repository group %s')
306 308 % group_name, category='error')
307 309
308 310 if gr.parent_group:
309 311 return redirect(url('repos_group_home', group_name=gr.parent_group.group_name))
310 312 return redirect(url('repos_groups'))
311 313
312 314 def show_by_name(self, group_name):
313 315 """
314 316 This is a proxy that does a lookup group_name -> id, and shows
315 317 the group by id view instead
316 318 """
317 319 group_name = group_name.rstrip('/')
318 320 id_ = RepoGroup.get_by_group_name(group_name)
319 321 if id_:
320 322 return self.show(group_name)
321 323 raise HTTPNotFound
322 324
323 325 @HasRepoGroupPermissionAnyDecorator('group.read', 'group.write',
324 326 'group.admin')
325 327 def show(self, group_name):
326 328 """GET /repo_groups/group_name: Show a specific item"""
327 329 # url('repos_group', group_name=GROUP_NAME)
328 330 c.active = 'settings'
329 331
330 332 c.group = c.repo_group = RepoGroupModel()._get_repo_group(group_name)
331 333 c.group_repos = c.group.repositories.all()
332 334
333 335 #overwrite our cached list with current filter
334 336 c.repo_cnt = 0
335 337
336 338 groups = RepoGroup.query().order_by(RepoGroup.group_name)\
337 339 .filter(RepoGroup.group_parent_id == c.group.group_id).all()
338 340 c.groups = self.scm_model.get_repo_groups(groups)
339 341
340 342 c.repos_list = Repository.query()\
341 343 .filter(Repository.group_id == c.group.group_id)\
342 344 .order_by(func.lower(Repository.repo_name))\
343 345 .all()
344 346
345 347 repos_data = RepoModel().get_repos_as_dict(repos_list=c.repos_list,
346 348 admin=False)
347 349 #json used to render the grid
348 350 c.data = json.dumps(repos_data)
349 351
350 352 return render('admin/repo_groups/repo_group_show.html')
351 353
352 354 @HasRepoGroupPermissionAnyDecorator('group.admin')
353 355 def edit(self, group_name):
354 356 """GET /repo_groups/group_name/edit: Form to edit an existing item"""
355 357 # url('edit_repo_group', group_name=GROUP_NAME)
356 358 c.active = 'settings'
357 359
358 360 c.repo_group = RepoGroupModel()._get_repo_group(group_name)
359 361 #we can only allow moving empty group if it's already a top-level
360 362 #group, ie has no parents, or we're admin
361 363 if HasPermissionAll('hg.admin')('group edit'):
362 364 #we're global admin, we're ok and we can create TOP level groups
363 365 allow_empty_group = True
364 366 elif not c.repo_group.parent_group:
365 367 allow_empty_group = True
366 368 else:
367 369 allow_empty_group = False
368 370
369 371 self.__load_defaults(allow_empty_group=allow_empty_group,
370 372 exclude_group_ids=[c.repo_group.group_id])
371 373 defaults = self.__load_data(c.repo_group.group_id)
372 374
373 375 return htmlfill.render(
374 376 render('admin/repo_groups/repo_group_edit.html'),
375 377 defaults=defaults,
376 378 encoding="UTF-8",
377 379 force_defaults=False
378 380 )
379 381
380 382 @HasRepoGroupPermissionAnyDecorator('group.admin')
381 383 def edit_repo_group_advanced(self, group_name):
382 384 """GET /repo_groups/group_name/edit: Form to edit an existing item"""
383 385 # url('edit_repo_group', group_name=GROUP_NAME)
384 386 c.active = 'advanced'
385 387 c.repo_group = RepoGroupModel()._get_repo_group(group_name)
386 388
387 389 return render('admin/repo_groups/repo_group_edit.html')
388 390
389 391 @HasRepoGroupPermissionAnyDecorator('group.admin')
390 392 def edit_repo_group_perms(self, group_name):
391 393 """GET /repo_groups/group_name/edit: Form to edit an existing item"""
392 394 # url('edit_repo_group', group_name=GROUP_NAME)
393 395 c.active = 'perms'
394 396 c.repo_group = RepoGroupModel()._get_repo_group(group_name)
395 397 self.__load_defaults()
396 398 defaults = self.__load_data(c.repo_group.group_id)
397 399
398 400 return htmlfill.render(
399 401 render('admin/repo_groups/repo_group_edit.html'),
400 402 defaults=defaults,
401 403 encoding="UTF-8",
402 404 force_defaults=False
403 405 )
404 406
405 407 @HasRepoGroupPermissionAnyDecorator('group.admin')
406 408 def update_perms(self, group_name):
407 409 """
408 410 Update permissions for given repository group
409 411
410 412 :param group_name:
411 413 """
412 414
413 415 c.repo_group = RepoGroupModel()._get_repo_group(group_name)
414 416 valid_recursive_choices = ['none', 'repos', 'groups', 'all']
415 417 form_result = RepoGroupPermsForm(valid_recursive_choices)().to_python(request.POST)
416 418 if not c.authuser.is_admin:
417 419 if self._revoke_perms_on_yourself(form_result):
418 420 msg = _('Cannot revoke permission for yourself as admin')
419 421 h.flash(msg, category='warning')
420 422 return redirect(url('edit_repo_group_perms', group_name=group_name))
421 423 recursive = form_result['recursive']
422 424 # iterate over all members(if in recursive mode) of this groups and
423 425 # set the permissions !
424 426 # this can be potentially heavy operation
425 427 RepoGroupModel()._update_permissions(c.repo_group,
426 428 form_result['perms_new'],
427 429 form_result['perms_updates'],
428 430 recursive)
429 431 #TODO: implement this
430 432 #action_logger(self.authuser, 'admin_changed_repo_permissions',
431 433 # repo_name, self.ip_addr, self.sa)
432 434 Session().commit()
433 435 h.flash(_('Repository Group permissions updated'), category='success')
434 436 return redirect(url('edit_repo_group_perms', group_name=group_name))
435 437
436 438 @HasRepoGroupPermissionAnyDecorator('group.admin')
437 439 def delete_perms(self, group_name):
438 440 """
439 441 DELETE an existing repository group permission user
440 442
441 443 :param group_name:
442 444 """
443 445 try:
444 446 obj_type = request.POST.get('obj_type')
445 447 obj_id = None
446 448 if obj_type == 'user':
447 449 obj_id = safe_int(request.POST.get('user_id'))
448 450 elif obj_type == 'user_group':
449 451 obj_id = safe_int(request.POST.get('user_group_id'))
450 452
451 453 if not c.authuser.is_admin:
452 454 if obj_type == 'user' and c.authuser.user_id == obj_id:
453 455 msg = _('Cannot revoke permission for yourself as admin')
454 456 h.flash(msg, category='warning')
455 457 raise Exception('revoke admin permission on self')
456 458 recursive = request.POST.get('recursive', 'none')
457 459 if obj_type == 'user':
458 460 RepoGroupModel().delete_permission(repo_group=group_name,
459 461 obj=obj_id, obj_type='user',
460 462 recursive=recursive)
461 463 elif obj_type == 'user_group':
462 464 RepoGroupModel().delete_permission(repo_group=group_name,
463 465 obj=obj_id,
464 466 obj_type='user_group',
465 467 recursive=recursive)
466 468
467 469 Session().commit()
468 470 except Exception:
469 471 log.error(traceback.format_exc())
470 472 h.flash(_('An error occurred during revoking of permission'),
471 473 category='error')
472 474 raise HTTPInternalServerError()
@@ -1,679 +1,681 b''
1 1 # -*- coding: utf-8 -*-
2 2 # This program is free software: you can redistribute it and/or modify
3 3 # it under the terms of the GNU General Public License as published by
4 4 # the Free Software Foundation, either version 3 of the License, or
5 5 # (at your option) any later version.
6 6 #
7 7 # This program is distributed in the hope that it will be useful,
8 8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 10 # GNU General Public License for more details.
11 11 #
12 12 # You should have received a copy of the GNU General Public License
13 13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
14 14 """
15 15 kallithea.controllers.admin.repos
16 16 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
17 17
18 18 Repositories controller for Kallithea
19 19
20 20 This file was forked by the Kallithea project in July 2014.
21 21 Original author and date, and relevant copyright and licensing information is below:
22 22 :created_on: Apr 7, 2010
23 23 :author: marcink
24 24 :copyright: (c) 2013 RhodeCode GmbH, and others.
25 25 :license: GPLv3, see LICENSE.md for more details.
26 26 """
27 27
28 28 import logging
29 29 import traceback
30 30 import formencode
31 31 from formencode import htmlfill
32 32 from webob.exc import HTTPInternalServerError, HTTPForbidden, HTTPNotFound
33 33 from pylons import request, tmpl_context as c, url
34 34 from pylons.controllers.util import redirect
35 35 from pylons.i18n.translation import _
36 36 from sqlalchemy.sql.expression import func
37 37
38 38 from kallithea.lib import helpers as h
39 39 from kallithea.lib.auth import LoginRequired, HasPermissionAllDecorator, \
40 40 HasRepoPermissionAllDecorator, NotAnonymous,HasPermissionAny, \
41 41 HasRepoGroupPermissionAny, HasRepoPermissionAnyDecorator
42 42 from kallithea.lib.base import BaseRepoController, render
43 43 from kallithea.lib.utils import action_logger, repo_name_slug, jsonify
44 44 from kallithea.lib.helpers import get_token
45 45 from kallithea.lib.vcs import RepositoryError
46 46 from kallithea.model.meta import Session
47 47 from kallithea.model.db import User, Repository, UserFollowing, RepoGroup,\
48 48 Setting, RepositoryField
49 49 from kallithea.model.forms import RepoForm, RepoFieldForm, RepoPermsForm
50 50 from kallithea.model.scm import ScmModel, RepoGroupList, RepoList
51 51 from kallithea.model.repo import RepoModel
52 52 from kallithea.lib.compat import json
53 53 from kallithea.lib.exceptions import AttachedForksError
54 54 from kallithea.lib.utils2 import safe_int
55 55
56 56 log = logging.getLogger(__name__)
57 57
58 58
59 59 class ReposController(BaseRepoController):
60 60 """
61 61 REST Controller styled on the Atom Publishing Protocol"""
62 62 # To properly map this controller, ensure your config/routing.py
63 63 # file has a resource setup:
64 64 # map.resource('repo', 'repos')
65 65
66 66 @LoginRequired()
67 67 def __before__(self):
68 68 super(ReposController, self).__before__()
69 69
70 70 def _load_repo(self, repo_name):
71 71 repo_obj = Repository.get_by_repo_name(repo_name)
72 72
73 73 if repo_obj is None:
74 74 h.not_mapped_error(repo_name)
75 75 return redirect(url('repos'))
76 76
77 77 return repo_obj
78 78
79 79 def __load_defaults(self, repo=None):
80 80 acl_groups = RepoGroupList(RepoGroup.query().all(),
81 81 perm_set=['group.write', 'group.admin'])
82 82 c.repo_groups = RepoGroup.groups_choices(groups=acl_groups)
83 83 c.repo_groups_choices = map(lambda k: unicode(k[0]), c.repo_groups)
84 84
85 85 # in case someone no longer have a group.write access to a repository
86 86 # pre fill the list with this entry, we don't care if this is the same
87 87 # but it will allow saving repo data properly.
88 88
89 89 repo_group = None
90 90 if repo:
91 91 repo_group = repo.group
92 92 if repo_group and unicode(repo_group.group_id) not in c.repo_groups_choices:
93 93 c.repo_groups_choices.append(unicode(repo_group.group_id))
94 94 c.repo_groups.append(RepoGroup._generate_choice(repo_group))
95 95
96 96 choices, c.landing_revs = ScmModel().get_repo_landing_revs()
97 97 c.landing_revs_choices = choices
98 98
99 99 def __load_data(self, repo_name=None):
100 100 """
101 101 Load defaults settings for edit, and update
102 102
103 103 :param repo_name:
104 104 """
105 105 c.repo_info = self._load_repo(repo_name)
106 106 self.__load_defaults(c.repo_info)
107 107
108 108 ##override defaults for exact repo info here git/hg etc
109 109 choices, c.landing_revs = ScmModel().get_repo_landing_revs(c.repo_info)
110 110 c.landing_revs_choices = choices
111 111 defaults = RepoModel()._get_defaults(repo_name)
112 112
113 113 return defaults
114 114
115 115 def index(self, format='html'):
116 116 """GET /repos: All items in the collection"""
117 117 # url('repos')
118 118 _list = Repository.query()\
119 119 .order_by(func.lower(Repository.repo_name))\
120 120 .all()
121 121
122 122 c.repos_list = RepoList(_list, perm_set=['repository.admin'])
123 123 repos_data = RepoModel().get_repos_as_dict(repos_list=c.repos_list,
124 124 admin=True,
125 125 super_user_actions=True)
126 126 #json used to render the grid
127 127 c.data = json.dumps(repos_data)
128 128
129 129 return render('admin/repos/repos.html')
130 130
131 131 @NotAnonymous()
132 132 def create(self):
133 133 """
134 134 POST /repos: Create a new item"""
135 135 # url('repos')
136 136
137 137 self.__load_defaults()
138 138 form_result = {}
139 139 task_id = None
140 140 try:
141 141 # CanWriteToGroup validators checks permissions of this POST
142 142 form_result = RepoForm(repo_groups=c.repo_groups_choices,
143 143 landing_revs=c.landing_revs_choices)()\
144 144 .to_python(dict(request.POST))
145 145
146 146 # create is done sometimes async on celery, db transaction
147 147 # management is handled there.
148 148 task = RepoModel().create(form_result, self.authuser.user_id)
149 149 from celery.result import BaseAsyncResult
150 150 if isinstance(task, BaseAsyncResult):
151 151 task_id = task.task_id
152 152 except formencode.Invalid, errors:
153 153 return htmlfill.render(
154 154 render('admin/repos/repo_add.html'),
155 155 defaults=errors.value,
156 156 errors=errors.error_dict or {},
157 157 prefix_error=False,
158 force_defaults=False,
158 159 encoding="UTF-8")
159 160
160 161 except Exception:
161 162 log.error(traceback.format_exc())
162 163 msg = (_('Error creating repository %s')
163 164 % form_result.get('repo_name'))
164 165 h.flash(msg, category='error')
165 166 return redirect(url('home'))
166 167
167 168 return redirect(h.url('repo_creating_home',
168 169 repo_name=form_result['repo_name_full'],
169 170 task_id=task_id))
170 171
171 172 @NotAnonymous()
172 173 def create_repository(self):
173 174 """GET /_admin/create_repository: Form to create a new item"""
174 175 new_repo = request.GET.get('repo', '')
175 176 parent_group = request.GET.get('parent_group')
176 177 if not HasPermissionAny('hg.admin', 'hg.create.repository')():
177 178 #you're not super admin nor have global create permissions,
178 179 #but maybe you have at least write permission to a parent group ?
179 180 _gr = RepoGroup.get(parent_group)
180 181 gr_name = _gr.group_name if _gr else None
181 182 # create repositories with write permission on group is set to true
182 183 create_on_write = HasPermissionAny('hg.create.write_on_repogroup.true')()
183 184 group_admin = HasRepoGroupPermissionAny('group.admin')(group_name=gr_name)
184 185 group_write = HasRepoGroupPermissionAny('group.write')(group_name=gr_name)
185 186 if not (group_admin or (group_write and create_on_write)):
186 187 raise HTTPForbidden
187 188
188 189 acl_groups = RepoGroupList(RepoGroup.query().all(),
189 190 perm_set=['group.write', 'group.admin'])
190 191 c.repo_groups = RepoGroup.groups_choices(groups=acl_groups)
191 192 c.repo_groups_choices = map(lambda k: unicode(k[0]), c.repo_groups)
192 193 choices, c.landing_revs = ScmModel().get_repo_landing_revs()
193 194
194 195 c.new_repo = repo_name_slug(new_repo)
195 196
196 197 ## apply the defaults from defaults page
197 198 defaults = Setting.get_default_repo_settings(strip_prefix=True)
198 199 if parent_group:
199 200 defaults.update({'repo_group': parent_group})
200 201
201 202 return htmlfill.render(
202 203 render('admin/repos/repo_add.html'),
203 204 defaults=defaults,
204 205 errors={},
205 206 prefix_error=False,
206 encoding="UTF-8"
207 )
207 encoding="UTF-8",
208 force_defaults=False)
208 209
209 210 @LoginRequired()
210 211 @NotAnonymous()
211 212 def repo_creating(self, repo_name):
212 213 c.repo = repo_name
213 214 c.task_id = request.GET.get('task_id')
214 215 if not c.repo:
215 216 raise HTTPNotFound()
216 217 return render('admin/repos/repo_creating.html')
217 218
218 219 @LoginRequired()
219 220 @NotAnonymous()
220 221 @jsonify
221 222 def repo_check(self, repo_name):
222 223 c.repo = repo_name
223 224 task_id = request.GET.get('task_id')
224 225
225 226 if task_id and task_id not in ['None']:
226 227 from kallithea import CELERY_ON
227 228 from celery.result import AsyncResult
228 229 if CELERY_ON:
229 230 task = AsyncResult(task_id)
230 231 if task.failed():
231 232 raise HTTPInternalServerError(task.traceback)
232 233
233 234 repo = Repository.get_by_repo_name(repo_name)
234 235 if repo and repo.repo_state == Repository.STATE_CREATED:
235 236 if repo.clone_uri:
236 237 clone_uri = repo.clone_uri_hidden
237 238 h.flash(_('Created repository %s from %s')
238 239 % (repo.repo_name, clone_uri), category='success')
239 240 else:
240 241 repo_url = h.link_to(repo.repo_name,
241 242 h.url('summary_home',
242 243 repo_name=repo.repo_name))
243 244 fork = repo.fork
244 245 if fork:
245 246 fork_name = fork.repo_name
246 247 h.flash(h.literal(_('Forked repository %s as %s')
247 248 % (fork_name, repo_url)), category='success')
248 249 else:
249 250 h.flash(h.literal(_('Created repository %s') % repo_url),
250 251 category='success')
251 252 return {'result': True}
252 253 return {'result': False}
253 254
254 255 @HasRepoPermissionAllDecorator('repository.admin')
255 256 def update(self, repo_name):
256 257 """
257 258 PUT /repos/repo_name: Update an existing item"""
258 259 # Forms posted to this method should contain a hidden field:
259 260 # <input type="hidden" name="_method" value="PUT" />
260 261 # Or using helpers:
261 262 # h.form(url('repo', repo_name=ID),
262 263 # method='put')
263 264 # url('repo', repo_name=ID)
264 265 c.repo_info = self._load_repo(repo_name)
265 266 c.active = 'settings'
266 267 c.repo_fields = RepositoryField.query()\
267 268 .filter(RepositoryField.repository == c.repo_info).all()
268 269 self.__load_defaults(c.repo_info)
269 270
270 271 repo_model = RepoModel()
271 272 changed_name = repo_name
272 273 #override the choices with extracted revisions !
273 274 choices, c.landing_revs = ScmModel().get_repo_landing_revs(repo_name)
274 275 c.landing_revs_choices = choices
275 276 repo = Repository.get_by_repo_name(repo_name)
276 277 old_data = {
277 278 'repo_name': repo_name,
278 279 'repo_group': repo.group.get_dict() if repo.group else {},
279 280 'repo_type': repo.repo_type,
280 281 }
281 282 _form = RepoForm(edit=True, old_data=old_data,
282 283 repo_groups=c.repo_groups_choices,
283 284 landing_revs=c.landing_revs_choices)()
284 285
285 286 try:
286 287 form_result = _form.to_python(dict(request.POST))
287 288 repo = repo_model.update(repo_name, **form_result)
288 289 ScmModel().mark_for_invalidation(repo_name)
289 290 h.flash(_('Repository %s updated successfully') % repo_name,
290 291 category='success')
291 292 changed_name = repo.repo_name
292 293 action_logger(self.authuser, 'admin_updated_repo',
293 294 changed_name, self.ip_addr, self.sa)
294 295 Session().commit()
295 296 except formencode.Invalid, errors:
296 297 defaults = self.__load_data(repo_name)
297 298 defaults.update(errors.value)
298 299 return htmlfill.render(
299 300 render('admin/repos/repo_edit.html'),
300 301 defaults=defaults,
301 302 errors=errors.error_dict or {},
302 303 prefix_error=False,
303 encoding="UTF-8")
304 encoding="UTF-8",
305 force_defaults=False)
304 306
305 307 except Exception:
306 308 log.error(traceback.format_exc())
307 309 h.flash(_('Error occurred during update of repository %s') \
308 310 % repo_name, category='error')
309 311 return redirect(url('edit_repo', repo_name=changed_name))
310 312
311 313 @HasRepoPermissionAllDecorator('repository.admin')
312 314 def delete(self, repo_name):
313 315 """
314 316 DELETE /repos/repo_name: Delete an existing item"""
315 317 # Forms posted to this method should contain a hidden field:
316 318 # <input type="hidden" name="_method" value="DELETE" />
317 319 # Or using helpers:
318 320 # h.form(url('repo', repo_name=ID),
319 321 # method='delete')
320 322 # url('repo', repo_name=ID)
321 323
322 324 repo_model = RepoModel()
323 325 repo = repo_model.get_by_repo_name(repo_name)
324 326 if not repo:
325 327 h.not_mapped_error(repo_name)
326 328 return redirect(url('repos'))
327 329 try:
328 330 _forks = repo.forks.count()
329 331 handle_forks = None
330 332 if _forks and request.POST.get('forks'):
331 333 do = request.POST['forks']
332 334 if do == 'detach_forks':
333 335 handle_forks = 'detach'
334 336 h.flash(_('Detached %s forks') % _forks, category='success')
335 337 elif do == 'delete_forks':
336 338 handle_forks = 'delete'
337 339 h.flash(_('Deleted %s forks') % _forks, category='success')
338 340 repo_model.delete(repo, forks=handle_forks)
339 341 action_logger(self.authuser, 'admin_deleted_repo',
340 342 repo_name, self.ip_addr, self.sa)
341 343 ScmModel().mark_for_invalidation(repo_name)
342 344 h.flash(_('Deleted repository %s') % repo_name, category='success')
343 345 Session().commit()
344 346 except AttachedForksError:
345 347 h.flash(_('Cannot delete %s it still contains attached forks')
346 348 % repo_name, category='warning')
347 349
348 350 except Exception:
349 351 log.error(traceback.format_exc())
350 352 h.flash(_('An error occurred during deletion of %s') % repo_name,
351 353 category='error')
352 354
353 355 if repo.group:
354 356 return redirect(url('repos_group_home', group_name=repo.group.group_name))
355 357 return redirect(url('repos'))
356 358
357 359 @HasPermissionAllDecorator('hg.admin')
358 360 def show(self, repo_name, format='html'):
359 361 """GET /repos/repo_name: Show a specific item"""
360 362 # url('repo', repo_name=ID)
361 363
362 364 @HasRepoPermissionAllDecorator('repository.admin')
363 365 def edit(self, repo_name):
364 366 """GET /repo_name/settings: Form to edit an existing item"""
365 367 # url('edit_repo', repo_name=ID)
366 368 defaults = self.__load_data(repo_name)
367 369 if 'clone_uri' in defaults:
368 370 del defaults['clone_uri']
369 371
370 372 c.repo_fields = RepositoryField.query()\
371 373 .filter(RepositoryField.repository == c.repo_info).all()
372 374 c.active = 'settings'
373 375 return htmlfill.render(
374 376 render('admin/repos/repo_edit.html'),
375 377 defaults=defaults,
376 378 encoding="UTF-8",
377 379 force_defaults=False)
378 380
379 381 @HasRepoPermissionAllDecorator('repository.admin')
380 382 def edit_permissions(self, repo_name):
381 383 """GET /repo_name/settings: Form to edit an existing item"""
382 384 # url('edit_repo', repo_name=ID)
383 385 c.repo_info = self._load_repo(repo_name)
384 386 repo_model = RepoModel()
385 387 c.users_array = repo_model.get_users_js()
386 388 c.user_groups_array = repo_model.get_user_groups_js()
387 389 c.active = 'permissions'
388 390 defaults = RepoModel()._get_defaults(repo_name)
389 391
390 392 return htmlfill.render(
391 393 render('admin/repos/repo_edit.html'),
392 394 defaults=defaults,
393 395 encoding="UTF-8",
394 396 force_defaults=False)
395 397
396 398 def edit_permissions_update(self, repo_name):
397 399 form = RepoPermsForm()().to_python(request.POST)
398 400 RepoModel()._update_permissions(repo_name, form['perms_new'],
399 401 form['perms_updates'])
400 402 #TODO: implement this
401 403 #action_logger(self.authuser, 'admin_changed_repo_permissions',
402 404 # repo_name, self.ip_addr, self.sa)
403 405 Session().commit()
404 406 h.flash(_('Repository permissions updated'), category='success')
405 407 return redirect(url('edit_repo_perms', repo_name=repo_name))
406 408
407 409 def edit_permissions_revoke(self, repo_name):
408 410 try:
409 411 obj_type = request.POST.get('obj_type')
410 412 obj_id = None
411 413 if obj_type == 'user':
412 414 obj_id = safe_int(request.POST.get('user_id'))
413 415 elif obj_type == 'user_group':
414 416 obj_id = safe_int(request.POST.get('user_group_id'))
415 417
416 418 if obj_type == 'user':
417 419 RepoModel().revoke_user_permission(repo=repo_name, user=obj_id)
418 420 elif obj_type == 'user_group':
419 421 RepoModel().revoke_user_group_permission(
420 422 repo=repo_name, group_name=obj_id
421 423 )
422 424 #TODO: implement this
423 425 #action_logger(self.authuser, 'admin_revoked_repo_permissions',
424 426 # repo_name, self.ip_addr, self.sa)
425 427 Session().commit()
426 428 except Exception:
427 429 log.error(traceback.format_exc())
428 430 h.flash(_('An error occurred during revoking of permission'),
429 431 category='error')
430 432 raise HTTPInternalServerError()
431 433
432 434 @HasRepoPermissionAllDecorator('repository.admin')
433 435 def edit_fields(self, repo_name):
434 436 """GET /repo_name/settings: Form to edit an existing item"""
435 437 # url('edit_repo', repo_name=ID)
436 438 c.repo_info = self._load_repo(repo_name)
437 439 c.repo_fields = RepositoryField.query()\
438 440 .filter(RepositoryField.repository == c.repo_info).all()
439 441 c.active = 'fields'
440 442 if request.POST:
441 443
442 444 return redirect(url('repo_edit_fields'))
443 445 return render('admin/repos/repo_edit.html')
444 446
445 447 @HasRepoPermissionAllDecorator('repository.admin')
446 448 def create_repo_field(self, repo_name):
447 449 try:
448 450 form_result = RepoFieldForm()().to_python(dict(request.POST))
449 451 new_field = RepositoryField()
450 452 new_field.repository = Repository.get_by_repo_name(repo_name)
451 453 new_field.field_key = form_result['new_field_key']
452 454 new_field.field_type = form_result['new_field_type'] # python type
453 455 new_field.field_value = form_result['new_field_value'] # set initial blank value
454 456 new_field.field_desc = form_result['new_field_desc']
455 457 new_field.field_label = form_result['new_field_label']
456 458 Session().add(new_field)
457 459 Session().commit()
458 460 except Exception, e:
459 461 log.error(traceback.format_exc())
460 462 msg = _('An error occurred during creation of field')
461 463 if isinstance(e, formencode.Invalid):
462 464 msg += ". " + e.msg
463 465 h.flash(msg, category='error')
464 466 return redirect(url('edit_repo_fields', repo_name=repo_name))
465 467
466 468 @HasRepoPermissionAllDecorator('repository.admin')
467 469 def delete_repo_field(self, repo_name, field_id):
468 470 field = RepositoryField.get_or_404(field_id)
469 471 try:
470 472 Session().delete(field)
471 473 Session().commit()
472 474 except Exception, e:
473 475 log.error(traceback.format_exc())
474 476 msg = _('An error occurred during removal of field')
475 477 h.flash(msg, category='error')
476 478 return redirect(url('edit_repo_fields', repo_name=repo_name))
477 479
478 480 @HasRepoPermissionAllDecorator('repository.admin')
479 481 def edit_advanced(self, repo_name):
480 482 """GET /repo_name/settings: Form to edit an existing item"""
481 483 # url('edit_repo', repo_name=ID)
482 484 c.repo_info = self._load_repo(repo_name)
483 485 c.default_user_id = User.get_default_user().user_id
484 486 c.in_public_journal = UserFollowing.query()\
485 487 .filter(UserFollowing.user_id == c.default_user_id)\
486 488 .filter(UserFollowing.follows_repository == c.repo_info).scalar()
487 489
488 490 _repos = Repository.query().order_by(Repository.repo_name).all()
489 491 read_access_repos = RepoList(_repos)
490 492 c.repos_list = [(None, _('-- Not a fork --'))]
491 493 c.repos_list += [(x.repo_id, x.repo_name)
492 494 for x in read_access_repos
493 495 if x.repo_id != c.repo_info.repo_id]
494 496
495 497 defaults = {
496 498 'id_fork_of': c.repo_info.fork.repo_id if c.repo_info.fork else ''
497 499 }
498 500
499 501 c.active = 'advanced'
500 502 if request.POST:
501 503 return redirect(url('repo_edit_advanced'))
502 504 return htmlfill.render(
503 505 render('admin/repos/repo_edit.html'),
504 506 defaults=defaults,
505 507 encoding="UTF-8",
506 508 force_defaults=False)
507 509
508 510 @HasRepoPermissionAllDecorator('repository.admin')
509 511 def edit_advanced_journal(self, repo_name):
510 512 """
511 513 Sets this repository to be visible in public journal,
512 514 in other words asking default user to follow this repo
513 515
514 516 :param repo_name:
515 517 """
516 518
517 519 cur_token = request.POST.get('auth_token')
518 520 token = get_token()
519 521 if cur_token == token:
520 522 try:
521 523 repo_id = Repository.get_by_repo_name(repo_name).repo_id
522 524 user_id = User.get_default_user().user_id
523 525 self.scm_model.toggle_following_repo(repo_id, user_id)
524 526 h.flash(_('Updated repository visibility in public journal'),
525 527 category='success')
526 528 Session().commit()
527 529 except Exception:
528 530 h.flash(_('An error occurred during setting this'
529 531 ' repository in public journal'),
530 532 category='error')
531 533
532 534 else:
533 535 h.flash(_('Token mismatch'), category='error')
534 536 return redirect(url('edit_repo_advanced', repo_name=repo_name))
535 537
536 538
537 539 @HasRepoPermissionAllDecorator('repository.admin')
538 540 def edit_advanced_fork(self, repo_name):
539 541 """
540 542 Mark given repository as a fork of another
541 543
542 544 :param repo_name:
543 545 """
544 546 try:
545 547 fork_id = request.POST.get('id_fork_of')
546 548 repo = ScmModel().mark_as_fork(repo_name, fork_id,
547 549 self.authuser.username)
548 550 fork = repo.fork.repo_name if repo.fork else _('Nothing')
549 551 Session().commit()
550 552 h.flash(_('Marked repo %s as fork of %s') % (repo_name, fork),
551 553 category='success')
552 554 except RepositoryError, e:
553 555 log.error(traceback.format_exc())
554 556 h.flash(str(e), category='error')
555 557 except Exception, e:
556 558 log.error(traceback.format_exc())
557 559 h.flash(_('An error occurred during this operation'),
558 560 category='error')
559 561
560 562 return redirect(url('edit_repo_advanced', repo_name=repo_name))
561 563
562 564 @HasRepoPermissionAllDecorator('repository.admin')
563 565 def edit_advanced_locking(self, repo_name):
564 566 """
565 567 Unlock repository when it is locked !
566 568
567 569 :param repo_name:
568 570 """
569 571 try:
570 572 repo = Repository.get_by_repo_name(repo_name)
571 573 if request.POST.get('set_lock'):
572 574 Repository.lock(repo, c.authuser.user_id)
573 575 h.flash(_('Locked repository'), category='success')
574 576 elif request.POST.get('set_unlock'):
575 577 Repository.unlock(repo)
576 578 h.flash(_('Unlocked repository'), category='success')
577 579 except Exception, e:
578 580 log.error(traceback.format_exc())
579 581 h.flash(_('An error occurred during unlocking'),
580 582 category='error')
581 583 return redirect(url('edit_repo_advanced', repo_name=repo_name))
582 584
583 585 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
584 586 def toggle_locking(self, repo_name):
585 587 """
586 588 Toggle locking of repository by simple GET call to url
587 589
588 590 :param repo_name:
589 591 """
590 592
591 593 try:
592 594 repo = Repository.get_by_repo_name(repo_name)
593 595
594 596 if repo.enable_locking:
595 597 if repo.locked[0]:
596 598 Repository.unlock(repo)
597 599 action = _('Unlocked')
598 600 else:
599 601 Repository.lock(repo, c.authuser.user_id)
600 602 action = _('Locked')
601 603
602 604 h.flash(_('Repository has been %s') % action,
603 605 category='success')
604 606 except Exception, e:
605 607 log.error(traceback.format_exc())
606 608 h.flash(_('An error occurred during unlocking'),
607 609 category='error')
608 610 return redirect(url('summary_home', repo_name=repo_name))
609 611
610 612 @HasRepoPermissionAllDecorator('repository.admin')
611 613 def edit_caches(self, repo_name):
612 614 """GET /repo_name/settings: Form to edit an existing item"""
613 615 # url('edit_repo', repo_name=ID)
614 616 c.repo_info = self._load_repo(repo_name)
615 617 c.active = 'caches'
616 618 if request.POST:
617 619 try:
618 620 ScmModel().mark_for_invalidation(repo_name, delete=True)
619 621 Session().commit()
620 622 h.flash(_('Cache invalidation successful'),
621 623 category='success')
622 624 except Exception, e:
623 625 log.error(traceback.format_exc())
624 626 h.flash(_('An error occurred during cache invalidation'),
625 627 category='error')
626 628
627 629 return redirect(url('edit_repo_caches', repo_name=c.repo_name))
628 630 return render('admin/repos/repo_edit.html')
629 631
630 632 @HasRepoPermissionAllDecorator('repository.admin')
631 633 def edit_remote(self, repo_name):
632 634 """GET /repo_name/settings: Form to edit an existing item"""
633 635 # url('edit_repo', repo_name=ID)
634 636 c.repo_info = self._load_repo(repo_name)
635 637 c.active = 'remote'
636 638 if request.POST:
637 639 try:
638 640 ScmModel().pull_changes(repo_name, self.authuser.username)
639 641 h.flash(_('Pulled from remote location'), category='success')
640 642 except Exception, e:
641 643 log.error(traceback.format_exc())
642 644 h.flash(_('An error occurred during pull from remote location'),
643 645 category='error')
644 646 return redirect(url('edit_repo_remote', repo_name=c.repo_name))
645 647 return render('admin/repos/repo_edit.html')
646 648
647 649 @HasRepoPermissionAllDecorator('repository.admin')
648 650 def edit_statistics(self, repo_name):
649 651 """GET /repo_name/settings: Form to edit an existing item"""
650 652 # url('edit_repo', repo_name=ID)
651 653 c.repo_info = self._load_repo(repo_name)
652 654 repo = c.repo_info.scm_instance
653 655
654 656 if c.repo_info.stats:
655 657 # this is on what revision we ended up so we add +1 for count
656 658 last_rev = c.repo_info.stats.stat_on_revision + 1
657 659 else:
658 660 last_rev = 0
659 661 c.stats_revision = last_rev
660 662
661 663 c.repo_last_rev = repo.count() if repo.revisions else 0
662 664
663 665 if last_rev == 0 or c.repo_last_rev == 0:
664 666 c.stats_percentage = 0
665 667 else:
666 668 c.stats_percentage = '%.2f' % ((float((last_rev)) / c.repo_last_rev) * 100)
667 669
668 670 c.active = 'statistics'
669 671 if request.POST:
670 672 try:
671 673 RepoModel().delete_stats(repo_name)
672 674 Session().commit()
673 675 except Exception, e:
674 676 log.error(traceback.format_exc())
675 677 h.flash(_('An error occurred during deletion of repository stats'),
676 678 category='error')
677 679 return redirect(url('edit_repo_statistics', repo_name=c.repo_name))
678 680
679 681 return render('admin/repos/repo_edit.html')
@@ -1,524 +1,525 b''
1 1 # -*- coding: utf-8 -*-
2 2 # This program is free software: you can redistribute it and/or modify
3 3 # it under the terms of the GNU General Public License as published by
4 4 # the Free Software Foundation, either version 3 of the License, or
5 5 # (at your option) any later version.
6 6 #
7 7 # This program is distributed in the hope that it will be useful,
8 8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 10 # GNU General Public License for more details.
11 11 #
12 12 # You should have received a copy of the GNU General Public License
13 13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
14 14 """
15 15 kallithea.controllers.admin.settings
16 16 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
17 17
18 18 settings controller for Kallithea admin
19 19
20 20 This file was forked by the Kallithea project in July 2014.
21 21 Original author and date, and relevant copyright and licensing information is below:
22 22 :created_on: Jul 14, 2010
23 23 :author: marcink
24 24 :copyright: (c) 2013 RhodeCode GmbH, and others.
25 25 :license: GPLv3, see LICENSE.md for more details.
26 26 """
27 27
28 28 import logging
29 29 import traceback
30 30 import formencode
31 31
32 32 from formencode import htmlfill
33 33 from pylons import request, tmpl_context as c, url, config
34 34 from pylons.controllers.util import redirect
35 35 from pylons.i18n.translation import _
36 36
37 37 from kallithea.lib import helpers as h
38 38 from kallithea.lib.auth import LoginRequired, HasPermissionAllDecorator
39 39 from kallithea.lib.base import BaseController, render
40 40 from kallithea.lib.celerylib import tasks, run_task
41 41 from kallithea.lib.exceptions import HgsubversionImportError
42 42 from kallithea.lib.utils import repo2db_mapper, set_app_settings
43 43 from kallithea.model.db import Ui, Repository, Setting
44 44 from kallithea.model.forms import ApplicationSettingsForm, \
45 45 ApplicationUiSettingsForm, ApplicationVisualisationForm
46 46 from kallithea.model.scm import ScmModel
47 47 from kallithea.model.notification import EmailNotificationModel
48 48 from kallithea.model.meta import Session
49 49 from kallithea.lib.utils2 import str2bool, safe_unicode
50 50 log = logging.getLogger(__name__)
51 51
52 52
53 53 class SettingsController(BaseController):
54 54 """REST Controller styled on the Atom Publishing Protocol"""
55 55 # To properly map this controller, ensure your config/routing.py
56 56 # file has a resource setup:
57 57 # map.resource('setting', 'settings', controller='admin/settings',
58 58 # path_prefix='/admin', name_prefix='admin_')
59 59
60 60 @LoginRequired()
61 61 def __before__(self):
62 62 super(SettingsController, self).__before__()
63 63
64 64 def _get_hg_ui_settings(self):
65 65 ret = Ui.query().all()
66 66
67 67 if not ret:
68 68 raise Exception('Could not get application ui settings !')
69 69 settings = {}
70 70 for each in ret:
71 71 k = each.ui_key
72 72 v = each.ui_value
73 73 if k == '/':
74 74 k = 'root_path'
75 75
76 76 if k == 'push_ssl':
77 77 v = str2bool(v)
78 78
79 79 if k.find('.') != -1:
80 80 k = k.replace('.', '_')
81 81
82 82 if each.ui_section in ['hooks', 'extensions']:
83 83 v = each.ui_active
84 84
85 85 settings[each.ui_section + '_' + k] = v
86 86 return settings
87 87
88 88 @HasPermissionAllDecorator('hg.admin')
89 89 def settings_vcs(self):
90 90 """GET /admin/settings: All items in the collection"""
91 91 # url('admin_settings')
92 92 c.active = 'vcs'
93 93 if request.POST:
94 94 application_form = ApplicationUiSettingsForm()()
95 95 try:
96 96 form_result = application_form.to_python(dict(request.POST))
97 97 except formencode.Invalid, errors:
98 98 return htmlfill.render(
99 99 render('admin/settings/settings.html'),
100 100 defaults=errors.value,
101 101 errors=errors.error_dict or {},
102 102 prefix_error=False,
103 encoding="UTF-8"
104 )
103 encoding="UTF-8",
104 force_defaults=False)
105 105
106 106 try:
107 107 sett = Ui.get_by_key('push_ssl')
108 108 sett.ui_value = form_result['web_push_ssl']
109 109 Session().add(sett)
110 110 if c.visual.allow_repo_location_change:
111 111 sett = Ui.get_by_key('/')
112 112 sett.ui_value = form_result['paths_root_path']
113 113 Session().add(sett)
114 114
115 115 #HOOKS
116 116 sett = Ui.get_by_key(Ui.HOOK_UPDATE)
117 117 sett.ui_active = form_result['hooks_changegroup_update']
118 118 Session().add(sett)
119 119
120 120 sett = Ui.get_by_key(Ui.HOOK_REPO_SIZE)
121 121 sett.ui_active = form_result['hooks_changegroup_repo_size']
122 122 Session().add(sett)
123 123
124 124 sett = Ui.get_by_key(Ui.HOOK_PUSH)
125 125 sett.ui_active = form_result['hooks_changegroup_push_logger']
126 126 Session().add(sett)
127 127
128 128 sett = Ui.get_by_key(Ui.HOOK_PULL)
129 129 sett.ui_active = form_result['hooks_outgoing_pull_logger']
130 130
131 131 Session().add(sett)
132 132
133 133 ## EXTENSIONS
134 134 sett = Ui.get_by_key('largefiles')
135 135 if not sett:
136 136 #make one if it's not there !
137 137 sett = Ui()
138 138 sett.ui_key = 'largefiles'
139 139 sett.ui_section = 'extensions'
140 140 sett.ui_active = form_result['extensions_largefiles']
141 141 Session().add(sett)
142 142
143 143 sett = Ui.get_by_key('hgsubversion')
144 144 if not sett:
145 145 #make one if it's not there !
146 146 sett = Ui()
147 147 sett.ui_key = 'hgsubversion'
148 148 sett.ui_section = 'extensions'
149 149
150 150 sett.ui_active = form_result['extensions_hgsubversion']
151 151 if sett.ui_active:
152 152 try:
153 153 import hgsubversion # pragma: no cover
154 154 except ImportError:
155 155 raise HgsubversionImportError
156 156 Session().add(sett)
157 157
158 158 # sett = Ui.get_by_key('hggit')
159 159 # if not sett:
160 160 # #make one if it's not there !
161 161 # sett = Ui()
162 162 # sett.ui_key = 'hggit'
163 163 # sett.ui_section = 'extensions'
164 164 #
165 165 # sett.ui_active = form_result['extensions_hggit']
166 166 # Session().add(sett)
167 167
168 168 Session().commit()
169 169
170 170 h.flash(_('Updated VCS settings'), category='success')
171 171
172 172 except HgsubversionImportError:
173 173 log.error(traceback.format_exc())
174 174 h.flash(_('Unable to activate hgsubversion support. '
175 175 'The "hgsubversion" library is missing'),
176 176 category='error')
177 177
178 178 except Exception:
179 179 log.error(traceback.format_exc())
180 180 h.flash(_('Error occurred during updating '
181 181 'application settings'), category='error')
182 182
183 183 defaults = Setting.get_app_settings()
184 184 defaults.update(self._get_hg_ui_settings())
185 185
186 186 return htmlfill.render(
187 187 render('admin/settings/settings.html'),
188 188 defaults=defaults,
189 189 encoding="UTF-8",
190 190 force_defaults=False)
191 191
192 192 @HasPermissionAllDecorator('hg.admin')
193 193 def settings_mapping(self):
194 194 """GET /admin/settings/mapping: All items in the collection"""
195 195 # url('admin_settings_mapping')
196 196 c.active = 'mapping'
197 197 if request.POST:
198 198 rm_obsolete = request.POST.get('destroy', False)
199 199 install_git_hooks = request.POST.get('hooks', False)
200 200 invalidate_cache = request.POST.get('invalidate', False)
201 201 log.debug('rescanning repo location with destroy obsolete=%s and '
202 202 'install git hooks=%s' % (rm_obsolete,install_git_hooks))
203 203
204 204 if invalidate_cache:
205 205 log.debug('invalidating all repositories cache')
206 206 for repo in Repository.get_all():
207 207 ScmModel().mark_for_invalidation(repo.repo_name, delete=True)
208 208
209 209 filesystem_repos = ScmModel().repo_scan()
210 210 added, removed = repo2db_mapper(filesystem_repos, rm_obsolete,
211 211 install_git_hook=install_git_hooks,
212 212 user=c.authuser.username)
213 213 h.flash(h.literal(_('Repositories successfully rescanned. Added: %s. Removed: %s.') %
214 214 (', '.join(h.link_to(safe_unicode(repo_name), h.url('summary_home', repo_name=repo_name))
215 215 for repo_name in added) or '-',
216 216 ', '.join(h.escape(safe_unicode(repo_name)) for repo_name in removed) or '-')),
217 217 category='success')
218 218 return redirect(url('admin_settings_mapping'))
219 219
220 220 defaults = Setting.get_app_settings()
221 221 defaults.update(self._get_hg_ui_settings())
222 222
223 223 return htmlfill.render(
224 224 render('admin/settings/settings.html'),
225 225 defaults=defaults,
226 226 encoding="UTF-8",
227 227 force_defaults=False)
228 228
229 229 @HasPermissionAllDecorator('hg.admin')
230 230 def settings_global(self):
231 231 """GET /admin/settings/global: All items in the collection"""
232 232 # url('admin_settings_global')
233 233 c.active = 'global'
234 234 if request.POST:
235 235 application_form = ApplicationSettingsForm()()
236 236 try:
237 237 form_result = application_form.to_python(dict(request.POST))
238 238 except formencode.Invalid, errors:
239 239 return htmlfill.render(
240 240 render('admin/settings/settings.html'),
241 241 defaults=errors.value,
242 242 errors=errors.error_dict or {},
243 243 prefix_error=False,
244 encoding="UTF-8")
244 encoding="UTF-8",
245 force_defaults=False)
245 246
246 247 try:
247 248 sett1 = Setting.create_or_update('title',
248 249 form_result['title'])
249 250 Session().add(sett1)
250 251
251 252 sett2 = Setting.create_or_update('realm',
252 253 form_result['realm'])
253 254 Session().add(sett2)
254 255
255 256 sett3 = Setting.create_or_update('ga_code',
256 257 form_result['ga_code'])
257 258 Session().add(sett3)
258 259
259 260 sett4 = Setting.create_or_update('captcha_public_key',
260 261 form_result['captcha_public_key'])
261 262 Session().add(sett4)
262 263
263 264 sett5 = Setting.create_or_update('captcha_private_key',
264 265 form_result['captcha_private_key'])
265 266 Session().add(sett5)
266 267
267 268 Session().commit()
268 269 set_app_settings(config)
269 270 h.flash(_('Updated application settings'), category='success')
270 271
271 272 except Exception:
272 273 log.error(traceback.format_exc())
273 274 h.flash(_('Error occurred during updating '
274 275 'application settings'),
275 276 category='error')
276 277
277 278 return redirect(url('admin_settings_global'))
278 279
279 280 defaults = Setting.get_app_settings()
280 281 defaults.update(self._get_hg_ui_settings())
281 282
282 283 return htmlfill.render(
283 284 render('admin/settings/settings.html'),
284 285 defaults=defaults,
285 286 encoding="UTF-8",
286 287 force_defaults=False)
287 288
288 289 @HasPermissionAllDecorator('hg.admin')
289 290 def settings_visual(self):
290 291 """GET /admin/settings/visual: All items in the collection"""
291 292 # url('admin_settings_visual')
292 293 c.active = 'visual'
293 294 if request.POST:
294 295 application_form = ApplicationVisualisationForm()()
295 296 try:
296 297 form_result = application_form.to_python(dict(request.POST))
297 298 except formencode.Invalid, errors:
298 299 return htmlfill.render(
299 300 render('admin/settings/settings.html'),
300 301 defaults=errors.value,
301 302 errors=errors.error_dict or {},
302 303 prefix_error=False,
303 encoding="UTF-8"
304 )
304 encoding="UTF-8",
305 force_defaults=False)
305 306
306 307 try:
307 308 settings = [
308 309 ('show_public_icon', 'show_public_icon', 'bool'),
309 310 ('show_private_icon', 'show_private_icon', 'bool'),
310 311 ('stylify_metatags', 'stylify_metatags', 'bool'),
311 312 ('repository_fields', 'repository_fields', 'bool'),
312 313 ('dashboard_items', 'dashboard_items', 'int'),
313 314 ('admin_grid_items', 'admin_grid_items', 'int'),
314 315 ('show_version', 'show_version', 'bool'),
315 316 ('use_gravatar', 'use_gravatar', 'bool'),
316 317 ('gravatar_url', 'gravatar_url', 'unicode'),
317 318 ('clone_uri_tmpl', 'clone_uri_tmpl', 'unicode'),
318 319 ]
319 320 for setting, form_key, type_ in settings:
320 321 sett = Setting.create_or_update(setting,
321 322 form_result[form_key], type_)
322 323 Session().add(sett)
323 324
324 325 Session().commit()
325 326 set_app_settings(config)
326 327 h.flash(_('Updated visualisation settings'),
327 328 category='success')
328 329
329 330 except Exception:
330 331 log.error(traceback.format_exc())
331 332 h.flash(_('Error occurred during updating '
332 333 'visualisation settings'),
333 334 category='error')
334 335
335 336 return redirect(url('admin_settings_visual'))
336 337
337 338 defaults = Setting.get_app_settings()
338 339 defaults.update(self._get_hg_ui_settings())
339 340
340 341 return htmlfill.render(
341 342 render('admin/settings/settings.html'),
342 343 defaults=defaults,
343 344 encoding="UTF-8",
344 345 force_defaults=False)
345 346
346 347 @HasPermissionAllDecorator('hg.admin')
347 348 def settings_email(self):
348 349 """GET /admin/settings/email: All items in the collection"""
349 350 # url('admin_settings_email')
350 351 c.active = 'email'
351 352 if request.POST:
352 353 test_email = request.POST.get('test_email')
353 354 test_email_subj = 'Kallithea test email'
354 355 test_body = ('Kallithea Email test, '
355 356 'Kallithea version: %s' % c.kallithea_version)
356 357 if not test_email:
357 358 h.flash(_('Please enter email address'), category='error')
358 359 return redirect(url('admin_settings_email'))
359 360
360 361 test_email_txt_body = EmailNotificationModel()\
361 362 .get_email_tmpl(EmailNotificationModel.TYPE_DEFAULT,
362 363 'txt', body=test_body)
363 364 test_email_html_body = EmailNotificationModel()\
364 365 .get_email_tmpl(EmailNotificationModel.TYPE_DEFAULT,
365 366 'html', body=test_body)
366 367
367 368 recipients = [test_email] if test_email else None
368 369
369 370 run_task(tasks.send_email, recipients, test_email_subj,
370 371 test_email_txt_body, test_email_html_body)
371 372
372 373 h.flash(_('Send email task created'), category='success')
373 374 return redirect(url('admin_settings_email'))
374 375
375 376 defaults = Setting.get_app_settings()
376 377 defaults.update(self._get_hg_ui_settings())
377 378
378 379 import kallithea
379 380 c.ini = kallithea.CONFIG
380 381
381 382 return htmlfill.render(
382 383 render('admin/settings/settings.html'),
383 384 defaults=defaults,
384 385 encoding="UTF-8",
385 386 force_defaults=False)
386 387
387 388 @HasPermissionAllDecorator('hg.admin')
388 389 def settings_hooks(self):
389 390 """GET /admin/settings/hooks: All items in the collection"""
390 391 # url('admin_settings_hooks')
391 392 c.active = 'hooks'
392 393 if request.POST:
393 394 if c.visual.allow_custom_hooks_settings:
394 395 ui_key = request.POST.get('new_hook_ui_key')
395 396 ui_value = request.POST.get('new_hook_ui_value')
396 397
397 398 hook_id = request.POST.get('hook_id')
398 399
399 400 try:
400 401 ui_key = ui_key and ui_key.strip()
401 402 if ui_value and ui_key:
402 403 Ui.create_or_update_hook(ui_key, ui_value)
403 404 h.flash(_('Added new hook'), category='success')
404 405 elif hook_id:
405 406 Ui.delete(hook_id)
406 407 Session().commit()
407 408
408 409 # check for edits
409 410 update = False
410 411 _d = request.POST.dict_of_lists()
411 412 for k, v in zip(_d.get('hook_ui_key', []),
412 413 _d.get('hook_ui_value_new', [])):
413 414 Ui.create_or_update_hook(k, v)
414 415 update = True
415 416
416 417 if update:
417 418 h.flash(_('Updated hooks'), category='success')
418 419 Session().commit()
419 420 except Exception:
420 421 log.error(traceback.format_exc())
421 422 h.flash(_('Error occurred during hook creation'),
422 423 category='error')
423 424
424 425 return redirect(url('admin_settings_hooks'))
425 426
426 427 defaults = Setting.get_app_settings()
427 428 defaults.update(self._get_hg_ui_settings())
428 429
429 430 c.hooks = Ui.get_builtin_hooks()
430 431 c.custom_hooks = Ui.get_custom_hooks()
431 432
432 433 return htmlfill.render(
433 434 render('admin/settings/settings.html'),
434 435 defaults=defaults,
435 436 encoding="UTF-8",
436 437 force_defaults=False)
437 438
438 439 @HasPermissionAllDecorator('hg.admin')
439 440 def settings_search(self):
440 441 """GET /admin/settings/search: All items in the collection"""
441 442 # url('admin_settings_search')
442 443 c.active = 'search'
443 444 if request.POST:
444 445 repo_location = self._get_hg_ui_settings()['paths_root_path']
445 446 full_index = request.POST.get('full_index', False)
446 447 run_task(tasks.whoosh_index, repo_location, full_index)
447 448 h.flash(_('Whoosh reindex task scheduled'), category='success')
448 449 return redirect(url('admin_settings_search'))
449 450
450 451 defaults = Setting.get_app_settings()
451 452 defaults.update(self._get_hg_ui_settings())
452 453
453 454 return htmlfill.render(
454 455 render('admin/settings/settings.html'),
455 456 defaults=defaults,
456 457 encoding="UTF-8",
457 458 force_defaults=False)
458 459
459 460 @HasPermissionAllDecorator('hg.admin')
460 461 def settings_system(self):
461 462 """GET /admin/settings/system: All items in the collection"""
462 463 # url('admin_settings_system')
463 464 c.active = 'system'
464 465
465 466 defaults = Setting.get_app_settings()
466 467 defaults.update(self._get_hg_ui_settings())
467 468
468 469 import kallithea
469 470 c.ini = kallithea.CONFIG
470 471 c.update_url = defaults.get('update_url')
471 472 server_info = Setting.get_server_info()
472 473 for key, val in server_info.iteritems():
473 474 setattr(c, key, val)
474 475
475 476 return htmlfill.render(
476 477 render('admin/settings/settings.html'),
477 478 defaults=defaults,
478 479 encoding="UTF-8",
479 480 force_defaults=False)
480 481
481 482 @HasPermissionAllDecorator('hg.admin')
482 483 def settings_system_update(self):
483 484 """GET /admin/settings/system/updates: All items in the collection"""
484 485 # url('admin_settings_system_update')
485 486 import json
486 487 import urllib2
487 488 from kallithea.lib.verlib import NormalizedVersion
488 489 from kallithea import __version__
489 490
490 491 defaults = Setting.get_app_settings()
491 492 defaults.update(self._get_hg_ui_settings())
492 493 _update_url = defaults.get('update_url', '')
493 494 _update_url = "" # FIXME: disabled
494 495
495 496 _err = lambda s: '<div style="color:#ff8888; padding:4px 0px">%s</div>' % (s)
496 497 try:
497 498 import kallithea
498 499 ver = kallithea.__version__
499 500 log.debug('Checking for upgrade on `%s` server' % _update_url)
500 501 opener = urllib2.build_opener()
501 502 opener.addheaders = [('User-agent', 'Kallithea-SCM/%s' % ver)]
502 503 response = opener.open(_update_url)
503 504 response_data = response.read()
504 505 data = json.loads(response_data)
505 506 except urllib2.URLError, e:
506 507 log.error(traceback.format_exc())
507 508 return _err('Failed to contact upgrade server: %r' % e)
508 509 except ValueError, e:
509 510 log.error(traceback.format_exc())
510 511 return _err('Bad data sent from update server')
511 512
512 513 latest = data['versions'][0]
513 514
514 515 c.update_url = _update_url
515 516 c.latest_data = latest
516 517 c.latest_ver = latest['version']
517 518 c.cur_ver = __version__
518 519 c.should_upgrade = False
519 520
520 521 if NormalizedVersion(c.latest_ver) > NormalizedVersion(c.cur_ver):
521 522 c.should_upgrade = True
522 523 c.important_notices = latest['general']
523 524
524 525 return render('admin/settings/settings_system_update.html'),
@@ -1,464 +1,466 b''
1 1 # -*- coding: utf-8 -*-
2 2 # This program is free software: you can redistribute it and/or modify
3 3 # it under the terms of the GNU General Public License as published by
4 4 # the Free Software Foundation, either version 3 of the License, or
5 5 # (at your option) any later version.
6 6 #
7 7 # This program is distributed in the hope that it will be useful,
8 8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 10 # GNU General Public License for more details.
11 11 #
12 12 # You should have received a copy of the GNU General Public License
13 13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
14 14 """
15 15 kallithea.controllers.admin.users_groups
16 16 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
17 17
18 18 User Groups crud controller for pylons
19 19
20 20 This file was forked by the Kallithea project in July 2014.
21 21 Original author and date, and relevant copyright and licensing information is below:
22 22 :created_on: Jan 25, 2011
23 23 :author: marcink
24 24 :copyright: (c) 2013 RhodeCode GmbH, and others.
25 25 :license: GPLv3, see LICENSE.md for more details.
26 26 """
27 27
28 28 import logging
29 29 import traceback
30 30 import formencode
31 31
32 32 from formencode import htmlfill
33 33 from pylons import request, tmpl_context as c, url, config
34 34 from pylons.controllers.util import redirect
35 35 from pylons.i18n.translation import _
36 36
37 37 from sqlalchemy.orm import joinedload
38 38 from sqlalchemy.sql.expression import func
39 39 from webob.exc import HTTPInternalServerError
40 40
41 41 import kallithea
42 42 from kallithea.lib import helpers as h
43 43 from kallithea.lib.exceptions import UserGroupsAssignedException,\
44 44 RepoGroupAssignmentError
45 45 from kallithea.lib.utils2 import safe_unicode, safe_int
46 46 from kallithea.lib.auth import LoginRequired, HasPermissionAllDecorator,\
47 47 HasUserGroupPermissionAnyDecorator, HasPermissionAnyDecorator
48 48 from kallithea.lib.base import BaseController, render
49 49 from kallithea.model.scm import UserGroupList
50 50 from kallithea.model.user_group import UserGroupModel
51 51 from kallithea.model.repo import RepoModel
52 52 from kallithea.model.db import User, UserGroup, UserGroupToPerm,\
53 53 UserGroupRepoToPerm, UserGroupRepoGroupToPerm
54 54 from kallithea.model.forms import UserGroupForm, UserGroupPermsForm,\
55 55 CustomDefaultPermissionsForm
56 56 from kallithea.model.meta import Session
57 57 from kallithea.lib.utils import action_logger
58 58 from kallithea.lib.compat import json
59 59
60 60 log = logging.getLogger(__name__)
61 61
62 62
63 63 class UserGroupsController(BaseController):
64 64 """REST Controller styled on the Atom Publishing Protocol"""
65 65
66 66 @LoginRequired()
67 67 def __before__(self):
68 68 super(UserGroupsController, self).__before__()
69 69 c.available_permissions = config['available_permissions']
70 70
71 71 def __load_data(self, user_group_id):
72 72 c.group_members_obj = sorted((x.user for x in c.user_group.members),
73 73 key=lambda u: u.username.lower())
74 74
75 75 c.group_members = [(x.user_id, x.username) for x in c.group_members_obj]
76 76 c.available_members = sorted(((x.user_id, x.username) for x in
77 77 User.query().all()),
78 78 key=lambda u: u[1].lower())
79 79
80 80 def __load_defaults(self, user_group_id):
81 81 """
82 82 Load defaults settings for edit, and update
83 83
84 84 :param user_group_id:
85 85 """
86 86 user_group = UserGroup.get_or_404(user_group_id)
87 87 data = user_group.get_dict()
88 88 return data
89 89
90 90 def index(self, format='html'):
91 91 """GET /users_groups: All items in the collection"""
92 92 # url('users_groups')
93 93 _list = UserGroup.query()\
94 94 .order_by(func.lower(UserGroup.users_group_name))\
95 95 .all()
96 96 group_iter = UserGroupList(_list, perm_set=['usergroup.admin'])
97 97 user_groups_data = []
98 98 total_records = len(group_iter)
99 99 _tmpl_lookup = kallithea.CONFIG['pylons.app_globals'].mako_lookup
100 100 template = _tmpl_lookup.get_template('data_table/_dt_elements.html')
101 101
102 102 user_group_name = lambda user_group_id, user_group_name: (
103 103 template.get_def("user_group_name")
104 104 .render(user_group_id, user_group_name, _=_, h=h, c=c)
105 105 )
106 106 user_group_actions = lambda user_group_id, user_group_name: (
107 107 template.get_def("user_group_actions")
108 108 .render(user_group_id, user_group_name, _=_, h=h, c=c)
109 109 )
110 110 for user_gr in group_iter:
111 111
112 112 user_groups_data.append({
113 113 "raw_name": user_gr.users_group_name,
114 114 "group_name": user_group_name(user_gr.users_group_id,
115 115 user_gr.users_group_name),
116 116 "desc": user_gr.user_group_description,
117 117 "members": len(user_gr.members),
118 118 "active": h.boolicon(user_gr.users_group_active),
119 119 "owner": h.person(user_gr.user.username),
120 120 "action": user_group_actions(user_gr.users_group_id, user_gr.users_group_name)
121 121 })
122 122
123 123 c.data = json.dumps({
124 124 "totalRecords": total_records,
125 125 "startIndex": 0,
126 126 "sort": None,
127 127 "dir": "asc",
128 128 "records": user_groups_data
129 129 })
130 130
131 131 return render('admin/user_groups/user_groups.html')
132 132
133 133 @HasPermissionAnyDecorator('hg.admin', 'hg.usergroup.create.true')
134 134 def create(self):
135 135 """POST /users_groups: Create a new item"""
136 136 # url('users_groups')
137 137
138 138 users_group_form = UserGroupForm()()
139 139 try:
140 140 form_result = users_group_form.to_python(dict(request.POST))
141 141 ug = UserGroupModel().create(name=form_result['users_group_name'],
142 142 description=form_result['user_group_description'],
143 143 owner=self.authuser.user_id,
144 144 active=form_result['users_group_active'])
145 145
146 146 gr = form_result['users_group_name']
147 147 action_logger(self.authuser,
148 148 'admin_created_users_group:%s' % gr,
149 149 None, self.ip_addr, self.sa)
150 150 h.flash(h.literal(_('Created user group %s') % h.link_to(h.escape(gr), url('edit_users_group', id=ug.users_group_id))),
151 151 category='success')
152 152 Session().commit()
153 153 except formencode.Invalid, errors:
154 154 return htmlfill.render(
155 155 render('admin/user_groups/user_group_add.html'),
156 156 defaults=errors.value,
157 157 errors=errors.error_dict or {},
158 158 prefix_error=False,
159 encoding="UTF-8")
159 encoding="UTF-8",
160 force_defaults=False)
160 161 except Exception:
161 162 log.error(traceback.format_exc())
162 163 h.flash(_('Error occurred during creation of user group %s') \
163 164 % request.POST.get('users_group_name'), category='error')
164 165
165 166 return redirect(url('users_groups'))
166 167
167 168 @HasPermissionAnyDecorator('hg.admin', 'hg.usergroup.create.true')
168 169 def new(self, format='html'):
169 170 """GET /user_groups/new: Form to create a new item"""
170 171 # url('new_users_group')
171 172 return render('admin/user_groups/user_group_add.html')
172 173
173 174 @HasUserGroupPermissionAnyDecorator('usergroup.admin')
174 175 def update(self, id):
175 176 """PUT /user_groups/id: Update an existing item"""
176 177 # Forms posted to this method should contain a hidden field:
177 178 # <input type="hidden" name="_method" value="PUT" />
178 179 # Or using helpers:
179 180 # h.form(url('users_group', id=ID),
180 181 # method='put')
181 182 # url('users_group', id=ID)
182 183
183 184 c.user_group = UserGroup.get_or_404(id)
184 185 c.active = 'settings'
185 186 self.__load_data(id)
186 187
187 188 available_members = [safe_unicode(x[0]) for x in c.available_members]
188 189
189 190 users_group_form = UserGroupForm(edit=True,
190 191 old_data=c.user_group.get_dict(),
191 192 available_members=available_members)()
192 193
193 194 try:
194 195 form_result = users_group_form.to_python(request.POST)
195 196 UserGroupModel().update(c.user_group, form_result)
196 197 gr = form_result['users_group_name']
197 198 action_logger(self.authuser,
198 199 'admin_updated_users_group:%s' % gr,
199 200 None, self.ip_addr, self.sa)
200 201 h.flash(_('Updated user group %s') % gr, category='success')
201 202 Session().commit()
202 203 except formencode.Invalid, errors:
203 204 ug_model = UserGroupModel()
204 205 defaults = errors.value
205 206 e = errors.error_dict or {}
206 207 defaults.update({
207 208 'create_repo_perm': ug_model.has_perm(id,
208 209 'hg.create.repository'),
209 210 'fork_repo_perm': ug_model.has_perm(id,
210 211 'hg.fork.repository'),
211 212 '_method': 'put'
212 213 })
213 214
214 215 return htmlfill.render(
215 216 render('admin/user_groups/user_group_edit.html'),
216 217 defaults=defaults,
217 218 errors=e,
218 219 prefix_error=False,
219 encoding="UTF-8")
220 encoding="UTF-8",
221 force_defaults=False)
220 222 except Exception:
221 223 log.error(traceback.format_exc())
222 224 h.flash(_('Error occurred during update of user group %s') \
223 225 % request.POST.get('users_group_name'), category='error')
224 226
225 227 return redirect(url('edit_users_group', id=id))
226 228
227 229 @HasUserGroupPermissionAnyDecorator('usergroup.admin')
228 230 def delete(self, id):
229 231 """DELETE /user_groups/id: Delete an existing item"""
230 232 # Forms posted to this method should contain a hidden field:
231 233 # <input type="hidden" name="_method" value="DELETE" />
232 234 # Or using helpers:
233 235 # h.form(url('users_group', id=ID),
234 236 # method='delete')
235 237 # url('users_group', id=ID)
236 238 usr_gr = UserGroup.get_or_404(id)
237 239 try:
238 240 UserGroupModel().delete(usr_gr)
239 241 Session().commit()
240 242 h.flash(_('Successfully deleted user group'), category='success')
241 243 except UserGroupsAssignedException, e:
242 244 h.flash(e, category='error')
243 245 except Exception:
244 246 log.error(traceback.format_exc())
245 247 h.flash(_('An error occurred during deletion of user group'),
246 248 category='error')
247 249 return redirect(url('users_groups'))
248 250
249 251 def show(self, id, format='html'):
250 252 """GET /user_groups/id: Show a specific item"""
251 253 # url('users_group', id=ID)
252 254
253 255 @HasUserGroupPermissionAnyDecorator('usergroup.admin')
254 256 def edit(self, id, format='html'):
255 257 """GET /user_groups/id/edit: Form to edit an existing item"""
256 258 # url('edit_users_group', id=ID)
257 259
258 260 c.user_group = UserGroup.get_or_404(id)
259 261 c.active = 'settings'
260 262 self.__load_data(id)
261 263
262 264 defaults = self.__load_defaults(id)
263 265
264 266 return htmlfill.render(
265 267 render('admin/user_groups/user_group_edit.html'),
266 268 defaults=defaults,
267 269 encoding="UTF-8",
268 270 force_defaults=False
269 271 )
270 272
271 273 @HasUserGroupPermissionAnyDecorator('usergroup.admin')
272 274 def edit_perms(self, id):
273 275 c.user_group = UserGroup.get_or_404(id)
274 276 c.active = 'perms'
275 277
276 278 repo_model = RepoModel()
277 279 c.users_array = repo_model.get_users_js()
278 280 c.user_groups_array = repo_model.get_user_groups_js()
279 281
280 282 defaults = {}
281 283 # fill user group users
282 284 for p in c.user_group.user_user_group_to_perm:
283 285 defaults.update({'u_perm_%s' % p.user.username:
284 286 p.permission.permission_name})
285 287
286 288 for p in c.user_group.user_group_user_group_to_perm:
287 289 defaults.update({'g_perm_%s' % p.user_group.users_group_name:
288 290 p.permission.permission_name})
289 291
290 292 return htmlfill.render(
291 293 render('admin/user_groups/user_group_edit.html'),
292 294 defaults=defaults,
293 295 encoding="UTF-8",
294 296 force_defaults=False
295 297 )
296 298
297 299 @HasUserGroupPermissionAnyDecorator('usergroup.admin')
298 300 def update_perms(self, id):
299 301 """
300 302 grant permission for given usergroup
301 303
302 304 :param id:
303 305 """
304 306 user_group = UserGroup.get_or_404(id)
305 307 form = UserGroupPermsForm()().to_python(request.POST)
306 308
307 309 # set the permissions !
308 310 try:
309 311 UserGroupModel()._update_permissions(user_group, form['perms_new'],
310 312 form['perms_updates'])
311 313 except RepoGroupAssignmentError:
312 314 h.flash(_('Target group cannot be the same'), category='error')
313 315 return redirect(url('edit_user_group_perms', id=id))
314 316 #TODO: implement this
315 317 #action_logger(self.authuser, 'admin_changed_repo_permissions',
316 318 # repo_name, self.ip_addr, self.sa)
317 319 Session().commit()
318 320 h.flash(_('User Group permissions updated'), category='success')
319 321 return redirect(url('edit_user_group_perms', id=id))
320 322
321 323 @HasUserGroupPermissionAnyDecorator('usergroup.admin')
322 324 def delete_perms(self, id):
323 325 """
324 326 DELETE an existing repository group permission user
325 327
326 328 :param group_name:
327 329 """
328 330 try:
329 331 obj_type = request.POST.get('obj_type')
330 332 obj_id = None
331 333 if obj_type == 'user':
332 334 obj_id = safe_int(request.POST.get('user_id'))
333 335 elif obj_type == 'user_group':
334 336 obj_id = safe_int(request.POST.get('user_group_id'))
335 337
336 338 if not c.authuser.is_admin:
337 339 if obj_type == 'user' and c.authuser.user_id == obj_id:
338 340 msg = _('Cannot revoke permission for yourself as admin')
339 341 h.flash(msg, category='warning')
340 342 raise Exception('revoke admin permission on self')
341 343 if obj_type == 'user':
342 344 UserGroupModel().revoke_user_permission(user_group=id,
343 345 user=obj_id)
344 346 elif obj_type == 'user_group':
345 347 UserGroupModel().revoke_user_group_permission(target_user_group=id,
346 348 user_group=obj_id)
347 349 Session().commit()
348 350 except Exception:
349 351 log.error(traceback.format_exc())
350 352 h.flash(_('An error occurred during revoking of permission'),
351 353 category='error')
352 354 raise HTTPInternalServerError()
353 355
354 356 @HasUserGroupPermissionAnyDecorator('usergroup.admin')
355 357 def edit_default_perms(self, id):
356 358 c.user_group = UserGroup.get_or_404(id)
357 359 c.active = 'default_perms'
358 360
359 361 permissions = {
360 362 'repositories': {},
361 363 'repositories_groups': {}
362 364 }
363 365 ugroup_repo_perms = UserGroupRepoToPerm.query()\
364 366 .options(joinedload(UserGroupRepoToPerm.permission))\
365 367 .options(joinedload(UserGroupRepoToPerm.repository))\
366 368 .filter(UserGroupRepoToPerm.users_group_id == id)\
367 369 .all()
368 370
369 371 for gr in ugroup_repo_perms:
370 372 permissions['repositories'][gr.repository.repo_name] \
371 373 = gr.permission.permission_name
372 374
373 375 ugroup_group_perms = UserGroupRepoGroupToPerm.query()\
374 376 .options(joinedload(UserGroupRepoGroupToPerm.permission))\
375 377 .options(joinedload(UserGroupRepoGroupToPerm.group))\
376 378 .filter(UserGroupRepoGroupToPerm.users_group_id == id)\
377 379 .all()
378 380
379 381 for gr in ugroup_group_perms:
380 382 permissions['repositories_groups'][gr.group.group_name] \
381 383 = gr.permission.permission_name
382 384 c.permissions = permissions
383 385
384 386 ug_model = UserGroupModel()
385 387
386 388 defaults = c.user_group.get_dict()
387 389 defaults.update({
388 390 'create_repo_perm': ug_model.has_perm(c.user_group,
389 391 'hg.create.repository'),
390 392 'create_user_group_perm': ug_model.has_perm(c.user_group,
391 393 'hg.usergroup.create.true'),
392 394 'fork_repo_perm': ug_model.has_perm(c.user_group,
393 395 'hg.fork.repository'),
394 396 })
395 397
396 398 return htmlfill.render(
397 399 render('admin/user_groups/user_group_edit.html'),
398 400 defaults=defaults,
399 401 encoding="UTF-8",
400 402 force_defaults=False
401 403 )
402 404
403 405 @HasUserGroupPermissionAnyDecorator('usergroup.admin')
404 406 def update_default_perms(self, id):
405 407 """PUT /users_perm/id: Update an existing item"""
406 408 # url('users_group_perm', id=ID, method='put')
407 409
408 410 user_group = UserGroup.get_or_404(id)
409 411
410 412 try:
411 413 form = CustomDefaultPermissionsForm()()
412 414 form_result = form.to_python(request.POST)
413 415
414 416 inherit_perms = form_result['inherit_default_permissions']
415 417 user_group.inherit_default_permissions = inherit_perms
416 418 Session().add(user_group)
417 419 usergroup_model = UserGroupModel()
418 420
419 421 defs = UserGroupToPerm.query()\
420 422 .filter(UserGroupToPerm.users_group == user_group)\
421 423 .all()
422 424 for ug in defs:
423 425 Session().delete(ug)
424 426
425 427 if form_result['create_repo_perm']:
426 428 usergroup_model.grant_perm(id, 'hg.create.repository')
427 429 else:
428 430 usergroup_model.grant_perm(id, 'hg.create.none')
429 431 if form_result['create_user_group_perm']:
430 432 usergroup_model.grant_perm(id, 'hg.usergroup.create.true')
431 433 else:
432 434 usergroup_model.grant_perm(id, 'hg.usergroup.create.false')
433 435 if form_result['fork_repo_perm']:
434 436 usergroup_model.grant_perm(id, 'hg.fork.repository')
435 437 else:
436 438 usergroup_model.grant_perm(id, 'hg.fork.none')
437 439
438 440 h.flash(_("Updated permissions"), category='success')
439 441 Session().commit()
440 442 except Exception:
441 443 log.error(traceback.format_exc())
442 444 h.flash(_('An error occurred during permissions saving'),
443 445 category='error')
444 446
445 447 return redirect(url('edit_user_group_default_perms', id=id))
446 448
447 449 @HasUserGroupPermissionAnyDecorator('usergroup.admin')
448 450 def edit_advanced(self, id):
449 451 c.user_group = UserGroup.get_or_404(id)
450 452 c.active = 'advanced'
451 453 c.group_members_obj = sorted((x.user for x in c.user_group.members),
452 454 key=lambda u: u.username.lower())
453 455 return render('admin/user_groups/user_group_edit.html')
454 456
455 457
456 458 @HasUserGroupPermissionAnyDecorator('usergroup.admin')
457 459 def edit_members(self, id):
458 460 c.user_group = UserGroup.get_or_404(id)
459 461 c.active = 'members'
460 462 c.group_members_obj = sorted((x.user for x in c.user_group.members),
461 463 key=lambda u: u.username.lower())
462 464
463 465 c.group_members = [(x.user_id, x.username) for x in c.group_members_obj]
464 466 return render('admin/user_groups/user_group_edit.html')
@@ -1,504 +1,506 b''
1 1 # -*- coding: utf-8 -*-
2 2 # This program is free software: you can redistribute it and/or modify
3 3 # it under the terms of the GNU General Public License as published by
4 4 # the Free Software Foundation, either version 3 of the License, or
5 5 # (at your option) any later version.
6 6 #
7 7 # This program is distributed in the hope that it will be useful,
8 8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 10 # GNU General Public License for more details.
11 11 #
12 12 # You should have received a copy of the GNU General Public License
13 13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
14 14 """
15 15 kallithea.controllers.admin.users
16 16 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
17 17
18 18 Users crud controller for pylons
19 19
20 20 This file was forked by the Kallithea project in July 2014.
21 21 Original author and date, and relevant copyright and licensing information is below:
22 22 :created_on: Apr 4, 2010
23 23 :author: marcink
24 24 :copyright: (c) 2013 RhodeCode GmbH, and others.
25 25 :license: GPLv3, see LICENSE.md for more details.
26 26 """
27 27
28 28 import logging
29 29 import traceback
30 30 import formencode
31 31
32 32 from formencode import htmlfill
33 33 from pylons import request, tmpl_context as c, url, config
34 34 from pylons.controllers.util import redirect
35 35 from pylons.i18n.translation import _
36 36 from sqlalchemy.sql.expression import func
37 37
38 38 import kallithea
39 39 from kallithea.lib.exceptions import DefaultUserException, \
40 40 UserOwnsReposException, UserCreationError
41 41 from kallithea.lib import helpers as h
42 42 from kallithea.lib.auth import LoginRequired, HasPermissionAllDecorator, \
43 43 AuthUser, generate_api_key
44 44 import kallithea.lib.auth_modules.auth_internal
45 45 from kallithea.lib import auth_modules
46 46 from kallithea.lib.base import BaseController, render
47 47 from kallithea.model.api_key import ApiKeyModel
48 48
49 49 from kallithea.model.db import User, UserEmailMap, UserIpMap, UserToPerm
50 50 from kallithea.model.forms import UserForm, CustomDefaultPermissionsForm
51 51 from kallithea.model.user import UserModel
52 52 from kallithea.model.meta import Session
53 53 from kallithea.lib.utils import action_logger
54 54 from kallithea.lib.compat import json
55 55 from kallithea.lib.utils2 import datetime_to_time, safe_int
56 56
57 57 log = logging.getLogger(__name__)
58 58
59 59
60 60 class UsersController(BaseController):
61 61 """REST Controller styled on the Atom Publishing Protocol"""
62 62
63 63 @LoginRequired()
64 64 @HasPermissionAllDecorator('hg.admin')
65 65 def __before__(self):
66 66 super(UsersController, self).__before__()
67 67 c.available_permissions = config['available_permissions']
68 68 c.EXTERN_TYPE_INTERNAL = kallithea.EXTERN_TYPE_INTERNAL
69 69
70 70 def index(self, format='html'):
71 71 """GET /users: All items in the collection"""
72 72 # url('users')
73 73
74 74 c.users_list = User.query().order_by(User.username)\
75 75 .filter(User.username != User.DEFAULT_USER)\
76 76 .order_by(func.lower(User.username))\
77 77 .all()
78 78
79 79 users_data = []
80 80 total_records = len(c.users_list)
81 81 _tmpl_lookup = kallithea.CONFIG['pylons.app_globals'].mako_lookup
82 82 template = _tmpl_lookup.get_template('data_table/_dt_elements.html')
83 83
84 84 grav_tmpl = '<div class="gravatar">%s</div>'
85 85
86 86 username = lambda user_id, username: (
87 87 template.get_def("user_name")
88 88 .render(user_id, username, _=_, h=h, c=c))
89 89
90 90 user_actions = lambda user_id, username: (
91 91 template.get_def("user_actions")
92 92 .render(user_id, username, _=_, h=h, c=c))
93 93
94 94 for user in c.users_list:
95 95 users_data.append({
96 96 "gravatar": grav_tmpl % h.gravatar(user.email, size=20),
97 97 "raw_name": user.username,
98 98 "username": username(user.user_id, user.username),
99 99 "firstname": user.name,
100 100 "lastname": user.lastname,
101 101 "last_login": h.fmt_date(user.last_login),
102 102 "last_login_raw": datetime_to_time(user.last_login),
103 103 "active": h.boolicon(user.active),
104 104 "admin": h.boolicon(user.admin),
105 105 "extern_type": user.extern_type,
106 106 "extern_name": user.extern_name,
107 107 "action": user_actions(user.user_id, user.username),
108 108 })
109 109
110 110 c.data = json.dumps({
111 111 "totalRecords": total_records,
112 112 "startIndex": 0,
113 113 "sort": None,
114 114 "dir": "asc",
115 115 "records": users_data
116 116 })
117 117
118 118 return render('admin/users/users.html')
119 119
120 120 def create(self):
121 121 """POST /users: Create a new item"""
122 122 # url('users')
123 123 c.default_extern_type = auth_modules.auth_internal.KallitheaAuthPlugin.name
124 124 user_model = UserModel()
125 125 user_form = UserForm()()
126 126 try:
127 127 form_result = user_form.to_python(dict(request.POST))
128 128 user = user_model.create(form_result)
129 129 usr = form_result['username']
130 130 action_logger(self.authuser, 'admin_created_user:%s' % usr,
131 131 None, self.ip_addr, self.sa)
132 132 h.flash(h.literal(_('Created user %s') % h.link_to(h.escape(usr), url('edit_user', id=user.user_id))),
133 133 category='success')
134 134 Session().commit()
135 135 except formencode.Invalid, errors:
136 136 return htmlfill.render(
137 137 render('admin/users/user_add.html'),
138 138 defaults=errors.value,
139 139 errors=errors.error_dict or {},
140 140 prefix_error=False,
141 encoding="UTF-8")
141 encoding="UTF-8",
142 force_defaults=False)
142 143 except UserCreationError, e:
143 144 h.flash(e, 'error')
144 145 except Exception:
145 146 log.error(traceback.format_exc())
146 147 h.flash(_('Error occurred during creation of user %s') \
147 148 % request.POST.get('username'), category='error')
148 149 return redirect(url('users'))
149 150
150 151 def new(self, format='html'):
151 152 """GET /users/new: Form to create a new item"""
152 153 # url('new_user')
153 154 c.default_extern_type = auth_modules.auth_internal.KallitheaAuthPlugin.name
154 155 return render('admin/users/user_add.html')
155 156
156 157 def update(self, id):
157 158 """PUT /users/id: Update an existing item"""
158 159 # Forms posted to this method should contain a hidden field:
159 160 # <input type="hidden" name="_method" value="PUT" />
160 161 # Or using helpers:
161 162 # h.form(url('update_user', id=ID),
162 163 # method='put')
163 164 # url('user', id=ID)
164 165 c.active = 'profile'
165 166 user_model = UserModel()
166 167 c.user = user_model.get(id)
167 168 c.extern_type = c.user.extern_type
168 169 c.extern_name = c.user.extern_name
169 170 c.perm_user = AuthUser(user_id=id, ip_addr=self.ip_addr)
170 171 _form = UserForm(edit=True, old_data={'user_id': id,
171 172 'email': c.user.email})()
172 173 form_result = {}
173 174 try:
174 175 form_result = _form.to_python(dict(request.POST))
175 176 skip_attrs = ['extern_type', 'extern_name']
176 177 #TODO: plugin should define if username can be updated
177 178 if c.extern_type != kallithea.EXTERN_TYPE_INTERNAL:
178 179 # forbid updating username for external accounts
179 180 skip_attrs.append('username')
180 181
181 182 user_model.update(id, form_result, skip_attrs=skip_attrs)
182 183 usr = form_result['username']
183 184 action_logger(self.authuser, 'admin_updated_user:%s' % usr,
184 185 None, self.ip_addr, self.sa)
185 186 h.flash(_('User updated successfully'), category='success')
186 187 Session().commit()
187 188 except formencode.Invalid, errors:
188 189 defaults = errors.value
189 190 e = errors.error_dict or {}
190 191 defaults.update({
191 192 'create_repo_perm': user_model.has_perm(id,
192 193 'hg.create.repository'),
193 194 'fork_repo_perm': user_model.has_perm(id, 'hg.fork.repository'),
194 195 '_method': 'put'
195 196 })
196 197 return htmlfill.render(
197 198 render('admin/users/user_edit.html'),
198 199 defaults=defaults,
199 200 errors=e,
200 201 prefix_error=False,
201 encoding="UTF-8")
202 encoding="UTF-8",
203 force_defaults=False)
202 204 except Exception:
203 205 log.error(traceback.format_exc())
204 206 h.flash(_('Error occurred during update of user %s') \
205 207 % form_result.get('username'), category='error')
206 208 return redirect(url('edit_user', id=id))
207 209
208 210 def delete(self, id):
209 211 """DELETE /users/id: Delete an existing item"""
210 212 # Forms posted to this method should contain a hidden field:
211 213 # <input type="hidden" name="_method" value="DELETE" />
212 214 # Or using helpers:
213 215 # h.form(url('delete_user', id=ID),
214 216 # method='delete')
215 217 # url('user', id=ID)
216 218 usr = User.get_or_404(id)
217 219 try:
218 220 UserModel().delete(usr)
219 221 Session().commit()
220 222 h.flash(_('Successfully deleted user'), category='success')
221 223 except (UserOwnsReposException, DefaultUserException), e:
222 224 h.flash(e, category='warning')
223 225 except Exception:
224 226 log.error(traceback.format_exc())
225 227 h.flash(_('An error occurred during deletion of user'),
226 228 category='error')
227 229 return redirect(url('users'))
228 230
229 231 def show(self, id, format='html'):
230 232 """GET /users/id: Show a specific item"""
231 233 # url('user', id=ID)
232 234 User.get_or_404(-1)
233 235
234 236 def edit(self, id, format='html'):
235 237 """GET /users/id/edit: Form to edit an existing item"""
236 238 # url('edit_user', id=ID)
237 239 c.user = User.get_or_404(id)
238 240 if c.user.username == User.DEFAULT_USER:
239 241 h.flash(_("You can't edit this user"), category='warning')
240 242 return redirect(url('users'))
241 243
242 244 c.active = 'profile'
243 245 c.extern_type = c.user.extern_type
244 246 c.extern_name = c.user.extern_name
245 247 c.perm_user = AuthUser(user_id=id, ip_addr=self.ip_addr)
246 248
247 249 defaults = c.user.get_dict()
248 250 return htmlfill.render(
249 251 render('admin/users/user_edit.html'),
250 252 defaults=defaults,
251 253 encoding="UTF-8",
252 254 force_defaults=False)
253 255
254 256 def edit_advanced(self, id):
255 257 c.user = User.get_or_404(id)
256 258 if c.user.username == User.DEFAULT_USER:
257 259 h.flash(_("You can't edit this user"), category='warning')
258 260 return redirect(url('users'))
259 261
260 262 c.active = 'advanced'
261 263 c.perm_user = AuthUser(user_id=id, ip_addr=self.ip_addr)
262 264
263 265 umodel = UserModel()
264 266 defaults = c.user.get_dict()
265 267 defaults.update({
266 268 'create_repo_perm': umodel.has_perm(c.user, 'hg.create.repository'),
267 269 'create_user_group_perm': umodel.has_perm(c.user,
268 270 'hg.usergroup.create.true'),
269 271 'fork_repo_perm': umodel.has_perm(c.user, 'hg.fork.repository'),
270 272 })
271 273 return htmlfill.render(
272 274 render('admin/users/user_edit.html'),
273 275 defaults=defaults,
274 276 encoding="UTF-8",
275 277 force_defaults=False)
276 278
277 279 def edit_api_keys(self, id):
278 280 c.user = User.get_or_404(id)
279 281 if c.user.username == User.DEFAULT_USER:
280 282 h.flash(_("You can't edit this user"), category='warning')
281 283 return redirect(url('users'))
282 284
283 285 c.active = 'api_keys'
284 286 show_expired = True
285 287 c.lifetime_values = [
286 288 (str(-1), _('forever')),
287 289 (str(5), _('5 minutes')),
288 290 (str(60), _('1 hour')),
289 291 (str(60 * 24), _('1 day')),
290 292 (str(60 * 24 * 30), _('1 month')),
291 293 ]
292 294 c.lifetime_options = [(c.lifetime_values, _("Lifetime"))]
293 295 c.user_api_keys = ApiKeyModel().get_api_keys(c.user.user_id,
294 296 show_expired=show_expired)
295 297 defaults = c.user.get_dict()
296 298 return htmlfill.render(
297 299 render('admin/users/user_edit.html'),
298 300 defaults=defaults,
299 301 encoding="UTF-8",
300 302 force_defaults=False)
301 303
302 304 def add_api_key(self, id):
303 305 c.user = User.get_or_404(id)
304 306 if c.user.username == User.DEFAULT_USER:
305 307 h.flash(_("You can't edit this user"), category='warning')
306 308 return redirect(url('users'))
307 309
308 310 lifetime = safe_int(request.POST.get('lifetime'), -1)
309 311 description = request.POST.get('description')
310 312 ApiKeyModel().create(c.user.user_id, description, lifetime)
311 313 Session().commit()
312 314 h.flash(_("Api key successfully created"), category='success')
313 315 return redirect(url('edit_user_api_keys', id=c.user.user_id))
314 316
315 317 def delete_api_key(self, id):
316 318 c.user = User.get_or_404(id)
317 319 if c.user.username == User.DEFAULT_USER:
318 320 h.flash(_("You can't edit this user"), category='warning')
319 321 return redirect(url('users'))
320 322
321 323 api_key = request.POST.get('del_api_key')
322 324 if request.POST.get('del_api_key_builtin'):
323 325 user = User.get(c.user.user_id)
324 326 if user:
325 327 user.api_key = generate_api_key(user.username)
326 328 Session().add(user)
327 329 Session().commit()
328 330 h.flash(_("Api key successfully reset"), category='success')
329 331 elif api_key:
330 332 ApiKeyModel().delete(api_key, c.user.user_id)
331 333 Session().commit()
332 334 h.flash(_("Api key successfully deleted"), category='success')
333 335
334 336 return redirect(url('edit_user_api_keys', id=c.user.user_id))
335 337
336 338 def update_account(self, id):
337 339 pass
338 340
339 341 def edit_perms(self, id):
340 342 c.user = User.get_or_404(id)
341 343 if c.user.username == User.DEFAULT_USER:
342 344 h.flash(_("You can't edit this user"), category='warning')
343 345 return redirect(url('users'))
344 346
345 347 c.active = 'perms'
346 348 c.perm_user = AuthUser(user_id=id, ip_addr=self.ip_addr)
347 349
348 350 umodel = UserModel()
349 351 defaults = c.user.get_dict()
350 352 defaults.update({
351 353 'create_repo_perm': umodel.has_perm(c.user, 'hg.create.repository'),
352 354 'create_user_group_perm': umodel.has_perm(c.user,
353 355 'hg.usergroup.create.true'),
354 356 'fork_repo_perm': umodel.has_perm(c.user, 'hg.fork.repository'),
355 357 })
356 358 return htmlfill.render(
357 359 render('admin/users/user_edit.html'),
358 360 defaults=defaults,
359 361 encoding="UTF-8",
360 362 force_defaults=False)
361 363
362 364 def update_perms(self, id):
363 365 """PUT /users_perm/id: Update an existing item"""
364 366 # url('user_perm', id=ID, method='put')
365 367 user = User.get_or_404(id)
366 368
367 369 try:
368 370 form = CustomDefaultPermissionsForm()()
369 371 form_result = form.to_python(request.POST)
370 372
371 373 inherit_perms = form_result['inherit_default_permissions']
372 374 user.inherit_default_permissions = inherit_perms
373 375 Session().add(user)
374 376 user_model = UserModel()
375 377
376 378 defs = UserToPerm.query()\
377 379 .filter(UserToPerm.user == user)\
378 380 .all()
379 381 for ug in defs:
380 382 Session().delete(ug)
381 383
382 384 if form_result['create_repo_perm']:
383 385 user_model.grant_perm(id, 'hg.create.repository')
384 386 else:
385 387 user_model.grant_perm(id, 'hg.create.none')
386 388 if form_result['create_user_group_perm']:
387 389 user_model.grant_perm(id, 'hg.usergroup.create.true')
388 390 else:
389 391 user_model.grant_perm(id, 'hg.usergroup.create.false')
390 392 if form_result['fork_repo_perm']:
391 393 user_model.grant_perm(id, 'hg.fork.repository')
392 394 else:
393 395 user_model.grant_perm(id, 'hg.fork.none')
394 396 h.flash(_("Updated permissions"), category='success')
395 397 Session().commit()
396 398 except Exception:
397 399 log.error(traceback.format_exc())
398 400 h.flash(_('An error occurred during permissions saving'),
399 401 category='error')
400 402 return redirect(url('edit_user_perms', id=id))
401 403
402 404 def edit_emails(self, id):
403 405 c.user = User.get_or_404(id)
404 406 if c.user.username == User.DEFAULT_USER:
405 407 h.flash(_("You can't edit this user"), category='warning')
406 408 return redirect(url('users'))
407 409
408 410 c.active = 'emails'
409 411 c.user_email_map = UserEmailMap.query()\
410 412 .filter(UserEmailMap.user == c.user).all()
411 413
412 414 defaults = c.user.get_dict()
413 415 return htmlfill.render(
414 416 render('admin/users/user_edit.html'),
415 417 defaults=defaults,
416 418 encoding="UTF-8",
417 419 force_defaults=False)
418 420
419 421 def add_email(self, id):
420 422 """POST /user_emails:Add an existing item"""
421 423 # url('user_emails', id=ID, method='put')
422 424
423 425 email = request.POST.get('new_email')
424 426 user_model = UserModel()
425 427
426 428 try:
427 429 user_model.add_extra_email(id, email)
428 430 Session().commit()
429 431 h.flash(_("Added email %s to user") % email, category='success')
430 432 except formencode.Invalid, error:
431 433 msg = error.error_dict['email']
432 434 h.flash(msg, category='error')
433 435 except Exception:
434 436 log.error(traceback.format_exc())
435 437 h.flash(_('An error occurred during email saving'),
436 438 category='error')
437 439 return redirect(url('edit_user_emails', id=id))
438 440
439 441 def delete_email(self, id):
440 442 """DELETE /user_emails_delete/id: Delete an existing item"""
441 443 # url('user_emails_delete', id=ID, method='delete')
442 444 email_id = request.POST.get('del_email_id')
443 445 user_model = UserModel()
444 446 user_model.delete_extra_email(id, email_id)
445 447 Session().commit()
446 448 h.flash(_("Removed email from user"), category='success')
447 449 return redirect(url('edit_user_emails', id=id))
448 450
449 451 def edit_ips(self, id):
450 452 c.user = User.get_or_404(id)
451 453 if c.user.username == User.DEFAULT_USER:
452 454 h.flash(_("You can't edit this user"), category='warning')
453 455 return redirect(url('users'))
454 456
455 457 c.active = 'ips'
456 458 c.user_ip_map = UserIpMap.query()\
457 459 .filter(UserIpMap.user == c.user).all()
458 460
459 461 c.inherit_default_ips = c.user.inherit_default_permissions
460 462 c.default_user_ip_map = UserIpMap.query()\
461 463 .filter(UserIpMap.user == User.get_default_user()).all()
462 464
463 465 defaults = c.user.get_dict()
464 466 return htmlfill.render(
465 467 render('admin/users/user_edit.html'),
466 468 defaults=defaults,
467 469 encoding="UTF-8",
468 470 force_defaults=False)
469 471
470 472 def add_ip(self, id):
471 473 """POST /user_ips:Add an existing item"""
472 474 # url('user_ips', id=ID, method='put')
473 475
474 476 ip = request.POST.get('new_ip')
475 477 user_model = UserModel()
476 478
477 479 try:
478 480 user_model.add_extra_ip(id, ip)
479 481 Session().commit()
480 482 h.flash(_("Added ip %s to user whitelist") % ip, category='success')
481 483 except formencode.Invalid, error:
482 484 msg = error.error_dict['ip']
483 485 h.flash(msg, category='error')
484 486 except Exception:
485 487 log.error(traceback.format_exc())
486 488 h.flash(_('An error occurred during ip saving'),
487 489 category='error')
488 490
489 491 if 'default_user' in request.POST:
490 492 return redirect(url('admin_permissions_ips'))
491 493 return redirect(url('edit_user_ips', id=id))
492 494
493 495 def delete_ip(self, id):
494 496 """DELETE /user_ips_delete/id: Delete an existing item"""
495 497 # url('user_ips_delete', id=ID, method='delete')
496 498 ip_id = request.POST.get('del_ip_id')
497 499 user_model = UserModel()
498 500 user_model.delete_extra_ip(id, ip_id)
499 501 Session().commit()
500 502 h.flash(_("Removed ip address from user whitelist"), category='success')
501 503
502 504 if 'default_user' in request.POST:
503 505 return redirect(url('admin_permissions_ips'))
504 506 return redirect(url('edit_user_ips', id=id))
@@ -1,192 +1,192 b''
1 1 # -*- coding: utf-8 -*-
2 2 # This program is free software: you can redistribute it and/or modify
3 3 # it under the terms of the GNU General Public License as published by
4 4 # the Free Software Foundation, either version 3 of the License, or
5 5 # (at your option) any later version.
6 6 #
7 7 # This program is distributed in the hope that it will be useful,
8 8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 10 # GNU General Public License for more details.
11 11 #
12 12 # You should have received a copy of the GNU General Public License
13 13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
14 14 """
15 15 kallithea.controllers.forks
16 16 ~~~~~~~~~~~~~~~~~~~~~~~~~~~
17 17
18 18 forks controller for Kallithea
19 19
20 20 This file was forked by the Kallithea project in July 2014.
21 21 Original author and date, and relevant copyright and licensing information is below:
22 22 :created_on: Apr 23, 2011
23 23 :author: marcink
24 24 :copyright: (c) 2013 RhodeCode GmbH, and others.
25 25 :license: GPLv3, see LICENSE.md for more details.
26 26 """
27 27
28 28 import logging
29 29 import formencode
30 30 import traceback
31 31 from formencode import htmlfill
32 32
33 33 from pylons import tmpl_context as c, request, url
34 34 from pylons.controllers.util import redirect
35 35 from pylons.i18n.translation import _
36 36
37 37 import kallithea.lib.helpers as h
38 38
39 39 from kallithea.lib.helpers import Page
40 40 from kallithea.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator, \
41 41 NotAnonymous, HasRepoPermissionAny, HasPermissionAnyDecorator
42 42 from kallithea.lib.base import BaseRepoController, render
43 43 from kallithea.model.db import Repository, RepoGroup, UserFollowing, User,\
44 44 Ui
45 45 from kallithea.model.repo import RepoModel
46 46 from kallithea.model.forms import RepoForkForm
47 47 from kallithea.model.scm import ScmModel, RepoGroupList
48 48 from kallithea.lib.utils2 import safe_int
49 49
50 50 log = logging.getLogger(__name__)
51 51
52 52
53 53 class ForksController(BaseRepoController):
54 54
55 55 def __before__(self):
56 56 super(ForksController, self).__before__()
57 57
58 58 def __load_defaults(self):
59 59 acl_groups = RepoGroupList(RepoGroup.query().all(),
60 60 perm_set=['group.write', 'group.admin'])
61 61 c.repo_groups = RepoGroup.groups_choices(groups=acl_groups)
62 62 c.repo_groups_choices = map(lambda k: unicode(k[0]), c.repo_groups)
63 63 choices, c.landing_revs = ScmModel().get_repo_landing_revs()
64 64 c.landing_revs_choices = choices
65 65 c.can_update = Ui.get_by_key(Ui.HOOK_UPDATE).ui_active
66 66
67 67 def __load_data(self, repo_name=None):
68 68 """
69 69 Load defaults settings for edit, and update
70 70
71 71 :param repo_name:
72 72 """
73 73 self.__load_defaults()
74 74
75 75 c.repo_info = db_repo = Repository.get_by_repo_name(repo_name)
76 76 repo = db_repo.scm_instance
77 77
78 78 if c.repo_info is None:
79 79 h.not_mapped_error(repo_name)
80 80 return redirect(url('repos'))
81 81
82 82 c.default_user_id = User.get_default_user().user_id
83 83 c.in_public_journal = UserFollowing.query()\
84 84 .filter(UserFollowing.user_id == c.default_user_id)\
85 85 .filter(UserFollowing.follows_repository == c.repo_info).scalar()
86 86
87 87 if c.repo_info.stats:
88 88 last_rev = c.repo_info.stats.stat_on_revision+1
89 89 else:
90 90 last_rev = 0
91 91 c.stats_revision = last_rev
92 92
93 93 c.repo_last_rev = repo.count() if repo.revisions else 0
94 94
95 95 if last_rev == 0 or c.repo_last_rev == 0:
96 96 c.stats_percentage = 0
97 97 else:
98 98 c.stats_percentage = '%.2f' % ((float((last_rev)) /
99 99 c.repo_last_rev) * 100)
100 100
101 101 defaults = RepoModel()._get_defaults(repo_name)
102 102 # alter the description to indicate a fork
103 103 defaults['description'] = ('fork of repository: %s \n%s'
104 104 % (defaults['repo_name'],
105 105 defaults['description']))
106 106 # add suffix to fork
107 107 defaults['repo_name'] = '%s-fork' % defaults['repo_name']
108 108
109 109 return defaults
110 110
111 111 @LoginRequired()
112 112 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
113 113 'repository.admin')
114 114 def forks(self, repo_name):
115 115 p = safe_int(request.GET.get('page', 1), 1)
116 116 repo_id = c.db_repo.repo_id
117 117 d = []
118 118 for r in Repository.get_repo_forks(repo_id):
119 119 if not HasRepoPermissionAny(
120 120 'repository.read', 'repository.write', 'repository.admin'
121 121 )(r.repo_name, 'get forks check'):
122 122 continue
123 123 d.append(r)
124 124 c.forks_pager = Page(d, page=p, items_per_page=20)
125 125
126 126 if request.environ.get('HTTP_X_PARTIAL_XHR'):
127 127 return render('/forks/forks_data.html')
128 128
129 129 return render('/forks/forks.html')
130 130
131 131 @LoginRequired()
132 132 @NotAnonymous()
133 133 @HasPermissionAnyDecorator('hg.admin', 'hg.fork.repository')
134 134 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
135 135 'repository.admin')
136 136 def fork(self, repo_name):
137 137 c.repo_info = Repository.get_by_repo_name(repo_name)
138 138 if not c.repo_info:
139 139 h.not_mapped_error(repo_name)
140 140 return redirect(url('home'))
141 141
142 142 defaults = self.__load_data(repo_name)
143 143
144 144 return htmlfill.render(
145 145 render('forks/fork.html'),
146 146 defaults=defaults,
147 147 encoding="UTF-8",
148 force_defaults=False
149 )
148 force_defaults=False)
150 149
151 150 @LoginRequired()
152 151 @NotAnonymous()
153 152 @HasPermissionAnyDecorator('hg.admin', 'hg.fork.repository')
154 153 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
155 154 'repository.admin')
156 155 def fork_create(self, repo_name):
157 156 self.__load_defaults()
158 157 c.repo_info = Repository.get_by_repo_name(repo_name)
159 158 _form = RepoForkForm(old_data={'repo_type': c.repo_info.repo_type},
160 159 repo_groups=c.repo_groups_choices,
161 160 landing_revs=c.landing_revs_choices)()
162 161 form_result = {}
163 162 task_id = None
164 163 try:
165 164 form_result = _form.to_python(dict(request.POST))
166 165
167 166 # an approximation that is better than nothing
168 167 if not Ui.get_by_key(Ui.HOOK_UPDATE).ui_active:
169 168 form_result['update_after_clone'] = False
170 169
171 170 # create fork is done sometimes async on celery, db transaction
172 171 # management is handled there.
173 172 task = RepoModel().create_fork(form_result, self.authuser.user_id)
174 173 from celery.result import BaseAsyncResult
175 174 if isinstance(task, BaseAsyncResult):
176 175 task_id = task.task_id
177 176 except formencode.Invalid, errors:
178 177 c.new_repo = errors.value['repo_name']
179 178 return htmlfill.render(
180 179 render('forks/fork.html'),
181 180 defaults=errors.value,
182 181 errors=errors.error_dict or {},
183 182 prefix_error=False,
184 encoding="UTF-8")
183 encoding="UTF-8",
184 force_defaults=False)
185 185 except Exception:
186 186 log.error(traceback.format_exc())
187 187 h.flash(_('An error occurred during repository forking %s') %
188 188 repo_name, category='error')
189 189
190 190 return redirect(h.url('repo_creating_home',
191 191 repo_name=form_result['repo_name_full'],
192 192 task_id=task_id))
@@ -1,269 +1,272 b''
1 1 # -*- coding: utf-8 -*-
2 2 # This program is free software: you can redistribute it and/or modify
3 3 # it under the terms of the GNU General Public License as published by
4 4 # the Free Software Foundation, either version 3 of the License, or
5 5 # (at your option) any later version.
6 6 #
7 7 # This program is distributed in the hope that it will be useful,
8 8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 10 # GNU General Public License for more details.
11 11 #
12 12 # You should have received a copy of the GNU General Public License
13 13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
14 14 """
15 15 kallithea.controllers.login
16 16 ~~~~~~~~~~~~~~~~~~~~~~~~~~~
17 17
18 18 Login controller for Kallithea
19 19
20 20 This file was forked by the Kallithea project in July 2014.
21 21 Original author and date, and relevant copyright and licensing information is below:
22 22 :created_on: Apr 22, 2010
23 23 :author: marcink
24 24 :copyright: (c) 2013 RhodeCode GmbH, and others.
25 25 :license: GPLv3, see LICENSE.md for more details.
26 26 """
27 27
28 28
29 29 import logging
30 30 import formencode
31 31 import datetime
32 32 import urlparse
33 33
34 34 from formencode import htmlfill
35 35 from webob.exc import HTTPFound
36 36 from pylons.i18n.translation import _
37 37 from pylons.controllers.util import redirect
38 38 from pylons import request, session, tmpl_context as c, url
39 39
40 40 import kallithea.lib.helpers as h
41 41 from kallithea.lib.auth import AuthUser, HasPermissionAnyDecorator
42 42 from kallithea.lib.auth_modules import importplugin
43 43 from kallithea.lib.base import BaseController, render
44 44 from kallithea.lib.exceptions import UserCreationError
45 45 from kallithea.model.db import User, Setting
46 46 from kallithea.model.forms import LoginForm, RegisterForm, PasswordResetForm
47 47 from kallithea.model.user import UserModel
48 48 from kallithea.model.meta import Session
49 49
50 50
51 51 log = logging.getLogger(__name__)
52 52
53 53
54 54 class LoginController(BaseController):
55 55
56 56 def __before__(self):
57 57 super(LoginController, self).__before__()
58 58
59 59 def _store_user_in_session(self, username, remember=False):
60 60 user = User.get_by_username(username, case_insensitive=True)
61 61 auth_user = AuthUser(user.user_id)
62 62 auth_user.set_authenticated()
63 63 cs = auth_user.get_cookie_store()
64 64 session['authuser'] = cs
65 65 user.update_lastlogin()
66 66 Session().commit()
67 67
68 68 # If they want to be remembered, update the cookie
69 69 if remember:
70 70 _year = (datetime.datetime.now() +
71 71 datetime.timedelta(seconds=60 * 60 * 24 * 365))
72 72 session._set_cookie_expires(_year)
73 73
74 74 session.save()
75 75
76 76 log.info('user %s is now authenticated and stored in '
77 77 'session, session attrs %s' % (username, cs))
78 78
79 79 # dumps session attrs back to cookie
80 80 session._update_cookie_out()
81 81 # we set new cookie
82 82 headers = None
83 83 if session.request['set_cookie']:
84 84 # send set-cookie headers back to response to update cookie
85 85 headers = [('Set-Cookie', session.request['cookie_out'])]
86 86 return headers
87 87
88 88 def _validate_came_from(self, came_from):
89 89 if not came_from:
90 90 return came_from
91 91
92 92 parsed = urlparse.urlparse(came_from)
93 93 server_parsed = urlparse.urlparse(url.current())
94 94 allowed_schemes = ['http', 'https']
95 95 if parsed.scheme and parsed.scheme not in allowed_schemes:
96 96 log.error('Suspicious URL scheme detected %s for url %s' %
97 97 (parsed.scheme, parsed))
98 98 came_from = url('home')
99 99 elif server_parsed.netloc != parsed.netloc:
100 100 log.error('Suspicious NETLOC detected %s for url %s server url '
101 101 'is: %s' % (parsed.netloc, parsed, server_parsed))
102 102 came_from = url('home')
103 103 return came_from
104 104
105 105 def index(self):
106 106 _default_came_from = url('home')
107 107 came_from = self._validate_came_from(request.GET.get('came_from'))
108 108 c.came_from = came_from or _default_came_from
109 109
110 110 not_default = self.authuser.username != User.DEFAULT_USER
111 111 ip_allowed = self.authuser.ip_allowed
112 112
113 113 # redirect if already logged in
114 114 if self.authuser.is_authenticated and not_default and ip_allowed:
115 115 raise HTTPFound(location=c.came_from)
116 116
117 117 if request.POST:
118 118 # import Login Form validator class
119 119 login_form = LoginForm()
120 120 try:
121 121 session.invalidate()
122 122 c.form_result = login_form.to_python(dict(request.POST))
123 123 # form checks for username/password, now we're authenticated
124 124 headers = self._store_user_in_session(
125 125 username=c.form_result['username'],
126 126 remember=c.form_result['remember'])
127 127 raise HTTPFound(location=c.came_from, headers=headers)
128 128 except formencode.Invalid, errors:
129 129 defaults = errors.value
130 130 # remove password from filling in form again
131 131 del defaults['password']
132 132 return htmlfill.render(
133 133 render('/login.html'),
134 134 defaults=errors.value,
135 135 errors=errors.error_dict or {},
136 136 prefix_error=False,
137 encoding="UTF-8")
137 encoding="UTF-8",
138 force_defaults=False)
138 139 except UserCreationError, e:
139 140 # container auth or other auth functions that create users on
140 141 # the fly can throw this exception signaling that there's issue
141 142 # with user creation, explanation should be provided in
142 143 # Exception itself
143 144 h.flash(e, 'error')
144 145
145 146 # check if we use container plugin, and try to login using it.
146 147 auth_plugins = Setting.get_auth_plugins()
147 148 if any((importplugin(name).is_container_auth for name in auth_plugins)):
148 149 from kallithea.lib import auth_modules
149 150 try:
150 151 auth_info = auth_modules.authenticate('', '', request.environ)
151 152 except UserCreationError, e:
152 153 log.error(e)
153 154 h.flash(e, 'error')
154 155 # render login, with flash message about limit
155 156 return render('/login.html')
156 157
157 158 if auth_info:
158 159 headers = self._store_user_in_session(auth_info.get('username'))
159 160 raise HTTPFound(location=c.came_from, headers=headers)
160 161 return render('/login.html')
161 162
162 163 @HasPermissionAnyDecorator('hg.admin', 'hg.register.auto_activate',
163 164 'hg.register.manual_activate')
164 165 def register(self):
165 166 c.auto_active = 'hg.register.auto_activate' in User.get_default_user()\
166 167 .AuthUser.permissions['global']
167 168
168 169 settings = Setting.get_app_settings()
169 170 captcha_private_key = settings.get('captcha_private_key')
170 171 c.captcha_active = bool(captcha_private_key)
171 172 c.captcha_public_key = settings.get('captcha_public_key')
172 173
173 174 if request.POST:
174 175 register_form = RegisterForm()()
175 176 try:
176 177 form_result = register_form.to_python(dict(request.POST))
177 178 form_result['active'] = c.auto_active
178 179
179 180 if c.captcha_active:
180 181 from kallithea.lib.recaptcha import submit
181 182 response = submit(request.POST.get('recaptcha_challenge_field'),
182 183 request.POST.get('recaptcha_response_field'),
183 184 private_key=captcha_private_key,
184 185 remoteip=self.ip_addr)
185 186 if c.captcha_active and not response.is_valid:
186 187 _value = form_result
187 188 _msg = _('bad captcha')
188 189 error_dict = {'recaptcha_field': _msg}
189 190 raise formencode.Invalid(_msg, _value, None,
190 191 error_dict=error_dict)
191 192
192 193 UserModel().create_registration(form_result)
193 194 h.flash(_('You have successfully registered into Kallithea'),
194 195 category='success')
195 196 Session().commit()
196 197 return redirect(url('login_home'))
197 198
198 199 except formencode.Invalid, errors:
199 200 return htmlfill.render(
200 201 render('/register.html'),
201 202 defaults=errors.value,
202 203 errors=errors.error_dict or {},
203 204 prefix_error=False,
204 encoding="UTF-8")
205 encoding="UTF-8",
206 force_defaults=False)
205 207 except UserCreationError, e:
206 208 # container auth or other auth functions that create users on
207 209 # the fly can throw this exception signaling that there's issue
208 210 # with user creation, explanation should be provided in
209 211 # Exception itself
210 212 h.flash(e, 'error')
211 213
212 214 return render('/register.html')
213 215
214 216 def password_reset(self):
215 217 settings = Setting.get_app_settings()
216 218 captcha_private_key = settings.get('captcha_private_key')
217 219 c.captcha_active = bool(captcha_private_key)
218 220 c.captcha_public_key = settings.get('captcha_public_key')
219 221
220 222 if request.POST:
221 223 password_reset_form = PasswordResetForm()()
222 224 try:
223 225 form_result = password_reset_form.to_python(dict(request.POST))
224 226 if c.captcha_active:
225 227 from kallithea.lib.recaptcha import submit
226 228 response = submit(request.POST.get('recaptcha_challenge_field'),
227 229 request.POST.get('recaptcha_response_field'),
228 230 private_key=captcha_private_key,
229 231 remoteip=self.ip_addr)
230 232 if c.captcha_active and not response.is_valid:
231 233 _value = form_result
232 234 _msg = _('bad captcha')
233 235 error_dict = {'recaptcha_field': _msg}
234 236 raise formencode.Invalid(_msg, _value, None,
235 237 error_dict=error_dict)
236 238 UserModel().reset_password_link(form_result)
237 239 h.flash(_('Your password reset link was sent'),
238 240 category='success')
239 241 return redirect(url('login_home'))
240 242
241 243 except formencode.Invalid, errors:
242 244 return htmlfill.render(
243 245 render('/password_reset.html'),
244 246 defaults=errors.value,
245 247 errors=errors.error_dict or {},
246 248 prefix_error=False,
247 encoding="UTF-8")
249 encoding="UTF-8",
250 force_defaults=False)
248 251
249 252 return render('/password_reset.html')
250 253
251 254 def password_reset_confirmation(self):
252 255 if request.GET and request.GET.get('key'):
253 256 try:
254 257 user = User.get_by_api_key(request.GET.get('key'))
255 258 data = dict(email=user.email)
256 259 UserModel().reset_password(data)
257 260 h.flash(_('Your password reset was successful, '
258 261 'new password has been sent to your email'),
259 262 category='success')
260 263 except Exception, e:
261 264 log.error(e)
262 265 return redirect(url('reset_password'))
263 266
264 267 return redirect(url('login_home'))
265 268
266 269 def logout(self):
267 270 session.delete()
268 271 log.info('Logging out and deleting session for user')
269 272 redirect(url('home'))
General Comments 0
You need to be logged in to leave comments. Login now