Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Patch for v14.2.12 #195

Open
wants to merge 4 commits into
base: origin-v14.2.12-1733930363
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
99 changes: 61 additions & 38 deletions qa/tasks/mgr/dashboard/helper.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,17 +73,20 @@ def create_user(cls, username, password, roles):
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

Expand Down Expand Up @@ -164,29 +167,49 @@ def setUp(self):
def tearDownClass(cls):
super(DashboardTestCase, cls).tearDownClass()

# pylint: disable=inconsistent-return-statements
# pylint: disable=inconsistent-return-statements, too-many-arguments, 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.
Expand All @@ -200,8 +223,8 @@ def _request(cls, url, method, data=None, params=None):
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):
Expand All @@ -222,16 +245,16 @@ def _view_cache_get(cls, url, retries=5):
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):
Expand All @@ -250,9 +273,9 @@ def _assertIsInst(cls, v1, v2):

# pylint: disable=too-many-arguments
@classmethod
def _task_request(cls, method, url, data, timeout):
res = cls._request(url, method, data)
cls._assertIn(cls._resp.status_code, [200, 201, 202, 204, 400, 403])
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:
return None
Expand Down Expand Up @@ -303,16 +326,16 @@ def _task_request(cls, method, url, data, timeout):
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):
Expand Down
106 changes: 106 additions & 0 deletions qa/tasks/mgr/dashboard/test_auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,20 +30,37 @@ def _validate_jwt_token(self, token, username, permissions):
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)
data = self.jsonBody()
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()
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._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({
Expand All @@ -63,7 +80,17 @@ def test_login_without_password(self):
})
self.delete_user('admin2')

# 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_logout(self):
# test with Authorization header
self._post("/api/auth", {'username': 'admin', 'password': 'admin'})
self.assertStatus(201)
data = self.jsonBody()
Expand All @@ -78,7 +105,23 @@ def test_logout(self):
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)
Expand All @@ -91,7 +134,21 @@ def test_token_ttl(self):
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")
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)
Expand All @@ -111,11 +168,37 @@ def test_remove_from_blacklist(self):
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'])
Expand All @@ -137,3 +220,26 @@ def test_invalidate_token_by_admin(self):
self._get("/api/host")
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_with_secret(['dashboard', 'ac-user-set-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")
12 changes: 9 additions & 3 deletions src/auth/cephx/CephxServiceHandler.cc
Original file line number Diff line number Diff line change
Expand Up @@ -200,11 +200,14 @@ int CephxServiceHandler::handle_request(
}
}
encode(cbl, *result_bl);
// provite all of the other tickets at the same time
// provide requested service tickets at the same time
vector<CephXSessionAuthInfo> info_vec;
for (uint32_t service_id = 1; service_id <= req.other_keys;
service_id <<= 1) {
if (req.other_keys & service_id) {
// skip CEPH_ENTITY_TYPE_AUTH: auth ticket is already encoded
// (possibly encrypted with the old session key)
if ((req.other_keys & service_id) &&
service_id != CEPH_ENTITY_TYPE_AUTH) {
ldout(cct, 10) << " adding key for service "
<< ceph_entity_type_name(service_id) << dendl;
CephXSessionAuthInfo svc_info;
Expand Down Expand Up @@ -264,7 +267,10 @@ int CephxServiceHandler::handle_request(
int service_err = 0;
for (uint32_t service_id = 1; service_id <= ticket_req.keys;
service_id <<= 1) {
if (ticket_req.keys & service_id) {
// skip CEPH_ENTITY_TYPE_AUTH: auth ticket must be obtained with
// CEPHX_GET_AUTH_SESSION_KEY
if ((ticket_req.keys & service_id) &&
service_id != CEPH_ENTITY_TYPE_AUTH) {
ldout(cct, 10) << " adding key for service "
<< ceph_entity_type_name(service_id) << dendl;
CephXSessionAuthInfo info;
Expand Down
9 changes: 9 additions & 0 deletions src/pybind/mgr/dashboard/controllers/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -946,3 +946,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'
Loading