diff --git a/rhodecode/apps/_base/__init__.py b/rhodecode/apps/_base/__init__.py --- a/rhodecode/apps/_base/__init__.py +++ b/rhodecode/apps/_base/__init__.py @@ -26,6 +26,10 @@ from rhodecode.lib.utils2 import StrictA log = logging.getLogger(__name__) +ADMIN_PREFIX = '/_admin' +STATIC_FILE_PREFIX = '/_static' + + class TemplateArgs(StrictAttributeDict): pass @@ -41,10 +45,17 @@ class BaseAppView(object): def _get_local_tmpl_context(self): return TemplateArgs() + def _register_global_c(self, tmpl_args): + """ + Registers attributes to pylons global `c` + """ + # TODO(marcink): remove once pyramid migration is finished + for k, v in tmpl_args.items(): + setattr(c, k, v) + def _get_template_context(self, tmpl_args): - for k, v in tmpl_args.items(): - setattr(c, k, v) + self._register_global_c(tmpl_args) return { 'defaults': {}, diff --git a/rhodecode/apps/my_account/__init__.py b/rhodecode/apps/my_account/__init__.py new file mode 100644 --- /dev/null +++ b/rhodecode/apps/my_account/__init__.py @@ -0,0 +1,39 @@ +# -*- coding: utf-8 -*- + +# Copyright (C) 2016-2017 RhodeCode GmbH +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License, version 3 +# (only), as published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# +# This program is dual-licensed. If you wish to learn more about the +# RhodeCode Enterprise Edition, including its added features, Support services, +# and proprietary license terms, please see https://rhodecode.com/licenses/ + + +from rhodecode.apps._base import ADMIN_PREFIX + + +def includeme(config): + config.add_route( + name='my_account_auth_tokens', + pattern=ADMIN_PREFIX + '/my_account/auth_tokens') + config.add_route( + name='my_account_auth_tokens_add', + pattern=ADMIN_PREFIX + '/my_account/auth_tokens/new', + ) + config.add_route( + name='my_account_auth_tokens_delete', + pattern=ADMIN_PREFIX + '/my_account/auth_tokens/delete', + ) + + # Scan module for configuration decorators. + config.scan() diff --git a/rhodecode/apps/my_account/tests/__init__.py b/rhodecode/apps/my_account/tests/__init__.py new file mode 100644 --- /dev/null +++ b/rhodecode/apps/my_account/tests/__init__.py @@ -0,0 +1,19 @@ +# -*- coding: utf-8 -*- + +# Copyright (C) 2016-2017 RhodeCode GmbH +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License, version 3 +# (only), as published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# +# This program is dual-licensed. If you wish to learn more about the +# RhodeCode Enterprise Edition, including its added features, Support services, +# and proprietary license terms, please see https://rhodecode.com/licenses/ diff --git a/rhodecode/apps/my_account/tests/test_my_account_auth_tokens.py b/rhodecode/apps/my_account/tests/test_my_account_auth_tokens.py new file mode 100644 --- /dev/null +++ b/rhodecode/apps/my_account/tests/test_my_account_auth_tokens.py @@ -0,0 +1,111 @@ +# -*- coding: utf-8 -*- + +# Copyright (C) 2010-2017 RhodeCode GmbH +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License, version 3 +# (only), as published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# +# This program is dual-licensed. If you wish to learn more about the +# RhodeCode Enterprise Edition, including its added features, Support services, +# and proprietary license terms, please see https://rhodecode.com/licenses/ + +import pytest + +from rhodecode.apps._base import ADMIN_PREFIX +from rhodecode.model.db import User +from rhodecode.tests import ( + TestController, TEST_USER_ADMIN_LOGIN, TEST_USER_ADMIN_PASS, + TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS, assert_session_flash) +from rhodecode.tests.fixture import Fixture +from rhodecode.tests.utils import AssertResponse + +fixture = Fixture() + + +def route_path(name, **kwargs): + return { + 'my_account_auth_tokens': + ADMIN_PREFIX + '/my_account/auth_tokens', + 'my_account_auth_tokens_add': + ADMIN_PREFIX + '/my_account/auth_tokens/new', + 'my_account_auth_tokens_delete': + ADMIN_PREFIX + '/my_account/auth_tokens/delete', + }[name].format(**kwargs) + + +class TestMyAccountAuthTokens(TestController): + + def test_my_account_auth_tokens(self): + usr = self.log_user('test_regular2', 'test12') + user = User.get(usr['user_id']) + response = self.app.get(route_path('my_account_auth_tokens')) + for token in user.auth_tokens: + response.mustcontain(token) + response.mustcontain('never') + + def test_my_account_add_auth_tokens_wrong_csrf(self, user_util): + user = user_util.create_user(password='qweqwe') + self.log_user(user.username, 'qweqwe') + + self.app.post( + route_path('my_account_auth_tokens_add'), + {'description': 'desc', 'lifetime': -1}, status=403) + + @pytest.mark.parametrize("desc, lifetime", [ + ('forever', -1), + ('5mins', 60*5), + ('30days', 60*60*24*30), + ]) + def test_my_account_add_auth_tokens(self, desc, lifetime, user_util): + user = user_util.create_user(password='qweqwe') + user_id = user.user_id + self.log_user(user.username, 'qweqwe') + + response = self.app.post( + route_path('my_account_auth_tokens_add'), + {'description': desc, 'lifetime': lifetime, + 'csrf_token': self.csrf_token}) + assert_session_flash(response, 'Auth token successfully created') + + response = response.follow() + user = User.get(user_id) + for auth_token in user.auth_tokens: + response.mustcontain(auth_token) + + def test_my_account_delete_auth_token(self, user_util): + user = user_util.create_user(password='qweqwe') + user_id = user.user_id + self.log_user(user.username, 'qweqwe') + + user = User.get(user_id) + keys = user.extra_auth_tokens + assert 2 == len(keys) + + response = self.app.post( + route_path('my_account_auth_tokens_add'), + {'description': 'desc', 'lifetime': -1, + 'csrf_token': self.csrf_token}) + assert_session_flash(response, 'Auth token successfully created') + response.follow() + + user = User.get(user_id) + keys = user.extra_auth_tokens + assert 3 == len(keys) + + response = self.app.post( + route_path('my_account_auth_tokens_delete'), + {'del_auth_token': keys[0].api_key, 'csrf_token': self.csrf_token}) + assert_session_flash(response, 'Auth token successfully deleted') + + user = User.get(user_id) + keys = user.extra_auth_tokens + assert 2 == len(keys) diff --git a/rhodecode/apps/my_account/views.py b/rhodecode/apps/my_account/views.py new file mode 100644 --- /dev/null +++ b/rhodecode/apps/my_account/views.py @@ -0,0 +1,111 @@ +# -*- coding: utf-8 -*- + +# Copyright (C) 2016-2017 RhodeCode GmbH +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License, version 3 +# (only), as published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# +# This program is dual-licensed. If you wish to learn more about the +# RhodeCode Enterprise Edition, including its added features, Support services, +# and proprietary license terms, please see https://rhodecode.com/licenses/ + +import logging + +from pyramid.httpexceptions import HTTPFound +from pyramid.view import view_config + +from rhodecode.apps._base import BaseAppView +from rhodecode.lib.auth import LoginRequired, NotAnonymous, CSRFRequired +from rhodecode.lib.utils2 import safe_int +from rhodecode.lib import helpers as h +from rhodecode.model.auth_token import AuthTokenModel +from rhodecode.model.meta import Session + +log = logging.getLogger(__name__) + + +class MyAccountView(BaseAppView): + + def load_default_context(self): + c = self._get_local_tmpl_context() + + c.auth_user = self.request.user + c.user = c.auth_user.get_instance() + + self._register_global_c(c) + return c + + @LoginRequired() + @NotAnonymous() + @view_config( + route_name='my_account_auth_tokens', request_method='GET', + renderer='rhodecode:templates/admin/my_account/my_account.mako') + def my_account_auth_tokens(self): + _ = self.request.translate + + c = self.load_default_context() + c.active = 'auth_tokens' + + show_expired = True + + c.lifetime_values = [ + (str(-1), _('forever')), + (str(5), _('5 minutes')), + (str(60), _('1 hour')), + (str(60 * 24), _('1 day')), + (str(60 * 24 * 30), _('1 month')), + ] + c.lifetime_options = [(c.lifetime_values, _("Lifetime"))] + c.role_values = [ + (x, AuthTokenModel.cls._get_role_name(x)) + for x in AuthTokenModel.cls.ROLES] + c.role_options = [(c.role_values, _("Role"))] + c.user_auth_tokens = AuthTokenModel().get_auth_tokens( + c.user.user_id, show_expired=show_expired) + return self._get_template_context(c) + + @LoginRequired() + @NotAnonymous() + @CSRFRequired() + @view_config( + route_name='my_account_auth_tokens_add', request_method='POST') + def my_account_auth_tokens_add(self): + _ = self.request.translate + c = self.load_default_context() + + lifetime = safe_int(self.request.POST.get('lifetime'), -1) + description = self.request.POST.get('description') + role = self.request.POST.get('role') + + AuthTokenModel().create(c.user.user_id, description, lifetime, role) + Session().commit() + h.flash(_("Auth token successfully created"), category='success') + + return HTTPFound(h.route_path('my_account_auth_tokens')) + + @LoginRequired() + @NotAnonymous() + @CSRFRequired() + @view_config( + route_name='my_account_auth_tokens_delete', request_method='POST') + def my_account_auth_tokens_delete(self): + _ = self.request.translate + c = self.load_default_context() + + del_auth_token = self.request.POST.get('del_auth_token') + + if del_auth_token: + AuthTokenModel().delete(del_auth_token, c.user.user_id) + Session().commit() + h.flash(_("Auth token successfully deleted"), category='success') + + return HTTPFound(h.route_path('my_account_auth_tokens')) diff --git a/rhodecode/apps/user_profile/tests/__init__.py b/rhodecode/apps/user_profile/tests/__init__.py --- a/rhodecode/apps/user_profile/tests/__init__.py +++ b/rhodecode/apps/user_profile/tests/__init__.py @@ -0,0 +1,19 @@ +# -*- coding: utf-8 -*- + +# Copyright (C) 2016-2017 RhodeCode GmbH +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License, version 3 +# (only), as published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# +# This program is dual-licensed. If you wish to learn more about the +# RhodeCode Enterprise Edition, including its added features, Support services, +# and proprietary license terms, please see https://rhodecode.com/licenses/ diff --git a/rhodecode/config/middleware.py b/rhodecode/config/middleware.py --- a/rhodecode/config/middleware.py +++ b/rhodecode/config/middleware.py @@ -286,6 +286,7 @@ def includeme(config): config.include('rhodecode.apps.channelstream') config.include('rhodecode.apps.login') config.include('rhodecode.apps.user_profile') + config.include('rhodecode.apps.my_account') config.include('rhodecode.tweens') config.include('rhodecode.api') diff --git a/rhodecode/config/routing.py b/rhodecode/config/routing.py --- a/rhodecode/config/routing.py +++ b/rhodecode/config/routing.py @@ -543,12 +543,6 @@ def make_map(config): m.connect('my_account_emails', '/my_account/emails', action='my_account_emails_delete', conditions={'method': ['DELETE']}) - m.connect('my_account_auth_tokens', '/my_account/auth_tokens', - action='my_account_auth_tokens', conditions={'method': ['GET']}) - m.connect('my_account_auth_tokens', '/my_account/auth_tokens', - action='my_account_auth_tokens_add', conditions={'method': ['POST']}) - m.connect('my_account_auth_tokens', '/my_account/auth_tokens', - action='my_account_auth_tokens_delete', conditions={'method': ['DELETE']}) m.connect('my_account_notifications', '/my_account/notifications', action='my_notifications', conditions={'method': ['GET']}) diff --git a/rhodecode/controllers/admin/my_account.py b/rhodecode/controllers/admin/my_account.py --- a/rhodecode/controllers/admin/my_account.py +++ b/rhodecode/controllers/admin/my_account.py @@ -33,13 +33,12 @@ from pylons import request, tmpl_context from pylons.controllers.util import redirect from pylons.i18n.translation import _ from sqlalchemy.orm import joinedload -from webob.exc import HTTPBadGateway from rhodecode import forms from rhodecode.lib import helpers as h from rhodecode.lib import auth from rhodecode.lib.auth import ( - LoginRequired, NotAnonymous, AuthUser, generate_auth_token) + LoginRequired, NotAnonymous, AuthUser) from rhodecode.lib.base import BaseController, render from rhodecode.lib.utils import jsonify from rhodecode.lib.utils2 import safe_int, md5, str2bool @@ -54,7 +53,6 @@ from rhodecode.model.forms import UserFo from rhodecode.model.scm import RepoList from rhodecode.model.user import UserModel from rhodecode.model.repo import RepoModel -from rhodecode.model.auth_token import AuthTokenModel from rhodecode.model.meta import Session from rhodecode.model.pull_request import PullRequestModel from rhodecode.model.comment import CommentsModel @@ -376,47 +374,6 @@ class MyAccountController(BaseController else: return json.dumps(data) - def my_account_auth_tokens(self): - c.active = 'auth_tokens' - self.__load_data() - show_expired = True - c.lifetime_values = [ - (str(-1), _('forever')), - (str(5), _('5 minutes')), - (str(60), _('1 hour')), - (str(60 * 24), _('1 day')), - (str(60 * 24 * 30), _('1 month')), - ] - c.lifetime_options = [(c.lifetime_values, _("Lifetime"))] - c.role_values = [(x, AuthTokenModel.cls._get_role_name(x)) - for x in AuthTokenModel.cls.ROLES] - c.role_options = [(c.role_values, _("Role"))] - c.user_auth_tokens = AuthTokenModel().get_auth_tokens( - c.rhodecode_user.user_id, show_expired=show_expired) - return render('admin/my_account/my_account.mako') - - @auth.CSRFRequired() - def my_account_auth_tokens_add(self): - lifetime = safe_int(request.POST.get('lifetime'), -1) - description = request.POST.get('description') - role = request.POST.get('role') - AuthTokenModel().create(c.rhodecode_user.user_id, description, lifetime, - role) - Session().commit() - h.flash(_("Auth token successfully created"), category='success') - return redirect(url('my_account_auth_tokens')) - - @auth.CSRFRequired() - def my_account_auth_tokens_delete(self): - del_auth_token = request.POST.get('del_auth_token') - - if del_auth_token: - AuthTokenModel().delete(del_auth_token, c.rhodecode_user.user_id) - Session().commit() - h.flash(_("Auth token successfully deleted"), category='success') - - return redirect(url('my_account_auth_tokens')) - def my_notifications(self): c.active = 'notifications' return render('admin/my_account/my_account.mako') diff --git a/rhodecode/templates/admin/my_account/my_account.mako b/rhodecode/templates/admin/my_account/my_account.mako --- a/rhodecode/templates/admin/my_account/my_account.mako +++ b/rhodecode/templates/admin/my_account/my_account.mako @@ -28,7 +28,7 @@