diff options
author | Vilem Duha <vilem.duha@gmail.com> | 2019-04-25 23:35:26 +0300 |
---|---|---|
committer | Vilem Duha <vilem.duha@gmail.com> | 2019-05-19 16:45:48 +0300 |
commit | e16c55a110a5cde3bf5c83a156195b5d71481488 (patch) | |
tree | f902eae2914f0dc2759957e862d96e79843982ac /blenderkit | |
parent | d2808959bb845ec945aa7fac494db42a9228021b (diff) |
BlenderKit: Oauth browser login. No more hassle with API keys for the users. Needs testing.
renamed get_bkit_url to get_api_url
task queue is a new simple module to do tasks, more timed tasks should come here instead of being in assetbar as of now. (download, search checks and more)
Diffstat (limited to 'blenderkit')
-rw-r--r-- | blenderkit/__init__.py | 28 | ||||
-rw-r--r-- | blenderkit/categories.py | 2 | ||||
-rw-r--r-- | blenderkit/download.py | 2 | ||||
-rw-r--r-- | blenderkit/oauth.py | 176 | ||||
-rw-r--r-- | blenderkit/paths.py | 23 | ||||
-rw-r--r-- | blenderkit/ratings.py | 4 | ||||
-rw-r--r-- | blenderkit/search.py | 8 | ||||
-rw-r--r-- | blenderkit/tasks_queue.py | 20 | ||||
-rw-r--r-- | blenderkit/ui_panels.py | 18 | ||||
-rw-r--r-- | blenderkit/upload.py | 4 | ||||
-rw-r--r-- | blenderkit/upload_bg.py | 6 | ||||
-rw-r--r-- | blenderkit/utils.py | 13 |
12 files changed, 270 insertions, 34 deletions
diff --git a/blenderkit/__init__.py b/blenderkit/__init__.py index 0bafa699..6a8b1886 100644 --- a/blenderkit/__init__.py +++ b/blenderkit/__init__.py @@ -41,9 +41,11 @@ if "bpy" in locals(): importlib.reload(bg_blender) importlib.reload(paths) importlib.reload(utils) + importlib.reload(oauth) + importlib.reload(tasks_queue) else: from blenderkit import asset_inspector, search, download, upload, ratings, autothumb, ui, bg_blender, paths, utils, \ - overrides, ui_panels, categories + overrides, ui_panels, categories, oauth, tasks_queue import os import math @@ -71,7 +73,6 @@ from bpy.types import ( PropertyGroup, ) - # logging.basicConfig(filename = 'blenderkit.log', level = logging.INFO, # format = ' %(asctime)s:%(filename)s:%(funcName)s:%(lineno)d:%(message)s') @@ -1208,9 +1209,8 @@ class BlenderKitAddonPreferences(AddonPreferences): # this must match the addon name, use '__package__' # when defining this in a submodule of a python package. bl_idname = __name__ - from os.path import expanduser - home = expanduser("~") - default_global_dict = home + os.sep + 'blenderkit_data' + + default_global_dict = paths.default_global_dict() api_key: StringProperty( name="BlenderKit API Key", @@ -1220,6 +1220,20 @@ class BlenderKitAddonPreferences(AddonPreferences): update=utils.save_prefs ) + api_key_refresh: StringProperty( + name="BlenderKit refresh API Key", + description="API key used to refresh the token regularly.", + default="", + subtype="PASSWORD", + update=utils.save_prefs + ) + + login_attempt: BoolProperty( + name="Login/Signup attempt", + description="When this is on, BlenderKit is trying to connect and login.", + default=False + ) + global_dir: StringProperty( name="Global Files Directory", description="Global storage for your assets, will use subdirectories for the contents", @@ -1389,6 +1403,8 @@ def register(): bpy.app.handlers.load_post.append(scene_load) utils.load_prefs() overrides.register_overrides() + oauth.register() + tasks_queue.register() def unregister(): @@ -1403,6 +1419,8 @@ def unregister(): ui_panels.unregister_ui_panels() bg_blender.unregister() overrides.unregister_overrides() + oauth.unregister() + tasks_queue.unregister() del bpy.types.Scene.blenderkit_models del bpy.types.Scene.blenderkit_scene diff --git a/blenderkit/categories.py b/blenderkit/categories.py index 3c50f2c7..e2dfed3d 100644 --- a/blenderkit/categories.py +++ b/blenderkit/categories.py @@ -60,7 +60,7 @@ def copy_categories(): def fetch_categories(API_key): BLENDERKIT_API_MAIN = "https://www.blenderkit.com/api/v1/" - url = paths.get_bkit_url() + 'categories/' + url = paths.get_api_url() + 'categories/' headers = utils.get_headers(API_key) diff --git a/blenderkit/download.py b/blenderkit/download.py index 175a9771..bffb2e8a 100644 --- a/blenderkit/download.py +++ b/blenderkit/download.py @@ -174,7 +174,7 @@ def report_usages(): api_key = user_preferences.api_key sid = get_scene_id() headers = utils.get_headers(api_key) - url = paths.get_bkit_url() + paths.BLENDERKIT_REPORT_URL + url = paths.get_api_url() + paths.BLENDERKIT_REPORT_URL assets = {} asset_obs = [] diff --git a/blenderkit/oauth.py b/blenderkit/oauth.py new file mode 100644 index 00000000..78c8dfb3 --- /dev/null +++ b/blenderkit/oauth.py @@ -0,0 +1,176 @@ +# ##### BEGIN GPL LICENSE BLOCK ##### +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software Foundation, +# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# ##### END GPL LICENSE BLOCK ##### + +import bpy + +import json +import webbrowser +from http.server import BaseHTTPRequestHandler, HTTPServer +from urllib.parse import parse_qs, urlparse + +import requests +import threading +from blenderkit import tasks_queue, utils, paths + +CLIENT_ID = "IdFRwa3SGA8eMpzhRVFMg5Ts8sPK93xBjif93x0F" +PORTS = [62485, 1234] + + +class SimpleOAuthAuthenticator(object): + def __init__(self, server_url, client_id, ports): + self.server_url = server_url + self.client_id = client_id + self.ports = ports + + def _get_tokens(self, authorization_code=None, refresh_token=None, grant_type="authorization_code"): + data = { + "grant_type": grant_type, + "state": "random_state_string", + "client_id": self.client_id, + "scopes": "read write", + } + if authorization_code: + data['code'] = authorization_code + if refresh_token: + data['refresh_token'] = refresh_token + + response = requests.post( + '%s/o/token/' % self.server_url, + data=data + ) + if response.status_code != 200: + return None, None + refresh_token = json.loads(response.content)['refresh_token'] + access_token = json.loads(response.content)['access_token'] + return access_token, refresh_token + + def get_new_token(self): + class HTTPServerHandler(BaseHTTPRequestHandler): + def do_GET(self): + self.send_response(200) + self.send_header('Content-type', 'text/html') + self.end_headers() + if 'code' in self.path: + self.auth_code = self.path.split('=')[1] + # Display to the user that they no longer need the browser window + self.wfile.write(bytes('<html><h1>You may now close this window.</h1></html>', 'utf-8')) + qs = parse_qs(urlparse(self.path).query) + self.server.authorization_code = qs['code'][0] + + for port in self.ports: + try: + httpServer = HTTPServer(('localhost', port), HTTPServerHandler) + except OSError: + continue + break + webbrowser.open_new( + "%s/o/authorize?client_id=%s&state=random_state_string&response_type=code&" + "redirect_uri=http://localhost:%s/consumer/exchange/" % (self.server_url, self.client_id, port), + ) + + httpServer.handle_request() + authorization_code = httpServer.authorization_code + return self._get_tokens(authorization_code=authorization_code) + + def get_refreshed_token(self, refresh_token): + return self._get_tokens(refresh_token=refresh_token, grant_type="refresh_token") + + +def login_thread(): + thread = threading.Thread(target=login, args=([]), daemon=True) + thread.start() + + +def login(): + authenticator = SimpleOAuthAuthenticator(server_url=paths.get_bkit_url(), client_id=CLIENT_ID, ports=PORTS) + auth_token, refresh_token = authenticator.get_new_token() + print('tokens retrieved') + tasks_queue.tasks_queue.put('blenderkit.oauth.write_tokens("%s", "%s")' % (auth_token, refresh_token)) + + +def refresh_token_thread(): + preferences = bpy.context.preferences.addons['blenderkit'].preferences + if len(preferences.api_key_refresh) > 0: + thread = threading.Thread(target=refresh_token, args=([preferences.api_key_refresh]), daemon=True) + thread.start() + + +def refresh_token(api_key_refresh): + authenticator = SimpleOAuthAuthenticator(server_url=paths.get_bkit_url(), client_id=CLIENT_ID, ports=PORTS) + auth_token, refresh_token = authenticator.get_refreshed_token(api_key_refresh) + tasks_queue.tasks_queue.put('blenderkit.oauth.write_tokens("%s", "%s")' % (auth_token, refresh_token)) + + +def write_tokens(auth_token, refresh_token): + print('writing tokens?') + preferences = bpy.context.preferences.addons['blenderkit'].preferences + preferences.api_key = auth_token + preferences.api_key_refresh = refresh_token + preferences.login_attempt = False + props = utils.get_search_props() + props.report = 'Login success!' + + +class RegisterLoginOnline(bpy.types.Operator): + """Bring linked object hierarchy to scene and make it editable.""" + + bl_idname = "wm.blenderkit_login" + bl_label = "BlenderKit login or signup" + bl_options = {'REGISTER', 'UNDO'} + + @classmethod + def poll(cls, context): + return True + + def execute(self, context): + preferences = bpy.context.preferences.addons['blenderkit'].preferences + preferences.login_attempt = True + login_thread() + return {'FINISHED'} + + +class CancelLoginOnline(bpy.types.Operator): + """Cancel login attempt.""" + + bl_idname = "wm.blenderkit_login_cancel" + bl_label = "BlenderKit login cancel" + bl_options = {'REGISTER', 'UNDO'} + + @classmethod + def poll(cls, context): + return True + + def execute(self, context): + preferences = bpy.context.preferences.addons['blenderkit'].preferences + preferences.login_attempt = False + return {'FINISHED'} + +classess = ( + RegisterLoginOnline, + CancelLoginOnline, +) + + +def register(): + for c in classess: + bpy.utils.register_class(c) + + +def unregister(): + for c in classess: + bpy.utils.unregister_class(c) diff --git a/blenderkit/paths.py b/blenderkit/paths.py index 71479c7e..1595ad78 100644 --- a/blenderkit/paths.py +++ b/blenderkit/paths.py @@ -18,9 +18,10 @@ import bpy, os, sys -BLENDERKIT_API_LOCAL = "http://localhost:8001/api/v1/" -BLENDERKIT_API_MAIN = "https://www.blenderkit.com/api/v1/" -BLENDERKIT_API_DEVEL = "https://devel.blenderkit.com/api/v1/" +BLENDERKIT_LOCAL = "http://localhost:8001" +BLENDERKIT_MAIN = "https://www.blenderkit.com" +BLENDERKIT_DEVEL = "https://devel.blenderkit.com" +BLENDERKIT_API = "/api/v1/" BLENDERKIT_REPORT_URL = "usage_report/" BLENDERKIT_USER_ASSETS = "https://www.blenderkit.com/my-assets" BLENDERKIT_PLANS = "https://www.blenderkit.com/plans/pricing/" @@ -38,14 +39,24 @@ def get_bkit_url(): d = bpy.app.debug_value # d = 2 if d == 1: - url = BLENDERKIT_API_LOCAL + url = BLENDERKIT_LOCAL elif d == 2: - url = BLENDERKIT_API_DEVEL + url = BLENDERKIT_DEVEL else: - url = BLENDERKIT_API_MAIN + url = BLENDERKIT_MAIN return url +def get_api_url(): + return get_bkit_url() + BLENDERKIT_API + + +def default_global_dict(): + from os.path import expanduser + home = expanduser("~") + return home + os.sep + 'blenderkit_data' + + def get_categories_filepath(): tempdir = get_temp_dir() return os.path.join(tempdir, 'categories.json') diff --git a/blenderkit/ratings.py b/blenderkit/ratings.py index 122e89cb..8220b188 100644 --- a/blenderkit/ratings.py +++ b/blenderkit/ratings.py @@ -90,7 +90,7 @@ def upload_rating(asset): bkit_ratings = asset.bkit_ratings # print('rating asset', asset_data['name'], asset_data['asset_base_id']) - url = paths.get_bkit_url() + 'assets/' + asset['asset_data']['id'] + '/rating/' + url = paths.get_api_url() + 'assets/' + asset['asset_data']['id'] + '/rating/' ratings = [ ] @@ -103,7 +103,7 @@ def upload_rating(asset): thread = threading.Thread(target=uplaod_rating_thread, args=(url, ratings, headers)) thread.start() - url = paths.get_bkit_url() + 'assets/' + asset['asset_data']['id'] + '/review' + url = paths.get_api_url() + 'assets/' + asset['asset_data']['id'] + '/review' reviews = { 'reviewText': bkit_ratings.rating_compliments, diff --git a/blenderkit/search.py b/blenderkit/search.py index e47b3ad1..39a770ee 100644 --- a/blenderkit/search.py +++ b/blenderkit/search.py @@ -24,8 +24,9 @@ if "bpy" in locals(): imp.reload(categories) imp.reload(ui) imp.reload(version_checker) + imp.reload(oauth) else: - from blenderkit import paths, utils, categories, ui, version_checker + from blenderkit import paths, utils, categories, ui, oauth, version_checker import blenderkit from bpy.app.handlers import persistent @@ -79,7 +80,7 @@ def scene_load(context): wm = bpy.context.window_manager fetch_server_data() # following doesn't necessarilly happen if version isn't checked yet or similar, first run. - wm['bkit_update'] = version_checker.compare_versions(blenderkit) + # wm['bkit_update'] = version_checker.compare_versions(blenderkit) utils.load_categories() @@ -89,6 +90,7 @@ def fetch_server_data(): url = paths.BLENDERKIT_ADDON_URL api_key = user_preferences.api_key # version_checker.check_version_thread(url, api_key, blenderkit) + oauth.refresh_token_thread() categories.fetch_categories_thread(api_key) @@ -534,7 +536,7 @@ class Searcher(threading.Thread): params['get_next'] = False if not params['get_next']: # build a new request - url = paths.get_bkit_url() + 'search/' + url = paths.get_api_url() + 'search/' nquery = { # 'tags': query['keywords'], diff --git a/blenderkit/tasks_queue.py b/blenderkit/tasks_queue.py new file mode 100644 index 00000000..08ac90bf --- /dev/null +++ b/blenderkit/tasks_queue.py @@ -0,0 +1,20 @@ +import bpy + +import queue + +import blenderkit + +tasks_queue = queue.Queue() + +def every_2_seconds(): + while not tasks_queue.empty(): + print('as a task: ') + fstring = tasks_queue.get() + eval(fstring) + return 2.0 + +def register(): + bpy.app.timers.register(every_2_seconds) + +def unregister(): + bpy.app.timers.unregister(every_2_seconds)
\ No newline at end of file diff --git a/blenderkit/ui_panels.py b/blenderkit/ui_panels.py index 2f94ba13..1047eb98 100644 --- a/blenderkit/ui_panels.py +++ b/blenderkit/ui_panels.py @@ -523,13 +523,17 @@ class VIEW3D_PT_blenderkit_unified(Panel): row.prop(ui_props, 'asset_type', expand=True, icon_only=True) w = context.region.width - - if len(user_preferences.api_key) < 35 and user_preferences.asset_counter >25: - op = layout.operator("wm.url_open", text="Register online", - icon='QUESTION') - op.url = paths.BLENDERKIT_SIGNUP_URL - layout.label(text='Paste your API Key:') - layout.prop(user_preferences, 'api_key', text='') + if user_preferences.login_attempt: + layout.label(text = 'Login through browser') + layout.label(text = 'in progress.') + layout.operator("wm.blenderkit_login_cancel", text = "Cancel", icon = 'CANCEL') + return + + if len(user_preferences.api_key) < 20 and user_preferences.asset_counter >-10: + layout.operator("wm.blenderkit_login", text="Login/ Sign up", + icon='URL') + # layout.label(text='Paste your API Key:') + # layout.prop(user_preferences, 'api_key', text='') layout.separator() elif bpy.data.filepath == '': diff --git a/blenderkit/upload.py b/blenderkit/upload.py index be00430c..6a512e4a 100644 --- a/blenderkit/upload.py +++ b/blenderkit/upload.py @@ -465,7 +465,7 @@ def mark_for_validation(self, context, asset_type): "verificationStatus": "ready" } - url = paths.get_bkit_url() + 'assets/' + url = paths.get_api_url() + 'assets/' headers = utils.get_headers(user_preferences.api_key) @@ -559,7 +559,7 @@ def start_upload(self, context, asset_type, as_new, metadata_only): return {'CANCELLED'} # first upload metadata to server, so it can be saved inside the current file - url = paths.get_bkit_url() + 'assets/' + url = paths.get_api_url() + 'assets/' headers = utils.get_headers(upload_data['token']) diff --git a/blenderkit/upload_bg.py b/blenderkit/upload_bg.py index 538f9784..1fa6f6fb 100644 --- a/blenderkit/upload_bg.py +++ b/blenderkit/upload_bg.py @@ -76,7 +76,7 @@ def upload_files(filepath, upload_data, files): 'fileIndex': f['index'], 'originalFilename': os.path.basename(f['file_path']) } - upload_create_url = paths.get_bkit_url() + 'uploads/' + upload_create_url = paths.get_api_url() + 'uploads/' upload = requests.post(upload_create_url, json=upload_info, headers=headers, verify=True) upload = upload.json() @@ -108,7 +108,7 @@ def upload_files(filepath, upload_data, files): time.sleep(1) # confirm single file upload to bkit server - upload_done_url = paths.get_bkit_url() + 'uploads_s3/' + upload['id'] + '/upload-file/' + upload_done_url = paths.get_api_url() + 'uploads_s3/' + upload['id'] + '/upload-file/' upload_response = requests.post(upload_done_url, headers=headers, verify=True) bg_blender.progress('finished uploading') @@ -187,7 +187,7 @@ if __name__ == "__main__": "verificationStatus": "uploaded" } - url = paths.get_bkit_url() + 'assets/' + url = paths.get_api_url() + 'assets/' headers = utils.get_headers(upload_data['token']) diff --git a/blenderkit/utils.py b/blenderkit/utils.py index fd6c7d83..cebc2eaf 100644 --- a/blenderkit/utils.py +++ b/blenderkit/utils.py @@ -171,17 +171,22 @@ def load_prefs(): if os.path.exists(fpath): with open(fpath, 'r') as s: prefs = json.load(s) - user_preferences.api_key = prefs['API_key'] - user_preferences.global_dir = prefs['global_dir'] + user_preferences.api_key = prefs.get('API_key','') + user_preferences.global_dir = prefs.get('global_dir', paths.default_global_dict()) + + user_preferences.api_key_refresh = prefs.get('API_key_refresh','') def save_prefs(self, context): # print(type(context),type(bpy.context)) if not bpy.app.background and hasattr(bpy.context, 'view_layer'): user_preferences = bpy.context.preferences.addons['blenderkit'].preferences if user_preferences.api_key != '': - if len(user_preferences.api_key)>35: + #we test the api key for lenght, so not a random accidentaly typed sequence gets saved. + if len(user_preferences.api_key)>25: + prefs = { 'API_key': user_preferences.api_key, + 'API_key_refresh': user_preferences.api_key_refresh, 'global_dir': user_preferences.global_dir, } # user_preferences.api_key = user_preferences.api_key.strip() @@ -194,7 +199,7 @@ def save_prefs(self, context): # reset the api key in case the user writes some nonsense, e.g. a search string instead of the Key user_preferences.api_key = '' props = get_search_props() - props.report = 'Please paste a correct API Key.' + props.report = 'Login failed. Please paste a correct API Key.' def load_categories(): categories.copy_categories() |