Welcome to mirror list, hosted at ThFree Co, Russian Federation.

git.blender.org/blender-addons.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'blender_id/communication.py')
-rw-r--r--blender_id/communication.py250
1 files changed, 250 insertions, 0 deletions
diff --git a/blender_id/communication.py b/blender_id/communication.py
new file mode 100644
index 00000000..ee71c553
--- /dev/null
+++ b/blender_id/communication.py
@@ -0,0 +1,250 @@
+# ##### 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 #####
+
+# <pep8 compliant>
+
+import functools
+
+
+class BlenderIdCommError(RuntimeError):
+ """Raised when there was an error communicating with Blender ID"""
+
+
+@functools.lru_cache(maxsize=None)
+def host_label():
+ import socket
+
+ return 'Blender running on %r' % socket.gethostname()
+
+
+@functools.lru_cache(maxsize=None)
+def blender_id_endpoint(endpoint_path=None):
+ """Gets the endpoint for the authentication API. If the BLENDER_ID_ENDPOINT env variable
+ is defined, it's possible to override the (default) production address.
+ """
+ import os
+ import urllib.parse
+
+ base_url = os.environ.get('BLENDER_ID_ENDPOINT', 'https://www.blender.org/id/')
+
+ # urljoin() is None-safe for the 2nd parameter.
+ return urllib.parse.urljoin(base_url, endpoint_path)
+
+
+def blender_id_server_authenticate(username, password):
+ """Authenticate the user with the server with a single transaction
+ containing username and password (must happen via HTTPS).
+
+ If the transaction is successful, status will be 'successful' and we
+ return the user's unique blender id and a token (that will be used to
+ represent that username and password combination).
+ If there was a problem, status will be 'fail' and we return an error
+ message. Problems may be with the connection or wrong user/password.
+ """
+
+ import requests
+ import requests.exceptions
+
+ payload = dict(
+ username=username,
+ password=password,
+ host_label=host_label()
+ )
+
+ url = blender_id_endpoint('u/identify')
+ try:
+ r = requests.post(url, data=payload, verify=True)
+ except (requests.exceptions.SSLError,
+ requests.exceptions.HTTPError,
+ requests.exceptions.ConnectionError) as e:
+ print('Exception POSTing to {}: {}'.format(url, e))
+ return dict(
+ status='fail',
+ user_id=None,
+ token=None,
+ error_message=str(e)
+ )
+
+ user_id = None
+ token = None
+ error_message = None
+
+ if r.status_code == 200:
+ resp = r.json()
+ status = resp['status']
+ if status == 'success':
+ user_id = str(resp['data']['user_id'])
+ # We just use the access token for now.
+ token = resp['data']['oauth_token']['access_token']
+ elif status == 'fail':
+ error_message = 'Username and/or password is incorrect'
+ else:
+ status = 'fail'
+ error_message = format('There was a problem communicating with'
+ ' the server. Error code is: %s' % r.status_code)
+
+ return dict(
+ status=status,
+ user_id=user_id,
+ token=token,
+ error_message=error_message
+ )
+
+
+def blender_id_server_validate(token):
+ """Validate the auth token with the server.
+
+ @param token: the authentication token
+ @type token: str
+ @returns: None if the token is valid, or an error message when it's invalid.
+ """
+
+ import requests
+ import requests.exceptions
+
+ try:
+ r = requests.post(blender_id_endpoint('u/validate_token'),
+ data={'token': token}, verify=True)
+ except requests.exceptions.RequestException as e:
+ return str(e)
+
+ if r.status_code == 200:
+ return None
+
+ return 'Authentication token invalid'
+
+
+def blender_id_server_logout(user_id, token):
+ """Logs out of the Blender ID service by removing the token server-side.
+
+ @param user_id: the email address of the user.
+ @type user_id: str
+ @param token: the token to remove
+ @type token: str
+ @return: {'status': 'fail' or 'success', 'error_message': str}
+ @rtype: dict
+ """
+
+ import requests
+ import requests.exceptions
+
+ payload = dict(
+ user_id=user_id,
+ token=token
+ )
+ try:
+ r = requests.post(blender_id_endpoint('u/delete_token'),
+ data=payload, verify=True)
+ except (requests.exceptions.SSLError,
+ requests.exceptions.HTTPError,
+ requests.exceptions.ConnectionError) as e:
+ return dict(
+ status='fail',
+ error_message=format('There was a problem setting up a connection to '
+ 'the server. Error type is: %s' % type(e).__name__)
+ )
+
+ if r.status_code != 200:
+ return dict(
+ status='fail',
+ error_message=format('There was a problem communicating with'
+ ' the server. Error code is: %s' % r.status_code)
+ )
+
+ resp = r.json()
+ return dict(
+ status=resp['status'],
+ error_message=None
+ )
+
+
+def subclient_create_token(auth_token: str, subclient_id: str) -> dict:
+ """Creates a subclient-specific authentication token.
+
+ :returns: the token along with its expiry timestamp, in a {'scst': 'token',
+ 'expiry': datetime.datetime} dict.
+ """
+
+ payload = {'subclient_id': subclient_id,
+ 'host_label': host_label()}
+
+ r = make_authenticated_call('POST', 'subclients/create_token', auth_token, payload)
+ if r.status_code == 401:
+ raise BlenderIdCommError('Your Blender ID login is not valid, try logging in again.')
+
+ if r.status_code != 201:
+ raise BlenderIdCommError('Invalid response, HTTP code %i received' % r.status_code)
+
+ resp = r.json()
+ if resp['status'] != 'success':
+ raise BlenderIdCommError(resp['message'])
+
+ return resp['data']
+
+
+def make_authenticated_call(method, url, auth_token, data):
+ """Makes a HTTP call authenticated with the OAuth token."""
+
+ import requests
+ import requests.exceptions
+
+ try:
+ r = requests.request(method,
+ blender_id_endpoint(url),
+ data=data,
+ headers={'Authorization': 'Bearer %s' % auth_token},
+ verify=True)
+ except (requests.exceptions.HTTPError,
+ requests.exceptions.ConnectionError) as e:
+ raise BlenderIdCommError(str(e))
+
+ return r
+
+
+def send_token_to_subclient(webservice_endpoint: str, user_id: str,
+ subclient_token: str, subclient_id: str) -> str:
+ """Sends the subclient-specific token to the subclient.
+
+ The subclient verifies this token with BlenderID. If it's accepted, the
+ subclient ensures there is a valid user created server-side. The ID of
+ that user is returned.
+
+ :returns: the user ID at the subclient.
+ """
+
+ import requests
+ import urllib.parse
+
+ url = urllib.parse.urljoin(webservice_endpoint, 'blender_id/store_scst')
+ try:
+ r = requests.post(url,
+ data={'user_id': user_id,
+ 'subclient_id': subclient_id,
+ 'token': subclient_token},
+ verify=True)
+ r.raise_for_status()
+ except (requests.exceptions.HTTPError,
+ requests.exceptions.ConnectionError) as e:
+ raise BlenderIdCommError(str(e))
+ resp = r.json()
+
+ if resp['status'] != 'success':
+ raise BlenderIdCommError('Error sending subclient-specific token to %s, error is: %s'
+ % (webservice_endpoint, resp))
+
+ return resp['subclient_user_id']