Skip to content
This repository has been archived by the owner on Jun 20, 2023. It is now read-only.

Commit

Permalink
fix: get and decode jwt token
Browse files Browse the repository at this point in the history
  • Loading branch information
Ian2012 committed Jun 5, 2023
1 parent 94c066a commit 8922ee2
Show file tree
Hide file tree
Showing 9 changed files with 54 additions and 141 deletions.
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ format: ## Format code automatically
black $(BLACK_OPTS)

isort: ## Sort imports. This target is not mandatory because the output may be incompatible with black formatting. Provided for convenience purposes.
isort --skip=templates ${SRC_DIRS}
isort ${SRC_DIRS}

ESCAPE = 
help: ## Print this help
Expand Down
28 changes: 0 additions & 28 deletions tutorsuperset/patches/k8s-deployments
Original file line number Diff line number Diff line change
Expand Up @@ -35,16 +35,6 @@ spec:
value: "{{ SUPERSET_DB_PASSWORD }}"
- name: DATABASE_USER
value: "{{ SUPERSET_DB_USERNAME }}"
- name: OPENEDX_MYSQL_HOST
value: "{{ MYSQL_HOST }}"
- name: OPENEDX_MYSQL_PORT
value: "{{ MYSQL_PORT }}"
- name: OPENEDX_MYSQL_DATABASE
value: "{{ OPENEDX_MYSQL_DATABASE }}"
- name: OPENEDX_MYSQL_USERNAME
value: "{{ OPENEDX_MYSQL_USERNAME }}"
- name: OPENEDX_MYSQL_PASSWORD
value: "{{ OPENEDX_MYSQL_PASSWORD }}"
- name: OAUTH2_CLIENT_ID
value: "{{ SUPERSET_OAUTH2_CLIENT_ID }}"
- name: OAUTH2_CLIENT_SECRET
Expand All @@ -71,10 +61,6 @@ spec:
value: "{{ SUPERSET_OAUTH2_ACCESS_TOKEN_PATH }}"
- name: OAUTH2_AUTHORIZE_PATH
value: "{{ SUPERSET_OAUTH2_AUTHORIZE_PATH }}"
- name: OPENEDX_USERNAME_PATH
value: "{{ SUPERSET_OPENEDX_USERNAME_PATH }}"
- name: OPENEDX_USER_PROFILE_PATH
value: "{{ SUPERSET_OPENEDX_USER_PROFILE_PATH }}"
- name: OPENEDX_COURSES_LIST_PATH
value: "{{ SUPERSET_OPENEDX_COURSES_LIST_PATH }}"
- name: OPENEDX_LMS_ROOT_URL
Expand Down Expand Up @@ -138,16 +124,6 @@ spec:
value: "{{ SUPERSET_DB_PASSWORD }}"
- name: DATABASE_USER
value: "{{ SUPERSET_DB_USERNAME }}"
- name: OPENEDX_MYSQL_HOST
value: "{{ MYSQL_HOST }}"
- name: OPENEDX_MYSQL_PORT
value: "{{ MYSQL_PORT }}"
- name: OPENEDX_MYSQL_DATABASE
value: "{{ OPENEDX_MYSQL_DATABASE }}"
- name: OPENEDX_MYSQL_USERNAME
value: "{{ OPENEDX_MYSQL_USERNAME }}"
- name: OPENEDX_MYSQL_PASSWORD
value: "{{ OPENEDX_MYSQL_PASSWORD }}"
- name: OAUTH2_CLIENT_ID
value: "{{ SUPERSET_OAUTH2_CLIENT_ID }}"
- name: OAUTH2_CLIENT_SECRET
Expand All @@ -174,10 +150,6 @@ spec:
value: "{{ SUPERSET_OAUTH2_ACCESS_TOKEN_PATH }}"
- name: OAUTH2_AUTHORIZE_PATH
value: "{{ SUPERSET_OAUTH2_AUTHORIZE_PATH }}"
- name: OPENEDX_USERNAME_PATH
value: "{{ SUPERSET_OPENEDX_USERNAME_PATH }}"
- name: OPENEDX_USER_PROFILE_PATH
value: "{{ SUPERSET_OPENEDX_USER_PROFILE_PATH }}"
- name: OPENEDX_COURSES_LIST_PATH
value: "{{ SUPERSET_OPENEDX_COURSES_LIST_PATH }}"
- name: OPENEDX_LMS_ROOT_URL
Expand Down
28 changes: 0 additions & 28 deletions tutorsuperset/patches/k8s-jobs
Original file line number Diff line number Diff line change
Expand Up @@ -26,16 +26,6 @@ spec:
value: "{{ SUPERSET_DB_PASSWORD }}"
- name: DATABASE_USER
value: "{{ SUPERSET_DB_USERNAME }}"
- name: OPENEDX_MYSQL_HOST
value: "{{ MYSQL_HOST }}"
- name: OPENEDX_MYSQL_PORT
value: "{{ MYSQL_PORT }}"
- name: OPENEDX_MYSQL_DATABASE
value: "{{ OPENEDX_MYSQL_DATABASE }}"
- name: OPENEDX_MYSQL_USERNAME
value: "{{ OPENEDX_MYSQL_USERNAME }}"
- name: OPENEDX_MYSQL_PASSWORD
value: "{{ OPENEDX_MYSQL_PASSWORD }}"
- name: OAUTH2_CLIENT_ID
value: "{{ SUPERSET_OAUTH2_CLIENT_ID }}"
- name: OAUTH2_CLIENT_SECRET
Expand All @@ -62,10 +52,6 @@ spec:
value: "{{ SUPERSET_OAUTH2_ACCESS_TOKEN_PATH }}"
- name: OAUTH2_AUTHORIZE_PATH
value: "{{ SUPERSET_OAUTH2_AUTHORIZE_PATH }}"
- name: OPENEDX_USERNAME_PATH
value: "{{ SUPERSET_OPENEDX_USERNAME_PATH }}"
- name: OPENEDX_USER_PROFILE_PATH
value: "{{ SUPERSET_OPENEDX_USER_PROFILE_PATH }}"
- name: OPENEDX_COURSES_LIST_PATH
value: "{{ SUPERSET_OPENEDX_COURSES_LIST_PATH }}"
- name: OPENEDX_LMS_ROOT_URL
Expand Down Expand Up @@ -115,16 +101,6 @@ spec:
value: "{{ SUPERSET_DB_PASSWORD }}"
- name: DATABASE_USER
value: "{{ SUPERSET_DB_USERNAME }}"
- name: OPENEDX_MYSQL_HOST
value: "{{ MYSQL_HOST }}"
- name: OPENEDX_MYSQL_PORT
value: "{{ MYSQL_PORT }}"
- name: OPENEDX_MYSQL_DATABASE
value: "{{ OPENEDX_MYSQL_DATABASE }}"
- name: OPENEDX_MYSQL_USERNAME
value: "{{ OPENEDX_MYSQL_USERNAME }}"
- name: OPENEDX_MYSQL_PASSWORD
value: "{{ OPENEDX_MYSQL_PASSWORD }}"
- name: OAUTH2_CLIENT_ID
value: "{{ SUPERSET_OAUTH2_CLIENT_ID }}"
- name: OAUTH2_CLIENT_SECRET
Expand All @@ -151,10 +127,6 @@ spec:
value: "{{ SUPERSET_OAUTH2_ACCESS_TOKEN_PATH }}"
- name: OAUTH2_AUTHORIZE_PATH
value: "{{ SUPERSET_OAUTH2_AUTHORIZE_PATH }}"
- name: OPENEDX_USERNAME_PATH
value: "{{ SUPERSET_OPENEDX_USERNAME_PATH }}"
- name: OPENEDX_USER_PROFILE_PATH
value: "{{ SUPERSET_OPENEDX_USER_PROFILE_PATH }}"
- name: OPENEDX_COURSES_LIST_PATH
value: "{{ SUPERSET_OPENEDX_COURSES_LIST_PATH }}"
- name: OPENEDX_LMS_ROOT_URL
Expand Down
7 changes: 2 additions & 5 deletions tutorsuperset/plugin.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
from glob import glob
import os
import pkg_resources
from glob import glob

import pkg_resources
from tutor import hooks

from .__about__ import __version__


########################################
# CONFIGURATION
########################################
Expand All @@ -27,8 +26,6 @@
("SUPERSET_DB_USERNAME", "superset"),
("SUPERSET_OAUTH2_ACCESS_TOKEN_PATH", "/oauth2/access_token/"),
("SUPERSET_OAUTH2_AUTHORIZE_PATH", "/oauth2/authorize/"),
("SUPERSET_OPENEDX_USERNAME_PATH", "/api/user/v1/me"),
("SUPERSET_OPENEDX_USER_PROFILE_PATH", "/api/user/v1/accounts/{username}"),
(
"SUPERSET_OPENEDX_COURSES_LIST_PATH",
"/api/courses/v1/courses/?permissions={permission}&username={username}",
Expand Down
7 changes: 0 additions & 7 deletions tutorsuperset/templates/base-docker-compose-services
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,6 @@ image: apache/superset:{{ SUPERSET_TAG }}
DATABASE_DB: {{ SUPERSET_DB_NAME }}
DATABASE_PASSWORD: {{ SUPERSET_DB_PASSWORD }}
DATABASE_USER: {{ SUPERSET_DB_USERNAME }}
OPENEDX_MYSQL_HOST: {{ MYSQL_HOST }}
OPENEDX_MYSQL_PORT: {{ MYSQL_PORT }}
OPENEDX_MYSQL_DATABASE: {{ OPENEDX_MYSQL_DATABASE }}
OPENEDX_MYSQL_USERNAME: {{ OPENEDX_MYSQL_USERNAME }}
OPENEDX_MYSQL_PASSWORD: {{ OPENEDX_MYSQL_PASSWORD }}
OAUTH2_CLIENT_ID: {{ SUPERSET_OAUTH2_CLIENT_ID }}
OAUTH2_CLIENT_SECRET: {{ SUPERSET_OAUTH2_CLIENT_SECRET }}
SECRET_KEY: {{ SUPERSET_SECRET_KEY }}
Expand All @@ -31,6 +26,4 @@ image: apache/superset:{{ SUPERSET_TAG }}
SUPERSET_PORT: {{ SUPERSET_PORT }}
OAUTH2_ACCESS_TOKEN_PATH: "{{ SUPERSET_OAUTH2_ACCESS_TOKEN_PATH }}"
OAUTH2_AUTHORIZE_PATH: "{{ SUPERSET_OAUTH2_AUTHORIZE_PATH }}"
OPENEDX_USERNAME_PATH: "{{ SUPERSET_OPENEDX_USERNAME_PATH }}"
OPENEDX_USER_PROFILE_PATH: "{{ SUPERSET_OPENEDX_USER_PROFILE_PATH }}"
OPENEDX_COURSES_LIST_PATH: "{{ SUPERSET_OPENEDX_COURSES_LIST_PATH }}"
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
"""
from superset.extensions import security_manager


ALL_COURSES = "1 = 1"
NO_COURSES = "1 = 0"

Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,44 @@
from collections import namedtuple
import logging
import MySQLdb
from collections import namedtuple

import jwt
from authlib.common.urls import add_params_to_qs, add_params_to_uri
from flask import current_app, session

from superset.security import SupersetSecurityManager
from superset.utils.memoized import memoized

log = logging.getLogger(__name__)


def add_to_headers(token, headers=None):
"""Add a Bearer Token to the request URI.
Recommended method of passing bearer tokens.
Authorization: Bearer h480djs93hd8
"""
headers = headers or {}
headers['Authorization'] = 'JWT {}'.format(token)
return headers


def add_bearer_jwt_token(token, uri, headers, body, placement='header'):
"""Add a Bearer Token to the request."""
if placement in ('uri', 'url', 'query'):
uri = add_params_to_uri(token, uri)
elif placement in ('header', 'headers'):
headers = add_to_headers(token, headers)
elif placement == 'body':
body = add_params_to_qs(token, body)
return uri, headers, body


class OpenEdxSsoSecurityManager(SupersetSecurityManager):

def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.oauth.oauth2_client_cls.client_cls.token_auth_class.SIGN_METHODS.update({
"jwt": add_bearer_jwt_token,
})

def set_oauth_session(self, provider, oauth_response):
"""
Store the oauth token in the session for later retrieval.
Expand All @@ -19,27 +48,23 @@ def set_oauth_session(self, provider, oauth_response):
if provider == "openedxsso":
session["oauth_token"] = oauth_response
return res

def decoded_user_info(self):
return jwt.decode(self.access_token, algorithms=["HS256"], options={"verify_signature": False})

def oauth_user_info(self, provider, response=None):
openedx_apis = current_app.config['OPENEDX_API_URLS']
if provider == 'openedxsso':
oauth_remote = self.appbuilder.sm.oauth_remotes[provider]
username_url = openedx_apis['get_username']
me = oauth_remote.get(username_url).json()
username = me['username']

user_profile_url = openedx_apis['get_profile'].format(username=username)
user_profile = oauth_remote.get(user_profile_url).json()
user_profile = self.decoded_user_info()

user_roles = self._get_user_roles(username)
user_roles = self._get_user_roles(user_profile.get('preferred_username'))

return {
'name': user_profile['name'],
'email': user_profile['email'],
'id': user_profile['username'],
'username': user_profile['username'],
'first_name': '',
'last_name': '',
'id': user_profile['preferred_username'],
'username': user_profile['preferred_username'],
'first_name': user_profile.get('given_name') or user_profile.get('name', ''),
'last_name': user_profile.get('family_name'),
'role_keys': user_roles,
}

Expand All @@ -63,10 +88,11 @@ def _get_user_roles(self, username):
"""
Returns the Superset roles that should be associated with the given user.
"""
user_access = _fetch_openedx_user_access(username)
if user_access.is_superuser:
decoded_access_token = self.decoded_user_info()

if decoded_access_token.get("superuser", False):
return ["admin", "openedx"]
elif user_access.is_staff:
elif decoded_access_token.get("administrator", False):
return ["alpha", "openedx"]
else:
# User has to have staff access to one or more courses to view any content here.
Expand Down Expand Up @@ -114,41 +140,3 @@ def get_courses(self, username, permission="staff", next_url=None):
UserAccess = namedtuple(
"UserAccess", ["username", "is_superuser", "is_staff"]
)


def _fetch_openedx_user_access(username):
"""
Fetches the given user's access details from the Open edX User database
NOTE: Open edX JWT seems to provide this info with the "profile" scope.
How do we access this via the AllAuth OAuth2?
"""
cxn = _connect_openedx_db()
cursor = cxn.cursor()

query = "SELECT is_staff, is_superuser FROM auth_user WHERE username=%s"
if cursor.execute(query, (username,)):
(is_staff, is_superuser) = cursor.fetchone()
user_access = UserAccess(
username=username,
is_superuser=is_superuser,
is_staff=is_staff,
)
else:
user_access = UserAccess(
username=username,
is_superuser=False,
is_staff=False,
)

cursor.close()
cxn.close()
return user_access


def _connect_openedx_db():
"""
Return an open connection to the Open edX MySQL database.
"""
openedx_database = current_app.config['OPENEDX_DATABASE']
return MySQLdb.connect(**openedx_database)
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,6 @@

from cachelib.redis import RedisCache
from celery.schedules import crontab

from superset.superset_typing import CacheConfig


Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import os
from urllib.parse import urljoin

from flask_appbuilder.security.manager import AUTH_OAUTH

# Application secret key
Expand All @@ -10,19 +11,8 @@
ROW_LIMIT = int({{ SUPERSET_ROW_LIMIT }})
SQL_MAX_ROW = ROW_LIMIT

# Credentials for connecting to the Open edX MySQL database
OPENEDX_DATABASE = {
'host': os.environ["OPENEDX_MYSQL_HOST"],
'port': int(os.environ["OPENEDX_MYSQL_PORT"]),
'database': os.environ["OPENEDX_MYSQL_DATABASE"],
'user': os.environ["OPENEDX_MYSQL_USERNAME"],
'password': os.environ["OPENEDX_MYSQL_PASSWORD"],
}

OPENEDX_LMS_ROOT_URL = os.environ["OPENEDX_LMS_ROOT_URL"]
OPENEDX_API_URLS = {
"get_username": urljoin(OPENEDX_LMS_ROOT_URL, os.environ["OPENEDX_USERNAME_PATH"]),
"get_profile": urljoin(OPENEDX_LMS_ROOT_URL, os.environ["OPENEDX_USER_PROFILE_PATH"]),
"get_courses": urljoin(OPENEDX_LMS_ROOT_URL, os.environ["OPENEDX_COURSES_LIST_PATH"]),
}

Expand All @@ -37,14 +27,15 @@
'client_id': os.environ["OAUTH2_CLIENT_ID"],
'client_secret': os.environ["OAUTH2_CLIENT_SECRET"],
'client_kwargs':{
'scope': 'read' # Scope for the Authorization
'scope': 'profile email user_id' # Scope for the Authorization
},
'access_token_method':'POST', # HTTP Method to call access_token_url
'access_token_params':{ # Additional parameters for calls to access_token_url
'client_id': os.environ["OAUTH2_CLIENT_ID"],
'token_type': 'jwt'
},
'access_token_headers':{ # Additional headers for calls to access_token_url
'Authorization': 'Basic Base64EncodedClientIdAndSecret'
'Authorization': 'JWT Base64EncodedClientIdAndSecret'
},
'api_base_url': OPENEDX_LMS_ROOT_URL,
'access_token_url': urljoin(OPENEDX_LMS_ROOT_URL, os.environ["OAUTH2_ACCESS_TOKEN_PATH"]),
Expand Down Expand Up @@ -73,6 +64,7 @@
}

from openedx_sso_security_manager import OpenEdxSsoSecurityManager

CUSTOM_SECURITY_MANAGER = OpenEdxSsoSecurityManager


Expand All @@ -87,6 +79,7 @@

# Add this custom template processor which returns the list of courses the current user can access
from openedx_jinja_filters import can_view_courses

JINJA_CONTEXT_ADDONS = {
'can_view_courses': can_view_courses,
}
Expand Down

0 comments on commit 8922ee2

Please sign in to comment.