convert to an abstract class. improve logging and qc

This commit is contained in:
lpm0073 2022-10-04 08:04:14 -05:00
parent 4714f46d03
commit bab30abe34
3 changed files with 77 additions and 25 deletions

View File

@ -26,7 +26,7 @@ include this repo in your project's requiremets.txt, or install it from the comm
ADDL_INSTALLED_APPS: ADDL_INSTALLED_APPS:
- "wp_oauth_backend" - "wp_oauth_backend"
THIRD_PARTY_AUTH_BACKENDS: THIRD_PARTY_AUTH_BACKENDS:
- "wp_oauth_backend.wp_oauth.WPOAuth2" - "wp_oauth_backend.wp_oauth.StepwiseMathOAuth2"
ENABLE_REQUIRE_THIRD_PARTY_AUTH: true ENABLE_REQUIRE_THIRD_PARTY_AUTH: true
add these settings to django.conf: add these settings to django.conf:

View File

@ -61,8 +61,8 @@ VERSION = ABOUT["__version__"]
setup( setup(
name='wp-oauth-backend', name='wp-oauth-backend',
version=VERSION, version=VERSION,
description=('An OAuth backend for the WP OAuth Plugin, ' description=('An OAuth backend for the WP OAuth Wordpress Plugin, '
'mostly used for Open edX but can be used elsewhere.'), 'that is customized for use in Open edX installations.'),
long_description=README, long_description=README,
author='Lawrence McDaniel, lpm0073@gmail.com', author='Lawrence McDaniel, lpm0073@gmail.com',
author_email='lpm0073@gmail.com', author_email='lpm0073@gmail.com',

View File

@ -1,3 +1,14 @@
"""
written by: Lawrence McDaniel
https://lawrencemcdaniel.com
date: oct-2022
usage: Abstract class implementation of BaseOAuth2 to handle the field
mapping and data converstions between the dict that WP Oauth
returns versus the dict that Open edX actually needs.
"""
from abc import abstractmethod
import json import json
from urllib.parse import urlencode from urllib.parse import urlencode
from urllib.request import urlopen from urllib.request import urlopen
@ -6,17 +17,27 @@ from social_core.backends.oauth import BaseOAuth2
from logging import getLogger from logging import getLogger
logger = getLogger(__name__) logger = getLogger(__name__)
VERBOSE_LOGGING = True
class WPOAuth2(BaseOAuth2): class WPOpenedxOAuth2AbstractClass(BaseOAuth2):
"""WP OAuth authentication backend""" """
WP OAuth authentication backend customized for Open edX
name = 'wp-oauth' """
# https://python-social-auth.readthedocs.io/en/latest/configuration/settings.html # https://python-social-auth.readthedocs.io/en/latest/configuration/settings.html
SOCIAL_AUTH_SANITIZE_REDIRECTS = True # for redirect domain to exactly match the initiating domain. SOCIAL_AUTH_SANITIZE_REDIRECTS = True # requires redirect domain to match the original initiating domain.
ACCESS_TOKEN_METHOD = 'POST' ACCESS_TOKEN_METHOD = 'POST'
SCOPE_SEPARATOR = ','
BASE_URL = "https://stepwisemath.ai" @abstractmethod
def name(self):
raise NotImplementedError("Subclasses should implement this property.")
@abstractmethod
def BASE_URL(self):
raise NotImplementedError("Subclasses should implement this property.")
@abstractmethod
def SCOPE_SEPARATOR(self):
raise NotImplementedError("Subclasses should implement this property.")
@property @property
def base_url(self) -> str: def base_url(self) -> str:
@ -51,6 +72,20 @@ class WPOAuth2(BaseOAuth2):
def get_user_details(self, response) -> dict: def get_user_details(self, response) -> dict:
"""Return user details from the WP account""" """Return user details from the WP account"""
if type(response)==dict:
if ('ID' not in response.keys()) or ('user_email' not in response.keys()):
logger.info('get_user_details() - response object lacks required keys. exiting.')
return {}
if VERBOSE_LOGGING:
if not response:
logger.info('get_user_details() - response is missing. exiting.')
return {}
logger.info('get_user_details() - start. response: {response}'.format(
response=json.dumps(response, sort_keys=True, indent=4)
))
# try to parse out the first and last names # try to parse out the first and last names
split_name = response.get('display_name', '').split() split_name = response.get('display_name', '').split()
first_name = split_name[0] if len(split_name) > 0 else '' first_name = split_name[0] if len(split_name) > 0 else ''
@ -59,9 +94,10 @@ class WPOAuth2(BaseOAuth2):
# check for superuser / staff status # check for superuser / staff status
user_roles = response.get('user_roles', []) user_roles = response.get('user_roles', [])
super_user = 'administrator' in user_roles super_user = 'administrator' in user_roles
is_staff = 'administrator' in user_roles
user_details = { user_details = {
'id': int(response.get('ID')), 'id': int(response.get('ID'), 0),
'username': response.get('user_email', ''), 'username': response.get('user_email', ''),
'wp_username': response.get('user_login', ''), 'wp_username': response.get('user_login', ''),
'email': response.get('user_email', ''), 'email': response.get('user_email', ''),
@ -69,16 +105,17 @@ class WPOAuth2(BaseOAuth2):
'last_name': last_name, 'last_name': last_name,
'fullname': response.get('display_name', ''), 'fullname': response.get('display_name', ''),
'is_superuser': super_user, 'is_superuser': super_user,
'is_staff': super_user, 'is_staff': is_staff,
'refresh_token': response.get('refresh_token', ''), 'refresh_token': response.get('refresh_token', ''),
'scope': response.get('scope'), 'scope': response.get('scope', ''),
'token_type': response.get('token_type', ''), 'token_type': response.get('token_type', ''),
'date_joined': response.get('user_registered', ''), 'date_joined': response.get('user_registered', ''),
'user_status': response.get('user_status', ''), 'user_status': response.get('user_status', ''),
} }
logger.info('get_user_details() - user_details: {user_details}'.format( if VERBOSE_LOGGING:
user_details=json.dumps(user_details, sort_keys=True, indent=4) logger.info('get_user_details() - complete. user_details: {user_details}'.format(
)) user_details=json.dumps(user_details, sort_keys=True, indent=4)
))
return user_details return user_details
def user_data(self, access_token, *args, **kwargs) -> dict: def user_data(self, access_token, *args, **kwargs) -> dict:
@ -88,24 +125,39 @@ class WPOAuth2(BaseOAuth2):
'access_token': access_token 'access_token': access_token
}) })
logger.info("user_data() url: {url}".format(url=url)) if VERBOSE_LOGGING:
logger.info("user_data() url: {url}".format(url=url))
try: try:
response = json.loads(self.urlopen(url)) response = json.loads(self.urlopen(url))
logger.info('user_data() - response: {response}'.format(
response=json.dumps(response, sort_keys=True, indent=4)
))
user_details = self.get_user_details(response) user_details = self.get_user_details(response)
return user_details return user_details
except ValueError as e: except ValueError as e:
logger.error('user_data() did not work: {err}'.format(err=e)) logger.error('user_data() did not work: {err}'.format(err=e))
return None return None
# utility function. not part of psa.
def urlopen(self, url): def urlopen(self, url):
return urlopen(url).read().decode("utf-8") return urlopen(url).read().decode("utf-8")
# def get_user_id(self, details, response): class StepwiseMathOAuth2 (WPOpenedxOAuth2AbstractClass):
# return details['id']
# def get_username(self, strategy, details, backend, user=None, *args, **kwargs): # This is the string value that will appear in the LMS Django Admin
# return details['username'] # Third Party Authentication / Provider Configuration (OAuth)
# setup page drop-down box titled, "Backend name:", just above
# the "Client ID:" and "Client Secret:" fields.
def name(self):
return 'wp-oauth'
# note: no slash at the end of the base url. Python Social Auth
# might clean this up for you, but i'm not 100% certain of that.
def BASE_URL(self):
return "https://stepwisemath.ai"
# the value of the scope separator is user-defined. Check the
# scopes field value for your oauth client in your wordpress host.
# the wp-oauth default value for scopes is 'basic' but can be
# changed to a list. example 'basic, email, profile'. This
# list can be delimited with commas, spaces, whatever.
def SCOPE_SEPARATOR(self):
return ","