Commit ab35cee8 authored by Daniele Venzano's avatar Daniele Venzano

Remove oauth authentication for the API

parent 713258e5
......@@ -25,7 +25,6 @@ from zoe_api.rest_api.userinfo import UserInfoAPI
from zoe_api.rest_api.service import ServiceAPI, ServiceLogsAPI
from zoe_api.rest_api.discovery import DiscoveryAPI
from zoe_api.rest_api.statistics import SchedulerStatsAPI
from zoe_api.rest_api.oauth import OAuthGetAPI, OAuthRevokeAPI
from zoe_api.rest_api.login import LoginAPI
from zoe_api.rest_api.validation import ZAppValidateAPI
......@@ -56,10 +55,7 @@ def api_init(api_endpoint) -> List[tornado.web.URLSpec]:
tornado.web.url(API_PATH + r'/discovery/by_group/([0-9]+)/([a-z0-9A-Z\-]+)', DiscoveryAPI, route_args),
tornado.web.url(API_PATH + r'/statistics/scheduler', SchedulerStatsAPI, route_args),
tornado.web.url(API_PATH + r'/oauth/token', OAuthGetAPI, route_args),
tornado.web.url(API_PATH + r'/oauth/revoke/([a-z0-9A-Z\-]+)', OAuthRevokeAPI, route_args)
tornado.web.url(API_PATH + r'/statistics/scheduler', SchedulerStatsAPI, route_args)
]
return api_routes
# Copyright (c) 2016, Daniele Venzano
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
# implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""The oAuth2 API endpoints."""
import logging
import json
import psycopg2
from tornado.web import RequestHandler
import oauth2.grant
from zoe_api.rest_api.utils import catch_exceptions, get_auth
from zoe_api.rest_api.oauth_utils import auth_controller, client_store, token_store
from zoe_api.rest_api.utils import manage_cors_headers
from zoe_api.api_endpoint import APIEndpoint # pylint: disable=unused-import
log = logging.getLogger(__name__)
"""
Example of using:
*To request a new token of type:
Input: curl -u 'admin:admin' http://localhost:5001/api/0.6/oauth/token -X POST -H 'Content-Type: application/json' -d '{"grant_type": "password"}'
Output: {"token_type": "Bearer", "access_token": "3ddbe9ba-6a21-4e4d-993b-70556390c5d3", "refresh_token": "9bab190f-e211-42aa-917e-20ce987e355e", "expires_in": 36000}
*To refresh a token
Input: curl -H 'Authorization: Bearer 9bab190f-e211-42aa-917e-20ce987e355e' http://localhost:5001/api/0.6/oauth/token -X POST -H 'Content-Type: application/json' -d '{"grant_type": "refresh_token"}'
Output: {"token_type": "Bearer", "access_token": "378f8d5f-2eb5-4181-b632-ad23c4534d32", "expires_in": 36000}
*To revoke a token, the passed token could be the access token or refresh token
curl -u 'admin:admin' -X DELETE http://localhost:5001/api/0.6/oauth/revoke/378f8d5f-2eb5-4181-b632-ad23c4534d32
*To authenticate with other rest api services, using a header with: "Authorization: Bearer access_token"
curl -H 'Authorization: Bearer 378f8d5f-2eb5-4181-b632-ad23c4534d32' http://localhost:5001/api/0.6/execution
"""
class OAuthGetAPI(RequestHandler):
"""The OAuthGetAPI endpoint."""
def initialize(self, **kwargs):
"""Initializes the request handler."""
self.api_endpoint = kwargs['api_endpoint'] # type: APIEndpoint
self.auth_controller = auth_controller
self.client_store = client_store
def set_default_headers(self):
"""Set up the headers for enabling CORS."""
manage_cors_headers(self)
@catch_exceptions
def options(self): # pylint: disable=unused-argument
"""Needed for CORS."""
self.set_status(204)
self.finish()
@catch_exceptions
def post(self):
"""REQUEST/REFRESH token"""
uid, role = get_auth(self)
grant_type = oauth2.grant.RefreshToken.grant_type + ':' + oauth2.grant.ResourceOwnerGrant.grant_type
try:
self.client_store.save_client(uid, '', role, '', grant_type, '')
except psycopg2.IntegrityError:
log.info('User is already had')
response = self._dispatch_request(uid)
self._map_response(response)
def _dispatch_request(self, uid):
request = self.request
params = json.loads(request.body.decode())
if params['grant_type'] == 'refresh_token':
auth_header = self.request.headers.get('Authorization')
refresh_token = auth_header[7:]
params['refresh_token'] = refresh_token
params['password'] = ''
params['username'] = ''
params['client_secret'] = ''
params['scope'] = ''
params['client_id'] = uid
request.post_param = lambda key: params[key]
return self.auth_controller.dispatch(request, environ={})
def _map_response(self, response):
for name, value in list(response.headers.items()):
self.set_header(name, value)
self.set_status(response.status_code)
if response.status_code == 200:
log.info("New token granted...")
self.write(response.body)
def data_received(self, chunk):
"""Not implemented as we do not use stream uploads"""
pass
class OAuthRevokeAPI(RequestHandler):
"""The OAuthRevokeAPI endpoint."""
def initialize(self, **kwargs):
"""Initializes the request handler."""
self.api_endpoint = kwargs['api_endpoint'] # type: APIEndpoint
self.auth_controller = auth_controller
self.token_store = token_store
def set_default_headers(self):
"""Set up the headers for enabling CORS."""
manage_cors_headers(self)
@catch_exceptions
def options(self, execution_id): # pylint: disable=unused-argument
"""Needed for CORS."""
self.set_status(204)
self.finish()
@catch_exceptions
def delete(self, token):
"""DELETE token (logout)"""
get_auth(self)
res = self.token_store.delete_refresh_token(token)
if res == 0:
ret = {'ret' :'No token found in database.'}
else:
ret = {'res': 'Revoked token.'}
self.write(ret)
def data_received(self, chunk):
"""Not implemented as we do not use stream uploads"""
pass
# Copyright (c) 2017, Quang-Nhat HOANG-XUAN
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
# implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Authentication controller for oauth2."""
import logging
import oauth2.web
import oauth2.grant
from zoe_api.auth.oauth2.postgresql import AccessTokenStorePg, ClientStorePg
from zoe_api.auth.oauth2.tokengenerator import Uuid4
log = logging.getLogger(__name__)
class OAuthSiteAdapter(oauth2.web.ResourceOwnerGrantSiteAdapter):
"""OAuth Simple SiteAdapter"""
def authenticate(self, request, environ, scopes, client):
return {}
client_store = ClientStorePg() #pylint: disable=invalid-name
token_store = AccessTokenStorePg() #pylint: disable=invalid-name
token_generator = Uuid4() #pylint: disable=invalid-name
token_generator.expires_in[oauth2.grant.ClientCredentialsGrant.grant_type] = 3600
auth_controller = oauth2.Provider( #pylint: disable=invalid-name
access_token_store=token_store,
auth_code_store=token_store,
client_store=client_store,
token_generator=token_generator
)
site_adapter = OAuthSiteAdapter() #pylint: disable=invalid-name
auth_controller.token_path = '/api/0.7/oauth/token'
auth_controller.add_grant(oauth2.grant.ClientCredentialsGrant())
auth_controller.add_grant(oauth2.grant.RefreshToken(expires_in=3600))
auth_controller.add_grant(oauth2.grant.ResourceOwnerGrant(site_adapter=site_adapter))
......@@ -29,7 +29,6 @@ from zoe_api.auth.base import BaseAuthenticator # pylint-bug #1063 pylint: disa
from zoe_api.auth.ldap import LDAPAuthenticator
from zoe_api.auth.file import PlainTextAuthenticator
from zoe_api.auth.ldapsasl import LDAPSASLAuthenticator
from zoe_api.rest_api.oauth_utils import client_store, token_store
log = logging.getLogger(__name__)
......@@ -82,26 +81,6 @@ def get_auth(handler: tornado.web.RequestHandler):
if auth_header is None or not (auth_header.startswith('Basic ') or auth_header.startswith('Bearer ')):
raise ZoeRestAPIException('missing or wrong authentication information', 401, {'WWW-Authenticate': 'Basic realm="Login Required"'})
# Process for authentication with token
if "Bearer" in auth_header:
token = auth_header[7:]
if 'token' in handler.request.uri:
data = token_store.get_client_id_by_refresh_token(token)
else:
data = token_store.get_client_id_by_access_token(token)
if data:
uid = data["client_id"]
role = client_store.get_role_by_client_id(uid)
else:
raise ZoeRestAPIException('Invalid Token', 401, {'WWW-Authenticate': 'Basic realm="Login Required"'})
if int(data['expires_at'].timestamp()) <= int(time.time()):
raise ZoeRestAPIException('Expired token', 401, {'WWW-Authenticate': 'Basic realm="Login Required"'})
return uid, role
# Process for authentication with username, password
else:
auth_decoded = base64.decodebytes(bytes(auth_header[6:], 'ascii')).decode('utf-8')
......
......@@ -256,78 +256,3 @@ class SQLManager:
cur.execute(query)
self.conn.commit()
return cur.fetchone()[0]
# The section below is used for Oauth2 authentication mechanism
def fetch_by_refresh_token(self, refresh_token):
""" get info from refreshtoken """
cur = self._cursor()
query = 'SELECT * FROM oauth_token WHERE refresh_token = %s'
cur.execute(query, (refresh_token,))
return cur.fetchone()
def delete_refresh_token(self, refresh_token):
""" delete info by refreshtoken """
cur = self._cursor()
check_exists = 'SELECT * FROM oauth_token WHERE refresh_token = %s OR token = %s'
cur.execute(check_exists, (refresh_token, refresh_token))
res = 0
if cur.fetchone():
res = 1
query = 'DELETE FROM oauth_token WHERE refresh_token = %s OR token = %s'
cur.execute(query, (refresh_token, refresh_token))
self.conn.commit()
return res
def fetch_existing_token_of_user(self, client_id, grant_type, user_id):
""" get info from clientid granttype userid """
cur = self._cursor()
query = 'SELECT * FROM oauth_token WHERE client_id = %s AND grant_type = %s AND user_id = %s'
cur.execute(query, (client_id, grant_type, user_id,))
return cur.fetchone()
def get_client_id_by_access_token(self, access_token):
""" get clientid from accesstoken """
cur = self._cursor()
query = 'SELECT * FROM oauth_token WHERE token = %s'
cur.execute(query, (access_token,))
return cur.fetchone()
def get_client_id_by_refresh_token(self, refresh_token):
""" get clientid from refreshtoken """
cur = self._cursor()
query = 'SELECT * FROM oauth_token WHERE refresh_token = %s'
cur.execute(query, (refresh_token,))
return cur.fetchone()
def save_token(self, client_id, grant_type, token, data, expires_at, refresh_token, refresh_expires_at, scopes, user_id): #pylint: disable=too-many-arguments
""" save token to db """
cur = self._cursor()
expires_at = datetime.datetime.utcfromtimestamp(expires_at)
if refresh_expires_at is None:
query = cur.mogrify('UPDATE oauth_token SET token = %s, expires_at = %s WHERE client_id=%s', (token, expires_at, client_id))
else:
refresh_token_expires_at = datetime.datetime.utcfromtimestamp(refresh_expires_at)
query = cur.mogrify('INSERT INTO oauth_token (client_id, grant_type, token, data, expires_at, refresh_token, refresh_token_expires_at, scopes, user_id) VALUES (%s,%s,%s,%s,%s,%s,%s,%s,%s) ON CONFLICT (client_id) DO UPDATE SET token = %s, expires_at = %s, refresh_token = %s, refresh_token_expires_at = %s', (client_id, grant_type, token, data, expires_at, refresh_token, refresh_token_expires_at, scopes, user_id, token, expires_at, refresh_token, refresh_token_expires_at))
cur.execute(query)
self.conn.commit()
def save_client(self, identifier, secret, role, redirect_uris, authorized_grants, authorized_response_types):
""" save clientinfo to db """
cur = self._cursor()
query = cur.mogrify('INSERT INTO oauth_client (identifier, secret, role, redirect_uris, authorized_grants, authorized_response_types) VALUES (%s,%s,%s,%s,%s,%s)', (identifier, secret, role, redirect_uris, authorized_grants, authorized_response_types))
cur.execute(query)
self.conn.commit()
def fetch_by_client_id(self, client_id):
""" get info from clientid """
cur = self._cursor()
query = 'SELECT * FROM oauth_client WHERE identifier = %s'
cur.execute(query, (client_id,))
return cur.fetchone()
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment