diff --git a/ovos_local_backend/backend/auth.py b/ovos_local_backend/backend/auth.py index 4a5b0b8..61e62ca 100644 --- a/ovos_local_backend/backend/auth.py +++ b/ovos_local_backend/backend/auth.py @@ -11,16 +11,23 @@ # limitations under the License. # +import os +import time + +import requests +from flask import request +from oauthlib.oauth2 import WebApplicationClient + from ovos_local_backend.backend import API_VERSION -from ovos_local_backend.utils import nice_json from ovos_local_backend.backend.decorators import noindex, requires_auth -from flask import request -import time +from ovos_local_backend.database.oauth import OAuthTokenDatabase, OAuthApplicationDatabase +from ovos_local_backend.utils import nice_json +os.environ['OAUTHLIB_INSECURE_TRANSPORT'] = '1' -def get_auth_routes(app): - @app.route("/" + API_VERSION + "/auth/token", methods=['GET']) +def get_auth_routes(app): + @app.route(f"/{API_VERSION}/auth/token", methods=['GET']) @requires_auth @noindex def token(): @@ -36,4 +43,73 @@ def token(): "refreshToken": token} return nice_json(device) + @app.route(f"/{API_VERSION}/auth//auth_url", methods=['GET']) + @requires_auth + @noindex + def oauth_url(oauth_id): + """ send auth url to user to confirm authorization, + once user opens it callback is triggered + """ + params = dict(request.args) + params["callback_endpoint"] = request.base_url + f"/{API_VERSION}/auth/callback/{oauth_id}" + + client = WebApplicationClient(params["client_id"]) + request_uri = client.prepare_request_uri( + params["auth_endpoint"], + redirect_uri=params["callback_endpoint"], + scope=params["scope"], + ) + with OAuthApplicationDatabase() as db: + db.add_application(oauth_id, + params["client_id"], + params["client_secret"], + params["auth_endpoint"], + params["token_endpoint"], + params["refresh_endpoint"], + params["callback_endpoint"], + params["scope"]) + + return request_uri, 200 + + @app.route(f"/{API_VERSION}/auth/callback/", methods=['GET']) + @noindex + def oauth_callback(oauth_id): + """ user completed oauth, save token to db + """ + params = dict(request.args) + code = params["code"] + + data = OAuthApplicationDatabase()[oauth_id] + client_id = data["client_id"] + client_secret = data["client_secret"] + token_endpoint = data["token_endpoint"] + + # Prepare and send a request to get tokens! Yay tokens! + client = WebApplicationClient(client_id) + token_url, headers, body = client.prepare_token_request( + token_endpoint, + authorization_response=request.url, + redirect_url=request.base_url, + code=code + ) + token_response = requests.post( + token_url, + headers=headers, + data=body, + auth=(client_id, client_secret), + ).json() + + with OAuthTokenDatabase() as db: + db.add_token(oauth_id, token_response) + + return nice_json(params) + + @app.route(f"/{API_VERSION}/device//token/", methods=['GET']) + @requires_auth + @noindex + def oauth_token(uuid, oauth_id): + """a device is requesting a token for a previously approved OAuth app""" + data = OAuthTokenDatabase().get(oauth_id) or {} + return nice_json(data) + return app diff --git a/ovos_local_backend/database/oauth.py b/ovos_local_backend/database/oauth.py new file mode 100644 index 0000000..175cafd --- /dev/null +++ b/ovos_local_backend/database/oauth.py @@ -0,0 +1,33 @@ +from json_database import JsonStorageXDG + + +class OAuthTokenDatabase(JsonStorageXDG): + def __init__(self): + super().__init__("ovos_oauth") + + def add_token(self, oauth_service, token_data): + self[oauth_service] = token_data + + def total_tokens(self): + return len(self) + + +class OAuthApplicationDatabase(JsonStorageXDG): + def __init__(self): + super().__init__("ovos_oauth_apps") + + def add_application(self, oauth_service, + client_id, client_secret, + auth_endpoint, token_endpoint, refresh_endpoint, + callback_endpoint, scope): + self[oauth_service] = {"oauth_service": oauth_service, + "client_id": client_id, + "client_secret": client_secret, + "auth_endpoint": auth_endpoint, + "token_endpoint": token_endpoint, + "refresh_endpoint": refresh_endpoint, + "callback_endpoint": callback_endpoint, + "scope": scope} + + def total_apps(self): + return len(self) diff --git a/requirements/requirements.txt b/requirements/requirements.txt index 9c2fca1..d16214e 100644 --- a/requirements/requirements.txt +++ b/requirements/requirements.txt @@ -8,4 +8,5 @@ ovos-stt-plugin-server geocoder timezonefinder requests_cache -selene_api>=0.0.2 \ No newline at end of file +selene_api>=0.0.2 +oauthlib~=3.0 \ No newline at end of file