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= |
|
|
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