From 9e36409f1bf52a6fd510c4f4896d85761b59cfa9 Mon Sep 17 00:00:00 2001 From: SerenaFeng Date: Wed, 30 Aug 2017 11:59:46 +0800 Subject: leverage LFID as Authentication delete openid authentication add LFID authentication Change-Id: Iead144b5130bce51448024e65092fdea3bb2f07a Signed-off-by: SerenaFeng --- testapi/.gitignore | 2 +- .../testapi-ui/components/profile/profile.html | 11 ++- .../components/profile/profileController.js | 2 +- testapi/etc/config.ini | 48 +------------ testapi/opnfv_testapi/common/check.py | 35 ++++++++++ testapi/opnfv_testapi/common/constants.py | 5 ++ testapi/opnfv_testapi/resources/result_handlers.py | 19 +++-- testapi/opnfv_testapi/router/url_mappings.py | 3 +- testapi/opnfv_testapi/ui/auth/base.py | 35 ---------- testapi/opnfv_testapi/ui/auth/constants.py | 18 ----- testapi/opnfv_testapi/ui/auth/sign.py | 80 ++++------------------ testapi/opnfv_testapi/ui/auth/user.py | 43 ++++++------ testapi/opnfv_testapi/ui/root.py | 6 +- testapi/requirements.txt | 1 + 14 files changed, 104 insertions(+), 204 deletions(-) create mode 100644 testapi/opnfv_testapi/common/constants.py delete mode 100644 testapi/opnfv_testapi/ui/auth/base.py delete mode 100644 testapi/opnfv_testapi/ui/auth/constants.py (limited to 'testapi') diff --git a/testapi/.gitignore b/testapi/.gitignore index 00f8a03..86ec0d2 100644 --- a/testapi/.gitignore +++ b/testapi/.gitignore @@ -4,4 +4,4 @@ setup.cfg-e opnfv_testapi/static build *.egg-info - +3rd_party/static/static diff --git a/testapi/3rd_party/static/testapi-ui/components/profile/profile.html b/testapi/3rd_party/static/testapi-ui/components/profile/profile.html index dc97c41..763f5d1 100644 --- a/testapi/3rd_party/static/testapi-ui/components/profile/profile.html +++ b/testapi/3rd_party/static/testapi-ui/components/profile/profile.html @@ -3,9 +3,16 @@
- - + + + + +
User name {{auth.currentUser.fullname}}
User OpenId {{auth.currentUser.openid}}
User {{auth.currentUser.user}}
Fullname {{auth.currentUser.fullname}}
Email {{auth.currentUser.email}}
Groups +
+ {{group}}
+
+
diff --git a/testapi/3rd_party/static/testapi-ui/components/profile/profileController.js b/testapi/3rd_party/static/testapi-ui/components/profile/profileController.js index 0660e19..5dbdf7b 100644 --- a/testapi/3rd_party/static/testapi-ui/components/profile/profileController.js +++ b/testapi/3rd_party/static/testapi-ui/components/profile/profileController.js @@ -26,7 +26,7 @@ * This is a provider for the user's uploaded public keys. */ function PubKeys($resource, testapiApiUrl) { - return $resource(testapiApiUrl + '/profile/pubkeys/:id', null, null); + return $resource(testapiApiUrl + '/user/pubkeys/:id', null, null); } angular diff --git a/testapi/etc/config.ini b/testapi/etc/config.ini index 1ec899f..db0e191 100644 --- a/testapi/etc/config.ini +++ b/testapi/etc/config.ini @@ -21,48 +21,6 @@ authenticate = False [ui] url = http://localhost:8000 -[osid] - -# OpenStackID Auth Server URI. (string value) -openstack_openid_endpoint = https://openstackid.org/accounts/openid2 - -# OpenStackID logout URI. (string value) -openid_logout_endpoint = https://openstackid.org/accounts/user/logout - -# Interaction mode. Specifies whether Openstack Id IdP may interact -# with the user to determine the outcome of the request. (string -# value) -openid_mode = checkid_setup - -# Protocol version. Value identifying the OpenID protocol version -# being used. This value should be "http://specs.openid.net/auth/2.0". -# (string value) -openid_ns = http://specs.openid.net/auth/2.0 - -# Return endpoint in Refstack's API. Value indicating the endpoint -# where the user should be returned to after signing in. Openstack Id -# Idp only supports HTTPS address types. (string value) -openid_return_to = v1/auth/signin_return - -# Claimed identifier. This value must be set to -# "http://specs.openid.net/auth/2.0/identifier_select". or to user -# claimed identity (user local identifier or user owned identity [ex: -# custom html hosted on a owned domain set to html discover]). (string -# value) -openid_claimed_id = http://specs.openid.net/auth/2.0/identifier_select - -# Alternate identifier. This value must be set to -# http://specs.openid.net/auth/2.0/identifier_select. (string value) -openid_identity = http://specs.openid.net/auth/2.0/identifier_select - -# Indicates request for user attribute information. This value must be -# set to "http://openid.net/extensions/sreg/1.1". (string value) -openid_ns_sreg = http://openid.net/extensions/sreg/1.1 - -# Comma-separated list of field names which, if absent from the -# response, will prevent the Consumer from completing the registration -# without End User interation. The field names are those that are -# specified in the Response Format, with the "openid.sreg." prefix -# removed. Valid values include: "country", "email", "firstname", -# "language", "lastname" (string value) -openid_sreg_required = email,fullname +[lfid] +# Linux Foundation cas URL +cas_url = https://identity.linuxfoundation.org/cas/ diff --git a/testapi/opnfv_testapi/common/check.py b/testapi/opnfv_testapi/common/check.py index 24ba876..009d3d4 100644 --- a/testapi/opnfv_testapi/common/check.py +++ b/testapi/opnfv_testapi/common/check.py @@ -8,14 +8,49 @@ ############################################################################## import functools +import cas from tornado import gen from tornado import web +from opnfv_testapi.common import constants from opnfv_testapi.common import message from opnfv_testapi.common import raises +from opnfv_testapi.common.config import CONF from opnfv_testapi.db import api as dbapi +def login(method): + @web.asynchronous + @gen.coroutine + @functools.wraps(method) + def wrapper(self, *args, **kwargs): + ticket = self.get_query_argument('ticket', default=None) + if ticket: + client = cas.CASClient(version='2', + server_url=CONF.lfid_cas_url, + service_url=CONF.ui_url) + (user, attrs, _) = client.verify_ticket(ticket=ticket) + print 'login user: {}'.format(user) + login_user = { + 'user': user, + 'email': attrs.get('mail'), + 'fullname': attrs.get('field_lf_full_name'), + 'groups': constants.TESTAPI_USERS + attrs.get('group', []) + } + q_user = {'user': user} + db_user = yield dbapi.db_find_one(constants.USER_TABLE, q_user) + if not db_user: + dbapi.db_save(constants.USER_TABLE, login_user) + else: + dbapi.db_update(constants.USER_TABLE, q_user, login_user) + + self.clear_cookie(constants.TESTAPI_ID) + self.set_secure_cookie(constants.TESTAPI_ID, user) + ret = yield gen.coroutine(method)(self, *args, **kwargs) + raise gen.Return(ret) + return wrapper + + def authenticate(method): @web.asynchronous @gen.coroutine diff --git a/testapi/opnfv_testapi/common/constants.py b/testapi/opnfv_testapi/common/constants.py new file mode 100644 index 0000000..b37ebb3 --- /dev/null +++ b/testapi/opnfv_testapi/common/constants.py @@ -0,0 +1,5 @@ +TESTAPI_ID = 'testapi_id' +CSRF_TOKEN = 'csrf_token' +ROLE = 'role' +TESTAPI_USERS = ['opnfv-testapi-users'] +USER_TABLE = 'users' diff --git a/testapi/opnfv_testapi/resources/result_handlers.py b/testapi/opnfv_testapi/resources/result_handlers.py index 9389d26..e202f5c 100644 --- a/testapi/opnfv_testapi/resources/result_handlers.py +++ b/testapi/opnfv_testapi/resources/result_handlers.py @@ -6,20 +6,20 @@ # 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 +import logging from bson import objectid +from datetime import datetime +from datetime import timedelta -from opnfv_testapi.common.config import CONF +from opnfv_testapi.common import constants from opnfv_testapi.common import message from opnfv_testapi.common import raises +from opnfv_testapi.common.config import CONF 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 class GenericResultHandler(handlers.GenericApiHandler): @@ -59,13 +59,12 @@ class GenericResultHandler(handlers.GenericApiHandler): 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) + username = self.get_secure_cookie(constants.TESTAPI_ID) + role = self.get_secure_cookie(constants.ROLE) if role: del query['public'] if role != "reviewer": - query['user'] = openid + query['user'] = username elif k not in ['last', 'page', 'descend']: query[k] = v if date_range: @@ -246,7 +245,7 @@ class ResultsUploadHandler(ResultsCLHandler): self.json_args = json.loads(fileinfo['body']).copy() self.json_args['public'] = is_public - openid = self.get_secure_cookie(auth_const.OPENID) + openid = self.get_secure_cookie(constants.TESTAPI_ID) if openid: self.json_args['user'] = openid diff --git a/testapi/opnfv_testapi/router/url_mappings.py b/testapi/opnfv_testapi/router/url_mappings.py index 3e3ab87..be6240e 100644 --- a/testapi/opnfv_testapi/router/url_mappings.py +++ b/testapi/opnfv_testapi/router/url_mappings.py @@ -76,8 +76,7 @@ mappings = [ (r'/', root.RootHandler), (r'/api/v1/auth/signin', sign.SigninHandler), - (r'/api/v1/auth/signin_return', sign.SigninReturnHandler), (r'/api/v1/auth/signout', sign.SignoutHandler), - (r'/api/v1/profile', user.ProfileHandler), + (r'/api/v1/profile', user.UserHandler), ] diff --git a/testapi/opnfv_testapi/ui/auth/base.py b/testapi/opnfv_testapi/ui/auth/base.py deleted file mode 100644 index bea87c4..0000000 --- a/testapi/opnfv_testapi/ui/auth/base.py +++ /dev/null @@ -1,35 +0,0 @@ -import random -import string - -from six.moves.urllib import parse - -from opnfv_testapi.resources import handlers - - -class BaseHandler(handlers.GenericApiHandler): - def __init__(self, application, request, **kwargs): - super(BaseHandler, self).__init__(application, request, **kwargs) - self.table = 'users' - - def set_cookies(self, cookies): - for cookie_n, cookie_v in cookies: - self.set_secure_cookie(cookie_n, cookie_v) - - -def get_token(length=30): - """Get random token.""" - return ''.join(random.choice(string.ascii_lowercase) - for i in range(length)) - - -def set_query_params(url, params): - """Set params in given query.""" - url_parts = parse.urlparse(url) - url = parse.urlunparse(( - url_parts.scheme, - url_parts.netloc, - url_parts.path, - url_parts.params, - parse.urlencode(params), - url_parts.fragment)) - return url diff --git a/testapi/opnfv_testapi/ui/auth/constants.py b/testapi/opnfv_testapi/ui/auth/constants.py deleted file mode 100644 index 44ccb46..0000000 --- a/testapi/opnfv_testapi/ui/auth/constants.py +++ /dev/null @@ -1,18 +0,0 @@ -OPENID = 'openid' -ROLE = 'role' -DEFAULT_ROLE = 'user' - -# OpenID parameters -OPENID_MODE = 'openid.mode' -OPENID_NS = 'openid.ns' -OPENID_RETURN_TO = 'openid.return_to' -OPENID_CLAIMED_ID = 'openid.claimed_id' -OPENID_IDENTITY = 'openid.identity' -OPENID_REALM = 'openid.realm' -OPENID_NS_SREG = 'openid.ns.sreg' -OPENID_NS_SREG_REQUIRED = 'openid.sreg.required' -OPENID_NS_SREG_EMAIL = 'openid.sreg.email' -OPENID_NS_SREG_FULLNAME = 'openid.sreg.fullname' -OPENID_ERROR = 'openid.error' - -CSRF_TOKEN = 'csrf_token' diff --git a/testapi/opnfv_testapi/ui/auth/sign.py b/testapi/opnfv_testapi/ui/auth/sign.py index 4623952..01cd0f7 100644 --- a/testapi/opnfv_testapi/ui/auth/sign.py +++ b/testapi/opnfv_testapi/ui/auth/sign.py @@ -1,76 +1,22 @@ -from six.moves.urllib import parse -from tornado import gen -from tornado import web +from cas import CASClient +from opnfv_testapi.common import constants from opnfv_testapi.common.config import CONF -from opnfv_testapi.db import api as dbapi -from opnfv_testapi.ui.auth import base -from opnfv_testapi.ui.auth import constants as const +from opnfv_testapi.resources import handlers -class SigninHandler(base.BaseHandler): +class SigninHandler(handlers.GenericApiHandler): def get(self): - csrf_token = base.get_token() - return_endpoint = parse.urljoin(CONF.api_url, - CONF.osid_openid_return_to) - return_to = base.set_query_params(return_endpoint, - {const.CSRF_TOKEN: csrf_token}) + client = CASClient(version='2', + server_url=CONF.lfid_cas_url, + service_url=CONF.ui_url) + self.redirect(url=(client.get_login_url())) - params = { - const.OPENID_MODE: CONF.osid_openid_mode, - const.OPENID_NS: CONF.osid_openid_ns, - const.OPENID_RETURN_TO: return_to, - const.OPENID_CLAIMED_ID: CONF.osid_openid_claimed_id, - const.OPENID_IDENTITY: CONF.osid_openid_identity, - const.OPENID_REALM: CONF.api_url, - const.OPENID_NS_SREG: CONF.osid_openid_ns_sreg, - const.OPENID_NS_SREG_REQUIRED: CONF.osid_openid_sreg_required, - } - url = CONF.osid_openstack_openid_endpoint - url = base.set_query_params(url, params) - self.redirect(url=url, permanent=False) - -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) - 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), - const.ROLE: role - } - user = yield dbapi.db_find_one(self.table, {'openid': openid}) - if not user: - dbapi.db_save(self.table, new_user_info) - else: - role = user.get(const.ROLE) - - 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): - params = {'message': message} - url = parse.urljoin(CONF.ui_url, - '/#/auth_failure?' + parse.urlencode(params)) - self.redirect(url) - - -class SignoutHandler(base.BaseHandler): +class SignoutHandler(handlers.GenericApiHandler): def get(self): """Handle signout request.""" - 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)) - self.redirect(url) + self.clear_cookie(constants.TESTAPI_ID) + client = CASClient(version='2', + server_url=CONF.lfid_cas_url) + self.redirect(url=(client.get_logout_url(redirect_url=CONF.ui_url))) diff --git a/testapi/opnfv_testapi/ui/auth/user.py b/testapi/opnfv_testapi/ui/auth/user.py index 955cdee..ab86007 100644 --- a/testapi/opnfv_testapi/ui/auth/user.py +++ b/testapi/opnfv_testapi/ui/auth/user.py @@ -1,25 +1,26 @@ -from tornado import gen -from tornado import web - +from opnfv_testapi.common import constants from opnfv_testapi.common import raises -from opnfv_testapi.db import api as dbapi -from opnfv_testapi.ui.auth import base +from opnfv_testapi.resources import handlers +from opnfv_testapi.resources import models + + +class User(models.ModelBase): + def __init__(self, user=None, email=None, fullname=None, groups=None): + self.user = user + self.email = email + self.fullname = fullname + self.groups = groups + +class UserHandler(handlers.GenericApiHandler): + def __init__(self, application, request, **kwargs): + super(UserHandler, self).__init__(application, request, **kwargs) + self.table = 'users' + self.table_cls = User -class ProfileHandler(base.BaseHandler): - @web.asynchronous - @gen.coroutine def get(self): - openid = self.get_secure_cookie('openid') - if openid: - try: - user = yield dbapi.db_find_one(self.table, {'openid': openid}) - self.finish_request({ - "openid": user.get('openid'), - "email": user.get('email'), - "fullname": user.get('fullname'), - "role": user.get('role', 'user') - }) - except Exception: - pass - raises.Unauthorized('Unauthorized') + username = self.get_secure_cookie(constants.TESTAPI_ID) + if username: + self._get_one(query={'user': username}) + else: + raises.Unauthorized('Unauthorized') diff --git a/testapi/opnfv_testapi/ui/root.py b/testapi/opnfv_testapi/ui/root.py index 5b2c922..069ad5e 100644 --- a/testapi/opnfv_testapi/ui/root.py +++ b/testapi/opnfv_testapi/ui/root.py @@ -1,10 +1,12 @@ -from opnfv_testapi.resources.handlers import GenericApiHandler +from opnfv_testapi.common import check from opnfv_testapi.common.config import CONF +from opnfv_testapi.resources import handlers -class RootHandler(GenericApiHandler): +class RootHandler(handlers.GenericApiHandler): def get_template_path(self): return CONF.static_path + @check.login def get(self): self.render('testapi-ui/index.html') diff --git a/testapi/requirements.txt b/testapi/requirements.txt index 4b6f75c..fbd2e0e 100644 --- a/testapi/requirements.txt +++ b/testapi/requirements.txt @@ -8,3 +8,4 @@ tornado>=3.1,<=4.3 # Apache-2.0 epydoc>=0.3.1 six>=1.9.0 # MIT motor # Apache-2.0 +python-cas -- cgit 1.2.3-korg