Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: support new create access token API #192

Merged
merged 1 commit into from
Sep 20, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 2 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -204,17 +204,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:
Expand Down
35 changes: 12 additions & 23 deletions pyartifactory/objects/security.py
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -14,6 +13,7 @@ class ArtifactorySecurity(ArtifactoryObject):
"""Models artifactory security."""

_uri = "security"
_tokens_uri = "tokens"

def get_encrypted_password(self) -> PasswordModel:
"""
Expand All @@ -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.
Expand All @@ -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
Expand Down
25 changes: 9 additions & 16 deletions tests/test_security.py
Original file line number Diff line number Diff line change
@@ -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"
Expand Down Expand Up @@ -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": "<the 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
Expand All @@ -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
Loading