From 7479f29b3d0a613d459cd26b31eab38f00a00b22 Mon Sep 17 00:00:00 2001 From: SerenaFeng Date: Mon, 11 Sep 2017 12:37:22 +0800 Subject: leverage LFID authentication to pod creation only valid linux foundation user is allowed to create the new pod add owner field in pods to track the pod creator Change-Id: Icada07152069f7c826bfa6122cb86db8c4e3bf68 Signed-off-by: SerenaFeng --- .../static/testapi-ui/components/pods/pods.html | 1 + testapi/opnfv_testapi/common/check.py | 17 ++++++++ testapi/opnfv_testapi/common/message.py | 8 ++++ testapi/opnfv_testapi/resources/handlers.py | 1 + testapi/opnfv_testapi/resources/pod_models.py | 3 +- testapi/opnfv_testapi/tests/unit/executor.py | 33 ++++++++++++++++ testapi/opnfv_testapi/tests/unit/fake_pymongo.py | 1 + .../tests/unit/resources/test_base.py | 25 ++++++++++++ .../opnfv_testapi/tests/unit/resources/test_pod.py | 45 ++++++++++++++++------ .../tests/unit/resources/test_result.py | 17 ++++---- 10 files changed, 128 insertions(+), 23 deletions(-) (limited to 'testapi') diff --git a/testapi/3rd_party/static/testapi-ui/components/pods/pods.html b/testapi/3rd_party/static/testapi-ui/components/pods/pods.html index e366670..22f2934 100644 --- a/testapi/3rd_party/static/testapi-ui/components/pods/pods.html +++ b/testapi/3rd_party/static/testapi-ui/components/pods/pods.html @@ -54,6 +54,7 @@ {{pod.name}}

+ owner: {{pod.owner}}
role: {{pod.role}}
mode: {{pod.mode}}
create_date: {{pod.creation_date}}
diff --git a/testapi/opnfv_testapi/common/check.py b/testapi/opnfv_testapi/common/check.py index 9ded48d..e80b1c6 100644 --- a/testapi/opnfv_testapi/common/check.py +++ b/testapi/opnfv_testapi/common/check.py @@ -11,11 +11,28 @@ import re from tornado import gen +from opnfv_testapi.common import constants from opnfv_testapi.common import message from opnfv_testapi.common import raises from opnfv_testapi.db import api as dbapi +def is_authorized(method): + @functools.wraps(method) + def wrapper(self, *args, **kwargs): + if self.table in ['pods']: + testapi_id = self.get_secure_cookie(constants.TESTAPI_ID) + if not testapi_id: + raises.Unauthorized(message.not_login()) + user_info = yield dbapi.db_find_one('users', {'user': testapi_id}) + if not user_info: + raises.Unauthorized(message.not_lfid()) + kwargs['owner'] = testapi_id + ret = yield gen.coroutine(method)(self, *args, **kwargs) + raise gen.Return(ret) + return wrapper + + def valid_token(method): @functools.wraps(method) def wrapper(self, *args, **kwargs): diff --git a/testapi/opnfv_testapi/common/message.py b/testapi/opnfv_testapi/common/message.py index 951cbaf..8b5c3fb 100644 --- a/testapi/opnfv_testapi/common/message.py +++ b/testapi/opnfv_testapi/common/message.py @@ -42,6 +42,14 @@ def invalid_token(): return 'Invalid Token' +def not_login(): + return 'TestAPI id is not provided' + + +def not_lfid(): + return 'Not a valid Linux Foundation Account' + + def no_update(): return 'Nothing to update' diff --git a/testapi/opnfv_testapi/resources/handlers.py b/testapi/opnfv_testapi/resources/handlers.py index 757c817..8e5dab2 100644 --- a/testapi/opnfv_testapi/resources/handlers.py +++ b/testapi/opnfv_testapi/resources/handlers.py @@ -75,6 +75,7 @@ class GenericApiHandler(web.RequestHandler): @web.asynchronous @gen.coroutine + @check.is_authorized @check.valid_token @check.no_body @check.miss_fields diff --git a/testapi/opnfv_testapi/resources/pod_models.py b/testapi/opnfv_testapi/resources/pod_models.py index 2c3ea97..415d3d6 100644 --- a/testapi/opnfv_testapi/resources/pod_models.py +++ b/testapi/opnfv_testapi/resources/pod_models.py @@ -29,13 +29,14 @@ class PodCreateRequest(models.ModelBase): class Pod(models.ModelBase): def __init__(self, name='', mode='', details='', - role="", _id='', create_date=''): + role="", _id='', create_date='', owner=''): self.name = name self.mode = mode self.details = details self.role = role self._id = _id self.creation_date = create_date + self.owner = owner @swagger.model() diff --git a/testapi/opnfv_testapi/tests/unit/executor.py b/testapi/opnfv_testapi/tests/unit/executor.py index b8f696c..aa99b90 100644 --- a/testapi/opnfv_testapi/tests/unit/executor.py +++ b/testapi/opnfv_testapi/tests/unit/executor.py @@ -9,6 +9,39 @@ import functools import httplib +from concurrent.futures import ThreadPoolExecutor +import mock + + +O_get_secure_cookie = ( + 'opnfv_testapi.resources.handlers.GenericApiHandler.get_secure_cookie') + + +def thread_execute(method, *args, **kwargs): + with ThreadPoolExecutor(max_workers=2) as executor: + result = executor.submit(method, *args, **kwargs) + return result + + +def mock_invalid_lfid(): + def _mock_invalid_lfid(xstep): + def wrap(self, *args, **kwargs): + with mock.patch(O_get_secure_cookie) as m_cookie: + m_cookie.return_value = 'InvalidUser' + return xstep(self, *args, **kwargs) + return wrap + return _mock_invalid_lfid + + +def mock_valid_lfid(): + def _mock_valid_lfid(xstep): + def wrap(self, *args, **kwargs): + with mock.patch(O_get_secure_cookie) as m_cookie: + m_cookie.return_value = 'ValidUser' + return xstep(self, *args, **kwargs) + return wrap + return _mock_valid_lfid + def upload(excepted_status, excepted_response): def _upload(create_request): diff --git a/testapi/opnfv_testapi/tests/unit/fake_pymongo.py b/testapi/opnfv_testapi/tests/unit/fake_pymongo.py index 3320a86..c44a92c 100644 --- a/testapi/opnfv_testapi/tests/unit/fake_pymongo.py +++ b/testapi/opnfv_testapi/tests/unit/fake_pymongo.py @@ -288,3 +288,4 @@ testcases = MemDb('testcases') results = MemDb('results') scenarios = MemDb('scenarios') tokens = MemDb('tokens') +users = MemDb('users') diff --git a/testapi/opnfv_testapi/tests/unit/resources/test_base.py b/testapi/opnfv_testapi/tests/unit/resources/test_base.py index 39633e5..89cd7e8 100644 --- a/testapi/opnfv_testapi/tests/unit/resources/test_base.py +++ b/testapi/opnfv_testapi/tests/unit/resources/test_base.py @@ -6,13 +6,16 @@ # which accompanies this distribution, and is available at # http://www.apache.org/licenses/LICENSE-2.0 ############################################################################## +from datetime import datetime import json from os import path +from bson.objectid import ObjectId import mock from tornado import testing from opnfv_testapi.resources import models +from opnfv_testapi.resources import pod_models from opnfv_testapi.tests.unit import fake_pymongo @@ -26,10 +29,32 @@ class TestBase(testing.AsyncHTTPTestCase): self.get_res = None self.list_res = None self.update_res = None + self.pod_d = pod_models.Pod(name='zte-pod1', + mode='virtual', + details='zte pod 1', + role='community-ci', + _id=str(ObjectId()), + owner='ValidUser', + create_date=str(datetime.now())) + self.pod_e = pod_models.Pod(name='zte-pod2', + mode='metal', + details='zte pod 2', + role='production-ci', + _id=str(ObjectId()), + owner='ValidUser', + create_date=str(datetime.now())) self.req_d = None self.req_e = None self.addCleanup(self._clear) super(TestBase, self).setUp() + fake_pymongo.users.insert({"user": "ValidUser", + 'email': 'validuser@lf.com', + 'fullname': 'Valid User', + 'groups': [ + 'opnfv-testapi-users', + 'opnfv-gerrit-functest-submitters', + 'opnfv-gerrit-qtip-contributors'] + }) def tearDown(self): self.db_patcher.stop() diff --git a/testapi/opnfv_testapi/tests/unit/resources/test_pod.py b/testapi/opnfv_testapi/tests/unit/resources/test_pod.py index d1a19f7..5d9da3a 100644 --- a/testapi/opnfv_testapi/tests/unit/resources/test_pod.py +++ b/testapi/opnfv_testapi/tests/unit/resources/test_pod.py @@ -12,24 +12,29 @@ import unittest from opnfv_testapi.common import message from opnfv_testapi.resources import pod_models from opnfv_testapi.tests.unit import executor +from opnfv_testapi.tests.unit import fake_pymongo from opnfv_testapi.tests.unit.resources import test_base as base class TestPodBase(base.TestBase): def setUp(self): super(TestPodBase, self).setUp() - self.req_d = pod_models.PodCreateRequest('zte-1', 'virtual', - 'zte pod 1', 'ci-pod') - self.req_e = pod_models.PodCreateRequest('zte-2', 'metal', 'zte pod 2') - self.req_f = pod_models.PodCreateRequest('Zte-1', 'virtual', - 'zte pod 1', 'ci-pod') self.get_res = pod_models.Pod self.list_res = pod_models.Pods self.basePath = '/api/v1/pods' + self.req_d = pod_models.PodCreateRequest(name=self.pod_d.name, + mode=self.pod_d.mode, + details=self.pod_d.details, + role=self.pod_d.role) + self.req_e = pod_models.PodCreateRequest(name=self.pod_e.name, + mode=self.pod_e.mode, + details=self.pod_e.details, + role=self.pod_e.role) def assert_get_body(self, pod, req=None): if not req: req = self.req_d + self.assertEqual(pod.owner, 'ValidUser') self.assertEqual(pod.name, req.name) self.assertEqual(pod.mode, req.mode) self.assertEqual(pod.details, req.details) @@ -39,38 +44,54 @@ class TestPodBase(base.TestBase): class TestPodCreate(TestPodBase): + @executor.create(httplib.BAD_REQUEST, message.not_login()) + def test_notlogin(self): + return self.req_d + + @executor.mock_invalid_lfid() + @executor.create(httplib.BAD_REQUEST, message.not_lfid()) + def test_invalidLfid(self): + return self.req_d + + @executor.mock_valid_lfid() @executor.create(httplib.BAD_REQUEST, message.no_body()) def test_withoutBody(self): return None + @executor.mock_valid_lfid() @executor.create(httplib.BAD_REQUEST, message.missing('name')) def test_emptyName(self): return pod_models.PodCreateRequest('') + @executor.mock_valid_lfid() @executor.create(httplib.BAD_REQUEST, message.missing('name')) def test_noneName(self): return pod_models.PodCreateRequest(None) + @executor.mock_valid_lfid() @executor.create(httplib.OK, 'assert_create_body') def test_success(self): return self.req_d + @executor.mock_valid_lfid() @executor.create(httplib.FORBIDDEN, message.exist_base) def test_alreadyExist(self): - self.create_d() + fake_pymongo.pods.insert(self.pod_d.format()) return self.req_d + @executor.mock_valid_lfid() @executor.create(httplib.FORBIDDEN, message.exist_base) def test_alreadyExistCaseInsensitive(self): - self.create(self.req_f) + fake_pymongo.pods.insert(self.pod_d.format()) + self.req_d.name = self.req_d.name.upper() return self.req_d class TestPodGet(TestPodBase): def setUp(self): super(TestPodGet, self).setUp() - self.create_d() - self.create_e() + fake_pymongo.pods.insert(self.pod_d.format()) + fake_pymongo.pods.insert(self.pod_e.format()) @executor.get(httplib.NOT_FOUND, message.not_found_base) def test_notExist(self): @@ -78,7 +99,7 @@ class TestPodGet(TestPodBase): @executor.get(httplib.OK, 'assert_get_body') def test_getOne(self): - return self.req_d.name + return self.pod_d.name @executor.get(httplib.OK, '_assert_list') def test_list(self): @@ -87,10 +108,10 @@ class TestPodGet(TestPodBase): def _assert_list(self, body): self.assertEqual(len(body.pods), 2) for pod in body.pods: - if self.req_d.name == pod.name: + if self.pod_d.name == pod.name: self.assert_get_body(pod) else: - self.assert_get_body(pod, self.req_e) + self.assert_get_body(pod, self.pod_e) if __name__ == '__main__': diff --git a/testapi/opnfv_testapi/tests/unit/resources/test_result.py b/testapi/opnfv_testapi/tests/unit/resources/test_result.py index 1e83ed3..f5026c9 100644 --- a/testapi/opnfv_testapi/tests/unit/resources/test_result.py +++ b/testapi/opnfv_testapi/tests/unit/resources/test_result.py @@ -7,17 +7,18 @@ # http://www.apache.org/licenses/LICENSE-2.0 ############################################################################## import copy +from datetime import datetime +from datetime import timedelta import httplib -import unittest -from datetime import datetime, timedelta import json +import unittest from opnfv_testapi.common import message -from opnfv_testapi.resources import pod_models from opnfv_testapi.resources import project_models from opnfv_testapi.resources import result_models from opnfv_testapi.resources import testcase_models from opnfv_testapi.tests.unit import executor +from opnfv_testapi.tests.unit import fake_pymongo from opnfv_testapi.tests.unit.resources import test_base as base @@ -52,7 +53,8 @@ class Details(object): class TestResultBase(base.TestBase): def setUp(self): - self.pod = 'zte-pod1' + super(TestResultBase, self).setUp() + self.pod = self.pod_d.name self.project = 'functest' self.case = 'vPing' self.installer = 'fuel' @@ -65,7 +67,6 @@ class TestResultBase(base.TestBase): self.stop_date = str(datetime.now() + timedelta(minutes=1)) self.update_date = str(datetime.now() + timedelta(days=1)) self.update_step = -0.05 - super(TestResultBase, self).setUp() self.details = Details(timestart='0', duration='9s', status='OK') self.req_d = result_models.ResultCreateRequest( pod_name=self.pod, @@ -84,10 +85,6 @@ class TestResultBase(base.TestBase): self.list_res = result_models.TestResults self.update_res = result_models.TestResult self.basePath = '/api/v1/results' - self.req_pod = pod_models.PodCreateRequest( - self.pod, - 'metal', - 'zte pod 1') self.req_project = project_models.ProjectCreateRequest( self.project, 'vping test') @@ -95,7 +92,7 @@ class TestResultBase(base.TestBase): self.case, '/cases/vping', 'vping-ssh test') - self.create_help('/api/v1/pods', self.req_pod) + fake_pymongo.pods.insert(self.pod_d.format()) self.create_help('/api/v1/projects', self.req_project) self.create_help('/api/v1/projects/%s/cases', self.req_testcase, -- cgit 1.2.3-korg