##// END OF EJS Templates
auth: UI changes...
marcink -
r3257:92a130b7 default
parent child Browse files
Show More
@@ -1,155 +1,155 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2012-2018 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 import logging
22 22 import collections
23 23
24 24 from pyramid.exceptions import ConfigurationError
25 25
26 26 from rhodecode.lib.utils2 import safe_str
27 27 from rhodecode.model.settings import SettingsModel
28 28 from rhodecode.translation import _
29 29
30 30
31 31 log = logging.getLogger(__name__)
32 32
33 33
34 34 class AuthnResourceBase(object):
35 35 __name__ = None
36 36 __parent__ = None
37 37
38 38 def get_root(self):
39 39 current = self
40 40 while current.__parent__ is not None:
41 41 current = current.__parent__
42 42 return current
43 43
44 44
45 45 class AuthnPluginResourceBase(AuthnResourceBase):
46 46
47 47 def __init__(self, plugin):
48 48 self.plugin = plugin
49 49 self.__name__ = plugin.get_url_slug()
50 50 self.display_name = plugin.get_display_name()
51 51
52 52
53 53 class AuthnRootResource(AuthnResourceBase):
54 54 """
55 55 This is the root traversal resource object for the authentication settings.
56 56 """
57 57
58 58 def __init__(self):
59 59 self._store = collections.OrderedDict()
60 60 self._resource_name_map = {}
61 61 self.display_name = _('Authentication Plugins')
62 62
63 63 def __getitem__(self, key):
64 64 """
65 65 Customized get item function to return only items (plugins) that are
66 66 activated.
67 67 """
68 68 if self._is_item_active(key):
69 69 return self._store[key]
70 70 else:
71 71 raise KeyError('Authentication plugin "{}" is not active.'.format(
72 72 key))
73 73
74 74 def __iter__(self):
75 75 for key in self._store.keys():
76 76 if self._is_item_active(key):
77 77 yield self._store[key]
78 78
79 79 def _is_item_active(self, key):
80 80 activated_plugins = SettingsModel().get_auth_plugins()
81 81 plugin_id = self.get_plugin_id(key)
82 82 return plugin_id in activated_plugins
83 83
84 84 def get_plugin_id(self, resource_name):
85 85 """
86 86 Return the plugin id for the given traversal resource name.
87 87 """
88 88 # TODO: Store this info in the resource element.
89 89 return self._resource_name_map[resource_name]
90 90
91 def get_sorted_list(self):
91 def get_sorted_list(self, sort_key=None):
92 92 """
93 93 Returns a sorted list of sub resources for displaying purposes.
94 94 """
95 def sort_key(resource):
95 def default_sort_key(resource):
96 96 return str.lower(safe_str(resource.display_name))
97 97
98 98 active = [item for item in self]
99 return sorted(active, key=sort_key)
99 return sorted(active, key=sort_key or default_sort_key)
100 100
101 101 def get_nav_list(self, sort=True):
102 102 """
103 103 Returns a sorted list of resources for displaying the navigation.
104 104 """
105 105 if sort:
106 106 nav_list = self.get_sorted_list()
107 107 else:
108 108 nav_list = [item for item in self]
109 109
110 110 nav_list.insert(0, self)
111 111 return nav_list
112 112
113 113 def add_authn_resource(self, config, plugin_id, resource):
114 114 """
115 115 Register a traversal resource as a sub element to the authentication
116 116 settings. This method is registered as a directive on the pyramid
117 117 configurator object and called by plugins.
118 118 """
119 119
120 120 def _ensure_unique_name(name, limit=100):
121 121 counter = 1
122 122 current = name
123 123 while current in self._store.keys():
124 124 current = '{}{}'.format(name, counter)
125 125 counter += 1
126 126 if counter > limit:
127 127 raise ConfigurationError(
128 128 'Cannot build unique name for traversal resource "%s" '
129 129 'registered by plugin "%s"', name, plugin_id)
130 130 return current
131 131
132 132 # Allow plugin resources with identical names by rename duplicates.
133 133 unique_name = _ensure_unique_name(resource.__name__)
134 134 if unique_name != resource.__name__:
135 135 log.warn('Name collision for traversal resource "%s" registered '
136 136 'by authentication plugin "%s"', resource.__name__,
137 137 plugin_id)
138 138 resource.__name__ = unique_name
139 139
140 140 log.debug('Register traversal resource "%s" for plugin "%s"',
141 141 unique_name, plugin_id)
142 142 self._resource_name_map[unique_name] = plugin_id
143 143 resource.__parent__ = self
144 144 self._store[unique_name] = resource
145 145
146 146
147 147 root = AuthnRootResource()
148 148
149 149
150 150 def root_factory(request=None):
151 151 """
152 152 Returns the root traversal resource instance used for the authentication
153 153 settings route.
154 154 """
155 155 return root
@@ -1,418 +1,418 b''
1 1
2 2
3 3 //BUTTONS
4 4 button,
5 5 .btn,
6 6 input[type="button"] {
7 7 -webkit-appearance: none;
8 8 display: inline-block;
9 9 margin: 0 @padding/3 0 0;
10 10 padding: @button-padding;
11 11 text-align: center;
12 12 font-size: @basefontsize;
13 13 line-height: 1em;
14 14 font-family: @text-light;
15 15 text-decoration: none;
16 16 text-shadow: none;
17 17 color: @grey4;
18 18 background-color: white;
19 19 background-image: none;
20 20 border: none;
21 21 .border ( @border-thickness-buttons, @grey4 );
22 22 .border-radius (@border-radius);
23 23 cursor: pointer;
24 24 white-space: nowrap;
25 25 -webkit-transition: background .3s,color .3s;
26 26 -moz-transition: background .3s,color .3s;
27 27 -o-transition: background .3s,color .3s;
28 28 transition: background .3s,color .3s;
29 29
30 30 a {
31 31 display: block;
32 32 margin: 0;
33 33 padding: 0;
34 34 color: inherit;
35 35 text-decoration: none;
36 36
37 37 &:hover {
38 38 text-decoration: none;
39 39 }
40 40 }
41 41
42 42 &:focus,
43 43 &:active {
44 44 outline:none;
45 45 }
46 46 &:hover {
47 47 color: white;
48 48 background-color: @grey4;
49 49 }
50 50
51 51 .icon-remove-sign {
52 52 display: none;
53 53 }
54 54
55 55 //disabled buttons
56 56 //last; overrides any other styles
57 57 &:disabled {
58 58 opacity: .7;
59 59 cursor: auto;
60 60 background-color: white;
61 61 color: @grey4;
62 62 text-shadow: none;
63 63 }
64 64
65 65 &.no-margin {
66 66 margin: 0 0 0 0;
67 67 }
68 68
69 69 }
70 70
71 71
72 72 .btn-default {
73 73 .border ( @border-thickness-buttons, @rcblue );
74 74 background-image: none;
75 75 color: @rcblue;
76 76
77 77 a {
78 78 color: @rcblue;
79 79 }
80 80
81 81 &:hover,
82 82 &.active {
83 83 color: white;
84 84 background-color: @rcdarkblue;
85 85 .border ( @border-thickness, @rcdarkblue );
86 86
87 87 a {
88 88 color: white;
89 89 }
90 90 }
91 91 &:disabled {
92 92 .border ( @border-thickness-buttons, @grey4 );
93 93 background-color: transparent;
94 94 }
95 95 }
96 96
97 97 .btn-primary,
98 98 .btn-small, /* TODO: anderson: remove .btn-small to not mix with the new btn-sm */
99 99 .btn-success {
100 100 .border ( @border-thickness, @rcblue );
101 101 background-color: @rcblue;
102 102 color: white;
103 103
104 104 a {
105 105 color: white;
106 106 }
107 107
108 108 &:hover,
109 109 &.active {
110 110 .border ( @border-thickness, @rcdarkblue );
111 111 color: white;
112 112 background-color: @rcdarkblue;
113 113
114 114 a {
115 115 color: white;
116 116 }
117 117 }
118 118 &:disabled {
119 119 background-color: @rcblue;
120 120 }
121 121 }
122 122
123 123 .btn-secondary {
124 124 &:extend(.btn-default);
125 125
126 126 background-color: white;
127 127
128 128 &:focus {
129 129 outline: 0;
130 130 }
131 131
132 132 &:hover {
133 133 &:extend(.btn-default:hover);
134 134 }
135 135
136 136 &.btn-link {
137 137 &:extend(.btn-link);
138 138 color: @rcblue;
139 139 }
140 140
141 141 &:disabled {
142 142 color: @rcblue;
143 143 background-color: white;
144 144 }
145 145 }
146 146
147 147 .btn-warning,
148 148 .btn-danger,
149 149 .revoke_perm,
150 150 .btn-x,
151 151 .form .action_button.btn-x {
152 152 .border ( @border-thickness, @alert2 );
153 153 background-color: white;
154 154 color: @alert2;
155 155
156 156 a {
157 157 color: @alert2;
158 158 }
159 159
160 160 &:hover,
161 161 &.active {
162 162 .border ( @border-thickness, @alert2 );
163 163 color: white;
164 164 background-color: @alert2;
165 165
166 166 a {
167 167 color: white;
168 168 }
169 169 }
170 170
171 171 i {
172 172 display:none;
173 173 }
174 174
175 175 &:disabled {
176 176 background-color: white;
177 177 color: @alert2;
178 178 }
179 179 }
180 180
181 181 .btn-approved-status {
182 182 .border ( @border-thickness, @alert1 );
183 183 background-color: white;
184 184 color: @alert1;
185 185
186 186 }
187 187
188 188 .btn-rejected-status {
189 189 .border ( @border-thickness, @alert2 );
190 190 background-color: white;
191 191 color: @alert2;
192 192 }
193 193
194 194 .btn-sm,
195 195 .btn-mini,
196 196 .field-sm .btn {
197 197 padding: @padding/3;
198 198 }
199 199
200 200 .btn-xs {
201 201 padding: @padding/4;
202 202 }
203 203
204 204 .btn-lg {
205 205 padding: @padding * 1.2;
206 206 }
207 207
208 208 .btn-group {
209 209 display: inline-block;
210 210 .btn {
211 211 float: left;
212 212 margin: 0 0 0 -1px;
213 213 }
214 214 }
215 215
216 216 .btn-link {
217 217 background: transparent;
218 218 border: none;
219 219 padding: 0;
220 220 color: @rcblue;
221 221
222 222 &:hover {
223 223 background: transparent;
224 224 border: none;
225 225 color: @rcdarkblue;
226 226 }
227 227
228 228 //disabled buttons
229 229 //last; overrides any other styles
230 230 &:disabled {
231 231 opacity: .7;
232 232 cursor: auto;
233 233 background-color: white;
234 234 color: @grey4;
235 235 text-shadow: none;
236 236 }
237 237
238 238 // TODO: johbo: Check if we can avoid this, indicates that the structure
239 239 // is not yet good.
240 240 // lisa: The button CSS reflects the button HTML; both need a cleanup.
241 241 &.btn-danger {
242 242 color: @alert2;
243 243
244 244 &:hover {
245 245 color: darken(@alert2,30%);
246 246 }
247 247
248 248 &:disabled {
249 249 color: @alert2;
250 250 }
251 251 }
252 252 }
253 253
254 254 .btn-social {
255 255 &:extend(.btn-default);
256 256 margin: 5px 5px 5px 0px;
257 min-width: 150px;
257 min-width: 160px;
258 258 }
259 259
260 260 // TODO: johbo: check these exceptions
261 261
262 262 .links {
263 263
264 264 .btn + .btn {
265 265 margin-top: @padding;
266 266 }
267 267 }
268 268
269 269
270 270 .action_button {
271 271 display:inline;
272 272 margin: 0;
273 273 padding: 0 1em 0 0;
274 274 font-size: inherit;
275 275 color: @rcblue;
276 276 border: none;
277 277 border-radius: 0;
278 278 background-color: transparent;
279 279
280 280 &.last-item {
281 281 border: none;
282 282 padding: 0 0 0 0;
283 283 }
284 284
285 285 &:last-child {
286 286 border: none;
287 287 padding: 0 0 0 0;
288 288 }
289 289
290 290 &:hover {
291 291 color: @rcdarkblue;
292 292 background-color: transparent;
293 293 border: none;
294 294 }
295 295 }
296 296 .grid_delete {
297 297 .action_button {
298 298 border: none;
299 299 }
300 300 }
301 301
302 302
303 303 // TODO: johbo: Form button tweaks, check if we can use the classes instead
304 304 input[type="submit"] {
305 305 &:extend(.btn-primary);
306 306
307 307 &:focus {
308 308 outline: 0;
309 309 }
310 310
311 311 &:hover {
312 312 &:extend(.btn-primary:hover);
313 313 }
314 314
315 315 &.btn-link {
316 316 &:extend(.btn-link);
317 317 color: @rcblue;
318 318
319 319 &:disabled {
320 320 color: @rcblue;
321 321 background-color: transparent;
322 322 }
323 323 }
324 324
325 325 &:disabled {
326 326 .border ( @border-thickness-buttons, @rcblue );
327 327 background-color: @rcblue;
328 328 color: white;
329 329 }
330 330 }
331 331
332 332 input[type="reset"] {
333 333 &:extend(.btn-default);
334 334
335 335 // TODO: johbo: Check if this tweak can be avoided.
336 336 background: transparent;
337 337
338 338 &:focus {
339 339 outline: 0;
340 340 }
341 341
342 342 &:hover {
343 343 &:extend(.btn-default:hover);
344 344 }
345 345
346 346 &.btn-link {
347 347 &:extend(.btn-link);
348 348 color: @rcblue;
349 349
350 350 &:disabled {
351 351 border: none;
352 352 }
353 353 }
354 354
355 355 &:disabled {
356 356 .border ( @border-thickness-buttons, @rcblue );
357 357 background-color: white;
358 358 color: @rcblue;
359 359 }
360 360 }
361 361
362 362 input[type="submit"],
363 363 input[type="reset"] {
364 364 &.btn-danger {
365 365 &:extend(.btn-danger);
366 366
367 367 &:focus {
368 368 outline: 0;
369 369 }
370 370
371 371 &:hover {
372 372 &:extend(.btn-danger:hover);
373 373 }
374 374
375 375 &.btn-link {
376 376 &:extend(.btn-link);
377 377 color: @alert2;
378 378
379 379 &:hover {
380 380 color: darken(@alert2,30%);
381 381 }
382 382 }
383 383
384 384 &:disabled {
385 385 color: @alert2;
386 386 background-color: white;
387 387 }
388 388 }
389 389 &.btn-danger-action {
390 390 .border ( @border-thickness, @alert2 );
391 391 background-color: @alert2;
392 392 color: white;
393 393
394 394 a {
395 395 color: white;
396 396 }
397 397
398 398 &:hover {
399 399 background-color: darken(@alert2,20%);
400 400 }
401 401
402 402 &.active {
403 403 .border ( @border-thickness, @alert2 );
404 404 color: white;
405 405 background-color: @alert2;
406 406
407 407 a {
408 408 color: white;
409 409 }
410 410 }
411 411
412 412 &:disabled {
413 413 background-color: white;
414 414 color: @alert2;
415 415 }
416 416 }
417 417 }
418 418
@@ -1,124 +1,124 b''
1 1 ## -*- coding: utf-8 -*-
2 2 <%inherit file="/base/base.mako"/>
3 3
4 4 <%def name="title()">
5 5 ${_('Authentication Settings')}
6 6 %if c.rhodecode_name:
7 7 &middot; ${h.branding(c.rhodecode_name)}}
8 8 %endif
9 9 </%def>
10 10
11 11 <%def name="breadcrumbs_links()">
12 12 ${h.link_to(_('Admin'),h.route_path('admin_home'))}
13 13 &raquo;
14 14 ${_('Authentication Plugins')}
15 15 </%def>
16 16
17 17 <%def name="menu_bar_nav()">
18 18 ${self.menu_items(active='admin')}
19 19 </%def>
20 20
21 21 <%def name="main()">
22 22
23 23 <div class="box">
24 24 <div class="title">
25 25 ${self.breadcrumbs()}
26 26 </div>
27 27
28 28 <div class='sidebar-col-wrapper'>
29 29
30 30 <div class="sidebar">
31 31 <ul class="nav nav-pills nav-stacked">
32 % for item in resource.get_root().get_nav_list(sort=False):
32 % for item in resource.get_root().get_nav_list():
33 33 <li ${'class=active' if item == resource else ''}>
34 34 <a href="${request.resource_path(item, route_name='auth_home')}">${item.display_name}</a>
35 35 </li>
36 36 % endfor
37 37 </ul>
38 38 </div>
39 39
40 40 <div class="main-content-full-width">
41 41 ${h.secure_form(request.resource_path(resource, route_name='auth_home'), request=request)}
42 42 <div class="panel panel-default">
43 43
44 44 <div class="panel-heading">
45 45 <h3 class="panel-title">${_("Enabled and Available Plugins")}</h3>
46 46 </div>
47 47
48 48 <div class="panel-body">
49 49
50 50
51 51 <div class="label">${_("Ordered Enabled Plugins")}</div>
52 52 <div class="textarea text-area editor">
53 53 ${h.textarea('auth_plugins',cols=120,rows=20,class_="medium")}
54 54 </div>
55 55 <div class="field">
56 56 <p class="help-block pre-formatting">${_('List of plugins, separated by commas.'
57 57 '\nThe order of the plugins is also the order in which '
58 58 'RhodeCode Enterprise will try to authenticate a user.')}
59 59 </p>
60 60 </div>
61 61
62 62 <table class="rctable">
63 63 <th>${_('Activate')}</th>
64 64 <th>${_('Plugin Name')}</th>
65 65 <th>${_('Documentation')}</th>
66 66 <th>${_('Plugin ID')}</th>
67 67 %for plugin in available_plugins:
68 68 <tr>
69 69 <td>
70 70 <span plugin_id="${plugin.get_id()}" class="toggle-plugin btn ${'btn-success' if plugin.get_id() in enabled_plugins else ''}">
71 71 ${_('enabled') if plugin.get_id() in enabled_plugins else _('disabled')}
72 72 </span>
73 73 </td>
74 74 <td>${plugin.get_display_name()}</td>
75 75 <td>
76 76 % if plugin.docs():
77 77 <a href="${plugin.docs()}">docs</a>
78 78 % endif
79 79 </td>
80 80 <td>${plugin.get_id()}</td>
81 81 </tr>
82 82 %endfor
83 83 </table>
84 84
85 85 <div class="buttons">
86 86 ${h.submit('save',_('Save'),class_="btn")}
87 87 </div>
88 88 </div>
89 89 </div>
90 90 ${h.end_form()}
91 91 </div>
92 92 </div>
93 93 </div>
94 94
95 95 <script>
96 96 $('.toggle-plugin').click(function(e){
97 97 var auth_plugins_input = $('#auth_plugins');
98 98 var elems = [];
99 99
100 100 $.each(auth_plugins_input.val().split(',') , function (index, element) {
101 101 if (element !== "") {
102 102 elems.push(element.strip())
103 103 }
104 104 });
105 105
106 106 var cur_button = e.currentTarget;
107 107 var plugin_id = $(cur_button).attr('plugin_id');
108 108 if($(cur_button).hasClass('btn-success')){
109 109 elems.splice(elems.indexOf(plugin_id), 1);
110 110 auth_plugins_input.val(elems.join(',\n'));
111 111 $(cur_button).removeClass('btn-success');
112 112 cur_button.innerHTML = _gettext('disabled');
113 113 }
114 114 else{
115 115 if(elems.indexOf(plugin_id) == -1){
116 116 elems.push(plugin_id);
117 117 }
118 118 auth_plugins_input.val(elems.join(',\n'));
119 119 $(cur_button).addClass('btn-success');
120 120 cur_button.innerHTML = _gettext('enabled');
121 121 }
122 122 });
123 123 </script>
124 124 </%def>
@@ -1,147 +1,149 b''
1 1 ## -*- coding: utf-8 -*-
2 2 <%inherit file="base/root.mako"/>
3 3
4 4 <%def name="title()">
5 5 ${_('Create an Account')}
6 6 %if c.rhodecode_name:
7 7 &middot; ${h.branding(c.rhodecode_name)}
8 8 %endif
9 9 </%def>
10 10 <style>body{background-color:#eeeeee;}</style>
11 11
12 12 <div class="loginbox">
13 13 <div class="header">
14 14 <div id="header-inner" class="title">
15 15 <div id="logo">
16 16 <div class="logo-wrapper">
17 17 <a href="${h.route_path('home')}"><img src="${h.asset('images/rhodecode-logo-white-216x60.png')}" alt="RhodeCode"/></a>
18 18 </div>
19 19 %if c.rhodecode_name:
20 20 <div class="branding">- ${h.branding(c.rhodecode_name)}</div>
21 21 %endif
22 22 </div>
23 23 </div>
24 24 </div>
25 25
26 26 <div class="loginwrapper">
27 27 <rhodecode-toast id="notifications"></rhodecode-toast>
28 28 <div class="left-column">
29 29 <img class="sign-in-image" src="${h.asset('images/sign-in.png')}" alt="RhodeCode"/>
30 30 </div>
31 31 <%block name="above_register_button" />
32 32 <div id="register" class="right-column">
33 33 <!-- login -->
34 34 <div class="sign-in-title">
35 % if social_auth_provider:
36 <h1>${_('Create an account linked with {}').format(social_auth_provider)}</h1>
35 % if external_auth_provider:
36 <h1>${_('Create an account linked with {}').format(external_auth_provider)}</h1>
37 37 % else:
38 38 <h1>${_('Create an account')}</h1>
39 39 % endif
40 40
41 41 <h4>${h.link_to(_("Go to the login page to sign in with an existing account."), request.route_path('login'))}</h4>
42 42 </div>
43 43 <div class="inner form">
44 44 ${h.form(request.route_path('register'), needs_csrf_token=False)}
45 45
46 46 <label for="username">${_('Username')}:</label>
47 47 ${h.text('username', defaults.get('username'))}
48 48 %if 'username' in errors:
49 49 <span class="error-message">${errors.get('username')}</span>
50 50 <br />
51 51 %endif
52 52
53 % if social_auth_provider:
53 % if external_auth_provider:
54 ## store internal marker about external identity
55 ${h.hidden('external_identity', external_auth_provider)}
54 56 ## hide password prompts for social auth
55 57 <div style="display: none">
56 58 % endif
57 59
58 60 <label for="password">${_('Password')}:</label>
59 61 ${h.password('password', defaults.get('password'))}
60 62 %if 'password' in errors:
61 63 <span class="error-message">${errors.get('password')}</span>
62 64 <br />
63 65 %endif
64 66
65 67 <label for="password_confirmation">${_('Re-enter password')}:</label>
66 68 ${h.password('password_confirmation', defaults.get('password_confirmation'))}
67 69 %if 'password_confirmation' in errors:
68 70 <span class="error-message">${errors.get('password_confirmation')}</span>
69 71 <br />
70 72 %endif
71 73
72 % if social_auth_provider:
74 % if external_auth_provider:
73 75 ## hide password prompts for social auth
74 76 </div>
75 77 % endif
76 78
77 79 <label for="firstname">${_('First Name')}:</label>
78 80 ${h.text('firstname', defaults.get('firstname'))}
79 81 %if 'firstname' in errors:
80 82 <span class="error-message">${errors.get('firstname')}</span>
81 83 <br />
82 84 %endif
83 85
84 86 <label for="lastname">${_('Last Name')}:</label>
85 87 ${h.text('lastname', defaults.get('lastname'))}
86 88 %if 'lastname' in errors:
87 89 <span class="error-message">${errors.get('lastname')}</span>
88 90 <br />
89 91 %endif
90 92
91 93 <label for="email">${_('Email')}:</label>
92 94 ${h.text('email', defaults.get('email'))}
93 95 %if 'email' in errors:
94 96 <span class="error-message">${errors.get('email')}</span>
95 97 <br />
96 98 %endif
97 99
98 100 %if captcha_active:
99 101 <div>
100 102 <label for="recaptcha">${_('Captcha')}:</label>
101 103 ${h.hidden('recaptcha_field')}
102 104 <div id="recaptcha"></div>
103 105 %if 'recaptcha_field' in errors:
104 106 <span class="error-message">${errors.get('recaptcha_field')}</span>
105 107 <br />
106 108 %endif
107 109 </div>
108 110 %endif
109 111
110 112 %if not auto_active:
111 113 <p class="activation_msg">
112 114 ${_('Account activation requires admin approval.')}
113 115 </p>
114 116 %endif
115 117 <p class="register_message">
116 118 ${register_message|n}
117 119 </p>
118 120
119 121 ${h.submit('sign_up',_('Create Account'),class_="btn sign-in")}
120 122 <p class="help-block pull-right">
121 123 RhodeCode ${c.rhodecode_edition}
122 124 </p>
123 125 ${h.end_form()}
124 126 </div>
125 127 <%block name="below_register_button" />
126 128 </div>
127 129 </div>
128 130 </div>
129 131
130 132
131 133 <script type="text/javascript">
132 134 $(document).ready(function(){
133 135 $('#username').focus();
134 136 });
135 137 </script>
136 138
137 139 % if captcha_active:
138 140 <script type="text/javascript">
139 141 var onloadCallback = function() {
140 142 grecaptcha.render('recaptcha', {
141 143 'sitekey' : "${captcha_public_key}"
142 144 });
143 145 };
144 146 </script>
145 147 <script src="https://www.google.com/recaptcha/api.js?onload=onloadCallback&render=explicit" async defer></script>
146 148 % endif
147 149
@@ -1,109 +1,109 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2018 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 """
22 22 External module for testing plugins
23 23
24 24 rhodecode.tests.auth_external_test
25 25
26 26 """
27 27 import logging
28 28 import traceback
29 29
30 30 from rhodecode.authentication.base import (
31 31 RhodeCodeExternalAuthPlugin, hybrid_property)
32 32 from rhodecode.model.db import User
33 33 from rhodecode.lib.ext_json import formatted_json
34 34
35 35 log = logging.getLogger(__name__)
36 36
37 37
38 38 class RhodeCodeAuthPlugin(RhodeCodeExternalAuthPlugin):
39 39 def __init__(self):
40 40 self._logger = logging.getLogger(__name__)
41 41
42 42 @hybrid_property
43 43 def allows_creating_users(self):
44 44 return True
45 45
46 46 @hybrid_property
47 47 def name(self):
48 return "external_test"
48 return u"external_test"
49 49
50 50 def settings(self):
51 51 settings = [
52 52 ]
53 53 return settings
54 54
55 55 def use_fake_password(self):
56 56 return True
57 57
58 58 def user_activation_state(self):
59 59 def_user_perms = User.get_default_user().AuthUser().permissions['global']
60 60 return 'hg.extern_activate.auto' in def_user_perms
61 61
62 62 def auth(self, userobj, username, password, settings, **kwargs):
63 63 """
64 64 Given a user object (which may be null), username, a plaintext password,
65 65 and a settings object (containing all the keys needed as listed in settings()),
66 66 authenticate this user's login attempt.
67 67
68 68 Return None on failure. On success, return a dictionary of the form:
69 69
70 70 see: RhodeCodeAuthPluginBase.auth_func_attrs
71 71 This is later validated for correctness
72 72 """
73 73
74 74 if not username or not password:
75 75 log.debug('Empty username or password skipping...')
76 76 return None
77 77
78 78 try:
79 79 user_dn = username
80 80
81 81 # # old attrs fetched from RhodeCode database
82 82 admin = getattr(userobj, 'admin', False)
83 83 active = getattr(userobj, 'active', True)
84 84 email = getattr(userobj, 'email', '')
85 85 firstname = getattr(userobj, 'firstname', '')
86 86 lastname = getattr(userobj, 'lastname', '')
87 87 extern_type = getattr(userobj, 'extern_type', '')
88 88 #
89 89 user_attrs = {
90 90 'username': username,
91 91 'firstname': firstname,
92 92 'lastname': lastname,
93 93 'groups': [],
94 94 'email': '%s@rhodecode.com' % username,
95 95 'admin': admin,
96 96 'active': active,
97 97 "active_from_extern": None,
98 98 'extern_name': user_dn,
99 99 'extern_type': extern_type,
100 100 }
101 101
102 102 log.debug('EXTERNAL user: \n%s', formatted_json(user_attrs))
103 103 log.info('user `%s` authenticated correctly', user_attrs['username'])
104 104
105 105 return user_attrs
106 106
107 107 except (Exception,):
108 108 log.error(traceback.format_exc())
109 109 return None
@@ -1,189 +1,189 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2018 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 import mock
22 22 import pytest
23 23
24 24 from rhodecode.lib.auth import _RhodeCodeCryptoBCrypt
25 25 from rhodecode.authentication.base import RhodeCodeAuthPluginBase
26 26 from rhodecode.authentication.plugins.auth_ldap import RhodeCodeAuthPlugin
27 27 from rhodecode.model import db
28 28
29 29
30 30 class RcTestAuthPlugin(RhodeCodeAuthPluginBase):
31 31
32 32 def name(self):
33 return 'stub_auth'
33 return u'stub_auth'
34 34
35 35
36 36 def test_authenticate_returns_from_auth(stub_auth_data):
37 37 plugin = RcTestAuthPlugin('stub_id')
38 38 with mock.patch.object(plugin, 'auth') as auth_mock:
39 39 auth_mock.return_value = stub_auth_data
40 40 result = plugin._authenticate(mock.Mock(), 'test', 'password', {})
41 41 assert stub_auth_data == result
42 42
43 43
44 44 def test_authenticate_returns_empty_auth_data():
45 45 auth_data = {}
46 46 plugin = RcTestAuthPlugin('stub_id')
47 47 with mock.patch.object(plugin, 'auth') as auth_mock:
48 48 auth_mock.return_value = auth_data
49 49 result = plugin._authenticate(mock.Mock(), 'test', 'password', {})
50 50 assert auth_data == result
51 51
52 52
53 53 def test_authenticate_skips_hash_migration_if_mismatch(stub_auth_data):
54 54 stub_auth_data['_hash_migrate'] = 'new-hash'
55 55 plugin = RcTestAuthPlugin('stub_id')
56 56 with mock.patch.object(plugin, 'auth') as auth_mock:
57 57 auth_mock.return_value = stub_auth_data
58 58 result = plugin._authenticate(mock.Mock(), 'test', 'password', {})
59 59
60 60 user = db.User.get_by_username(stub_auth_data['username'])
61 61 assert user.password != 'new-hash'
62 62 assert result == stub_auth_data
63 63
64 64
65 65 def test_authenticate_migrates_to_new_hash(stub_auth_data):
66 66 new_password = b'new-password'
67 67 new_hash = _RhodeCodeCryptoBCrypt().hash_create(new_password)
68 68 stub_auth_data['_hash_migrate'] = new_hash
69 69 plugin = RcTestAuthPlugin('stub_id')
70 70 with mock.patch.object(plugin, 'auth') as auth_mock:
71 71 auth_mock.return_value = stub_auth_data
72 72 result = plugin._authenticate(
73 73 mock.Mock(), stub_auth_data['username'], new_password, {})
74 74
75 75 user = db.User.get_by_username(stub_auth_data['username'])
76 76 assert user.password == new_hash
77 77 assert result == stub_auth_data
78 78
79 79
80 80 @pytest.fixture
81 81 def stub_auth_data(user_util):
82 82 user = user_util.create_user()
83 83 data = {
84 84 'username': user.username,
85 85 'password': 'password',
86 86 'email': 'test@example.org',
87 87 'firstname': 'John',
88 88 'lastname': 'Smith',
89 89 'groups': [],
90 90 'active': True,
91 91 'admin': False,
92 92 'extern_name': 'test',
93 93 'extern_type': 'ldap',
94 94 'active_from_extern': True
95 95 }
96 96 return data
97 97
98 98
99 99 class TestRhodeCodeAuthPlugin(object):
100 100 def setup_method(self, method):
101 101 self.finalizers = []
102 102 self.user = mock.Mock()
103 103 self.user.username = 'test'
104 104 self.user.password = 'old-password'
105 105 self.fake_auth = {
106 106 'username': 'test',
107 107 'password': 'test',
108 108 'email': 'test@example.org',
109 109 'firstname': 'John',
110 110 'lastname': 'Smith',
111 111 'groups': [],
112 112 'active': True,
113 113 'admin': False,
114 114 'extern_name': 'test',
115 115 'extern_type': 'ldap',
116 116 'active_from_extern': True
117 117 }
118 118
119 119 def teardown_method(self, method):
120 120 if self.finalizers:
121 121 for finalizer in self.finalizers:
122 122 finalizer()
123 123 self.finalizers = []
124 124
125 125 def test_fake_password_is_created_for_the_new_user(self):
126 126 self._patch()
127 127 auth_plugin = RhodeCodeAuthPlugin('stub_id')
128 128 auth_plugin._authenticate(self.user, 'test', 'test', [])
129 129 self.password_generator_mock.assert_called_once_with(length=16)
130 130 create_user_kwargs = self.create_user_mock.call_args[1]
131 131 assert create_user_kwargs['password'] == 'new-password'
132 132
133 133 def test_fake_password_is_not_created_for_the_existing_user(self):
134 134 self._patch()
135 135 self.get_user_mock.return_value = self.user
136 136 auth_plugin = RhodeCodeAuthPlugin('stub_id')
137 137 auth_plugin._authenticate(self.user, 'test', 'test', [])
138 138 assert self.password_generator_mock.called is False
139 139 create_user_kwargs = self.create_user_mock.call_args[1]
140 140 assert create_user_kwargs['password'] == self.user.password
141 141
142 142 def _patch(self):
143 143 get_user_patch = mock.patch('rhodecode.model.db.User.get_by_username')
144 144 self.get_user_mock = get_user_patch.start()
145 145 self.get_user_mock.return_value = None
146 146 self.finalizers.append(get_user_patch.stop)
147 147
148 148 create_user_patch = mock.patch(
149 149 'rhodecode.model.user.UserModel.create_or_update')
150 150 self.create_user_mock = create_user_patch.start()
151 151 self.create_user_mock.return_value = None
152 152 self.finalizers.append(create_user_patch.stop)
153 153
154 154 auth_patch = mock.patch.object(RhodeCodeAuthPlugin, 'auth')
155 155 self.auth_mock = auth_patch.start()
156 156 self.auth_mock.return_value = self.fake_auth
157 157 self.finalizers.append(auth_patch.stop)
158 158
159 159 password_generator_patch = mock.patch(
160 160 'rhodecode.lib.auth.PasswordGenerator.gen_password')
161 161 self.password_generator_mock = password_generator_patch.start()
162 162 self.password_generator_mock.return_value = 'new-password'
163 163 self.finalizers.append(password_generator_patch.stop)
164 164
165 165
166 166 def test_missing_ldap():
167 167 from rhodecode.model.validators import Missing
168 168
169 169 try:
170 170 import ldap_not_existing
171 171 except ImportError:
172 172 # means that python-ldap is not installed
173 173 ldap_not_existing = Missing
174 174
175 175 # missing is singleton
176 176 assert ldap_not_existing == Missing
177 177
178 178
179 179 def test_import_ldap():
180 180 from rhodecode.model.validators import Missing
181 181
182 182 try:
183 183 import ldap
184 184 except ImportError:
185 185 # means that python-ldap is not installed
186 186 ldap = Missing
187 187
188 188 # missing is singleton
189 189 assert False is (ldap == Missing)
General Comments 0
You need to be logged in to leave comments. Login now