diff --git a/README.md b/README.md index 33b2452..e38f161 100644 --- a/README.md +++ b/README.md @@ -205,17 +205,9 @@ art.security.create_api_key( art.security.get_encrypted_password( art. art.security.get_api_key( art.security.regenerate_api_key( art.security.revoke_user_api_key( ``` -Create an access token (for a transient user): +Create an access token: ```python -token = art.security.create_access_token(user_name='transient_artifactory_user', - groups=['g1', 'g2'], - refreshable=True) -``` - -Create an access token for an existing user (groups are implied from the existing user): -```python -token = art.security.create_access_token(user_name='existing_artifactory_user', - refreshable=True) +token = art.security.create_access_token(user_name='artifactory_user', refreshable=True, scope="applied-permissions/user") ``` Revoke an existing revocable token: diff --git a/pyartifactory/objects/security.py b/pyartifactory/objects/security.py index 6db53a0..d5a2f0c 100644 --- a/pyartifactory/objects/security.py +++ b/pyartifactory/objects/security.py @@ -1,7 +1,6 @@ from __future__ import annotations import logging -from typing import Dict, Optional from pyartifactory.exception import InvalidTokenDataError from pyartifactory.models.auth import AccessTokenModel, ApiKeyModel, PasswordModel @@ -14,6 +13,7 @@ class ArtifactorySecurity(ArtifactoryObject): """Models artifactory security.""" _uri = "security" + _tokens_uri = "tokens" def get_encrypted_password(self) -> PasswordModel: """ @@ -29,7 +29,7 @@ def create_access_token( user_name: str, expires_in: int = 3600, refreshable: bool = False, - groups: Optional[str] = None, + scope: str = "applied-permissions/user", ) -> AccessTokenModel: """ Creates an access token. @@ -38,39 +38,28 @@ def create_access_token( is created if user doesn't exist in artifactory. :param expires_in: Expiry time for the token in seconds. For eternal tokens specify 0. :param refreshable: If set to true token can be refreshed using the refresh token returned. - :param groups: A list of groups the token has membership of. - If an existing user in artifactory is used with existing memberships - groups are automatically implied without specification. + :param scope: The scope of access that the token provides. :return: AccessToken """ - payload = { - "username": user_name, - "expires_in": expires_in, - "refreshable": refreshable, - } - if groups: - if not isinstance(groups, list): - raise ValueError(groups) - scope = f'member-of-groups:"{",".join(groups)}"' - payload.update({"scope": scope}) - response = self._post(f"api/{self._uri}/token", data=payload, raise_for_status=False) + payload = {"username": user_name, "expires_in": expires_in, "refreshable": refreshable, "scope": scope} + response = self._post(f"access/api/v1/{self._tokens_uri}", data=payload, raise_for_status=False) if response.ok: return AccessTokenModel(**response.json()) raise InvalidTokenDataError(response.json().get("error_description", "Unknown error")) - def revoke_access_token(self, token: Optional[str] = None, token_id: Optional[str] = None) -> bool: + def revoke_access_token(self, token: str) -> bool: """ Revokes an access token. :param token: The token to revoke - :param token_id: The id of a token to revoke :return: bool True or False indicating success or failure of token revocation attempt. """ - if not any([token, token_id]): - logger.error("Neither a token or a token id was specified") - raise InvalidTokenDataError - payload: Dict[str, Optional[str]] = {"token": token} if token else {"token_id": token_id} - response = self._post(f"api/{self._uri}/token/revoke", data=payload, raise_for_status=False) + + response = self._delete( + f"access/api/v1/{self._tokens_uri}/revoke", + data={"token": token}, + raise_for_status=False, + ) if response.ok: logger.debug("Token revoked successfully, or token did not exist") return True diff --git a/tests/test_security.py b/tests/test_security.py index e09f66d..908cb4c 100644 --- a/tests/test_security.py +++ b/tests/test_security.py @@ -1,10 +1,8 @@ from __future__ import annotations -import pytest import responses from pyartifactory import ArtifactorySecurity -from pyartifactory.exception import InvalidTokenDataError from pyartifactory.models import ApiKeyModel, AuthModel, PasswordModel URL = "http://localhost:8080/artifactory" @@ -77,29 +75,24 @@ def test_revoke_user_api_key(): def test_create_access_token(): responses.add( responses.POST, - f"{URL}/api/security/token", + f"{URL}/access/api/v1/tokens", status=200, json={ "access_token": "", "expires_in": 3600, - "scope": "api:* member-of-groups:g1, g2", - "token_type": "Bearer", + "scope": "applied-permissions/user", + "token_type": "access_token", }, ) artifactory_security = ArtifactorySecurity(AuthModel(url=URL, auth=AUTH)) - access_token = artifactory_security.create_access_token( - user_name="my-username", - expires_in=3600, - refreshable=False, - groups=["g1", "g2"], - ) - assert access_token.scope == "api:* member-of-groups:g1, g2" + access_token = artifactory_security.create_access_token(user_name="my-username", expires_in=3600, refreshable=False) + assert access_token.scope == "applied-permissions/user" @responses.activate def test_revoke_access_token_success(): - responses.add(responses.POST, f"{URL}/api/security/token/revoke", status=200) + responses.add(responses.DELETE, f"{URL}/access/api/v1/tokens/revoke", status=200) artifactory_security = ArtifactorySecurity(AuthModel(url=URL, auth=AUTH)) result = artifactory_security.revoke_access_token(token="my-token") # noqa: S106 @@ -108,8 +101,8 @@ def test_revoke_access_token_success(): @responses.activate def test_revoke_access_token_fail_no_token_provided(): - responses.add(responses.POST, f"{URL}/api/security/token/revoke", status=400) + responses.add(responses.DELETE, f"{URL}/access/api/v1/tokens/revoke", status=400) artifactory_security = ArtifactorySecurity(AuthModel(url=URL, auth=AUTH)) - with pytest.raises(InvalidTokenDataError): - artifactory_security.revoke_access_token() + result = artifactory_security.revoke_access_token(token="my-token") # noqa: S106 + assert result is False