Skip to content

Commit

Permalink
Temporary workaround for broken OAuth2 authentication
Browse files Browse the repository at this point in the history
This workaround uses a keycloak admin account to impersonate
a keycloak user and simulate a browser flow authentication to
ColdFront, fetching the cookies and using Session for authentication
instead.
  • Loading branch information
knikolla committed Aug 20, 2024
1 parent 2331171 commit aa16f27
Showing 1 changed file with 82 additions and 3 deletions.
85 changes: 82 additions & 3 deletions tools/register_users_from_csv.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,12 @@
(TAs/Profs). Requires `oc` command and
cluster admin access to OpenShift.
--auth-type Method of authentication to Keycloak. Pick either 'secret'
to use client sercet, or 'oauth2' to use the device
authorization grant flow"
to use client sercet, 'oauth2' to use the device
authorization grant flow, or 'workaround'.
--help Show this message and exit.
"""

import csv
import logging
import os
Expand All @@ -34,6 +35,70 @@
logger = logging.getLogger(__name__)


KEYCLOAK_URL = "https://keycloak.mss.mghpcc.org"
KEYCLOAK_USERNAME = os.getenv("KEYCLOAK_USERNAME")
KEYCLOAK_PASSWORD = os.getenv("KEYCLOAK_PASSWORD")
IMPERSONATE_USER = ""


class KeycloakClient(object):
def __init__(self):
self.session = requests.session()
self._admin_auth()

@staticmethod
def construct_url(realm, path):
return f"{KEYCLOAK_URL}/auth/admin/realms/{realm}/{path}"

@property
def url_base(self):
return f"{KEYCLOAK_URL}/auth/admin/realms"

@staticmethod
def auth_endpoint(realm):
return f"{KEYCLOAK_URL}/auth/realms/{realm}/protocol/openid-connect/auth"

@staticmethod
def token_endpoint(realm):
return f"{KEYCLOAK_URL}/auth/realms/{realm}/protocol/openid-connect/token"

def _admin_auth(self):
params = {
"grant_type": "password",
"client_id": "admin-cli",
"username": KEYCLOAK_USERNAME,
"password": KEYCLOAK_PASSWORD,
"scope": "openid",
}
r = requests.post(self.token_endpoint("master"), data=params).json()
headers = {
"Authorization": ("Bearer %s" % r["access_token"]),
"Content-Type": "application/json",
}
self.session.headers.update(headers)
return r

def get_user_id(self, username):
self._admin_auth()
r = self.session.get(
self.construct_url("mss", f"users?username={username}")
).json()
return r[0]["id"]

def impersonate(self, user):
self._admin_auth()
user = self.get_user_id(user)
return self.session.post(
self.construct_url("mss", f"users/{user}/impersonation")
)

def get_session_for_user(self, user):
user_session = requests.session()
user_session.cookies.update(self.impersonate(user).cookies)
user_session.get("https://coldfront.mss.mghpcc.org/oidc/authenticate/")
return user_session


class ScimClient(object):
def __init__(self, scim_url, keycloak_url, auth_type):
self.scim_url = scim_url
Expand All @@ -53,6 +118,8 @@ def refresh_session(self):
data={"grant_type": "client_credentials"},
auth=HTTPBasicAuth(keycloak_client_id, keycloak_client_secret),
)
elif self.auth_type == "workaround":
return KeycloakClient().get_session_for_user(IMPERSONATE_USER)
elif self.auth_type == "oauth2":
device_url = f"{self.keycloak_url}/auth/realms/mss/protocol/openid-connect/auth/device"

Expand Down Expand Up @@ -202,7 +269,19 @@ def get_sanitized_name(name):
"authorization grant flow. Defaults to secret"
),
)
def main(csv_file, add_to_rhods_notebooks_namespace, auth_type):
@click.option(
"--impersonate-user",
default="",
help="User to impersonate for temporary authentication workaround.",
)
def main(csv_file, add_to_rhods_notebooks_namespace, auth_type, impersonate_user):
if auth_type == "workaround":
if not impersonate_user:
raise ValueError("Must provide --impersonate-user argument.")

global IMPERSONATE_USER
IMPERSONATE_USER = impersonate_user

client = ScimClient(
"https://coldfront.mss.mghpcc.org/api/scim/v2",
"https://keycloak.mss.mghpcc.org",
Expand Down

0 comments on commit aa16f27

Please sign in to comment.