##// END OF EJS Templates
my-account-auth-tokens: moved into pyramid apps....
marcink -
r1505:0cb9b007 default
parent child Browse files
Show More
@@ -0,0 +1,39 b''
1 # -*- coding: utf-8 -*-
2
3 # Copyright (C) 2016-2017 RhodeCode GmbH
4 #
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
7 # (only), as published by the Free Software Foundation.
8 #
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
13 #
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/>.
16 #
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
21
22 from rhodecode.apps._base import ADMIN_PREFIX
23
24
25 def includeme(config):
26 config.add_route(
27 name='my_account_auth_tokens',
28 pattern=ADMIN_PREFIX + '/my_account/auth_tokens')
29 config.add_route(
30 name='my_account_auth_tokens_add',
31 pattern=ADMIN_PREFIX + '/my_account/auth_tokens/new',
32 )
33 config.add_route(
34 name='my_account_auth_tokens_delete',
35 pattern=ADMIN_PREFIX + '/my_account/auth_tokens/delete',
36 )
37
38 # Scan module for configuration decorators.
39 config.scan()
@@ -0,0 +1,19 b''
1 # -*- coding: utf-8 -*-
2
3 # Copyright (C) 2016-2017 RhodeCode GmbH
4 #
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
7 # (only), as published by the Free Software Foundation.
8 #
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
13 #
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/>.
16 #
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
@@ -0,0 +1,111 b''
1 # -*- coding: utf-8 -*-
2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 #
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
7 # (only), as published by the Free Software Foundation.
8 #
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
13 #
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/>.
16 #
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
21 import pytest
22
23 from rhodecode.apps._base import ADMIN_PREFIX
24 from rhodecode.model.db import User
25 from rhodecode.tests import (
26 TestController, TEST_USER_ADMIN_LOGIN, TEST_USER_ADMIN_PASS,
27 TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS, assert_session_flash)
28 from rhodecode.tests.fixture import Fixture
29 from rhodecode.tests.utils import AssertResponse
30
31 fixture = Fixture()
32
33
34 def route_path(name, **kwargs):
35 return {
36 'my_account_auth_tokens':
37 ADMIN_PREFIX + '/my_account/auth_tokens',
38 'my_account_auth_tokens_add':
39 ADMIN_PREFIX + '/my_account/auth_tokens/new',
40 'my_account_auth_tokens_delete':
41 ADMIN_PREFIX + '/my_account/auth_tokens/delete',
42 }[name].format(**kwargs)
43
44
45 class TestMyAccountAuthTokens(TestController):
46
47 def test_my_account_auth_tokens(self):
48 usr = self.log_user('test_regular2', 'test12')
49 user = User.get(usr['user_id'])
50 response = self.app.get(route_path('my_account_auth_tokens'))
51 for token in user.auth_tokens:
52 response.mustcontain(token)
53 response.mustcontain('never')
54
55 def test_my_account_add_auth_tokens_wrong_csrf(self, user_util):
56 user = user_util.create_user(password='qweqwe')
57 self.log_user(user.username, 'qweqwe')
58
59 self.app.post(
60 route_path('my_account_auth_tokens_add'),
61 {'description': 'desc', 'lifetime': -1}, status=403)
62
63 @pytest.mark.parametrize("desc, lifetime", [
64 ('forever', -1),
65 ('5mins', 60*5),
66 ('30days', 60*60*24*30),
67 ])
68 def test_my_account_add_auth_tokens(self, desc, lifetime, user_util):
69 user = user_util.create_user(password='qweqwe')
70 user_id = user.user_id
71 self.log_user(user.username, 'qweqwe')
72
73 response = self.app.post(
74 route_path('my_account_auth_tokens_add'),
75 {'description': desc, 'lifetime': lifetime,
76 'csrf_token': self.csrf_token})
77 assert_session_flash(response, 'Auth token successfully created')
78
79 response = response.follow()
80 user = User.get(user_id)
81 for auth_token in user.auth_tokens:
82 response.mustcontain(auth_token)
83
84 def test_my_account_delete_auth_token(self, user_util):
85 user = user_util.create_user(password='qweqwe')
86 user_id = user.user_id
87 self.log_user(user.username, 'qweqwe')
88
89 user = User.get(user_id)
90 keys = user.extra_auth_tokens
91 assert 2 == len(keys)
92
93 response = self.app.post(
94 route_path('my_account_auth_tokens_add'),
95 {'description': 'desc', 'lifetime': -1,
96 'csrf_token': self.csrf_token})
97 assert_session_flash(response, 'Auth token successfully created')
98 response.follow()
99
100 user = User.get(user_id)
101 keys = user.extra_auth_tokens
102 assert 3 == len(keys)
103
104 response = self.app.post(
105 route_path('my_account_auth_tokens_delete'),
106 {'del_auth_token': keys[0].api_key, 'csrf_token': self.csrf_token})
107 assert_session_flash(response, 'Auth token successfully deleted')
108
109 user = User.get(user_id)
110 keys = user.extra_auth_tokens
111 assert 2 == len(keys)
@@ -0,0 +1,111 b''
1 # -*- coding: utf-8 -*-
2
3 # Copyright (C) 2016-2017 RhodeCode GmbH
4 #
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
7 # (only), as published by the Free Software Foundation.
8 #
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
13 #
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/>.
16 #
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
21 import logging
22
23 from pyramid.httpexceptions import HTTPFound
24 from pyramid.view import view_config
25
26 from rhodecode.apps._base import BaseAppView
27 from rhodecode.lib.auth import LoginRequired, NotAnonymous, CSRFRequired
28 from rhodecode.lib.utils2 import safe_int
29 from rhodecode.lib import helpers as h
30 from rhodecode.model.auth_token import AuthTokenModel
31 from rhodecode.model.meta import Session
32
33 log = logging.getLogger(__name__)
34
35
36 class MyAccountView(BaseAppView):
37
38 def load_default_context(self):
39 c = self._get_local_tmpl_context()
40
41 c.auth_user = self.request.user
42 c.user = c.auth_user.get_instance()
43
44 self._register_global_c(c)
45 return c
46
47 @LoginRequired()
48 @NotAnonymous()
49 @view_config(
50 route_name='my_account_auth_tokens', request_method='GET',
51 renderer='rhodecode:templates/admin/my_account/my_account.mako')
52 def my_account_auth_tokens(self):
53 _ = self.request.translate
54
55 c = self.load_default_context()
56 c.active = 'auth_tokens'
57
58 show_expired = True
59
60 c.lifetime_values = [
61 (str(-1), _('forever')),
62 (str(5), _('5 minutes')),
63 (str(60), _('1 hour')),
64 (str(60 * 24), _('1 day')),
65 (str(60 * 24 * 30), _('1 month')),
66 ]
67 c.lifetime_options = [(c.lifetime_values, _("Lifetime"))]
68 c.role_values = [
69 (x, AuthTokenModel.cls._get_role_name(x))
70 for x in AuthTokenModel.cls.ROLES]
71 c.role_options = [(c.role_values, _("Role"))]
72 c.user_auth_tokens = AuthTokenModel().get_auth_tokens(
73 c.user.user_id, show_expired=show_expired)
74 return self._get_template_context(c)
75
76 @LoginRequired()
77 @NotAnonymous()
78 @CSRFRequired()
79 @view_config(
80 route_name='my_account_auth_tokens_add', request_method='POST')
81 def my_account_auth_tokens_add(self):
82 _ = self.request.translate
83 c = self.load_default_context()
84
85 lifetime = safe_int(self.request.POST.get('lifetime'), -1)
86 description = self.request.POST.get('description')
87 role = self.request.POST.get('role')
88
89 AuthTokenModel().create(c.user.user_id, description, lifetime, role)
90 Session().commit()
91 h.flash(_("Auth token successfully created"), category='success')
92
93 return HTTPFound(h.route_path('my_account_auth_tokens'))
94
95 @LoginRequired()
96 @NotAnonymous()
97 @CSRFRequired()
98 @view_config(
99 route_name='my_account_auth_tokens_delete', request_method='POST')
100 def my_account_auth_tokens_delete(self):
101 _ = self.request.translate
102 c = self.load_default_context()
103
104 del_auth_token = self.request.POST.get('del_auth_token')
105
106 if del_auth_token:
107 AuthTokenModel().delete(del_auth_token, c.user.user_id)
108 Session().commit()
109 h.flash(_("Auth token successfully deleted"), category='success')
110
111 return HTTPFound(h.route_path('my_account_auth_tokens'))
@@ -26,6 +26,10 b' from rhodecode.lib.utils2 import StrictA'
26 26 log = logging.getLogger(__name__)
27 27
28 28
29 ADMIN_PREFIX = '/_admin'
30 STATIC_FILE_PREFIX = '/_static'
31
32
29 33 class TemplateArgs(StrictAttributeDict):
30 34 pass
31 35
@@ -41,10 +45,17 b' class BaseAppView(object):'
41 45 def _get_local_tmpl_context(self):
42 46 return TemplateArgs()
43 47
48 def _register_global_c(self, tmpl_args):
49 """
50 Registers attributes to pylons global `c`
51 """
52 # TODO(marcink): remove once pyramid migration is finished
53 for k, v in tmpl_args.items():
54 setattr(c, k, v)
55
44 56 def _get_template_context(self, tmpl_args):
45 57
46 for k, v in tmpl_args.items():
47 setattr(c, k, v)
58 self._register_global_c(tmpl_args)
48 59
49 60 return {
50 61 'defaults': {},
@@ -0,0 +1,19 b''
1 # -*- coding: utf-8 -*-
2
3 # Copyright (C) 2016-2017 RhodeCode GmbH
4 #
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
7 # (only), as published by the Free Software Foundation.
8 #
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
13 #
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/>.
16 #
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
@@ -286,6 +286,7 b' def includeme(config):'
286 286 config.include('rhodecode.apps.channelstream')
287 287 config.include('rhodecode.apps.login')
288 288 config.include('rhodecode.apps.user_profile')
289 config.include('rhodecode.apps.my_account')
289 290
290 291 config.include('rhodecode.tweens')
291 292 config.include('rhodecode.api')
@@ -543,12 +543,6 b' def make_map(config):'
543 543 m.connect('my_account_emails', '/my_account/emails',
544 544 action='my_account_emails_delete', conditions={'method': ['DELETE']})
545 545
546 m.connect('my_account_auth_tokens', '/my_account/auth_tokens',
547 action='my_account_auth_tokens', conditions={'method': ['GET']})
548 m.connect('my_account_auth_tokens', '/my_account/auth_tokens',
549 action='my_account_auth_tokens_add', conditions={'method': ['POST']})
550 m.connect('my_account_auth_tokens', '/my_account/auth_tokens',
551 action='my_account_auth_tokens_delete', conditions={'method': ['DELETE']})
552 546 m.connect('my_account_notifications', '/my_account/notifications',
553 547 action='my_notifications',
554 548 conditions={'method': ['GET']})
@@ -33,13 +33,12 b' from pylons import request, tmpl_context'
33 33 from pylons.controllers.util import redirect
34 34 from pylons.i18n.translation import _
35 35 from sqlalchemy.orm import joinedload
36 from webob.exc import HTTPBadGateway
37 36
38 37 from rhodecode import forms
39 38 from rhodecode.lib import helpers as h
40 39 from rhodecode.lib import auth
41 40 from rhodecode.lib.auth import (
42 LoginRequired, NotAnonymous, AuthUser, generate_auth_token)
41 LoginRequired, NotAnonymous, AuthUser)
43 42 from rhodecode.lib.base import BaseController, render
44 43 from rhodecode.lib.utils import jsonify
45 44 from rhodecode.lib.utils2 import safe_int, md5, str2bool
@@ -54,7 +53,6 b' from rhodecode.model.forms import UserFo'
54 53 from rhodecode.model.scm import RepoList
55 54 from rhodecode.model.user import UserModel
56 55 from rhodecode.model.repo import RepoModel
57 from rhodecode.model.auth_token import AuthTokenModel
58 56 from rhodecode.model.meta import Session
59 57 from rhodecode.model.pull_request import PullRequestModel
60 58 from rhodecode.model.comment import CommentsModel
@@ -376,47 +374,6 b' class MyAccountController(BaseController'
376 374 else:
377 375 return json.dumps(data)
378 376
379 def my_account_auth_tokens(self):
380 c.active = 'auth_tokens'
381 self.__load_data()
382 show_expired = True
383 c.lifetime_values = [
384 (str(-1), _('forever')),
385 (str(5), _('5 minutes')),
386 (str(60), _('1 hour')),
387 (str(60 * 24), _('1 day')),
388 (str(60 * 24 * 30), _('1 month')),
389 ]
390 c.lifetime_options = [(c.lifetime_values, _("Lifetime"))]
391 c.role_values = [(x, AuthTokenModel.cls._get_role_name(x))
392 for x in AuthTokenModel.cls.ROLES]
393 c.role_options = [(c.role_values, _("Role"))]
394 c.user_auth_tokens = AuthTokenModel().get_auth_tokens(
395 c.rhodecode_user.user_id, show_expired=show_expired)
396 return render('admin/my_account/my_account.mako')
397
398 @auth.CSRFRequired()
399 def my_account_auth_tokens_add(self):
400 lifetime = safe_int(request.POST.get('lifetime'), -1)
401 description = request.POST.get('description')
402 role = request.POST.get('role')
403 AuthTokenModel().create(c.rhodecode_user.user_id, description, lifetime,
404 role)
405 Session().commit()
406 h.flash(_("Auth token successfully created"), category='success')
407 return redirect(url('my_account_auth_tokens'))
408
409 @auth.CSRFRequired()
410 def my_account_auth_tokens_delete(self):
411 del_auth_token = request.POST.get('del_auth_token')
412
413 if del_auth_token:
414 AuthTokenModel().delete(del_auth_token, c.rhodecode_user.user_id)
415 Session().commit()
416 h.flash(_("Auth token successfully deleted"), category='success')
417
418 return redirect(url('my_account_auth_tokens'))
419
420 377 def my_notifications(self):
421 378 c.active = 'notifications'
422 379 return render('admin/my_account/my_account.mako')
@@ -28,7 +28,7 b''
28 28 <ul class="nav nav-pills nav-stacked">
29 29 <li class="${'active' if c.active=='profile' or c.active=='profile_edit' else ''}"><a href="${h.url('my_account')}">${_('Profile')}</a></li>
30 30 <li class="${'active' if c.active=='password' else ''}"><a href="${h.url('my_account_password')}">${_('Password')}</a></li>
31 <li class="${'active' if c.active=='auth_tokens' else ''}"><a href="${h.url('my_account_auth_tokens')}">${_('Auth Tokens')}</a></li>
31 <li class="${'active' if c.active=='auth_tokens' else ''}"><a href="${h.route_path('my_account_auth_tokens')}">${_('Auth Tokens')}</a></li>
32 32 ## TODO: Find a better integration of oauth views into navigation.
33 33 <% my_account_oauth_url = h.route_path_or_none('my_account_oauth') %>
34 34 % if my_account_oauth_url:
@@ -42,7 +42,7 b''
42 42 %endif
43 43 </td>
44 44 <td class="td-action">
45 ${h.secure_form(url('my_account_auth_tokens'),method='delete')}
45 ${h.secure_form(h.route_path('my_account_auth_tokens_delete'), method='post')}
46 46 ${h.hidden('del_auth_token',auth_token.api_key)}
47 47 <button class="btn btn-link btn-danger" type="submit"
48 48 onclick="return confirm('${_('Confirm to remove this auth token: %s') % auth_token.api_key}');">
@@ -58,7 +58,7 b''
58 58 </table>
59 59
60 60 <div class="user_auth_tokens">
61 ${h.secure_form(url('my_account_auth_tokens'), method='post')}
61 ${h.secure_form(h.route_path('my_account_auth_tokens_add'), method='post')}
62 62 <div class="form form-vertical">
63 63 <!-- fields -->
64 64 <div class="fields">
@@ -254,63 +254,6 b' class TestMyAccountController(TestContro'
254 254 msg = h.html_escape(msg % {'username': 'test_admin'})
255 255 response.mustcontain(u"%s" % msg)
256 256
257 def test_my_account_auth_tokens(self):
258 usr = self.log_user('test_regular2', 'test12')
259 user = User.get(usr['user_id'])
260 response = self.app.get(url('my_account_auth_tokens'))
261 for token in user.auth_tokens:
262 response.mustcontain(token)
263 response.mustcontain('never')
264
265 @pytest.mark.parametrize("desc, lifetime", [
266 ('forever', -1),
267 ('5mins', 60*5),
268 ('30days', 60*60*24*30),
269 ])
270 def test_my_account_add_auth_tokens(self, desc, lifetime, user_util):
271 user = user_util.create_user(password='qweqwe')
272 user_id = user.user_id
273 self.log_user(user.username, 'qweqwe')
274
275 response = self.app.post(url('my_account_auth_tokens'),
276 {'description': desc, 'lifetime': lifetime,
277 'csrf_token': self.csrf_token})
278 assert_session_flash(response, 'Auth token successfully created')
279
280 response = response.follow()
281 user = User.get(user_id)
282 for auth_token in user.auth_tokens:
283 response.mustcontain(auth_token)
284
285 def test_my_account_remove_auth_token(self, user_util):
286 user = user_util.create_user(password='qweqwe')
287 user_id = user.user_id
288 self.log_user(user.username, 'qweqwe')
289
290 user = User.get(user_id)
291 keys = user.extra_auth_tokens
292 assert 2 == len(keys)
293
294 response = self.app.post(url('my_account_auth_tokens'),
295 {'description': 'desc', 'lifetime': -1,
296 'csrf_token': self.csrf_token})
297 assert_session_flash(response, 'Auth token successfully created')
298 response.follow()
299
300 user = User.get(user_id)
301 keys = user.extra_auth_tokens
302 assert 3 == len(keys)
303
304 response = self.app.post(
305 url('my_account_auth_tokens'),
306 {'_method': 'delete', 'del_auth_token': keys[0].api_key,
307 'csrf_token': self.csrf_token})
308 assert_session_flash(response, 'Auth token successfully deleted')
309
310 user = User.get(user_id)
311 keys = user.extra_auth_tokens
312 assert 2 == len(keys)
313
314 257 def test_valid_change_password(self, user_util):
315 258 new_password = 'my_new_valid_password'
316 259 user = user_util.create_user(password=self.test_user_1_password)
General Comments 0
You need to be logged in to leave comments. Login now