summaryrefslogtreecommitdiffstats
path: root/testapi/opnfv_testapi
diff options
context:
space:
mode:
authorgrakiss <grakiss.wanglei@huawei.com>2017-07-07 15:06:29 +0800
committergrakiss <grakiss.wanglei@huawei.com>2017-07-20 09:51:50 +0800
commitcf402a2a6888ade5c57165dc978a59d2330307a7 (patch)
tree64031db8f9b231867ab2466679b23eb6ade79b37 /testapi/opnfv_testapi
parent0c684cf169699a48570cb96c565d40007d4f006f (diff)
role based access control and result upload
1. add role for user 2. user can upload test results Change-Id: I1c5370be7818edb0394f05e8b81f975deb98b286 Signed-off-by: grakiss <grakiss.wanglei@huawei.com>
Diffstat (limited to 'testapi/opnfv_testapi')
-rw-r--r--testapi/opnfv_testapi/common/message.py4
-rw-r--r--testapi/opnfv_testapi/resources/result_handlers.py52
-rw-r--r--testapi/opnfv_testapi/resources/result_models.py8
-rw-r--r--testapi/opnfv_testapi/router/url_mappings.py2
-rw-r--r--testapi/opnfv_testapi/tests/unit/executor.py14
-rw-r--r--testapi/opnfv_testapi/tests/unit/fake_pymongo.py3
-rw-r--r--testapi/opnfv_testapi/tests/unit/resources/test_result.py48
-rw-r--r--testapi/opnfv_testapi/ui/auth/constants.py2
-rw-r--r--testapi/opnfv_testapi/ui/auth/sign.py29
-rw-r--r--testapi/opnfv_testapi/ui/auth/user.py2
10 files changed, 147 insertions, 17 deletions
diff --git a/testapi/opnfv_testapi/common/message.py b/testapi/opnfv_testapi/common/message.py
index 98536ff..951cbaf 100644
--- a/testapi/opnfv_testapi/common/message.py
+++ b/testapi/opnfv_testapi/common/message.py
@@ -10,6 +10,10 @@ not_found_base = 'Could Not Found'
exist_base = 'Already Exists'
+def key_error(key):
+ return "KeyError: '{}'".format(key)
+
+
def no_body():
return 'No Body'
diff --git a/testapi/opnfv_testapi/resources/result_handlers.py b/testapi/opnfv_testapi/resources/result_handlers.py
index f9706fc..5eb1b92 100644
--- a/testapi/opnfv_testapi/resources/result_handlers.py
+++ b/testapi/opnfv_testapi/resources/result_handlers.py
@@ -6,8 +6,10 @@
# which accompanies this distribution, and is available at
# http://www.apache.org/licenses/LICENSE-2.0
##############################################################################
+import logging
from datetime import datetime
from datetime import timedelta
+import json
from bson import objectid
@@ -17,6 +19,7 @@ from opnfv_testapi.common import raises
from opnfv_testapi.resources import handlers
from opnfv_testapi.resources import result_models
from opnfv_testapi.tornado_swagger import swagger
+from opnfv_testapi.ui.auth import constants as auth_const
CONF = config.Config()
@@ -40,6 +43,7 @@ class GenericResultHandler(handlers.GenericApiHandler):
query = dict()
date_range = dict()
+ query['public'] = {'$not': {'$eq': 'false'}}
for k in self.request.query_arguments.keys():
v = self.get_query_argument(k)
if k == 'project' or k == 'pod' or k == 'case':
@@ -56,6 +60,14 @@ class GenericResultHandler(handlers.GenericApiHandler):
date_range.update({'$gte': str(v)})
elif k == 'to':
date_range.update({'$lt': str(v)})
+ elif k == 'signed':
+ openid = self.get_secure_cookie(auth_const.OPENID)
+ role = self.get_secure_cookie(auth_const.ROLE)
+ logging.info('role:%s', role)
+ if role:
+ del query['public']
+ if role != "reviewer":
+ query['user'] = openid
elif k != 'last' and k != 'page':
query[k] = v
if date_range:
@@ -90,9 +102,10 @@ class ResultsCLHandler(GenericResultHandler):
- criteria : the global criteria status passed or failed
- trust_indicator : evaluate the stability of the test case
to avoid running systematically long and stable test case
+ - signed : get logined user result
GET /results/project=functest&case=vPing&version=Arno-R1 \
- &pod=pod_name&period=15
+ &pod=pod_name&period=15&signed
@return 200: all test results consist with query,
empty list if no result is found
@rtype: L{TestResults}
@@ -152,6 +165,10 @@ class ResultsCLHandler(GenericResultHandler):
@type trust_indicator: L{float}
@in trust_indicator: query
@required trust_indicator: False
+ @param signed: user results or all results
+ @type signed: L{string}
+ @in signed: query
+ @required signed: False
"""
limitations = {'sort': {'_id': -1}}
last = self.get_query_argument('last', 0)
@@ -179,6 +196,9 @@ class ResultsCLHandler(GenericResultHandler):
@raise 404: pod/project/testcase not exist
@raise 400: body/pod_name/project_name/case_name not provided
"""
+ self._post()
+
+ def _post(self):
def pod_query():
return {'name': self.json_args.get('pod_name')}
@@ -193,9 +213,39 @@ class ResultsCLHandler(GenericResultHandler):
carriers = [('pods', pod_query),
('projects', project_query),
('testcases', testcase_query)]
+
self._create(miss_fields=miss_fields, carriers=carriers)
+class ResultsUploadHandler(ResultsCLHandler):
+ @swagger.operation(nickname="uploadTestResult")
+ def post(self):
+ """
+ @description: upload and create a test result
+ @param body: result to be created
+ @type body: L{ResultCreateRequest}
+ @in body: body
+ @rtype: L{CreateResponse}
+ @return 200: result is created.
+ @raise 404: pod/project/testcase not exist
+ @raise 400: body/pod_name/project_name/case_name not provided
+ """
+ logging.info('file upload')
+ fileinfo = self.request.files['file'][0]
+ is_public = self.get_body_argument('public')
+ logging.warning('public:%s', is_public)
+ logging.info('results is :%s', fileinfo['filename'])
+ logging.info('results is :%s', fileinfo['body'])
+ self.json_args = json.loads(fileinfo['body']).copy()
+ self.json_args['public'] = is_public
+
+ openid = self.get_secure_cookie(auth_const.OPENID)
+ if openid:
+ self.json_args['user'] = openid
+
+ super(ResultsUploadHandler, self)._post()
+
+
class ResultsGURHandler(GenericResultHandler):
@swagger.operation(nickname='getTestResultById')
def get(self, result_id):
diff --git a/testapi/opnfv_testapi/resources/result_models.py b/testapi/opnfv_testapi/resources/result_models.py
index 62a6dac..890bf82 100644
--- a/testapi/opnfv_testapi/resources/result_models.py
+++ b/testapi/opnfv_testapi/resources/result_models.py
@@ -54,6 +54,8 @@ class ResultCreateRequest(models.ModelBase):
build_tag=None,
scenario=None,
criteria=None,
+ user=None,
+ public="true",
trust_indicator=None):
self.pod_name = pod_name
self.project_name = project_name
@@ -66,6 +68,8 @@ class ResultCreateRequest(models.ModelBase):
self.build_tag = build_tag
self.scenario = scenario
self.criteria = criteria
+ self.user = user
+ self.public = public
self.trust_indicator = trust_indicator if trust_indicator else TI(0)
@@ -89,7 +93,7 @@ class TestResult(models.ModelBase):
pod_name=None, installer=None, version=None,
start_date=None, stop_date=None, details=None,
build_tag=None, scenario=None, criteria=None,
- trust_indicator=None):
+ user=None, public="true", trust_indicator=None):
self._id = _id
self.case_name = case_name
self.project_name = project_name
@@ -102,6 +106,8 @@ class TestResult(models.ModelBase):
self.build_tag = build_tag
self.scenario = scenario
self.criteria = criteria
+ self.user = user
+ self.public = public
self.trust_indicator = trust_indicator
@staticmethod
diff --git a/testapi/opnfv_testapi/router/url_mappings.py b/testapi/opnfv_testapi/router/url_mappings.py
index a2312de..37e719b 100644
--- a/testapi/opnfv_testapi/router/url_mappings.py
+++ b/testapi/opnfv_testapi/router/url_mappings.py
@@ -48,6 +48,7 @@ mappings = [
# Push results with mandatory request payload parameters
# (project, case, and pod)
(r"/api/v1/results", result_handlers.ResultsCLHandler),
+ (r'/api/v1/results/upload', result_handlers.ResultsUploadHandler),
(r"/api/v1/results/([^/]+)", result_handlers.ResultsGURHandler),
# scenarios
@@ -64,4 +65,5 @@ mappings = [
(r'/api/v1/auth/signin_return', sign.SigninReturnHandler),
(r'/api/v1/auth/signout', sign.SignoutHandler),
(r'/api/v1/profile', user.ProfileHandler),
+
]
diff --git a/testapi/opnfv_testapi/tests/unit/executor.py b/testapi/opnfv_testapi/tests/unit/executor.py
index b30c325..b8f696c 100644
--- a/testapi/opnfv_testapi/tests/unit/executor.py
+++ b/testapi/opnfv_testapi/tests/unit/executor.py
@@ -10,6 +10,20 @@ import functools
import httplib
+def upload(excepted_status, excepted_response):
+ def _upload(create_request):
+ @functools.wraps(create_request)
+ def wrap(self):
+ request = create_request(self)
+ status, body = self.upload(request)
+ if excepted_status == httplib.OK:
+ getattr(self, excepted_response)(body)
+ else:
+ self.assertIn(excepted_response, body)
+ return wrap
+ return _upload
+
+
def create(excepted_status, excepted_response):
def _create(create_request):
@functools.wraps(create_request)
diff --git a/testapi/opnfv_testapi/tests/unit/fake_pymongo.py b/testapi/opnfv_testapi/tests/unit/fake_pymongo.py
index 04785d2..d95ff37 100644
--- a/testapi/opnfv_testapi/tests/unit/fake_pymongo.py
+++ b/testapi/opnfv_testapi/tests/unit/fake_pymongo.py
@@ -189,9 +189,8 @@ class MemDb(object):
elif k == 'trust_indicator.current':
if content.get('trust_indicator').get('current') != v:
return False
- elif content.get(k, None) != v:
+ elif not isinstance(v, dict) and content.get(k, None) != v:
return False
-
return True
def _find(self, *args):
diff --git a/testapi/opnfv_testapi/tests/unit/resources/test_result.py b/testapi/opnfv_testapi/tests/unit/resources/test_result.py
index 2bff048..f199bc7 100644
--- a/testapi/opnfv_testapi/tests/unit/resources/test_result.py
+++ b/testapi/opnfv_testapi/tests/unit/resources/test_result.py
@@ -10,6 +10,7 @@ import copy
import httplib
import unittest
from datetime import datetime, timedelta
+import json
from opnfv_testapi.common import message
from opnfv_testapi.resources import pod_models
@@ -131,6 +132,22 @@ class TestResultBase(base.TestBase):
_, res = self.create_d()
return res.href.split('/')[-1]
+ def upload(self, req):
+ if req and not isinstance(req, str) and hasattr(req, 'format'):
+ req = req.format()
+ res = self.fetch(self.basePath + '/upload',
+ method='POST',
+ body=json.dumps(req),
+ headers=self.headers)
+
+ return self._get_return(res, self.create_res)
+
+
+class TestResultUpload(TestResultBase):
+ @executor.upload(httplib.BAD_REQUEST, message.key_error('file'))
+ def test_filenotfind(self):
+ return None
+
class TestResultCreate(TestResultBase):
@executor.create(httplib.BAD_REQUEST, message.no_body())
@@ -268,6 +285,16 @@ class TestResultGet(TestResultBase):
def test_queryLast(self):
return self._set_query('last=1')
+ @executor.query(httplib.OK, '_query_success', 4)
+ def test_queryPublic(self):
+ self._create_public_data()
+ return self._set_query('')
+
+ @executor.query(httplib.OK, '_query_success', 1)
+ def test_queryPrivate(self):
+ self._create_private_data()
+ return self._set_query('public=false')
+
@executor.query(httplib.OK, '_query_period_one', 1)
def test_combination(self):
return self._set_query('pod',
@@ -327,16 +354,29 @@ class TestResultGet(TestResultBase):
self.create(req)
return req
+ def _create_public_data(self, **kwargs):
+ req = copy.deepcopy(self.req_d)
+ req.public = 'true'
+ self.create(req)
+ return req
+
+ def _create_private_data(self, **kwargs):
+ req = copy.deepcopy(self.req_d)
+ req.public = 'false'
+ self.create(req)
+ return req
+
def _set_query(self, *args):
def get_value(arg):
return self.__getattribute__(arg) \
if arg != 'trust_indicator' else self.trust_indicator.current
uri = ''
for arg in args:
- if '=' in arg:
- uri += arg + '&'
- else:
- uri += '{}={}&'.format(arg, get_value(arg))
+ if arg:
+ if '=' in arg:
+ uri += arg + '&'
+ else:
+ uri += '{}={}&'.format(arg, get_value(arg))
return uri[0: -1]
diff --git a/testapi/opnfv_testapi/ui/auth/constants.py b/testapi/opnfv_testapi/ui/auth/constants.py
index 43f69d7..44ccb46 100644
--- a/testapi/opnfv_testapi/ui/auth/constants.py
+++ b/testapi/opnfv_testapi/ui/auth/constants.py
@@ -1,4 +1,6 @@
OPENID = 'openid'
+ROLE = 'role'
+DEFAULT_ROLE = 'user'
# OpenID parameters
OPENID_MODE = 'openid.mode'
diff --git a/testapi/opnfv_testapi/ui/auth/sign.py b/testapi/opnfv_testapi/ui/auth/sign.py
index 6a9d94e..5b36225 100644
--- a/testapi/opnfv_testapi/ui/auth/sign.py
+++ b/testapi/opnfv_testapi/ui/auth/sign.py
@@ -1,4 +1,7 @@
from six.moves.urllib import parse
+from tornado import gen
+from tornado import web
+import logging
from opnfv_testapi.common import config
from opnfv_testapi.ui.auth import base
@@ -31,20 +34,31 @@ class SigninHandler(base.BaseHandler):
class SigninReturnHandler(base.BaseHandler):
+ @web.asynchronous
+ @gen.coroutine
def get(self):
if self.get_query_argument(const.OPENID_MODE) == 'cancel':
self._auth_failure('Authentication canceled.')
openid = self.get_query_argument(const.OPENID_CLAIMED_ID)
- user_info = {
+ role = const.DEFAULT_ROLE
+ new_user_info = {
'openid': openid,
'email': self.get_query_argument(const.OPENID_NS_SREG_EMAIL),
- 'fullname': self.get_query_argument(const.OPENID_NS_SREG_FULLNAME)
+ 'fullname': self.get_query_argument(const.OPENID_NS_SREG_FULLNAME),
+ const.ROLE: role
}
+ user = yield self.db_find_one({'openid': openid})
+ if not user:
+ self.db_save(self.table, new_user_info)
+ logging.info('save to db:%s', new_user_info)
+ else:
+ role = user.get(const.ROLE)
- self.db_save(self.table, user_info)
- if not self.get_secure_cookie('openid'):
- self.set_secure_cookie('openid', openid)
+ self.clear_cookie(const.OPENID)
+ self.clear_cookie(const.ROLE)
+ self.set_secure_cookie(const.OPENID, openid)
+ self.set_secure_cookie(const.ROLE, role)
self.redirect(url=CONF.ui_url)
def _auth_failure(self, message):
@@ -57,9 +71,8 @@ class SigninReturnHandler(base.BaseHandler):
class SignoutHandler(base.BaseHandler):
def get(self):
"""Handle signout request."""
- openid = self.get_secure_cookie(const.OPENID)
- if openid:
- self.clear_cookie(const.OPENID)
+ self.clear_cookie(const.OPENID)
+ self.clear_cookie(const.ROLE)
params = {'openid_logout': CONF.osid_openid_logout_endpoint}
url = parse.urljoin(CONF.ui_url,
'/#/logout?' + parse.urlencode(params))
diff --git a/testapi/opnfv_testapi/ui/auth/user.py b/testapi/opnfv_testapi/ui/auth/user.py
index 140bca5..2fca2a8 100644
--- a/testapi/opnfv_testapi/ui/auth/user.py
+++ b/testapi/opnfv_testapi/ui/auth/user.py
@@ -17,7 +17,7 @@ class ProfileHandler(base.BaseHandler):
"openid": user.get('openid'),
"email": user.get('email'),
"fullname": user.get('fullname'),
- "is_admin": False
+ "role": user.get('role', 'user')
})
except Exception:
pass