##// END OF EJS Templates
authn: Fix handling of form errors and default values.
johbo -
r90:1b568d51 default
parent child Browse files
Show More
@@ -1,178 +1,182 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2012-2016 RhodeCode GmbH
3 # Copyright (C) 2012-2016 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import colander
21 import colander
22 import formencode.htmlfill
22 import formencode.htmlfill
23 import logging
23 import logging
24
24
25 from pyramid.httpexceptions import HTTPFound
25 from pyramid.httpexceptions import HTTPFound
26 from pyramid.renderers import render
26 from pyramid.renderers import render
27 from pyramid.response import Response
27 from pyramid.response import Response
28
28
29 from rhodecode.authentication.base import get_auth_cache_manager
29 from rhodecode.authentication.base import get_auth_cache_manager
30 from rhodecode.authentication.interface import IAuthnPluginRegistry
30 from rhodecode.authentication.interface import IAuthnPluginRegistry
31 from rhodecode.lib import auth
31 from rhodecode.lib import auth
32 from rhodecode.lib.auth import LoginRequired, HasPermissionAllDecorator
32 from rhodecode.lib.auth import LoginRequired, HasPermissionAllDecorator
33 from rhodecode.model.forms import AuthSettingsForm
33 from rhodecode.model.forms import AuthSettingsForm
34 from rhodecode.model.meta import Session
34 from rhodecode.model.meta import Session
35 from rhodecode.model.settings import SettingsModel
35 from rhodecode.model.settings import SettingsModel
36 from rhodecode.translation import _
36 from rhodecode.translation import _
37
37
38 log = logging.getLogger(__name__)
38 log = logging.getLogger(__name__)
39
39
40
40
41 class AuthnPluginViewBase(object):
41 class AuthnPluginViewBase(object):
42
42
43 def __init__(self, context, request):
43 def __init__(self, context, request):
44 self.request = request
44 self.request = request
45 self.context = context
45 self.context = context
46 self.plugin = context.plugin
46 self.plugin = context.plugin
47
47
48 def settings_get(self, errors={}):
48 def settings_get(self, defaults=None, errors=None):
49 """
49 """
50 View that displays the plugin settings as a form.
50 View that displays the plugin settings as a form.
51 """
51 """
52 defaults = defaults or {}
53 errors = errors or {}
52 schema = self.plugin.get_settings_schema()
54 schema = self.plugin.get_settings_schema()
53
55
54 # Get default values for the form.
56 # Get default values for the form.
55 for node in schema.children:
57 for node in schema:
56 value = self.plugin.get_setting_by_name(node.name)
58 db_value = self.plugin.get_setting_by_name(node.name)
57 if value:
59 defaults.setdefault(node.name, db_value)
58 node.default = value
59
60
60 template_context = {
61 template_context = {
62 'defaults': defaults,
61 'errors': errors,
63 'errors': errors,
62 'plugin': self.context.plugin,
64 'plugin': self.context.plugin,
63 'resource': self.context,
65 'resource': self.context,
64 }
66 }
65
67
66 return template_context
68 return template_context
67
69
68 def settings_post(self):
70 def settings_post(self):
69 """
71 """
70 View that validates and stores the plugin settings.
72 View that validates and stores the plugin settings.
71 """
73 """
72 schema = self.plugin.get_settings_schema()
74 schema = self.plugin.get_settings_schema()
73 try:
75 try:
74 valid_data = schema.deserialize(self.request.params)
76 valid_data = schema.deserialize(self.request.params)
75 except colander.Invalid, e:
77 except colander.Invalid, e:
76 # Display error message and display form again.
78 # Display error message and display form again.
77 self.request.session.flash(
79 self.request.session.flash(
78 _('Errors exist when saving plugin settings. '
80 _('Errors exist when saving plugin settings. '
79 'Please check the form inputs.'),
81 'Please check the form inputs.'),
80 queue='error')
82 queue='error')
81 return self.settings_get(errors=e.asdict())
83 defaults = schema.flatten(self.request.params)
84 return self.settings_get(errors=e.asdict(), defaults=defaults)
82
85
83 # Store validated data.
86 # Store validated data.
84 for name, value in valid_data.items():
87 for name, value in valid_data.items():
85 self.plugin.create_or_update_setting(name, value)
88 self.plugin.create_or_update_setting(name, value)
86 Session.commit()
89 Session.commit()
87
90
88 # Display success message and redirect.
91 # Display success message and redirect.
89 self.request.session.flash(
92 self.request.session.flash(
90 _('Auth settings updated successfully.'),
93 _('Auth settings updated successfully.'),
91 queue='success')
94 queue='success')
92 redirect_to = self.request.resource_path(
95 redirect_to = self.request.resource_path(
93 self.context, route_name='auth_home')
96 self.context, route_name='auth_home')
94 return HTTPFound(redirect_to)
97 return HTTPFound(redirect_to)
95
98
96
99
97 # TODO: Ongoing migration in these views.
100 # TODO: Ongoing migration in these views.
98 # - Maybe we should also use a colander schema for these views.
101 # - Maybe we should also use a colander schema for these views.
99 class AuthSettingsView(object):
102 class AuthSettingsView(object):
100 def __init__(self, context, request):
103 def __init__(self, context, request):
101 self.context = context
104 self.context = context
102 self.request = request
105 self.request = request
103
106
104 # TODO: Move this into a utility function. It is needed in all view
107 # TODO: Move this into a utility function. It is needed in all view
105 # classes during migration. Maybe a mixin?
108 # classes during migration. Maybe a mixin?
106
109
107 # Some of the decorators rely on this attribute to be present on the
110 # Some of the decorators rely on this attribute to be present on the
108 # class of the decorated method.
111 # class of the decorated method.
109 self._rhodecode_user = request.user
112 self._rhodecode_user = request.user
110
113
111 @LoginRequired()
114 @LoginRequired()
112 @HasPermissionAllDecorator('hg.admin')
115 @HasPermissionAllDecorator('hg.admin')
113 def index(self, defaults={}, errors=None, prefix_error=False):
116 def index(self, defaults=None, errors=None, prefix_error=False):
117 defaults = defaults or {}
114 authn_registry = self.request.registry.getUtility(IAuthnPluginRegistry)
118 authn_registry = self.request.registry.getUtility(IAuthnPluginRegistry)
115 enabled_plugins = SettingsModel().get_auth_plugins()
119 enabled_plugins = SettingsModel().get_auth_plugins()
116
120
117 # Create template context and render it.
121 # Create template context and render it.
118 template_context = {
122 template_context = {
119 'resource': self.context,
123 'resource': self.context,
120 'available_plugins': authn_registry.get_plugins(),
124 'available_plugins': authn_registry.get_plugins(),
121 'enabled_plugins': enabled_plugins,
125 'enabled_plugins': enabled_plugins,
122 }
126 }
123 html = render('rhodecode:templates/admin/auth/auth_settings.html',
127 html = render('rhodecode:templates/admin/auth/auth_settings.html',
124 template_context,
128 template_context,
125 request=self.request)
129 request=self.request)
126
130
127 # Create form default values and fill the form.
131 # Create form default values and fill the form.
128 form_defaults = {
132 form_defaults = {
129 'auth_plugins': ','.join(enabled_plugins)
133 'auth_plugins': ','.join(enabled_plugins)
130 }
134 }
131 form_defaults.update(defaults)
135 form_defaults.update(defaults)
132 html = formencode.htmlfill.render(
136 html = formencode.htmlfill.render(
133 html,
137 html,
134 defaults=form_defaults,
138 defaults=form_defaults,
135 errors=errors,
139 errors=errors,
136 prefix_error=prefix_error,
140 prefix_error=prefix_error,
137 encoding="UTF-8",
141 encoding="UTF-8",
138 force_defaults=False)
142 force_defaults=False)
139
143
140 return Response(html)
144 return Response(html)
141
145
142 @LoginRequired()
146 @LoginRequired()
143 @HasPermissionAllDecorator('hg.admin')
147 @HasPermissionAllDecorator('hg.admin')
144 @auth.CSRFRequired()
148 @auth.CSRFRequired()
145 def auth_settings(self):
149 def auth_settings(self):
146 try:
150 try:
147 form = AuthSettingsForm()()
151 form = AuthSettingsForm()()
148 form_result = form.to_python(self.request.params)
152 form_result = form.to_python(self.request.params)
149 plugins = ','.join(form_result['auth_plugins'])
153 plugins = ','.join(form_result['auth_plugins'])
150 setting = SettingsModel().create_or_update_setting(
154 setting = SettingsModel().create_or_update_setting(
151 'auth_plugins', plugins)
155 'auth_plugins', plugins)
152 Session().add(setting)
156 Session().add(setting)
153 Session().commit()
157 Session().commit()
154
158
155 cache_manager = get_auth_cache_manager()
159 cache_manager = get_auth_cache_manager()
156 cache_manager.clear()
160 cache_manager.clear()
157 self.request.session.flash(
161 self.request.session.flash(
158 _('Auth settings updated successfully.'),
162 _('Auth settings updated successfully.'),
159 queue='success')
163 queue='success')
160 except formencode.Invalid as errors:
164 except formencode.Invalid as errors:
161 e = errors.error_dict or {}
165 e = errors.error_dict or {}
162 self.request.session.flash(
166 self.request.session.flash(
163 _('Errors exist when saving plugin setting. '
167 _('Errors exist when saving plugin setting. '
164 'Please check the form inputs.'),
168 'Please check the form inputs.'),
165 queue='error')
169 queue='error')
166 return self.index(
170 return self.index(
167 defaults=errors.value,
171 defaults=errors.value,
168 errors=e,
172 errors=e,
169 prefix_error=False)
173 prefix_error=False)
170 except Exception:
174 except Exception:
171 log.exception('Exception in auth_settings')
175 log.exception('Exception in auth_settings')
172 self.request.session.flash(
176 self.request.session.flash(
173 _('Error occurred during update of auth settings.'),
177 _('Error occurred during update of auth settings.'),
174 queue='error')
178 queue='error')
175
179
176 redirect_to = self.request.resource_path(
180 redirect_to = self.request.resource_path(
177 self.context, route_name='auth_home')
181 self.context, route_name='auth_home')
178 return HTTPFound(redirect_to)
182 return HTTPFound(redirect_to)
@@ -1,116 +1,116 b''
1 ## -*- coding: utf-8 -*-
1 ## -*- coding: utf-8 -*-
2 <%inherit file="/base/base.html"/>
2 <%inherit file="/base/base.html"/>
3
3
4 <%def name="title()">
4 <%def name="title()">
5 ${_('Authentication Settings')}
5 ${_('Authentication Settings')}
6 %if c.rhodecode_name:
6 %if c.rhodecode_name:
7 &middot; ${h.branding(c.rhodecode_name)}}
7 &middot; ${h.branding(c.rhodecode_name)}}
8 %endif
8 %endif
9 </%def>
9 </%def>
10
10
11 <%def name="breadcrumbs_links()">
11 <%def name="breadcrumbs_links()">
12 ${h.link_to(_('Admin'),h.url('admin_home'))}
12 ${h.link_to(_('Admin'),h.url('admin_home'))}
13 &raquo;
13 &raquo;
14 ${h.link_to(_('Authentication Plugins'),request.resource_path(resource.__parent__, route_name='auth_home'))}
14 ${h.link_to(_('Authentication Plugins'),request.resource_path(resource.__parent__, route_name='auth_home'))}
15 &raquo;
15 &raquo;
16 ${resource.display_name}
16 ${resource.display_name}
17 </%def>
17 </%def>
18
18
19 <%def name="menu_bar_nav()">
19 <%def name="menu_bar_nav()">
20 ${self.menu_items(active='admin')}
20 ${self.menu_items(active='admin')}
21 </%def>
21 </%def>
22
22
23 <%def name="main()">
23 <%def name="main()">
24 <div class="box">
24 <div class="box">
25 <div class="title">
25 <div class="title">
26 ${self.breadcrumbs()}
26 ${self.breadcrumbs()}
27 </div>
27 </div>
28 <div class='sidebar-col-wrapper'>
28 <div class='sidebar-col-wrapper'>
29
29
30 ## TODO: This is repeated in the auth root template and should be merged
30 ## TODO: This is repeated in the auth root template and should be merged
31 ## into a single solution.
31 ## into a single solution.
32 <div class="sidebar">
32 <div class="sidebar">
33 <ul class="nav nav-pills nav-stacked">
33 <ul class="nav nav-pills nav-stacked">
34 % for item in resource.get_root().get_nav_list():
34 % for item in resource.get_root().get_nav_list():
35 <li ${'class=active' if item == resource else ''}>
35 <li ${'class=active' if item == resource else ''}>
36 <a href="${request.resource_path(item, route_name='auth_home')}">${item.display_name}</a>
36 <a href="${request.resource_path(item, route_name='auth_home')}">${item.display_name}</a>
37 </li>
37 </li>
38 % endfor
38 % endfor
39 </ul>
39 </ul>
40 </div>
40 </div>
41
41
42 <div class="main-content-full-width">
42 <div class="main-content-full-width">
43 <div class="panel panel-default">
43 <div class="panel panel-default">
44 <div class="panel-heading">
44 <div class="panel-heading">
45 <h3 class="panel-title">${_('Plugin')}: ${resource.display_name}</h3>
45 <h3 class="panel-title">${_('Plugin')}: ${resource.display_name}</h3>
46 </div>
46 </div>
47 <div class="panel-body">
47 <div class="panel-body">
48 <div class="plugin_form">
48 <div class="plugin_form">
49 <div class="fields">
49 <div class="fields">
50 ${h.secure_form(request.resource_path(resource, route_name='auth_home'))}
50 ${h.secure_form(request.resource_path(resource, route_name='auth_home'))}
51 <div class="form">
51 <div class="form">
52
52
53 %for node in plugin.get_settings_schema():
53 %for node in plugin.get_settings_schema():
54 <% label_css_class = ("label-checkbox" if (node.widget == "bool") else "") %>
54 <% label_css_class = ("label-checkbox" if (node.widget == "bool") else "") %>
55 <div class="field">
55 <div class="field">
56 <div class="label ${label_css_class}"><label for="${node.name}">${node.title}</label></div>
56 <div class="label ${label_css_class}"><label for="${node.name}">${node.title}</label></div>
57 <div class="input">
57 <div class="input">
58 %if node.widget in ["string", "int", "unicode"]:
58 %if node.widget in ["string", "int", "unicode"]:
59 ${h.text(node.name, node.default, class_="medium")}
59 ${h.text(node.name, defaults.get(node.name), class_="medium")}
60 %elif node.widget == "password":
60 %elif node.widget == "password":
61 ${h.password(node.name, node.default, class_="medium")}
61 ${h.password(node.name, defaults.get(node.name), class_="medium")}
62 %elif node.widget == "bool":
62 %elif node.widget == "bool":
63 <div class="checkbox">${h.checkbox(node.name, node.default)}</div>
63 <div class="checkbox">${h.checkbox(node.name, True, checked=defaults.get(node.name))}</div>
64 %elif node.widget == "select":
64 %elif node.widget == "select":
65 ${h.select(node.name, node.default, node.validator.choices)}
65 ${h.select(node.name, defaults.get(node.name), node.validator.choices)}
66 %elif node.widget == "readonly":
66 %elif node.widget == "readonly":
67 ${node.default}
67 ${node.default}
68 %else:
68 %else:
69 This field is of type ${node.typ}, which cannot be displayed. Must be one of [string|int|bool|select].
69 This field is of type ${node.typ}, which cannot be displayed. Must be one of [string|int|bool|select].
70 %endif
70 %endif
71 %if node.name in errors:
71 %if node.name in errors:
72 <span class="error-message">${errors.get(node.name)}</span>
72 <span class="error-message">${errors.get(node.name)}</span>
73 <br />
73 <br />
74 %endif
74 %endif
75 <p class="help-block">${node.description}</p>
75 <p class="help-block">${node.description}</p>
76 </div>
76 </div>
77 </div>
77 </div>
78 %endfor
78 %endfor
79
79
80 ## Allow derived templates to add something below the form
80 ## Allow derived templates to add something below the form
81 ## input fields
81 ## input fields
82 %if hasattr(next, 'below_form_fields'):
82 %if hasattr(next, 'below_form_fields'):
83 ${next.below_form_fields()}
83 ${next.below_form_fields()}
84 %endif
84 %endif
85
85
86 <div class="buttons">
86 <div class="buttons">
87 ${h.submit('save',_('Save'),class_="btn")}
87 ${h.submit('save',_('Save'),class_="btn")}
88 </div>
88 </div>
89
89
90 </div>
90 </div>
91 ${h.end_form()}
91 ${h.end_form()}
92 </div>
92 </div>
93 </div>
93 </div>
94 </div>
94 </div>
95 </div>
95 </div>
96 </div>
96 </div>
97
97
98 </div>
98 </div>
99 </div>
99 </div>
100 </%def>
100 </%def>
101
101
102 ## TODO: Ugly hack to get ldap select elements to work.
102 ## TODO: Ugly hack to get ldap select elements to work.
103 ## Find a solution to integrate this nicely.
103 ## Find a solution to integrate this nicely.
104 <script>
104 <script>
105 $(document).ready(function() {
105 $(document).ready(function() {
106 var select2Options = {
106 var select2Options = {
107 containerCssClass: 'drop-menu',
107 containerCssClass: 'drop-menu',
108 dropdownCssClass: 'drop-menu-dropdown',
108 dropdownCssClass: 'drop-menu-dropdown',
109 dropdownAutoWidth: true,
109 dropdownAutoWidth: true,
110 minimumResultsForSearch: -1
110 minimumResultsForSearch: -1
111 };
111 };
112 $("#tls_kind").select2(select2Options);
112 $("#tls_kind").select2(select2Options);
113 $("#tls_reqcert").select2(select2Options);
113 $("#tls_reqcert").select2(select2Options);
114 $("#search_scope").select2(select2Options);
114 $("#search_scope").select2(select2Options);
115 });
115 });
116 </script>
116 </script>
General Comments 0
You need to be logged in to leave comments. Login now