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"]]