/ mlflow / server / auth / client.py
client.py
   1  from urllib.parse import quote
   2  
   3  from mlflow.server.auth.entities import (
   4      ExperimentPermission,
   5      GatewayEndpointPermission,
   6      GatewayModelDefinitionPermission,
   7      GatewaySecretPermission,
   8      RegisteredModelPermission,
   9      Role,
  10      RolePermission,
  11      ScorerPermission,
  12      User,
  13      UserRoleAssignment,
  14      WorkspacePermission,
  15  )
  16  from mlflow.server.auth.routes import (
  17      ADD_ROLE_PERMISSION,
  18      ASSIGN_ROLE,
  19      CREATE_EXPERIMENT_PERMISSION,
  20      CREATE_GATEWAY_ENDPOINT_PERMISSION,
  21      CREATE_GATEWAY_MODEL_DEFINITION_PERMISSION,
  22      CREATE_GATEWAY_SECRET_PERMISSION,
  23      CREATE_REGISTERED_MODEL_PERMISSION,
  24      CREATE_ROLE,
  25      CREATE_SCORER_PERMISSION,
  26      CREATE_USER,
  27      DELETE_EXPERIMENT_PERMISSION,
  28      DELETE_GATEWAY_ENDPOINT_PERMISSION,
  29      DELETE_GATEWAY_MODEL_DEFINITION_PERMISSION,
  30      DELETE_GATEWAY_SECRET_PERMISSION,
  31      DELETE_REGISTERED_MODEL_PERMISSION,
  32      DELETE_ROLE,
  33      DELETE_SCORER_PERMISSION,
  34      DELETE_USER,
  35      GET_EXPERIMENT_PERMISSION,
  36      GET_GATEWAY_ENDPOINT_PERMISSION,
  37      GET_GATEWAY_MODEL_DEFINITION_PERMISSION,
  38      GET_GATEWAY_SECRET_PERMISSION,
  39      GET_REGISTERED_MODEL_PERMISSION,
  40      GET_ROLE,
  41      GET_SCORER_PERMISSION,
  42      GET_USER,
  43      LIST_ROLE_PERMISSIONS,
  44      LIST_ROLE_USERS,
  45      LIST_ROLES,
  46      LIST_USER_ROLES,
  47      LIST_USER_WORKSPACE_PERMISSIONS,
  48      LIST_WORKSPACE_PERMISSIONS,
  49      REMOVE_ROLE_PERMISSION,
  50      UNASSIGN_ROLE,
  51      UPDATE_EXPERIMENT_PERMISSION,
  52      UPDATE_GATEWAY_ENDPOINT_PERMISSION,
  53      UPDATE_GATEWAY_MODEL_DEFINITION_PERMISSION,
  54      UPDATE_GATEWAY_SECRET_PERMISSION,
  55      UPDATE_REGISTERED_MODEL_PERMISSION,
  56      UPDATE_ROLE,
  57      UPDATE_ROLE_PERMISSION,
  58      UPDATE_SCORER_PERMISSION,
  59      UPDATE_USER_ADMIN,
  60      UPDATE_USER_PASSWORD,
  61  )
  62  from mlflow.utils.credentials import get_default_host_creds
  63  from mlflow.utils.rest_utils import http_request, verify_rest_response
  64  
  65  
  66  class AuthServiceClient:
  67      """
  68      Client of an MLflow Tracking Server that enabled the default basic authentication plugin.
  69      It is recommended to use :py:func:`mlflow.server.get_app_client()` to instantiate this class.
  70      See https://mlflow.org/docs/latest/auth.html for more information.
  71      """
  72  
  73      def __init__(self, tracking_uri: str):
  74          """
  75          Args:
  76              tracking_uri: Address of local or remote tracking server.
  77          """
  78          self.tracking_uri = tracking_uri
  79  
  80      def _request(self, endpoint, method, *, expected_status: int = 200, **kwargs):
  81          host_creds = get_default_host_creds(self.tracking_uri)
  82          resp = http_request(host_creds, endpoint, method, **kwargs)
  83          resp = verify_rest_response(resp, endpoint, expected_status=expected_status)
  84          if resp.status_code == 204 or not resp.content:
  85              return {}
  86          return resp.json()
  87  
  88      def _workspace_endpoint(self, template: str, workspace_name: str) -> str:
  89          """
  90          Replace the workspace placeholder in ``template`` with a URL-encoded name.
  91          """
  92          return template.replace("<workspace_name>", quote(workspace_name, safe=""))
  93  
  94      def create_user(self, username: str, password: str):
  95          """
  96          Create a new user.
  97  
  98          Args:
  99              username: The username.
 100              password: The user's password. Must not be empty string.
 101  
 102          Raises:
 103              mlflow.exceptions.RestException: if the username is already taken.
 104  
 105          Returns:
 106              A single :py:class:`mlflow.server.auth.entities.User` object.
 107  
 108          .. code-block:: python
 109              :caption: Example
 110  
 111              from mlflow.server.auth.client import AuthServiceClient
 112  
 113              client = AuthServiceClient("tracking_uri")
 114              user = client.create_user("newuser", "newpassword")
 115              print(f"user_id: {user.id}")
 116              print(f"username: {user.username}")
 117              print(f"password_hash: {user.password_hash}")
 118              print(f"is_admin: {user.is_admin}")
 119  
 120          .. code-block:: text
 121              :caption: Output
 122  
 123              user_id: 3
 124              username: newuser
 125              password_hash: REDACTED
 126              is_admin: False
 127          """
 128          resp = self._request(
 129              CREATE_USER,
 130              "POST",
 131              json={"username": username, "password": password},
 132          )
 133          return User.from_json(resp["user"])
 134  
 135      def get_user(self, username: str):
 136          """
 137          Get a user with a specific username.
 138  
 139          Args:
 140              username: The username.
 141  
 142          Raises:
 143              mlflow.exceptions.RestException: if the user does not exist
 144  
 145          Returns:
 146              A single :py:class:`mlflow.server.auth.entities.User` object.
 147  
 148          .. code-block:: bash
 149              :caption: Example
 150  
 151              export MLFLOW_TRACKING_USERNAME=admin
 152              export MLFLOW_TRACKING_PASSWORD=password
 153  
 154          .. code-block:: python
 155  
 156              from mlflow.server.auth.client import AuthServiceClient
 157  
 158              client = AuthServiceClient("tracking_uri")
 159              client.create_user("newuser", "newpassword")
 160              user = client.get_user("newuser")
 161  
 162              print(f"user_id: {user.id}")
 163              print(f"username: {user.username}")
 164              print(f"password_hash: {user.password_hash}")
 165              print(f"is_admin: {user.is_admin}")
 166  
 167          .. code-block:: text
 168              :caption: Output
 169  
 170              user_id: 3
 171              username: newuser
 172              password_hash: REDACTED
 173              is_admin: False
 174          """
 175          resp = self._request(
 176              GET_USER,
 177              "GET",
 178              params={"username": username},
 179          )
 180          return User.from_json(resp["user"])
 181  
 182      def update_user_password(self, username: str, password: str):
 183          """
 184          Update the password of a specific user.
 185  
 186          Args:
 187              username: The username.
 188              password: The new password.
 189  
 190          Raises:
 191              mlflow.exceptions.RestException: if the user does not exist
 192  
 193          .. code-block:: bash
 194              :caption: Example
 195  
 196              export MLFLOW_TRACKING_USERNAME=admin
 197              export MLFLOW_TRACKING_PASSWORD=password
 198  
 199          .. code-block:: python
 200  
 201              from mlflow.server.auth.client import AuthServiceClient
 202  
 203              client = AuthServiceClient("tracking_uri")
 204              client.create_user("newuser", "newpassword")
 205  
 206              client.update_user_password("newuser", "anotherpassword")
 207          """
 208          self._request(
 209              UPDATE_USER_PASSWORD,
 210              "PATCH",
 211              json={"username": username, "password": password},
 212          )
 213  
 214      def update_user_admin(self, username: str, is_admin: bool):
 215          """
 216          Update the admin status of a specific user.
 217  
 218          Args:
 219              username: The username.
 220              is_admin: The new admin status.
 221  
 222          Raises:
 223              mlflow.exceptions.RestException: if the user does not exist
 224  
 225          .. code-block:: bash
 226              :caption: Example
 227  
 228              export MLFLOW_TRACKING_USERNAME=admin
 229              export MLFLOW_TRACKING_PASSWORD=password
 230  
 231          .. code-block:: python
 232  
 233              from mlflow.server.auth.client import AuthServiceClient
 234  
 235              client = AuthServiceClient("tracking_uri")
 236              client.create_user("newuser", "newpassword")
 237  
 238              client.update_user_admin("newuser", True)
 239          """
 240          self._request(
 241              UPDATE_USER_ADMIN,
 242              "PATCH",
 243              json={"username": username, "is_admin": is_admin},
 244          )
 245  
 246      def delete_user(self, username: str):
 247          """
 248          Delete a specific user.
 249  
 250          Args:
 251              username: The username.
 252  
 253          Raises:
 254              mlflow.exceptions.RestException: if the user does not exist
 255  
 256          .. code-block:: bash
 257              :caption: Example
 258  
 259              export MLFLOW_TRACKING_USERNAME=admin
 260              export MLFLOW_TRACKING_PASSWORD=password
 261  
 262          .. code-block:: python
 263  
 264              from mlflow.server.auth.client import AuthServiceClient
 265  
 266              client = AuthServiceClient("tracking_uri")
 267              client.create_user("newuser", "newpassword")
 268  
 269              client.delete_user("newuser")
 270          """
 271          self._request(
 272              DELETE_USER,
 273              "DELETE",
 274              json={"username": username},
 275          )
 276  
 277      def create_experiment_permission(self, experiment_id: str, username: str, permission: str):
 278          """
 279          Create a permission on an experiment for a user.
 280  
 281          Args:
 282              experiment_id: The id of the experiment.
 283              username: The username.
 284              permission: Permission to grant. Must be one of "READ", "USE", "EDIT", "MANAGE" and
 285                  "NO_PERMISSIONS".
 286  
 287          Raises:
 288              mlflow.exceptions.RestException: if the user does not exist, or a permission already
 289                  exists for this experiment user pair, or if the permission is invalid. Does not
 290                  require ``experiment_id`` to be an existing experiment.
 291  
 292          Returns:
 293              A single :py:class:`mlflow.server.auth.entities.ExperimentPermission` object.
 294  
 295          .. code-block:: bash
 296              :caption: Example
 297  
 298              export MLFLOW_TRACKING_USERNAME=admin
 299              export MLFLOW_TRACKING_PASSWORD=password
 300  
 301          .. code-block:: python
 302  
 303              from mlflow.server.auth.client import AuthServiceClient
 304  
 305              client = AuthServiceClient("tracking_uri")
 306              client.create_user("newuser", "newpassword")
 307              ep = client.create_experiment_permission("myexperiment", "newuser", "READ")
 308  
 309              print(f"experiment_id: {ep.experiment_id}")
 310              print(f"user_id: {ep.user_id}")
 311              print(f"permission: {ep.permission}")
 312  
 313          .. code-block:: text
 314              :caption: Output
 315  
 316              experiment_id: myexperiment
 317              user_id: 3
 318              permission: READ
 319          """
 320          resp = self._request(
 321              CREATE_EXPERIMENT_PERMISSION,
 322              "POST",
 323              json={"experiment_id": experiment_id, "username": username, "permission": permission},
 324          )
 325          return ExperimentPermission.from_json(resp["experiment_permission"])
 326  
 327      def get_experiment_permission(self, experiment_id: str, username: str):
 328          """
 329          Get an experiment permission for a user.
 330  
 331          Args:
 332              experiment_id: The id of the experiment.
 333              username: The username.
 334  
 335          Raises:
 336              mlflow.exceptions.RestException: if the user does not exist,
 337                  or no permission exists for this experiment user pair.
 338                  Note that the default permission will still be effective even if
 339                  no permission exists.
 340  
 341          Returns:
 342              A single :py:class:`mlflow.server.auth.entities.ExperimentPermission` object.
 343  
 344          .. code-block:: bash
 345              :caption: Example
 346  
 347              export MLFLOW_TRACKING_USERNAME=admin
 348              export MLFLOW_TRACKING_PASSWORD=password
 349  
 350          .. code-block:: python
 351  
 352              from mlflow.server.auth.client import AuthServiceClient
 353  
 354              client = AuthServiceClient("tracking_uri")
 355              client.create_user("newuser", "newpassword")
 356              client.create_experiment_permission("myexperiment", "newuser", "READ")
 357              ep = client.get_experiment_permission("myexperiment", "newuser")
 358              print(f"experiment_id: {ep.experiment_id}")
 359              print(f"user_id: {ep.user_id}")
 360              print(f"permission: {ep.permission}")
 361  
 362          .. code-block:: text
 363              :caption: Output
 364  
 365              experiment_id: myexperiment
 366              user_id: 3
 367              permission: READ
 368          """
 369          resp = self._request(
 370              GET_EXPERIMENT_PERMISSION,
 371              "GET",
 372              params={"experiment_id": experiment_id, "username": username},
 373          )
 374          return ExperimentPermission.from_json(resp["experiment_permission"])
 375  
 376      def update_experiment_permission(self, experiment_id: str, username: str, permission: str):
 377          """
 378          Update an existing experiment permission for a user.
 379  
 380          Args:
 381              experiment_id: The id of the experiment.
 382              username: The username.
 383              permission: New permission to grant. Must be one of "READ", "USE", "EDIT", "MANAGE" and
 384                  "NO_PERMISSIONS".
 385  
 386          Raises:
 387              mlflow.exceptions.RestException: if the user does not exist, or no permission exists for
 388                  this experiment user pair, or if the permission is invalid
 389  
 390          .. code-block:: bash
 391              :caption: Example
 392  
 393              export MLFLOW_TRACKING_USERNAME=admin
 394              export MLFLOW_TRACKING_PASSWORD=password
 395  
 396          .. code-block:: python
 397  
 398              from mlflow.server.auth.client import AuthServiceClient
 399  
 400              client = AuthServiceClient("tracking_uri")
 401              client.create_user("newuser", "newpassword")
 402              client.create_experiment_permission("myexperiment", "newuser", "READ")
 403              client.update_experiment_permission("myexperiment", "newuser", "EDIT")
 404          """
 405          self._request(
 406              UPDATE_EXPERIMENT_PERMISSION,
 407              "PATCH",
 408              json={"experiment_id": experiment_id, "username": username, "permission": permission},
 409          )
 410  
 411      def delete_experiment_permission(self, experiment_id: str, username: str):
 412          """
 413          Delete an existing experiment permission for a user.
 414  
 415          Args:
 416              experiment_id: The id of the experiment.
 417              username: The username.
 418  
 419          Raises:
 420              mlflow.exceptions.RestException: if the user does not exist, or no permission exists for
 421                  this experiment user pair, or if the permission is invalid.
 422                  Note that the default permission will still be effective even
 423                  after the permission has been deleted.
 424  
 425  
 426          .. code-block:: bash
 427              :caption: Example
 428  
 429              export MLFLOW_TRACKING_USERNAME=admin
 430              export MLFLOW_TRACKING_PASSWORD=password
 431  
 432          .. code-block:: python
 433  
 434              from mlflow.server.auth.client import AuthServiceClient
 435  
 436              client = AuthServiceClient("tracking_uri")
 437              client.create_user("newuser", "newpassword")
 438              client.create_experiment_permission("myexperiment", "newuser", "READ")
 439              client.delete_experiment_permission("myexperiment", "newuser")
 440          """
 441          self._request(
 442              DELETE_EXPERIMENT_PERMISSION,
 443              "DELETE",
 444              json={"experiment_id": experiment_id, "username": username},
 445          )
 446  
 447      def create_registered_model_permission(self, name: str, username: str, permission: str):
 448          """
 449          Create a permission on an registered model for a user.
 450  
 451          Args:
 452              name: The name of the registered model.
 453              username: The username.
 454              permission: Permission to grant. Must be one of "READ", "USE", "EDIT", "MANAGE" and
 455                  "NO_PERMISSIONS".
 456  
 457          Raises:
 458              mlflow.exceptions.RestException: if the user does not exist,
 459                  or a permission already exists for this registered model user pair,
 460                  or if the permission is invalid.
 461                  Does not require ``name`` to be an existing registered model.
 462  
 463          Returns:
 464              A single :py:class:`mlflow.server.auth.entities.RegisteredModelPermission` object.
 465          """
 466          resp = self._request(
 467              CREATE_REGISTERED_MODEL_PERMISSION,
 468              "POST",
 469              json={"name": name, "username": username, "permission": permission},
 470          )
 471          return RegisteredModelPermission.from_json(resp["registered_model_permission"])
 472  
 473      def get_registered_model_permission(self, name: str, username: str):
 474          """
 475          Get an registered model permission for a user.
 476  
 477          Args:
 478              name: The name of the registered model.
 479              username: The username.
 480  
 481          Raises:
 482              mlflow.exceptions.RestException: if the user does not
 483                  exist, or no permission exists for this registered model user pair. Note that the
 484                  default permission will still be effective even if no permission exists.
 485  
 486          Returns:
 487               A single :py:class:`mlflow.server.auth.entities.RegisteredModelPermission` object.
 488  
 489          .. code-block:: bash
 490              :caption: Example
 491  
 492              export MLFLOW_TRACKING_USERNAME=admin
 493              export MLFLOW_TRACKING_PASSWORD=password
 494  
 495          .. code-block:: python
 496  
 497              from mlflow.server.auth.client import AuthServiceClient
 498  
 499              client = AuthServiceClient("tracking_uri")
 500              client.create_user("newuser", "newpassword")
 501              client.create_registered_model_permission("myregisteredmodel", "newuser", "READ")
 502              rmp = client.get_registered_model_permission("myregisteredmodel", "newuser")
 503  
 504              print(f"name: {rmp.name}")
 505              print(f"user_id: {rmp.user_id}")
 506              print(f"permission: {rmp.permission}")
 507  
 508          .. code-block:: text
 509              :caption: Output
 510  
 511              name: myregisteredmodel
 512              user_id: 3
 513              permission: READ
 514          """
 515          resp = self._request(
 516              GET_REGISTERED_MODEL_PERMISSION,
 517              "GET",
 518              params={"name": name, "username": username},
 519          )
 520          return RegisteredModelPermission.from_json(resp["registered_model_permission"])
 521  
 522      def update_registered_model_permission(self, name: str, username: str, permission: str):
 523          """
 524          Update an existing registered model permission for a user.
 525  
 526          Args:
 527              name: The name of the registered model.
 528              username: The username.
 529              permission: New permission to grant. Must be one of "READ", "USE", "EDIT", "MANAGE" and
 530                  "NO_PERMISSIONS".
 531  
 532          Raises:
 533              mlflow.exceptions.RestException: if the user does not exist, or no permission exists for
 534                  this registered model user pair, or if the permission is invalid.
 535  
 536          .. code-block:: bash
 537              :caption: Example
 538  
 539              export MLFLOW_TRACKING_USERNAME=admin
 540              export MLFLOW_TRACKING_PASSWORD=password
 541  
 542          .. code-block:: python
 543  
 544              from mlflow.server.auth.client import AuthServiceClient
 545  
 546              client = AuthServiceClient("tracking_uri")
 547              client.create_user("newuser", "newpassword")
 548              client.create_registered_model_permission("myregisteredmodel", "newuser", "READ")
 549              client.update_registered_model_permission("myregisteredmodel", "newuser", "EDIT")
 550          """
 551          self._request(
 552              UPDATE_REGISTERED_MODEL_PERMISSION,
 553              "PATCH",
 554              json={"name": name, "username": username, "permission": permission},
 555          )
 556  
 557      def delete_registered_model_permission(self, name: str, username: str):
 558          """
 559          Delete an existing registered model permission for a user.
 560  
 561          Args:
 562              name: The name of the registered model.
 563              username: The username.
 564  
 565          Raises:
 566              mlflow.exceptions.RestException: if the user does not exist,
 567                  or no permission exists for this registered model user pair,
 568                  or if the permission is invalid.
 569                  Note that the default permission will still be effective even
 570                  after the permission has been deleted.
 571  
 572          .. code-block:: bash
 573              :caption: Example
 574  
 575              export MLFLOW_TRACKING_USERNAME=admin
 576              export MLFLOW_TRACKING_PASSWORD=password
 577  
 578          .. code-block:: python
 579  
 580              from mlflow.server.auth.client import AuthServiceClient
 581  
 582              client = AuthServiceClient("tracking_uri")
 583              client.create_user("newuser", "newpassword")
 584              client.create_registered_model_permission("myregisteredmodel", "newuser", "READ")
 585              client.delete_registered_model_permission("myregisteredmodel", "newuser")
 586          """
 587          self._request(
 588              DELETE_REGISTERED_MODEL_PERMISSION,
 589              "DELETE",
 590              json={"name": name, "username": username},
 591          )
 592  
 593      def create_scorer_permission(
 594          self, experiment_id: str, scorer_name: str, username: str, permission: str
 595      ):
 596          """
 597          Create a permission on a scorer for a user.
 598  
 599          Args:
 600              experiment_id: The id of the experiment containing the scorer.
 601              scorer_name: The name of the scorer.
 602              username: The username.
 603              permission: Permission to grant. Must be one of "READ", "USE", "EDIT", "MANAGE" and
 604                  "NO_PERMISSIONS".
 605  
 606          Raises:
 607              mlflow.exceptions.RestException: if the user does not exist,
 608                  or the scorer permission already exists.
 609  
 610          Returns:
 611              A single :py:class:`mlflow.server.auth.entities.ScorerPermission` object.
 612          """
 613          resp = self._request(
 614              CREATE_SCORER_PERMISSION,
 615              "POST",
 616              json={
 617                  "experiment_id": experiment_id,
 618                  "scorer_name": scorer_name,
 619                  "username": username,
 620                  "permission": permission,
 621              },
 622          )
 623          return ScorerPermission.from_json(resp["scorer_permission"])
 624  
 625      def get_scorer_permission(self, experiment_id: str, scorer_name: str, username: str):
 626          """
 627          Get a scorer permission for a user.
 628  
 629          Args:
 630              experiment_id: The id of the experiment containing the scorer.
 631              scorer_name: The name of the scorer.
 632              username: The username.
 633  
 634          Raises:
 635              mlflow.exceptions.RestException: if the user does not exist,
 636                  or no permission exists for this scorer user pair.
 637  
 638          Returns:
 639              A single :py:class:`mlflow.server.auth.entities.ScorerPermission` object.
 640          """
 641          resp = self._request(
 642              GET_SCORER_PERMISSION,
 643              "GET",
 644              params={
 645                  "experiment_id": experiment_id,
 646                  "scorer_name": scorer_name,
 647                  "username": username,
 648              },
 649          )
 650          return ScorerPermission.from_json(resp["scorer_permission"])
 651  
 652      def update_scorer_permission(
 653          self, experiment_id: str, scorer_name: str, username: str, permission: str
 654      ):
 655          """
 656          Update an existing scorer permission for a user.
 657  
 658          Args:
 659              experiment_id: The id of the experiment containing the scorer.
 660              scorer_name: The name of the scorer.
 661              username: The username.
 662              permission: New permission to grant. Must be one of "READ", "USE", "EDIT", "MANAGE" and
 663                  "NO_PERMISSIONS".
 664  
 665          Raises:
 666              mlflow.exceptions.RestException: if the user does not exist,
 667                  or no permission exists for this scorer user pair,
 668                  or if the permission is invalid.
 669          """
 670          self._request(
 671              UPDATE_SCORER_PERMISSION,
 672              "PATCH",
 673              json={
 674                  "experiment_id": experiment_id,
 675                  "scorer_name": scorer_name,
 676                  "username": username,
 677                  "permission": permission,
 678              },
 679          )
 680  
 681      def delete_scorer_permission(self, experiment_id: str, scorer_name: str, username: str):
 682          """
 683          Delete an existing scorer permission for a user.
 684  
 685          Args:
 686              experiment_id: The id of the experiment containing the scorer.
 687              scorer_name: The name of the scorer.
 688              username: The username.
 689  
 690          Raises:
 691              mlflow.exceptions.RestException: if the user does not exist,
 692                  or no permission exists for this scorer user pair.
 693          """
 694          self._request(
 695              DELETE_SCORER_PERMISSION,
 696              "DELETE",
 697              json={
 698                  "experiment_id": experiment_id,
 699                  "scorer_name": scorer_name,
 700                  "username": username,
 701              },
 702          )
 703  
 704      # Gateway secret permission methods
 705  
 706      def create_gateway_secret_permission(self, secret_id: str, username: str, permission: str):
 707          """
 708          Create a permission on a gateway secret for a user.
 709  
 710          Args:
 711              secret_id: The id of the gateway secret.
 712              username: The username.
 713              permission: Permission to grant. Must be one of "READ", "USE", "EDIT", "MANAGE" and
 714                  "NO_PERMISSIONS".
 715  
 716          Returns:
 717              A single :py:class:`mlflow.server.auth.entities.GatewaySecretPermission` object.
 718          """
 719          resp = self._request(
 720              CREATE_GATEWAY_SECRET_PERMISSION,
 721              "POST",
 722              json={"secret_id": secret_id, "username": username, "permission": permission},
 723          )
 724          return GatewaySecretPermission.from_json(resp["gateway_secret_permission"])
 725  
 726      def get_gateway_secret_permission(self, secret_id: str, username: str):
 727          """
 728          Get a gateway secret permission for a user.
 729  
 730          Args:
 731              secret_id: The id of the gateway secret.
 732              username: The username.
 733  
 734          Returns:
 735              A single :py:class:`mlflow.server.auth.entities.GatewaySecretPermission` object.
 736          """
 737          resp = self._request(
 738              GET_GATEWAY_SECRET_PERMISSION,
 739              "GET",
 740              params={"secret_id": secret_id, "username": username},
 741          )
 742          return GatewaySecretPermission.from_json(resp["gateway_secret_permission"])
 743  
 744      def update_gateway_secret_permission(self, secret_id: str, username: str, permission: str):
 745          """
 746          Update an existing gateway secret permission for a user.
 747  
 748          Args:
 749              secret_id: The id of the gateway secret.
 750              username: The username.
 751              permission: New permission to grant. Must be one of "READ", "USE", "EDIT", "MANAGE" and
 752                  "NO_PERMISSIONS".
 753          """
 754          self._request(
 755              UPDATE_GATEWAY_SECRET_PERMISSION,
 756              "PATCH",
 757              json={"secret_id": secret_id, "username": username, "permission": permission},
 758          )
 759  
 760      def delete_gateway_secret_permission(self, secret_id: str, username: str):
 761          """
 762          Delete an existing gateway secret permission for a user.
 763  
 764          Args:
 765              secret_id: The id of the gateway secret.
 766              username: The username.
 767          """
 768          self._request(
 769              DELETE_GATEWAY_SECRET_PERMISSION,
 770              "DELETE",
 771              json={"secret_id": secret_id, "username": username},
 772          )
 773  
 774      # Gateway endpoint permission methods
 775  
 776      def create_gateway_endpoint_permission(self, endpoint_id: str, username: str, permission: str):
 777          """
 778          Create a permission on a gateway endpoint for a user.
 779  
 780          Args:
 781              endpoint_id: The id of the gateway endpoint.
 782              username: The username.
 783              permission: Permission to grant. Must be one of "READ", "USE", "EDIT", "MANAGE" and
 784                  "NO_PERMISSIONS".
 785  
 786          Returns:
 787              A single :py:class:`mlflow.server.auth.entities.GatewayEndpointPermission` object.
 788          """
 789          resp = self._request(
 790              CREATE_GATEWAY_ENDPOINT_PERMISSION,
 791              "POST",
 792              json={"endpoint_id": endpoint_id, "username": username, "permission": permission},
 793          )
 794          return GatewayEndpointPermission.from_json(resp["gateway_endpoint_permission"])
 795  
 796      def get_gateway_endpoint_permission(self, endpoint_id: str, username: str):
 797          """
 798          Get a gateway endpoint permission for a user.
 799  
 800          Args:
 801              endpoint_id: The id of the gateway endpoint.
 802              username: The username.
 803  
 804          Returns:
 805              A single :py:class:`mlflow.server.auth.entities.GatewayEndpointPermission` object.
 806          """
 807          resp = self._request(
 808              GET_GATEWAY_ENDPOINT_PERMISSION,
 809              "GET",
 810              params={"endpoint_id": endpoint_id, "username": username},
 811          )
 812          return GatewayEndpointPermission.from_json(resp["gateway_endpoint_permission"])
 813  
 814      def update_gateway_endpoint_permission(self, endpoint_id: str, username: str, permission: str):
 815          """
 816          Update an existing gateway endpoint permission for a user.
 817  
 818          Args:
 819              endpoint_id: The id of the gateway endpoint.
 820              username: The username.
 821              permission: New permission to grant. Must be one of "READ", "USE", "EDIT", "MANAGE" and
 822                  "NO_PERMISSIONS".
 823          """
 824          self._request(
 825              UPDATE_GATEWAY_ENDPOINT_PERMISSION,
 826              "PATCH",
 827              json={"endpoint_id": endpoint_id, "username": username, "permission": permission},
 828          )
 829  
 830      def delete_gateway_endpoint_permission(self, endpoint_id: str, username: str):
 831          """
 832          Delete an existing gateway endpoint permission for a user.
 833  
 834          Args:
 835              endpoint_id: The id of the gateway endpoint.
 836              username: The username.
 837          """
 838          self._request(
 839              DELETE_GATEWAY_ENDPOINT_PERMISSION,
 840              "DELETE",
 841              json={"endpoint_id": endpoint_id, "username": username},
 842          )
 843  
 844      # Gateway model definition permission methods
 845  
 846      def create_gateway_model_definition_permission(
 847          self, model_definition_id: str, username: str, permission: str
 848      ):
 849          """
 850          Create a permission on a gateway model definition for a user.
 851  
 852          Args:
 853              model_definition_id: The id of the gateway model definition.
 854              username: The username.
 855              permission: Permission to grant. Must be one of "READ", "USE", "EDIT", "MANAGE" and
 856                  "NO_PERMISSIONS".
 857  
 858          Returns:
 859              A single :py:class:`mlflow.server.auth.entities.GatewayModelDefinitionPermission`
 860              object.
 861          """
 862          resp = self._request(
 863              CREATE_GATEWAY_MODEL_DEFINITION_PERMISSION,
 864              "POST",
 865              json={
 866                  "model_definition_id": model_definition_id,
 867                  "username": username,
 868                  "permission": permission,
 869              },
 870          )
 871          return GatewayModelDefinitionPermission.from_json(
 872              resp["gateway_model_definition_permission"]
 873          )
 874  
 875      def get_gateway_model_definition_permission(self, model_definition_id: str, username: str):
 876          """
 877          Get a gateway model definition permission for a user.
 878  
 879          Args:
 880              model_definition_id: The id of the gateway model definition.
 881              username: The username.
 882  
 883          Returns:
 884              A single :py:class:`mlflow.server.auth.entities.GatewayModelDefinitionPermission`
 885              object.
 886          """
 887          resp = self._request(
 888              GET_GATEWAY_MODEL_DEFINITION_PERMISSION,
 889              "GET",
 890              params={"model_definition_id": model_definition_id, "username": username},
 891          )
 892          return GatewayModelDefinitionPermission.from_json(
 893              resp["gateway_model_definition_permission"]
 894          )
 895  
 896      def update_gateway_model_definition_permission(
 897          self, model_definition_id: str, username: str, permission: str
 898      ):
 899          """
 900          Update an existing gateway model definition permission for a user.
 901  
 902          Args:
 903              model_definition_id: The id of the gateway model definition.
 904              username: The username.
 905              permission: New permission to grant. Must be one of "READ", "USE", "EDIT", "MANAGE" and
 906                  "NO_PERMISSIONS".
 907          """
 908          self._request(
 909              UPDATE_GATEWAY_MODEL_DEFINITION_PERMISSION,
 910              "PATCH",
 911              json={
 912                  "model_definition_id": model_definition_id,
 913                  "username": username,
 914                  "permission": permission,
 915              },
 916          )
 917  
 918      def delete_gateway_model_definition_permission(self, model_definition_id: str, username: str):
 919          """
 920          Delete an existing gateway model definition permission for a user.
 921  
 922          Args:
 923              model_definition_id: The id of the gateway model definition.
 924              username: The username.
 925          """
 926          self._request(
 927              DELETE_GATEWAY_MODEL_DEFINITION_PERMISSION,
 928              "DELETE",
 929              json={"model_definition_id": model_definition_id, "username": username},
 930          )
 931  
 932      def list_workspace_permissions(self, workspace_name: str) -> list[WorkspacePermission]:
 933          """
 934          List the permissions configured for the specified workspace.
 935  
 936          Args:
 937              workspace_name: The workspace name.
 938  
 939          Returns:
 940              A list of :py:class:`mlflow.server.auth.entities.WorkspacePermission` objects.
 941          """
 942  
 943          endpoint = self._workspace_endpoint(LIST_WORKSPACE_PERMISSIONS, workspace_name)
 944          resp = self._request(endpoint, "GET")
 945          return [WorkspacePermission.from_json(p) for p in resp["permissions"]]
 946  
 947      def set_workspace_permission(
 948          self, workspace_name: str, username: str, permission: str
 949      ) -> WorkspacePermission:
 950          """
 951          Create or update a workspace-level permission for a user.
 952  
 953          Args:
 954              workspace_name: The workspace name.
 955              username: The username receiving the permission.
 956              permission: Permission to grant. Must be one of "READ", "USE", "EDIT",
 957                  "MANAGE", "NO_PERMISSIONS".
 958  
 959          Returns:
 960              A :py:class:`mlflow.server.auth.entities.WorkspacePermission` object.
 961          """
 962  
 963          endpoint = self._workspace_endpoint(LIST_WORKSPACE_PERMISSIONS, workspace_name)
 964          resp = self._request(
 965              endpoint,
 966              "POST",
 967              json={
 968                  "username": username,
 969                  "permission": permission,
 970              },
 971          )
 972  
 973          return WorkspacePermission.from_json(resp["permission"])
 974  
 975      def delete_workspace_permission(self, workspace_name: str, username: str) -> None:
 976          """
 977          Delete a workspace-level permission for a user.
 978  
 979          Args:
 980              workspace_name: The workspace name.
 981              username: The username whose permission should be removed.
 982          """
 983  
 984          endpoint = self._workspace_endpoint(LIST_WORKSPACE_PERMISSIONS, workspace_name)
 985          self._request(
 986              endpoint,
 987              "DELETE",
 988              params={"username": username},
 989              expected_status=204,
 990          )
 991  
 992      def list_user_workspace_permissions(self, username: str) -> list[WorkspacePermission]:
 993          """
 994          List workspace-level permissions assigned to a user.
 995  
 996          Args:
 997              username: The username.
 998  
 999          Returns:
1000              A list of :py:class:`mlflow.server.auth.entities.WorkspacePermission` objects.
1001          """
1002  
1003          resp = self._request(
1004              LIST_USER_WORKSPACE_PERMISSIONS,
1005              "GET",
1006              params={"username": username},
1007          )
1008          return [WorkspacePermission.from_json(p) for p in resp["permissions"]]
1009  
1010      # ---- Role management (RBAC) ----
1011  
1012      def create_role(
1013          self,
1014          workspace: str,
1015          name: str,
1016          description: str | None = None,
1017      ) -> Role:
1018          payload = {"workspace": workspace, "name": name}
1019          if description is not None:
1020              payload["description"] = description
1021          resp = self._request(CREATE_ROLE, "POST", json=payload)
1022          return Role.from_json(resp["role"])
1023  
1024      def get_role(self, role_id: int) -> Role:
1025          resp = self._request(GET_ROLE, "GET", params={"role_id": str(role_id)})
1026          return Role.from_json(resp["role"])
1027  
1028      def list_roles(self, workspace: str) -> list[Role]:
1029          resp = self._request(LIST_ROLES, "GET", params={"workspace": workspace})
1030          return [Role.from_json(r) for r in resp["roles"]]
1031  
1032      def update_role(
1033          self, role_id: int, name: str | None = None, description: str | None = None
1034      ) -> Role:
1035          payload: dict[str, object] = {"role_id": role_id}
1036          if name is not None:
1037              payload["name"] = name
1038          if description is not None:
1039              payload["description"] = description
1040          resp = self._request(UPDATE_ROLE, "PATCH", json=payload)
1041          return Role.from_json(resp["role"])
1042  
1043      def delete_role(self, role_id: int) -> None:
1044          self._request(DELETE_ROLE, "DELETE", json={"role_id": role_id})
1045  
1046      def add_role_permission(
1047          self, role_id: int, resource_type: str, resource_pattern: str, permission: str
1048      ) -> RolePermission:
1049          resp = self._request(
1050              ADD_ROLE_PERMISSION,
1051              "POST",
1052              json={
1053                  "role_id": role_id,
1054                  "resource_type": resource_type,
1055                  "resource_pattern": resource_pattern,
1056                  "permission": permission,
1057              },
1058          )
1059          return RolePermission.from_json(resp["role_permission"])
1060  
1061      def remove_role_permission(self, role_permission_id: int) -> None:
1062          self._request(
1063              REMOVE_ROLE_PERMISSION, "DELETE", json={"role_permission_id": role_permission_id}
1064          )
1065  
1066      def list_role_permissions(self, role_id: int) -> list[RolePermission]:
1067          resp = self._request(LIST_ROLE_PERMISSIONS, "GET", params={"role_id": str(role_id)})
1068          return [RolePermission.from_json(p) for p in resp["role_permissions"]]
1069  
1070      def update_role_permission(self, role_permission_id: int, permission: str) -> RolePermission:
1071          resp = self._request(
1072              UPDATE_ROLE_PERMISSION,
1073              "PATCH",
1074              json={"role_permission_id": role_permission_id, "permission": permission},
1075          )
1076          return RolePermission.from_json(resp["role_permission"])
1077  
1078      def assign_role(self, username: str, role_id: int) -> UserRoleAssignment:
1079          resp = self._request(ASSIGN_ROLE, "POST", json={"username": username, "role_id": role_id})
1080          return UserRoleAssignment.from_json(resp["assignment"])
1081  
1082      def unassign_role(self, username: str, role_id: int) -> None:
1083          self._request(UNASSIGN_ROLE, "DELETE", json={"username": username, "role_id": role_id})
1084  
1085      def list_user_roles(self, username: str) -> list[Role]:
1086          resp = self._request(LIST_USER_ROLES, "GET", params={"username": username})
1087          return [Role.from_json(r) for r in resp["roles"]]
1088  
1089      def list_role_users(self, role_id: int) -> list[UserRoleAssignment]:
1090          resp = self._request(LIST_ROLE_USERS, "GET", params={"role_id": str(role_id)})
1091          return [UserRoleAssignment.from_json(a) for a in resp["assignments"]]
1092  
1093      def list_all_roles(self) -> list[Role]:
1094          # Same endpoint as list_roles; omitting the ``workspace`` param returns the
1095          # cross-workspace listing (admin-only, enforced server-side).
1096          resp = self._request(LIST_ROLES, "GET")
1097          return [Role.from_json(r) for r in resp["roles"]]