diff --git a/README.md b/README.md index b97b603..09deabb 100644 --- a/README.md +++ b/README.md @@ -27,8 +27,10 @@ The plugin required the following environment variables but also supported `.env | OIDC_GROUP_DETECTION_PLUGIN | OIDC plugin to detect groups | | OIDC_PROVIDER_DISPLAY_NAME | any text to display | | OIDC_SCOPE | OIDC scope | +| OIDC_GROUPS_ATTRIBUTE | If the group name field in userinfo is not `groups`, specify its json key | | OIDC_GROUP_NAME | User group name to be allowed login to MLFlow, currently supported groups in OIDC claims and Microsoft Entra ID groups | | OIDC_ADMIN_GROUP_NAME | User group name to be allowed login to MLFlow manage and define permissions, currently supported groups in OIDC claims and Microsoft Entra ID groups | +| OIDC_ALLOW_ALL_USERS | Allow all users to log in, even if they are not in OIDC_ADMIN_GROUP_NAME and OIDC_GROUP_NAME | | OIDC_AUTHORIZATION_URL | OIDC Auth URL (if discovery URL is not defined) | | OIDC_TOKEN_URL | OIDC Token URL (if discovery URL is not defined) | | OIDC_USER_URL | OIDC User info URL (if discovery URL is not defined) | @@ -81,6 +83,8 @@ OIDC_ADMIN_GROUP_NAME = "mlflow_admins_group_name" > please note, that for getting group membership information, the application should have "GroupMember.Read.All" permission +> Please note that for some OAuth2 providers like GitLab, use spaces instead of commas to separate scopes. If there is a scope-related error, please confirm the string format. + # Development Preconditions: diff --git a/mlflow_oidc_auth/config.py b/mlflow_oidc_auth/config.py index bf8f135..691d215 100644 --- a/mlflow_oidc_auth/config.py +++ b/mlflow_oidc_auth/config.py @@ -1,11 +1,11 @@ import os import secrets -import requests import secrets import importlib from dotenv import load_dotenv from mlflow.server import app +from mlflow_oidc_auth.string_utils import strtobool load_dotenv() # take environment variables from .env. app.logger.setLevel(os.environ.get("LOG_LEVEL", "INFO")) @@ -25,6 +25,7 @@ def __init__(self): self.OIDC_REDIRECT_URI = os.environ.get("OIDC_REDIRECT_URI", None) self.OIDC_CLIENT_ID = os.environ.get("OIDC_CLIENT_ID", None) self.OIDC_CLIENT_SECRET = os.environ.get("OIDC_CLIENT_SECRET", None) + self.OIDC_ALLOW_ALL_USERS = bool(strtobool(os.environ.get("OIDC_ALLOW_ALL_USERS", "False"))) # session self.SESSION_TYPE = os.environ.get("SESSION_TYPE", "cachelib") diff --git a/mlflow_oidc_auth/string_utils.py b/mlflow_oidc_auth/string_utils.py new file mode 100644 index 0000000..ea8bfab --- /dev/null +++ b/mlflow_oidc_auth/string_utils.py @@ -0,0 +1,13 @@ +def strtobool(val): + """Convert a string representation of truth to true (1) or false (0). + True values are 'y', 'yes', 't', 'true', 'on', and '1'; false values + are 'n', 'no', 'f', 'false', 'off', and '0'. Raises ValueError if + 'val' is anything else. + """ + val = val.lower() + if val in ('y', 'yes', 't', 'true', 'on', '1'): + return 1 + elif val in ('n', 'no', 'f', 'false', 'off', '0'): + return 0 + else: + raise ValueError("invalid truth value %r" % (val,)) \ No newline at end of file diff --git a/mlflow_oidc_auth/views/authentication.py b/mlflow_oidc_auth/views/authentication.py index 08ab3c0..2997c3b 100644 --- a/mlflow_oidc_auth/views/authentication.py +++ b/mlflow_oidc_auth/views/authentication.py @@ -44,14 +44,19 @@ def callback(): token["access_token"] ) else: - user_groups = token["userinfo"][config.OIDC_GROUPS_ATTRIBUTE] + try: + user_groups = token["userinfo"][config.OIDC_GROUPS_ATTRIBUTE] + except KeyError: + user_groups = [] app.logger.debug(f"User groups: {user_groups}") + app.logger.debug(f"OIDC_ALLOW_ALL_USERS:{config.OIDC_ALLOW_ALL_USERS}") if config.OIDC_ADMIN_GROUP_NAME in user_groups: is_admin = True - elif not any(group in user_groups for group in config.OIDC_GROUP_NAME): - return "User is not allowed to login", 401 + elif not config.OIDC_ALLOW_ALL_USERS: + if not any(group in user_groups for group in config.OIDC_GROUP_NAME): + return "The user is not in any group that is allowed to access, so login is not allowed.", 401 create_user(username=email.lower(), display_name=display_name, is_admin=is_admin) populate_groups(group_names=user_groups) diff --git a/scripts/init-dev-env.sh b/scripts/init-dev-env.sh new file mode 100755 index 0000000..87ab9c9 --- /dev/null +++ b/scripts/init-dev-env.sh @@ -0,0 +1,33 @@ +#!/bin/bash + +# Script to initialize/reset the development environment + +# Remove auth database +if [ -f "auth.db" ]; then + rm auth.db + echo "Removed auth.db" +fi + +# Remove Flask session cache +if [ -d "/tmp/flask_session" ]; then + rm -rf /tmp/flask_session/* + echo "Cleared /tmp/flask_session" +fi + +# Remove Flask session cache +if [ -d "/tmp/flask_cache/" ]; then + rm -rf /tmp/flask_cache/* + echo "Cleared /tmp/flask_cache/" +fi + +# Remove MLflow runs +if [ -d "mlruns" ]; then + rm -rf mlruns + echo "Removed mlruns directory" +fi + +# Optional: Recreate necessary directories +mkdir -p /tmp/flask_session +mkdir -p /tmp/flask_cache/ + +echo "Environment initialization complete." \ No newline at end of file diff --git a/web-ui/src/styles.scss b/web-ui/src/styles.scss index f222408..4b2aadb 100644 --- a/web-ui/src/styles.scss +++ b/web-ui/src/styles.scss @@ -4,4 +4,4 @@ html, body { height: 100%; } body { margin: 0; font-family: Roboto, "Helvetica Neue", sans-serif; } /* Importing Bootstrap SCSS file. */ -@import '~bootstrap/scss/bootstrap'; +@import 'bootstrap/scss/bootstrap';