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