##// 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 log = logging.getLogger(__name__)
26 log = logging.getLogger(__name__)
27
27
28
28
29 ADMIN_PREFIX = '/_admin'
30 STATIC_FILE_PREFIX = '/_static'
31
32
29 class TemplateArgs(StrictAttributeDict):
33 class TemplateArgs(StrictAttributeDict):
30 pass
34 pass
31
35
@@ -41,10 +45,17 b' class BaseAppView(object):'
41 def _get_local_tmpl_context(self):
45 def _get_local_tmpl_context(self):
42 return TemplateArgs()
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 def _get_template_context(self, tmpl_args):
56 def _get_template_context(self, tmpl_args):
45
57
46 for k, v in tmpl_args.items():
58 self._register_global_c(tmpl_args)
47 setattr(c, k, v)
48
59
49 return {
60 return {
50 'defaults': {},
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 config.include('rhodecode.apps.channelstream')
286 config.include('rhodecode.apps.channelstream')
287 config.include('rhodecode.apps.login')
287 config.include('rhodecode.apps.login')
288 config.include('rhodecode.apps.user_profile')
288 config.include('rhodecode.apps.user_profile')
289 config.include('rhodecode.apps.my_account')
289
290
290 config.include('rhodecode.tweens')
291 config.include('rhodecode.tweens')
291 config.include('rhodecode.api')
292 config.include('rhodecode.api')
@@ -543,12 +543,6 b' def make_map(config):'
543 m.connect('my_account_emails', '/my_account/emails',
543 m.connect('my_account_emails', '/my_account/emails',
544 action='my_account_emails_delete', conditions={'method': ['DELETE']})
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 m.connect('my_account_notifications', '/my_account/notifications',
546 m.connect('my_account_notifications', '/my_account/notifications',
553 action='my_notifications',
547 action='my_notifications',
554 conditions={'method': ['GET']})
548 conditions={'method': ['GET']})
@@ -33,13 +33,12 b' from pylons import request, tmpl_context'
33 from pylons.controllers.util import redirect
33 from pylons.controllers.util import redirect
34 from pylons.i18n.translation import _
34 from pylons.i18n.translation import _
35 from sqlalchemy.orm import joinedload
35 from sqlalchemy.orm import joinedload
36 from webob.exc import HTTPBadGateway
37
36
38 from rhodecode import forms
37 from rhodecode import forms
39 from rhodecode.lib import helpers as h
38 from rhodecode.lib import helpers as h
40 from rhodecode.lib import auth
39 from rhodecode.lib import auth
41 from rhodecode.lib.auth import (
40 from rhodecode.lib.auth import (
42 LoginRequired, NotAnonymous, AuthUser, generate_auth_token)
41 LoginRequired, NotAnonymous, AuthUser)
43 from rhodecode.lib.base import BaseController, render
42 from rhodecode.lib.base import BaseController, render
44 from rhodecode.lib.utils import jsonify
43 from rhodecode.lib.utils import jsonify
45 from rhodecode.lib.utils2 import safe_int, md5, str2bool
44 from rhodecode.lib.utils2 import safe_int, md5, str2bool
@@ -54,7 +53,6 b' from rhodecode.model.forms import UserFo'
54 from rhodecode.model.scm import RepoList
53 from rhodecode.model.scm import RepoList
55 from rhodecode.model.user import UserModel
54 from rhodecode.model.user import UserModel
56 from rhodecode.model.repo import RepoModel
55 from rhodecode.model.repo import RepoModel
57 from rhodecode.model.auth_token import AuthTokenModel
58 from rhodecode.model.meta import Session
56 from rhodecode.model.meta import Session
59 from rhodecode.model.pull_request import PullRequestModel
57 from rhodecode.model.pull_request import PullRequestModel
60 from rhodecode.model.comment import CommentsModel
58 from rhodecode.model.comment import CommentsModel
@@ -376,47 +374,6 b' class MyAccountController(BaseController'
376 else:
374 else:
377 return json.dumps(data)
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 def my_notifications(self):
377 def my_notifications(self):
421 c.active = 'notifications'
378 c.active = 'notifications'
422 return render('admin/my_account/my_account.mako')
379 return render('admin/my_account/my_account.mako')
@@ -28,7 +28,7 b''
28 <ul class="nav nav-pills nav-stacked">
28 <ul class="nav nav-pills nav-stacked">
29 <li class="${'active' if c.active=='profile' or c.active=='profile_edit' else ''}"><a href="${h.url('my_account')}">${_('Profile')}</a></li>
29 <li class="${'active' if c.active=='profile' or c.active=='profile_edit' else ''}"><a href="${h.url('my_account')}">${_('Profile')}</a></li>
30 <li class="${'active' if c.active=='password' else ''}"><a href="${h.url('my_account_password')}">${_('Password')}</a></li>
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 ## TODO: Find a better integration of oauth views into navigation.
32 ## TODO: Find a better integration of oauth views into navigation.
33 <% my_account_oauth_url = h.route_path_or_none('my_account_oauth') %>
33 <% my_account_oauth_url = h.route_path_or_none('my_account_oauth') %>
34 % if my_account_oauth_url:
34 % if my_account_oauth_url:
@@ -42,7 +42,7 b''
42 %endif
42 %endif
43 </td>
43 </td>
44 <td class="td-action">
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 ${h.hidden('del_auth_token',auth_token.api_key)}
46 ${h.hidden('del_auth_token',auth_token.api_key)}
47 <button class="btn btn-link btn-danger" type="submit"
47 <button class="btn btn-link btn-danger" type="submit"
48 onclick="return confirm('${_('Confirm to remove this auth token: %s') % auth_token.api_key}');">
48 onclick="return confirm('${_('Confirm to remove this auth token: %s') % auth_token.api_key}');">
@@ -58,7 +58,7 b''
58 </table>
58 </table>
59
59
60 <div class="user_auth_tokens">
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 <div class="form form-vertical">
62 <div class="form form-vertical">
63 <!-- fields -->
63 <!-- fields -->
64 <div class="fields">
64 <div class="fields">
@@ -254,63 +254,6 b' class TestMyAccountController(TestContro'
254 msg = h.html_escape(msg % {'username': 'test_admin'})
254 msg = h.html_escape(msg % {'username': 'test_admin'})
255 response.mustcontain(u"%s" % msg)
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 def test_valid_change_password(self, user_util):
257 def test_valid_change_password(self, user_util):
315 new_password = 'my_new_valid_password'
258 new_password = 'my_new_valid_password'
316 user = user_util.create_user(password=self.test_user_1_password)
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