From 67edff73234732e69b145d5270d744c3fb8168ab Mon Sep 17 00:00:00 2001 From: Aashish Sharma Date: Tue, 24 Nov 2020 11:28:28 +0530 Subject: [PATCH] mgr/dashboard: Use secure cookies to store JWT Token This PR intends to store the jwt token in secure cookies instead of local storage Fixes: https://tracker.ceph.com/issues/44591 Signed-off-by: Aashish Sharma Signed-off-by: Avan Thakkar (cherry picked from commit 36703c63381e6723fff57266235f8230e6af1d92) (cherry picked from commit 3c72dc309936b23e413dc1aee8ca49c795c48a0f) Conflicts: qa/tasks/mgr/dashboard/helper.py qa/tasks/mgr/dashboard/test_auth.py src/pybind/mgr/dashboard/controllers/__init__.py src/pybind/mgr/dashboard/controllers/auth.py src/pybind/mgr/dashboard/controllers/saml2.py src/pybind/mgr/dashboard/frontend/cypress/integration/orchestrator/01-hosts.e2e-spec.ts src/pybind/mgr/dashboard/frontend/cypress/integration/orchestrator/02-hosts-inventory.e2e-spec.ts src/pybind/mgr/dashboard/frontend/cypress/integration/orchestrator/03-inventory.e2e-spec.ts src/pybind/mgr/dashboard/frontend/cypress/integration/orchestrator/04-osds.e2e-spec.ts src/pybind/mgr/dashboard/frontend/cypress/integration/ui/language.e2e-spec.ts src/pybind/mgr/dashboard/frontend/cypress/integration/ui/navigation.e2e-spec.ts src/pybind/mgr/dashboard/frontend/package-lock.json src/pybind/mgr/dashboard/frontend/package.json src/pybind/mgr/dashboard/frontend/src/app/app.module.ts src/pybind/mgr/dashboard/frontend/src/app/core/navigation/dashboard-help/dashboard-help.component.ts - Adopting the changes from the master branch, ignoring few e2e changes as few files doesn't exist in octopus. --- qa/tasks/mgr/dashboard/helper.py | 95 ++++++---- qa/tasks/mgr/dashboard/test_auth.py | 178 ++++++++++++++++++ .../mgr/dashboard/controllers/__init__.py | 9 + src/pybind/mgr/dashboard/controllers/auth.py | 12 +- src/pybind/mgr/dashboard/controllers/docs.py | 5 +- src/pybind/mgr/dashboard/controllers/saml2.py | 5 +- .../integration/block/images.e2e-spec.ts | 3 + .../integration/block/iscsi.e2e-spec.ts | 1 + .../integration/block/mirroring.e2e-spec.ts | 1 + .../cluster/configuration.e2e-spec.ts | 1 + .../integration/cluster/crush-map.e2e-spec.ts | 1 + .../integration/cluster/hosts.e2e-spec.ts | 1 + .../integration/cluster/logs.e2e-spec.ts | 1 + .../cluster/mgr-modules.e2e-spec.ts | 1 + .../integration/cluster/monitors.e2e-spec.ts | 1 + .../integration/cluster/osds.e2e-spec.ts | 1 + .../filesystems/filesystems.e2e-spec.ts | 1 + .../integration/pools/pools.e2e-spec.ts | 1 + .../integration/rgw/buckets.e2e-spec.ts | 1 + .../integration/rgw/daemons.e2e-spec.ts | 1 + .../cypress/integration/rgw/users.e2e-spec.ts | 1 + .../integration/ui/dashboard.e2e-spec.ts | 1 + .../integration/ui/notification.e2e-spec.ts | 3 + .../integration/ui/role-mgmt.e2e-spec.ts | 1 + .../integration/ui/user-mgmt.e2e-spec.ts | 1 + .../frontend/cypress/support/commands.ts | 1 - .../mgr/dashboard/frontend/package-lock.json | 8 - .../mgr/dashboard/frontend/package.json | 1 - .../dashboard/frontend/src/app/app.module.ts | 10 - .../rbd-snapshot-list.component.spec.ts | 2 +- .../app/core/auth/login/login.component.ts | 1 - .../dashboard-help.component.ts | 9 +- .../src/app/shared/api/auth.service.spec.ts | 3 +- .../src/app/shared/api/auth.service.ts | 3 +- .../src/app/shared/models/login-response.ts | 1 - .../services/auth-storage.service.spec.ts | 4 +- .../shared/services/auth-storage.service.ts | 7 - src/pybind/mgr/dashboard/services/auth.py | 20 +- 38 files changed, 305 insertions(+), 92 deletions(-) diff --git a/qa/tasks/mgr/dashboard/helper.py b/qa/tasks/mgr/dashboard/helper.py index 1a7a6951c97..64cbba9e3f6 100644 --- a/qa/tasks/mgr/dashboard/helper.py +++ b/qa/tasks/mgr/dashboard/helper.py @@ -96,18 +96,19 @@ class DashboardTestCase(MgrTestCase): cls._ceph_cmd(set_roles_args) @classmethod - def login(cls, username, password): + def login(cls, username, password, set_cookies=False): if cls._loggedin: cls.logout() - cls._post('/api/auth', {'username': username, 'password': password}) + cls._post('/api/auth', {'username': username, + 'password': password}, set_cookies=set_cookies) cls._assertEq(cls._resp.status_code, 201) cls._token = cls.jsonBody()['token'] cls._loggedin = True @classmethod - def logout(cls): + def logout(cls, set_cookies=False): if cls._loggedin: - cls._post('/api/auth/logout') + cls._post('/api/auth/logout', set_cookies=set_cookies) cls._assertEq(cls._resp.status_code, 200) cls._token = None cls._loggedin = False @@ -195,29 +196,49 @@ class DashboardTestCase(MgrTestCase): def tearDownClass(cls): super(DashboardTestCase, cls).tearDownClass() - # pylint: disable=inconsistent-return-statements + # pylint: disable=inconsistent-return-statements, too-many-branches @classmethod - def _request(cls, url, method, data=None, params=None): + def _request(cls, url, method, data=None, params=None, set_cookies=False): url = "{}{}".format(cls._base_uri, url) log.info("Request %s to %s", method, url) headers = {} + cookies = {} if cls._token: - headers['Authorization'] = "Bearer {}".format(cls._token) - - if method == 'GET': - cls._resp = cls._session.get(url, params=params, verify=False, - headers=headers) - elif method == 'POST': - cls._resp = cls._session.post(url, json=data, params=params, - verify=False, headers=headers) - elif method == 'DELETE': - cls._resp = cls._session.delete(url, json=data, params=params, - verify=False, headers=headers) - elif method == 'PUT': - cls._resp = cls._session.put(url, json=data, params=params, - verify=False, headers=headers) + if set_cookies: + cookies['token'] = cls._token + else: + headers['Authorization'] = "Bearer {}".format(cls._token) + + if set_cookies: + if method == 'GET': + cls._resp = cls._session.get(url, params=params, verify=False, + headers=headers, cookies=cookies) + elif method == 'POST': + cls._resp = cls._session.post(url, json=data, params=params, + verify=False, headers=headers, cookies=cookies) + elif method == 'DELETE': + cls._resp = cls._session.delete(url, json=data, params=params, + verify=False, headers=headers, cookies=cookies) + elif method == 'PUT': + cls._resp = cls._session.put(url, json=data, params=params, + verify=False, headers=headers, cookies=cookies) + else: + assert False else: - assert False + if method == 'GET': + cls._resp = cls._session.get(url, params=params, verify=False, + headers=headers) + elif method == 'POST': + cls._resp = cls._session.post(url, json=data, params=params, + verify=False, headers=headers) + elif method == 'DELETE': + cls._resp = cls._session.delete(url, json=data, params=params, + verify=False, headers=headers) + elif method == 'PUT': + cls._resp = cls._session.put(url, json=data, params=params, + verify=False, headers=headers) + else: + assert False try: if not cls._resp.ok: # Output response for easier debugging. @@ -231,8 +252,8 @@ class DashboardTestCase(MgrTestCase): raise ex @classmethod - def _get(cls, url, params=None): - return cls._request(url, 'GET', params=params) + def _get(cls, url, params=None, set_cookies=False): + return cls._request(url, 'GET', params=params, set_cookies=set_cookies) @classmethod def _view_cache_get(cls, url, retries=5): @@ -253,16 +274,16 @@ class DashboardTestCase(MgrTestCase): return res @classmethod - def _post(cls, url, data=None, params=None): - cls._request(url, 'POST', data, params) + def _post(cls, url, data=None, params=None, set_cookies=False): + cls._request(url, 'POST', data, params, set_cookies=set_cookies) @classmethod - def _delete(cls, url, data=None, params=None): - cls._request(url, 'DELETE', data, params) + def _delete(cls, url, data=None, params=None, set_cookies=False): + cls._request(url, 'DELETE', data, params, set_cookies=set_cookies) @classmethod - def _put(cls, url, data=None, params=None): - cls._request(url, 'PUT', data, params) + def _put(cls, url, data=None, params=None, set_cookies=False): + cls._request(url, 'PUT', data, params, set_cookies=set_cookies) @classmethod def _assertEq(cls, v1, v2): @@ -281,8 +302,8 @@ class DashboardTestCase(MgrTestCase): # pylint: disable=too-many-arguments @classmethod - def _task_request(cls, method, url, data, timeout): - res = cls._request(url, method, data) + def _task_request(cls, method, url, data, timeout, set_cookies=False): + res = cls._request(url, method, data, set_cookies=set_cookies) cls._assertIn(cls._resp.status_code, [200, 201, 202, 204, 400, 403, 404]) if cls._resp.status_code == 403: @@ -334,16 +355,16 @@ class DashboardTestCase(MgrTestCase): return res_task['exception'] @classmethod - def _task_post(cls, url, data=None, timeout=60): - return cls._task_request('POST', url, data, timeout) + def _task_post(cls, url, data=None, timeout=60, set_cookies=False): + return cls._task_request('POST', url, data, timeout, set_cookies=set_cookies) @classmethod - def _task_delete(cls, url, timeout=60): - return cls._task_request('DELETE', url, None, timeout) + def _task_delete(cls, url, timeout=60, set_cookies=False): + return cls._task_request('DELETE', url, None, timeout, set_cookies=set_cookies) @classmethod - def _task_put(cls, url, data=None, timeout=60): - return cls._task_request('PUT', url, data, timeout) + def _task_put(cls, url, data=None, timeout=60, set_cookies=False): + return cls._task_request('PUT', url, data, timeout, set_cookies=set_cookies) @classmethod def cookies(cls): diff --git a/qa/tasks/mgr/dashboard/test_auth.py b/qa/tasks/mgr/dashboard/test_auth.py index 473a5a448fc..e1c9b8e63e6 100644 --- a/qa/tasks/mgr/dashboard/test_auth.py +++ b/qa/tasks/mgr/dashboard/test_auth.py @@ -30,6 +30,7 @@ class AuthTest(DashboardTestCase): self.assertIn('delete', perms) def test_a_set_login_credentials(self): + # test with Authorization header self.create_user('admin2', 'admin2', ['administrator']) self._post("/api/auth", {'username': 'admin2', 'password': 'admin2'}) self.assertStatus(201) @@ -37,7 +38,16 @@ class AuthTest(DashboardTestCase): self._validate_jwt_token(data['token'], "admin2", data['permissions']) self.delete_user('admin2') + # test with Cookies set + self.create_user('admin2', 'admin2', ['administrator']) + self._post("/api/auth", {'username': 'admin2', 'password': 'admin2'}, set_cookies=True) + self.assertStatus(201) + data = self.jsonBody() + self._validate_jwt_token(data['token'], "admin2", data['permissions']) + self.delete_user('admin2') + def test_login_valid(self): + # test with Authorization header self._post("/api/auth", {'username': 'admin', 'password': 'admin'}) self.assertStatus(201) data = self.jsonBody() @@ -51,7 +61,22 @@ class AuthTest(DashboardTestCase): }, allow_unknown=False)) self._validate_jwt_token(data['token'], "admin", data['permissions']) + # test with Cookies set + self._post("/api/auth", {'username': 'admin', 'password': 'admin'}, set_cookies=True) + self.assertStatus(201) + data = self.jsonBody() + self.assertSchema(data, JObj(sub_elems={ + 'token': JLeaf(str), + 'username': JLeaf(str), + 'permissions': JObj(sub_elems={}, allow_unknown=True), + 'sso': JLeaf(bool), + 'pwdExpirationDate': JLeaf(int, none=True), + 'pwdUpdateRequired': JLeaf(bool) + }, allow_unknown=False)) + self._validate_jwt_token(data['token'], "admin", data['permissions']) + def test_login_invalid(self): + # test with Authorization header self._post("/api/auth", {'username': 'admin', 'password': 'inval'}) self.assertStatus(400) self.assertJsonBody({ @@ -60,7 +85,17 @@ class AuthTest(DashboardTestCase): "detail": "Invalid credentials" }) + # test with Cookies set + self._post("/api/auth", {'username': 'admin', 'password': 'inval'}, set_cookies=True) + self.assertStatus(400) + self.assertJsonBody({ + "component": "auth", + "code": "invalid_credentials", + "detail": "Invalid credentials" + }) + def test_login_without_password(self): + # test with Authorization header self.create_user('admin2', '', ['administrator']) self._post("/api/auth", {'username': 'admin2', 'password': ''}) self.assertStatus(400) @@ -71,7 +106,19 @@ class AuthTest(DashboardTestCase): }) self.delete_user('admin2') + # test with Cookies set + self.create_user('admin2', '', ['administrator']) + self._post("/api/auth", {'username': 'admin2', 'password': ''}, set_cookies=True) + self.assertStatus(400) + self.assertJsonBody({ + "component": "auth", + "code": "invalid_credentials", + "detail": "Invalid credentials" + }) + self.delete_user('admin2') + def test_lockout_user(self): + # test with Authorization header self._ceph_cmd(['dashboard', 'set-account-lockout-attempts', '3']) for _ in range(3): self._post("/api/auth", {'username': 'admin', 'password': 'inval'}) @@ -96,7 +143,33 @@ class AuthTest(DashboardTestCase): }, allow_unknown=False)) self._validate_jwt_token(data['token'], "admin", data['permissions']) + # test with Cookies set + self._ceph_cmd(['dashboard', 'set-account-lockout-attempts', '3']) + for _ in range(3): + self._post("/api/auth", {'username': 'admin', 'password': 'inval'}, set_cookies=True) + self._post("/api/auth", {'username': 'admin', 'password': 'admin'}, set_cookies=True) + self.assertStatus(400) + self.assertJsonBody({ + "component": "auth", + "code": "invalid_credentials", + "detail": "Invalid credentials" + }) + self._ceph_cmd(['dashboard', 'ac-user-enable', 'admin']) + self._post("/api/auth", {'username': 'admin', 'password': 'admin'}, set_cookies=True) + self.assertStatus(201) + data = self.jsonBody() + self.assertSchema(data, JObj(sub_elems={ + 'token': JLeaf(str), + 'username': JLeaf(str), + 'permissions': JObj(sub_elems={}, allow_unknown=True), + 'sso': JLeaf(bool), + 'pwdExpirationDate': JLeaf(int, none=True), + 'pwdUpdateRequired': JLeaf(bool) + }, allow_unknown=False)) + self._validate_jwt_token(data['token'], "admin", data['permissions']) + def test_logout(self): + # test with Authorization header self._post("/api/auth", {'username': 'admin', 'password': 'admin'}) self.assertStatus(201) data = self.jsonBody() @@ -111,7 +184,23 @@ class AuthTest(DashboardTestCase): self.assertStatus(401) self.set_jwt_token(None) + # test with Cookies set + self._post("/api/auth", {'username': 'admin', 'password': 'admin'}, set_cookies=True) + self.assertStatus(201) + data = self.jsonBody() + self._validate_jwt_token(data['token'], "admin", data['permissions']) + self.set_jwt_token(data['token']) + self._post("/api/auth/logout", set_cookies=True) + self.assertStatus(200) + self.assertJsonBody({ + "redirect_url": "#/login" + }) + self._get("/api/host", set_cookies=True) + self.assertStatus(401) + self.set_jwt_token(None) + def test_token_ttl(self): + # test with Authorization header self._ceph_cmd(['dashboard', 'set-jwt-token-ttl', '5']) self._post("/api/auth", {'username': 'admin', 'password': 'admin'}) self.assertStatus(201) @@ -124,7 +213,21 @@ class AuthTest(DashboardTestCase): self._ceph_cmd(['dashboard', 'set-jwt-token-ttl', '28800']) self.set_jwt_token(None) + # test with Cookies set + self._ceph_cmd(['dashboard', 'set-jwt-token-ttl', '5']) + self._post("/api/auth", {'username': 'admin', 'password': 'admin'}, set_cookies=True) + self.assertStatus(201) + self.set_jwt_token(self.jsonBody()['token']) + self._get("/api/host", set_cookies=True) + self.assertStatus(200) + time.sleep(6) + self._get("/api/host", set_cookies=True) + self.assertStatus(401) + self._ceph_cmd(['dashboard', 'set-jwt-token-ttl', '28800']) + self.set_jwt_token(None) + def test_remove_from_blacklist(self): + # test with Authorization header self._ceph_cmd(['dashboard', 'set-jwt-token-ttl', '5']) self._post("/api/auth", {'username': 'admin', 'password': 'admin'}) self.assertStatus(201) @@ -144,11 +247,37 @@ class AuthTest(DashboardTestCase): self._post("/api/auth/logout") self.assertStatus(200) + # test with Cookies set + self._ceph_cmd(['dashboard', 'set-jwt-token-ttl', '5']) + self._post("/api/auth", {'username': 'admin', 'password': 'admin'}, set_cookies=True) + self.assertStatus(201) + self.set_jwt_token(self.jsonBody()['token']) + # the following call adds the token to the blocklist + self._post("/api/auth/logout", set_cookies=True) + self.assertStatus(200) + self._get("/api/host", set_cookies=True) + self.assertStatus(401) + time.sleep(6) + self._ceph_cmd(['dashboard', 'set-jwt-token-ttl', '28800']) + self.set_jwt_token(None) + self._post("/api/auth", {'username': 'admin', 'password': 'admin'}, set_cookies=True) + self.assertStatus(201) + self.set_jwt_token(self.jsonBody()['token']) + # the following call removes expired tokens from the blocklist + self._post("/api/auth/logout", set_cookies=True) + self.assertStatus(200) + def test_unauthorized(self): + # test with Authorization header self._get("/api/host") self.assertStatus(401) + # test with Cookies set + self._get("/api/host", set_cookies=True) + self.assertStatus(401) + def test_invalidate_token_by_admin(self): + # test with Authorization header self._get("/api/host") self.assertStatus(401) self.create_user('user', 'user', ['read-only']) @@ -172,7 +301,32 @@ class AuthTest(DashboardTestCase): self.assertStatus(200) self.delete_user("user") + # test with Cookies set + self._get("/api/host", set_cookies=True) + self.assertStatus(401) + self.create_user('user', 'user', ['read-only']) + time.sleep(1) + self._post("/api/auth", {'username': 'user', 'password': 'user'}, set_cookies=True) + self.assertStatus(201) + self.set_jwt_token(self.jsonBody()['token']) + self._get("/api/host", set_cookies=True) + self.assertStatus(200) + time.sleep(1) + self._ceph_cmd(['dashboard', 'ac-user-set-password', '--force-password', + 'user', 'user2']) + time.sleep(1) + self._get("/api/host", set_cookies=True) + self.assertStatus(401) + self.set_jwt_token(None) + self._post("/api/auth", {'username': 'user', 'password': 'user2'}, set_cookies=True) + self.assertStatus(201) + self.set_jwt_token(self.jsonBody()['token']) + self._get("/api/host", set_cookies=True) + self.assertStatus(200) + self.delete_user("user") + def test_check_token(self): + # test with Authorization header self.login("admin", "admin") self._post("/api/auth/check", {"token": self.jsonBody()["token"]}) self.assertStatus(200) @@ -185,7 +339,21 @@ class AuthTest(DashboardTestCase): }, allow_unknown=False)) self.logout() + # test with Cookies set + self.login("admin", "admin", set_cookies=True) + self._post("/api/auth/check", {"token": self.jsonBody()["token"]}, set_cookies=True) + self.assertStatus(200) + data = self.jsonBody() + self.assertSchema(data, JObj(sub_elems={ + "username": JLeaf(str), + "permissions": JObj(sub_elems={}, allow_unknown=True), + "sso": JLeaf(bool), + "pwdUpdateRequired": JLeaf(bool) + }, allow_unknown=False)) + self.logout(set_cookies=True) + def test_check_wo_token(self): + # test with Authorization header self.login("admin", "admin") self._post("/api/auth/check", {"token": ""}) self.assertStatus(200) @@ -194,3 +362,13 @@ class AuthTest(DashboardTestCase): "login_url": JLeaf(str) }, allow_unknown=False)) self.logout() + + # test with Cookies set + self.login("admin", "admin", set_cookies=True) + self._post("/api/auth/check", {"token": ""}, set_cookies=True) + self.assertStatus(200) + data = self.jsonBody() + self.assertSchema(data, JObj(sub_elems={ + "login_url": JLeaf(str) + }, allow_unknown=False)) + self.logout(set_cookies=True) diff --git a/src/pybind/mgr/dashboard/controllers/__init__.py b/src/pybind/mgr/dashboard/controllers/__init__.py index d872b8a3014..99fa37f258f 100644 --- a/src/pybind/mgr/dashboard/controllers/__init__.py +++ b/src/pybind/mgr/dashboard/controllers/__init__.py @@ -964,3 +964,12 @@ def allow_empty_body(func): # noqa: N802 except (AttributeError, KeyError): func._cp_config = {'tools.json_in.force': False} return func + + +def set_cookies(url_prefix, token): + cherrypy.response.cookie['token'] = token + if url_prefix == 'https': + cherrypy.response.cookie['token']['secure'] = True + cherrypy.response.cookie['token']['HttpOnly'] = True + cherrypy.response.cookie['token']['path'] = '/' + cherrypy.response.cookie['token']['SameSite'] = 'Strict' diff --git a/src/pybind/mgr/dashboard/controllers/auth.py b/src/pybind/mgr/dashboard/controllers/auth.py index a66356c87c1..d6dd12d6bda 100644 --- a/src/pybind/mgr/dashboard/controllers/auth.py +++ b/src/pybind/mgr/dashboard/controllers/auth.py @@ -1,16 +1,21 @@ # -*- coding: utf-8 -*- from __future__ import absolute_import +import http.cookies import logging -import cherrypy +import sys from . import ApiController, RESTController, \ - allow_empty_body + allow_empty_body, set_cookies from .. import mgr from ..exceptions import InvalidCredentialsError, UserDoesNotExist from ..services.auth import AuthManager, JwtManager from ..settings import Settings +# Python 3.8 introduced `samesite` attribute: +# https://docs.python.org/3/library/http.cookies.html#morsel-objects +if sys.version_info < (3, 8): + http.cookies.Morsel._reserved["samesite"] = "SameSite" # type: ignore # pylint: disable=W0212 logger = logging.getLogger('controllers.auth') @@ -31,12 +36,13 @@ class Auth(RESTController): pwd_update_required = user_data.get('pwdUpdateRequired', False) if user_perms is not None: + url_prefix = 'https' if mgr.get_localized_module_option('ssl') else 'http' logger.info('Login successful: %s', username) mgr.ACCESS_CTRL_DB.reset_attempt(username) mgr.ACCESS_CTRL_DB.save() token = JwtManager.gen_token(username) token = token.decode('utf-8') - cherrypy.response.headers['Authorization'] = "Bearer: {}".format(token) + set_cookies(url_prefix, token) return { 'token': token, 'username': username, diff --git a/src/pybind/mgr/dashboard/controllers/docs.py b/src/pybind/mgr/dashboard/controllers/docs.py index 09b646bc050..125ff06616c 100644 --- a/src/pybind/mgr/dashboard/controllers/docs.py +++ b/src/pybind/mgr/dashboard/controllers/docs.py @@ -379,8 +379,11 @@ class Docs(BaseController): spec_url = "{}/docs/api.json".format(base) auth_header = cherrypy.request.headers.get('authorization') + auth_cookie = cherrypy.request.cookie['token'] jwt_token = "" - if auth_header is not None: + if auth_cookie is not None: + jwt_token = auth_cookie.value + elif auth_header is not None: scheme, params = auth_header.split(' ', 1) if scheme.lower() == 'bearer': jwt_token = params diff --git a/src/pybind/mgr/dashboard/controllers/saml2.py b/src/pybind/mgr/dashboard/controllers/saml2.py index 4bafc6933ac..810455e36ab 100644 --- a/src/pybind/mgr/dashboard/controllers/saml2.py +++ b/src/pybind/mgr/dashboard/controllers/saml2.py @@ -16,7 +16,7 @@ from .. import mgr from ..exceptions import UserDoesNotExist from ..services.auth import JwtManager from ..tools import prepare_url_prefix -from . import Controller, Endpoint, BaseController +from . import BaseController, Controller, Endpoint, allow_empty_body, set_cookies @Controller('/auth/saml2', secure=False) @@ -43,6 +43,7 @@ class Saml2(BaseController): raise cherrypy.HTTPError(400, 'Single Sign-On is not configured.') @Endpoint('POST', path="") + @allow_empty_body def auth_response(self, **kwargs): Saml2._check_python_saml() req = Saml2._build_req(self._request, kwargs) @@ -70,6 +71,7 @@ class Saml2(BaseController): token = JwtManager.gen_token(username) JwtManager.set_user(JwtManager.decode_token(token)) token = token.decode('utf-8') + set_cookies(url_prefix, token) raise cherrypy.HTTPRedirect("{}/#/login?access_token={}".format(url_prefix, token)) return { @@ -103,5 +105,6 @@ class Saml2(BaseController): # pylint: disable=unused-argument Saml2._check_python_saml() JwtManager.reset_user() + cherrypy.response.cookie['token'] = {'expires': 0, 'max-age': 0} url_prefix = prepare_url_prefix(mgr.get_module_option('url_prefix', default='')) raise cherrypy.HTTPRedirect("{}/#/login".format(url_prefix)) diff --git a/src/pybind/mgr/dashboard/frontend/cypress/integration/block/images.e2e-spec.ts b/src/pybind/mgr/dashboard/frontend/cypress/integration/block/images.e2e-spec.ts index 87900a0e1a5..cf8832bb9bb 100644 --- a/src/pybind/mgr/dashboard/frontend/cypress/integration/block/images.e2e-spec.ts +++ b/src/pybind/mgr/dashboard/frontend/cypress/integration/block/images.e2e-spec.ts @@ -9,6 +9,7 @@ describe('Images page', () => { before(() => { cy.login(); + Cypress.Cookies.preserveOnce('token'); // Need pool for image testing pools.navigateTo('create'); pools.create(poolName, 8, 'rbd'); @@ -25,6 +26,7 @@ describe('Images page', () => { beforeEach(() => { cy.login(); + Cypress.Cookies.preserveOnce('token'); images.navigateTo(); }); @@ -68,6 +70,7 @@ describe('Images page', () => { before(() => { cy.login(); + Cypress.Cookies.preserveOnce('token'); // Need image for trash testing images.createImage(imageName, poolName, '1'); images.getFirstTableCell(imageName).should('exist'); diff --git a/src/pybind/mgr/dashboard/frontend/cypress/integration/block/iscsi.e2e-spec.ts b/src/pybind/mgr/dashboard/frontend/cypress/integration/block/iscsi.e2e-spec.ts index f7154fb59e0..fdc2f637058 100644 --- a/src/pybind/mgr/dashboard/frontend/cypress/integration/block/iscsi.e2e-spec.ts +++ b/src/pybind/mgr/dashboard/frontend/cypress/integration/block/iscsi.e2e-spec.ts @@ -5,6 +5,7 @@ describe('Iscsi Page', () => { beforeEach(() => { cy.login(); + Cypress.Cookies.preserveOnce('token'); iscsi.navigateTo(); }); diff --git a/src/pybind/mgr/dashboard/frontend/cypress/integration/block/mirroring.e2e-spec.ts b/src/pybind/mgr/dashboard/frontend/cypress/integration/block/mirroring.e2e-spec.ts index c7bd4278238..ddee817e18e 100644 --- a/src/pybind/mgr/dashboard/frontend/cypress/integration/block/mirroring.e2e-spec.ts +++ b/src/pybind/mgr/dashboard/frontend/cypress/integration/block/mirroring.e2e-spec.ts @@ -7,6 +7,7 @@ describe('Mirroring page', () => { beforeEach(() => { cy.login(); + Cypress.Cookies.preserveOnce('token'); mirroring.navigateTo(); }); diff --git a/src/pybind/mgr/dashboard/frontend/cypress/integration/cluster/configuration.e2e-spec.ts b/src/pybind/mgr/dashboard/frontend/cypress/integration/cluster/configuration.e2e-spec.ts index ab55fee2359..dad4701da25 100644 --- a/src/pybind/mgr/dashboard/frontend/cypress/integration/cluster/configuration.e2e-spec.ts +++ b/src/pybind/mgr/dashboard/frontend/cypress/integration/cluster/configuration.e2e-spec.ts @@ -5,6 +5,7 @@ describe('Configuration page', () => { beforeEach(() => { cy.login(); + Cypress.Cookies.preserveOnce('token'); configuration.navigateTo(); }); diff --git a/src/pybind/mgr/dashboard/frontend/cypress/integration/cluster/crush-map.e2e-spec.ts b/src/pybind/mgr/dashboard/frontend/cypress/integration/cluster/crush-map.e2e-spec.ts index 2c8d1322f4e..2274d72e7bc 100644 --- a/src/pybind/mgr/dashboard/frontend/cypress/integration/cluster/crush-map.e2e-spec.ts +++ b/src/pybind/mgr/dashboard/frontend/cypress/integration/cluster/crush-map.e2e-spec.ts @@ -5,6 +5,7 @@ describe('CRUSH map page', () => { beforeEach(() => { cy.login(); + Cypress.Cookies.preserveOnce('token'); crushmap.navigateTo(); }); diff --git a/src/pybind/mgr/dashboard/frontend/cypress/integration/cluster/hosts.e2e-spec.ts b/src/pybind/mgr/dashboard/frontend/cypress/integration/cluster/hosts.e2e-spec.ts index 045b18f60cd..c1935a78383 100644 --- a/src/pybind/mgr/dashboard/frontend/cypress/integration/cluster/hosts.e2e-spec.ts +++ b/src/pybind/mgr/dashboard/frontend/cypress/integration/cluster/hosts.e2e-spec.ts @@ -5,6 +5,7 @@ describe('Hosts page', () => { beforeEach(() => { cy.login(); + Cypress.Cookies.preserveOnce('token'); hosts.navigateTo(); }); diff --git a/src/pybind/mgr/dashboard/frontend/cypress/integration/cluster/logs.e2e-spec.ts b/src/pybind/mgr/dashboard/frontend/cypress/integration/cluster/logs.e2e-spec.ts index f5692bfe15f..731275e26d1 100644 --- a/src/pybind/mgr/dashboard/frontend/cypress/integration/cluster/logs.e2e-spec.ts +++ b/src/pybind/mgr/dashboard/frontend/cypress/integration/cluster/logs.e2e-spec.ts @@ -15,6 +15,7 @@ describe('Logs page', () => { beforeEach(() => { cy.login(); + Cypress.Cookies.preserveOnce('token'); }); describe('breadcrumb and tab tests', () => { diff --git a/src/pybind/mgr/dashboard/frontend/cypress/integration/cluster/mgr-modules.e2e-spec.ts b/src/pybind/mgr/dashboard/frontend/cypress/integration/cluster/mgr-modules.e2e-spec.ts index 67d27011102..0cbedfcfa1c 100644 --- a/src/pybind/mgr/dashboard/frontend/cypress/integration/cluster/mgr-modules.e2e-spec.ts +++ b/src/pybind/mgr/dashboard/frontend/cypress/integration/cluster/mgr-modules.e2e-spec.ts @@ -5,6 +5,7 @@ describe('Manager modules page', () => { beforeEach(() => { cy.login(); + Cypress.Cookies.preserveOnce('token'); mgrmodules.navigateTo(); }); diff --git a/src/pybind/mgr/dashboard/frontend/cypress/integration/cluster/monitors.e2e-spec.ts b/src/pybind/mgr/dashboard/frontend/cypress/integration/cluster/monitors.e2e-spec.ts index 8324ff8b5b0..a23d071e6d7 100644 --- a/src/pybind/mgr/dashboard/frontend/cypress/integration/cluster/monitors.e2e-spec.ts +++ b/src/pybind/mgr/dashboard/frontend/cypress/integration/cluster/monitors.e2e-spec.ts @@ -5,6 +5,7 @@ describe('Monitors page', () => { beforeEach(() => { cy.login(); + Cypress.Cookies.preserveOnce('token'); monitors.navigateTo(); }); diff --git a/src/pybind/mgr/dashboard/frontend/cypress/integration/cluster/osds.e2e-spec.ts b/src/pybind/mgr/dashboard/frontend/cypress/integration/cluster/osds.e2e-spec.ts index 3503471a0a4..1fb76e4d019 100644 --- a/src/pybind/mgr/dashboard/frontend/cypress/integration/cluster/osds.e2e-spec.ts +++ b/src/pybind/mgr/dashboard/frontend/cypress/integration/cluster/osds.e2e-spec.ts @@ -5,6 +5,7 @@ describe('OSDs page', () => { beforeEach(() => { cy.login(); + Cypress.Cookies.preserveOnce('token'); osds.navigateTo(); }); diff --git a/src/pybind/mgr/dashboard/frontend/cypress/integration/filesystems/filesystems.e2e-spec.ts b/src/pybind/mgr/dashboard/frontend/cypress/integration/filesystems/filesystems.e2e-spec.ts index 63f59916f99..83f280b477a 100644 --- a/src/pybind/mgr/dashboard/frontend/cypress/integration/filesystems/filesystems.e2e-spec.ts +++ b/src/pybind/mgr/dashboard/frontend/cypress/integration/filesystems/filesystems.e2e-spec.ts @@ -5,6 +5,7 @@ describe('Filesystems page', () => { beforeEach(() => { cy.login(); + Cypress.Cookies.preserveOnce('token'); filesystems.navigateTo(); }); diff --git a/src/pybind/mgr/dashboard/frontend/cypress/integration/pools/pools.e2e-spec.ts b/src/pybind/mgr/dashboard/frontend/cypress/integration/pools/pools.e2e-spec.ts index fd2217579a5..5339b26be31 100644 --- a/src/pybind/mgr/dashboard/frontend/cypress/integration/pools/pools.e2e-spec.ts +++ b/src/pybind/mgr/dashboard/frontend/cypress/integration/pools/pools.e2e-spec.ts @@ -6,6 +6,7 @@ describe('Pools page', () => { beforeEach(() => { cy.login(); + Cypress.Cookies.preserveOnce('token'); pools.navigateTo(); }); diff --git a/src/pybind/mgr/dashboard/frontend/cypress/integration/rgw/buckets.e2e-spec.ts b/src/pybind/mgr/dashboard/frontend/cypress/integration/rgw/buckets.e2e-spec.ts index e5e0daa4c1f..737c1126821 100644 --- a/src/pybind/mgr/dashboard/frontend/cypress/integration/rgw/buckets.e2e-spec.ts +++ b/src/pybind/mgr/dashboard/frontend/cypress/integration/rgw/buckets.e2e-spec.ts @@ -6,6 +6,7 @@ describe('RGW buckets page', () => { beforeEach(() => { cy.login(); + Cypress.Cookies.preserveOnce('token'); buckets.navigateTo(); }); diff --git a/src/pybind/mgr/dashboard/frontend/cypress/integration/rgw/daemons.e2e-spec.ts b/src/pybind/mgr/dashboard/frontend/cypress/integration/rgw/daemons.e2e-spec.ts index 03b2ca83421..4cad786c6fe 100644 --- a/src/pybind/mgr/dashboard/frontend/cypress/integration/rgw/daemons.e2e-spec.ts +++ b/src/pybind/mgr/dashboard/frontend/cypress/integration/rgw/daemons.e2e-spec.ts @@ -5,6 +5,7 @@ describe('RGW daemons page', () => { beforeEach(() => { cy.login(); + Cypress.Cookies.preserveOnce('token'); daemons.navigateTo(); }); diff --git a/src/pybind/mgr/dashboard/frontend/cypress/integration/rgw/users.e2e-spec.ts b/src/pybind/mgr/dashboard/frontend/cypress/integration/rgw/users.e2e-spec.ts index a8d7d45b419..1b580db7dcf 100644 --- a/src/pybind/mgr/dashboard/frontend/cypress/integration/rgw/users.e2e-spec.ts +++ b/src/pybind/mgr/dashboard/frontend/cypress/integration/rgw/users.e2e-spec.ts @@ -6,6 +6,7 @@ describe('RGW users page', () => { beforeEach(() => { cy.login(); + Cypress.Cookies.preserveOnce('token'); users.navigateTo(); }); diff --git a/src/pybind/mgr/dashboard/frontend/cypress/integration/ui/dashboard.e2e-spec.ts b/src/pybind/mgr/dashboard/frontend/cypress/integration/ui/dashboard.e2e-spec.ts index 397745f9745..2d604db3439 100644 --- a/src/pybind/mgr/dashboard/frontend/cypress/integration/ui/dashboard.e2e-spec.ts +++ b/src/pybind/mgr/dashboard/frontend/cypress/integration/ui/dashboard.e2e-spec.ts @@ -18,6 +18,7 @@ describe('Dashboard Main Page', () => { beforeEach(() => { cy.login(); + Cypress.Cookies.preserveOnce('token'); dashboard.navigateTo(); }); diff --git a/src/pybind/mgr/dashboard/frontend/cypress/integration/ui/notification.e2e-spec.ts b/src/pybind/mgr/dashboard/frontend/cypress/integration/ui/notification.e2e-spec.ts index b69f26f58dc..2ee73a70632 100644 --- a/src/pybind/mgr/dashboard/frontend/cypress/integration/ui/notification.e2e-spec.ts +++ b/src/pybind/mgr/dashboard/frontend/cypress/integration/ui/notification.e2e-spec.ts @@ -8,6 +8,7 @@ describe('Notification page', () => { before(() => { cy.login(); + Cypress.Cookies.preserveOnce('token'); pools.navigateTo('create'); pools.create(poolName, 8); pools.edit_pool_pg(poolName, 4, false); @@ -15,12 +16,14 @@ describe('Notification page', () => { after(() => { cy.login(); + Cypress.Cookies.preserveOnce('token'); pools.navigateTo(); pools.delete(poolName); }); beforeEach(() => { cy.login(); + Cypress.Cookies.preserveOnce('token'); pools.navigateTo(); }); diff --git a/src/pybind/mgr/dashboard/frontend/cypress/integration/ui/role-mgmt.e2e-spec.ts b/src/pybind/mgr/dashboard/frontend/cypress/integration/ui/role-mgmt.e2e-spec.ts index 7e76f168e6d..c3f325dbbe1 100644 --- a/src/pybind/mgr/dashboard/frontend/cypress/integration/ui/role-mgmt.e2e-spec.ts +++ b/src/pybind/mgr/dashboard/frontend/cypress/integration/ui/role-mgmt.e2e-spec.ts @@ -6,6 +6,7 @@ describe('Role Management page', () => { beforeEach(() => { cy.login(); + Cypress.Cookies.preserveOnce('token'); roleMgmt.navigateTo(); }); diff --git a/src/pybind/mgr/dashboard/frontend/cypress/integration/ui/user-mgmt.e2e-spec.ts b/src/pybind/mgr/dashboard/frontend/cypress/integration/ui/user-mgmt.e2e-spec.ts index 57818db0ae7..92dc772121b 100644 --- a/src/pybind/mgr/dashboard/frontend/cypress/integration/ui/user-mgmt.e2e-spec.ts +++ b/src/pybind/mgr/dashboard/frontend/cypress/integration/ui/user-mgmt.e2e-spec.ts @@ -6,6 +6,7 @@ describe('User Management page', () => { beforeEach(() => { cy.login(); + Cypress.Cookies.preserveOnce('token'); userMgmt.navigateTo(); }); diff --git a/src/pybind/mgr/dashboard/frontend/cypress/support/commands.ts b/src/pybind/mgr/dashboard/frontend/cypress/support/commands.ts index 0bcfe76ad12..57a06bf4e74 100644 --- a/src/pybind/mgr/dashboard/frontend/cypress/support/commands.ts +++ b/src/pybind/mgr/dashboard/frontend/cypress/support/commands.ts @@ -13,7 +13,6 @@ let auth: any; const fillAuth = () => { window.localStorage.setItem('dashboard_username', auth.username); - window.localStorage.setItem('access_token', auth.token); window.localStorage.setItem('dashboard_permissions', auth.permissions); window.localStorage.setItem('user_pwd_expiration_date', auth.pwdExpirationDate); window.localStorage.setItem('user_pwd_update_required', auth.pwdUpdateRequired); diff --git a/src/pybind/mgr/dashboard/frontend/package-lock.json b/src/pybind/mgr/dashboard/frontend/package-lock.json index 4e6d4f9da99..1a34514638d 100644 --- a/src/pybind/mgr/dashboard/frontend/package-lock.json +++ b/src/pybind/mgr/dashboard/frontend/package-lock.json @@ -821,14 +821,6 @@ "tslib": "^1.9.0" } }, - "@auth0/angular-jwt": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/@auth0/angular-jwt/-/angular-jwt-2.1.1.tgz", - "integrity": "sha512-kgTzPafHzoEQp5T9+FQMIaFfnacPwnzlvlwhCy6OYt/2w8uC8nFrN2pNQu29YnlJxLVXI5i0jgAT57+LrWFU/w==", - "requires": { - "url": "^0.11.0" - } - }, "@babel/code-frame": { "version": "7.10.4", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.10.4.tgz", diff --git a/src/pybind/mgr/dashboard/frontend/package.json b/src/pybind/mgr/dashboard/frontend/package.json index 75c9949270c..43d832c14f8 100644 --- a/src/pybind/mgr/dashboard/frontend/package.json +++ b/src/pybind/mgr/dashboard/frontend/package.json @@ -85,7 +85,6 @@ "@angular/platform-browser": "8.2.14", "@angular/platform-browser-dynamic": "8.2.14", "@angular/router": "8.2.14", - "@auth0/angular-jwt": "2.1.1", "@ngx-translate/i18n-polyfill": "1.0.0", "@swimlane/ngx-datatable": "16.0.3", "@types/file-saver": "^2.0.1", diff --git a/src/pybind/mgr/dashboard/frontend/src/app/app.module.ts b/src/pybind/mgr/dashboard/frontend/src/app/app.module.ts index c66974e112b..1f8e9bf5b16 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/app.module.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/app.module.ts @@ -9,7 +9,6 @@ import { import { BrowserModule } from '@angular/platform-browser'; import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; -import { JwtModule } from '@auth0/angular-jwt'; import { I18n } from '@ngx-translate/i18n-polyfill'; import { NgBootstrapFormValidationModule } from 'ng-bootstrap-form-validation'; @@ -28,10 +27,6 @@ import { SharedModule } from './shared/shared.module'; import { environment } from '../environments/environment'; -export function jwtTokenGetter() { - return localStorage.getItem('access_token'); -} - @NgModule({ declarations: [AppComponent], imports: [ @@ -50,11 +45,6 @@ export function jwtTokenGetter() { AccordionModule.forRoot(), BsDropdownModule.forRoot(), TabsModule.forRoot(), - JwtModule.forRoot({ - config: { - tokenGetter: jwtTokenGetter - } - }), NgBootstrapFormValidationModule.forRoot() ], exports: [SharedModule], diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/rbd-snapshot-list/rbd-snapshot-list.component.spec.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/rbd-snapshot-list/rbd-snapshot-list.component.spec.ts index c032d0d522b..044e3568f04 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/rbd-snapshot-list/rbd-snapshot-list.component.spec.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/rbd-snapshot-list/rbd-snapshot-list.component.spec.ts @@ -93,7 +93,7 @@ describe('RbdSnapshotListComponent', () => { rbdService = new RbdService(null, null); notificationService = new NotificationService(null, null, null); authStorageService = new AuthStorageService(); - authStorageService.set('user', '', { 'rbd-image': ['create', 'read', 'update', 'delete'] }); + authStorageService.set('user', { 'rbd-image': ['create', 'read', 'update', 'delete'] }); component = new RbdSnapshotListComponent( authStorageService, null, diff --git a/src/pybind/mgr/dashboard/frontend/src/app/core/auth/login/login.component.ts b/src/pybind/mgr/dashboard/frontend/src/app/core/auth/login/login.component.ts index 770a4821db1..47884af9c9f 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/core/auth/login/login.component.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/core/auth/login/login.component.ts @@ -54,7 +54,6 @@ export class LoginComponent implements OnInit { } else { this.authStorageService.set( login.username, - token, login.permissions, login.sso, login.pwdExpirationDate diff --git a/src/pybind/mgr/dashboard/frontend/src/app/core/navigation/dashboard-help/dashboard-help.component.ts b/src/pybind/mgr/dashboard/frontend/src/app/core/navigation/dashboard-help/dashboard-help.component.ts index 5d212d6cac4..1cdaf523ba5 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/core/navigation/dashboard-help/dashboard-help.component.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/core/navigation/dashboard-help/dashboard-help.component.ts @@ -3,7 +3,6 @@ import { Component, OnInit, ViewChild } from '@angular/core'; import { BsModalRef, BsModalService } from 'ngx-bootstrap/modal'; import { Icons } from '../../../shared/enum/icons.enum'; -import { AuthStorageService } from '../../../shared/services/auth-storage.service'; import { DocService } from '../../../shared/services/doc.service'; import { AboutComponent } from '../about/about.component'; @@ -19,11 +18,7 @@ export class DashboardHelpComponent implements OnInit { modalRef: BsModalRef; icons = Icons; - constructor( - private modalService: BsModalService, - private authStorageService: AuthStorageService, - private docService: DocService - ) {} + constructor(private modalService: BsModalService, private docService: DocService) {} ngOnInit() { this.docService.subscribeOnce('dashboard', (url: string) => { @@ -37,8 +32,6 @@ export class DashboardHelpComponent implements OnInit { } goToApiDocs() { - const tokenInput = this.docsFormElement.nativeElement.children[0]; - tokenInput.value = this.authStorageService.getToken(); this.docsFormElement.nativeElement.submit(); } } diff --git a/src/pybind/mgr/dashboard/frontend/src/app/shared/api/auth.service.spec.ts b/src/pybind/mgr/dashboard/frontend/src/app/shared/api/auth.service.spec.ts index 1dffaf04a77..58e44a0e4cb 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/shared/api/auth.service.spec.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/shared/api/auth.service.spec.ts @@ -33,7 +33,7 @@ describe('AuthService', () => { it('should login and save the user', fakeAsync(() => { const fakeCredentials = { username: 'foo', password: 'bar' }; - const fakeResponse = { username: 'foo', token: 'tokenbytes' }; + const fakeResponse = { username: 'foo' }; service.login(fakeCredentials).subscribe(); const req = httpTesting.expectOne('api/auth'); expect(req.request.method).toBe('POST'); @@ -41,7 +41,6 @@ describe('AuthService', () => { req.flush(fakeResponse); tick(); expect(localStorage.getItem('dashboard_username')).toBe('foo'); - expect(localStorage.getItem('access_token')).toBe('tokenbytes'); })); it('should logout and remove the user', fakeAsync(() => { diff --git a/src/pybind/mgr/dashboard/frontend/src/app/shared/api/auth.service.ts b/src/pybind/mgr/dashboard/frontend/src/app/shared/api/auth.service.ts index c8ac216f78b..022f88f1800 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/shared/api/auth.service.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/shared/api/auth.service.ts @@ -29,7 +29,6 @@ export class AuthService { tap((resp: LoginResponse) => { this.authStorageService.set( resp.username, - resp.token, resp.permissions, resp.sso, resp.pwdExpirationDate, @@ -41,8 +40,8 @@ export class AuthService { logout(callback: Function = null) { return this.http.post('api/auth/logout', null).subscribe((resp: any) => { - this.router.navigate(['/login'], { skipLocationChange: true }); this.authStorageService.remove(); + this.router.navigate(['/login'], { skipLocationChange: true }); if (callback) { callback(); } diff --git a/src/pybind/mgr/dashboard/frontend/src/app/shared/models/login-response.ts b/src/pybind/mgr/dashboard/frontend/src/app/shared/models/login-response.ts index 7b9fc4b277e..12b4b8348ea 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/shared/models/login-response.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/shared/models/login-response.ts @@ -1,6 +1,5 @@ export class LoginResponse { username: string; - token: string; permissions: object; pwdExpirationDate: number; sso: boolean; diff --git a/src/pybind/mgr/dashboard/frontend/src/app/shared/services/auth-storage.service.spec.ts b/src/pybind/mgr/dashboard/frontend/src/app/shared/services/auth-storage.service.spec.ts index 67c093de6ec..f202c095f47 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/shared/services/auth-storage.service.spec.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/shared/services/auth-storage.service.spec.ts @@ -34,13 +34,13 @@ describe('AuthStorageService', () => { }); it('should be SSO', () => { - service.set(username, '', {}, true); + service.set(username, {}, true); expect(localStorage.getItem('sso')).toBe('true'); expect(service.isSSO()).toBe(true); }); it('should not be SSO', () => { - service.set(username, ''); + service.set(username); expect(localStorage.getItem('sso')).toBe('false'); expect(service.isSSO()).toBe(false); }); diff --git a/src/pybind/mgr/dashboard/frontend/src/app/shared/services/auth-storage.service.ts b/src/pybind/mgr/dashboard/frontend/src/app/shared/services/auth-storage.service.ts index fcc24cf5b95..97a56e65c28 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/shared/services/auth-storage.service.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/shared/services/auth-storage.service.ts @@ -15,14 +15,12 @@ export class AuthStorageService { set( username: string, - token: string, permissions = {}, sso = false, pwdExpirationDate: number = null, pwdUpdateRequired: boolean = false ) { localStorage.setItem('dashboard_username', username); - localStorage.setItem('access_token', token); localStorage.setItem('dashboard_permissions', JSON.stringify(new Permissions(permissions))); localStorage.setItem('user_pwd_expiration_date', String(pwdExpirationDate)); localStorage.setItem('user_pwd_update_required', String(pwdUpdateRequired)); @@ -30,16 +28,11 @@ export class AuthStorageService { } remove() { - localStorage.removeItem('access_token'); localStorage.removeItem('dashboard_username'); localStorage.removeItem('user_pwd_expiration_data'); localStorage.removeItem('user_pwd_update_required'); } - getToken(): string { - return localStorage.getItem('access_token'); - } - isLoggedIn() { return localStorage.getItem('dashboard_username') !== null; } diff --git a/src/pybind/mgr/dashboard/services/auth.py b/src/pybind/mgr/dashboard/services/auth.py index d829362e63d..bbb8a2ecfe1 100644 --- a/src/pybind/mgr/dashboard/services/auth.py +++ b/src/pybind/mgr/dashboard/services/auth.py @@ -63,12 +63,20 @@ class JwtManager(object): @classmethod def get_token_from_header(cls): - auth_header = cherrypy.request.headers.get('authorization') - if auth_header is not None: - scheme, params = auth_header.split(' ', 1) - if scheme.lower() == 'bearer': - return params - return None + auth_cookie_name = 'token' + try: + # use cookie + return cherrypy.request.cookie[auth_cookie_name].value + except KeyError: + try: + # fall-back: use Authorization header + auth_header = cherrypy.request.headers.get('authorization') + if auth_header is not None: + scheme, params = auth_header.split(' ', 1) + if scheme.lower() == 'bearer': + return params + except IndexError: + return None @classmethod def set_user(cls, username): -- 2.47.3