From 64d74313e8137e50384ec8b91e590d00d56d5bdd Mon Sep 17 00:00:00 2001 From: Alexander Kharkevich Date: Wed, 10 Apr 2024 15:50:38 -0400 Subject: [PATCH 1/2] fix: fix internal communication --- README.md | 18 +++++++++++++----- mlflow_oidc_auth/config.py | 4 ++++ mlflow_oidc_auth/views.py | 37 +++++++++++++++++++++++++------------ scripts/run-dev-server.sh | 2 +- 4 files changed, 43 insertions(+), 18 deletions(-) diff --git a/README.md b/README.md index 13417b6..a49297f 100644 --- a/README.md +++ b/README.md @@ -24,6 +24,9 @@ The plugin required the following environment variables but also supported `.env | OAUTHLIB_INSECURE_TRANSPORT | Development only. Allow to use insecure endpoints for OIDC | | LOG_LEVEL | Application log level | | OIDC_USERS_DB_URI | Database connection string | +| MLFLOW_TRACKING_USERNAME | Credentials for internal communications via API | +| MLFLOW_TRACKING_PASSWORD | Credentials for internal communications via API | +| MLFLOW_TRACKING_URI | URI for internal communications via API | # Configuration examples @@ -56,16 +59,21 @@ OIDC_ADMIN_GROUP_NAME = "mlflow_admins_group_name" > please note, that for getting group membership information, the application should have "GroupMember.Read.All" permission # Development + +Preconditions: + +The following tools should be installed for local development: + +* git +* nodejs +* python + ```shell git clone https://github.com/data-platform-hq/mlflow-oidc-auth cd mlflow-oidc-auth -python3 -m venv venv -source venv/bin/activate -pip install --editable . -mlflow server --dev --app-name oidc-auth --host 0.0.0.0 --port 8080 +./scripts/run-dev-server.sh ``` - # License Apache 2 Licensed. For more information please see [LICENSE](./LICENSE) diff --git a/mlflow_oidc_auth/config.py b/mlflow_oidc_auth/config.py index de8657d..6fa3730 100644 --- a/mlflow_oidc_auth/config.py +++ b/mlflow_oidc_auth/config.py @@ -2,6 +2,7 @@ import os import secrets import requests +import secrets from dotenv import load_dotenv from mlflow_oidc_auth.app import app @@ -34,6 +35,9 @@ class AppConfig: OIDC_REDIRECT_URI = os.environ.get("OIDC_REDIRECT_URI", None) OIDC_CLIENT_ID = os.environ.get("OIDC_CLIENT_ID", None) OIDC_CLIENT_SECRET = os.environ.get("OIDC_CLIENT_SECRET", None) + MLFLOW_TRACKING_URI = os.environ.get("MLFLOW_TRACKING_URI", "http://localhost:8080") + MLFLOW_TRACKING_USERNAME = os.environ.get("MLFLOW_TRACKING_USERNAME", secrets.token_urlsafe(32)) + MLFLOW_TRACKING_PASSWORD = os.environ.get("MLFLOW_TRACKING_PASSWORD", secrets.token_urlsafe(72)) @staticmethod def get_property(property_name): diff --git a/mlflow_oidc_auth/views.py b/mlflow_oidc_auth/views.py index 9418b8f..86b6ca7 100644 --- a/mlflow_oidc_auth/views.py +++ b/mlflow_oidc_auth/views.py @@ -29,6 +29,7 @@ ) from mlflow.protos.service_pb2 import ( CreateExperiment, + SearchExperiments, ) from mlflow_oidc_auth.permissions import Permission, get_permission from mlflow.server.handlers import ( @@ -47,7 +48,7 @@ # Create the OAuth2 client auth_client = WebApplicationClient(AppConfig.get_property("OIDC_CLIENT_ID")) -mlflow_client = MlflowClient() +mlflow_client = MlflowClient(tracking_uri=AppConfig.get_property("MLFLOW_TRACKING_URI")) store = SqlAlchemyStore() store.init_db((AppConfig.get_property("OIDC_USERS_DB_URI"))) _logger = logging.getLogger(__name__) @@ -59,6 +60,7 @@ def _get_experiment_id(request_data: dict) -> str: experiment_id = mlflow_client.get_experiment_by_name(request_data.get("experiment_name")).experiment_id return experiment_id + def _get_request_param(param: str) -> str: if request.method == "GET": args = request.args @@ -83,7 +85,14 @@ def _get_request_param(param: str) -> str: def _is_unprotected_route(path: str) -> bool: - return path.startswith(("/static", "/favicon.ico", "/health", "/login", "/callback", "/oidc/static", "/oidc/ui")) + return path.startswith( + ( + "/health", + "/login", + "/callback", + "/oidc/static", + ) + ) def _get_permission_from_store_or_default(store_permission_func: Callable[[], str]) -> Permission: @@ -105,6 +114,14 @@ def authenticate_request_basic_auth() -> Union[Authorization, Response]: username = request.authorization.username password = request.authorization.password _logger.debug("Authenticating user %s", username) + # check for internal call, if credentials are correct, return True + if username == AppConfig.get_property("MLFLOW_TRACKING_USERNAME") and password == AppConfig.get_property( + "MLFLOW_TRACKING_PASSWORD" + ): + _set_username(username) + _set_is_admin(True) + _logger.debug("User %s authenticated", username) + return True if store.authenticate_user(username, password): _set_username(username) _logger.debug("User %s authenticated", username) @@ -232,9 +249,9 @@ def get_experiment_permission(): # TODO -@catch_mlflow_exception -def search_experiment(): - return render_template("home.html", username=_get_username()) +# @catch_mlflow_exception +# def search_experiment(): +# return render_template("home.html", username=_get_username()) def login(): @@ -338,9 +355,9 @@ def oidc_ui(filename=None): return send_from_directory(ui_directory, filename) -# TODO -def search_model(): - return render_template("home.html", username=_get_username()) +# # TODO +# def search_model(): +# return render_template("home.html", username=_get_username()) def create_user(): @@ -405,10 +422,6 @@ def get_user(): return jsonify({"user": user.to_json()}) -def oidc_home(): - return render_template("home.html", username=_get_username()) - - def permissions(): return redirect(url_for("list_users")) diff --git a/scripts/run-dev-server.sh b/scripts/run-dev-server.sh index eba58f2..a225025 100755 --- a/scripts/run-dev-server.sh +++ b/scripts/run-dev-server.sh @@ -10,9 +10,9 @@ cleanup() { python_preconfigure() { if [ ! -d venv ]; then python3 -m venv venv + source venv/bin/activate python3 -m pip install --upgrade pip python3 -m pip install build setuptools - source venv/bin/activate python3 -m pip install -e . fi } From 0ee029f293e9914fdf3c48393eba4bb105adabe7 Mon Sep 17 00:00:00 2001 From: Alexander Kharkevich Date: Wed, 10 Apr 2024 15:51:29 -0400 Subject: [PATCH 2/2] fix: fix button behavior --- .../home-page/components/home-page/home-page.component.html | 2 +- .../home-page/components/home-page/home-page.component.ts | 4 ++++ web-ui/src/app/shared/components/header/header.component.html | 2 +- web-ui/src/app/shared/components/header/header.component.ts | 4 ++++ 4 files changed, 10 insertions(+), 2 deletions(-) diff --git a/web-ui/src/app/features/home-page/components/home-page/home-page.component.html b/web-ui/src/app/features/home-page/components/home-page/home-page.component.html index f5b0555..32e00b2 100644 --- a/web-ui/src/app/features/home-page/components/home-page/home-page.component.html +++ b/web-ui/src/app/features/home-page/components/home-page/home-page.component.html @@ -1,5 +1,5 @@
- diff --git a/web-ui/src/app/shared/components/header/header.component.ts b/web-ui/src/app/shared/components/header/header.component.ts index c28a558..3cd74fd 100644 --- a/web-ui/src/app/shared/components/header/header.component.ts +++ b/web-ui/src/app/shared/components/header/header.component.ts @@ -12,4 +12,8 @@ export class HeaderComponent implements OnInit { ngOnInit(): void { } + + logout() { + window.location.href = '/logout'; + } }