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

Create token auth option #991

Merged
merged 7 commits into from
Oct 24, 2021
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
41 changes: 37 additions & 4 deletions jira/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -264,6 +264,19 @@ def start_session(self):
self._get_session(self.__auth)


class TokenAuth(AuthBase):
"""Bearer Token Authentication"""

def __init__(self, token: str):
# setup any auth-related data here
self._token = token

def __call__(self, r: requests.PreparedRequest):
# modify and return the request
r.headers["authorization"] = f"Bearer {self._token}"
return r


class JIRA:
"""User interface to Jira.

Expand Down Expand Up @@ -325,7 +338,8 @@ def __init__(
self,
server: str = None,
options: Dict[str, Union[str, bool, Any]] = None,
basic_auth: Union[None, Tuple[str, str]] = None,
basic_auth: Optional[Tuple[str, str]] = None,
token_auth: Optional[str] = None,
oauth: Dict[str, Any] = None,
jwt: Dict[str, Any] = None,
kerberos=False,
Expand All @@ -347,8 +361,8 @@ def __init__(
or ``atlas-run-standalone`` commands. By default, this instance runs at
``http://localhost:2990/jira``. The ``options`` argument can be used to set the Jira instance to use.

Authentication is handled with the ``basic_auth`` argument. If authentication is supplied (and is
accepted by Jira), the client will remember it for subsequent requests.
Authentication is handled with the ``basic_auth`` or ``token_auth`` argument.
If authentication is supplied (and is accepted by Jira), the client will remember it for subsequent requests.

For quick command line access to a server, see the ``jirashell`` script included with this distribution.

Expand All @@ -369,8 +383,11 @@ def __init__(
* check_update -- Check whether using the newest python-jira library version.
* headers -- a dict to update the default headers the session uses for all API requests.

basic_auth (Union[None, Tuple[str, str]]): A tuple of username and password to use when
basic_auth (Optional[Tuple[str, str]]): A tuple of username and password to use when
establishing a session via HTTP BASIC authentication.

token_auth (Optional[str]): A string containing the token necessary for (PAT) bearer token authorization.

oauth (Optional[Any]): A dict of properties for OAuth authentication. The following properties are required:

* access_token -- OAuth access token for the user
Expand Down Expand Up @@ -466,6 +483,8 @@ def __init__(
self._session.headers.update(self._options["headers"])
elif jwt:
self._create_jwt_session(jwt, timeout)
elif token_auth:
self._create_token_session(token_auth, timeout)
elif kerberos:
self._create_kerberos_session(timeout, kerberos_options=kerberos_options)
elif auth:
Expand Down Expand Up @@ -3412,6 +3431,20 @@ def _create_jwt_session(
self._session.verify = bool(self._options["verify"])
self._session.auth = jwt_auth

def _create_token_session(
self,
token_auth: str,
timeout: Optional[Union[Union[float, int], Tuple[float, float]]],
):
"""
Creates token-based session.
Header structure: "authorization": "Bearer <token_auth>"
"""
verify = self._options["verify"]
self._session = ResilientSession(timeout=timeout)
self._session.verify = verify
self._session.auth = TokenAuth(token_auth)

def _set_avatar(self, params, url, avatar):
data = {"id": avatar}
return self._session.put(url, params=params, data=json.dumps(data))
Expand Down
27 changes: 24 additions & 3 deletions tests/test_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,17 +16,17 @@ def prep():


@pytest.fixture(scope="module")
def test_manager():
def test_manager() -> JiraTestManager:
return JiraTestManager()


@pytest.fixture()
def cl_admin(test_manager):
def cl_admin(test_manager: JiraTestManager) -> jira.client.JIRA:
return test_manager.jira_admin


@pytest.fixture()
def cl_normal(test_manager):
def cl_normal(test_manager: JiraTestManager) -> jira.client.JIRA:
return test_manager.jira_normal


Expand Down Expand Up @@ -194,3 +194,24 @@ def test_headers_unclobbered_update_with_no_provided_headers(no_fields):

# THEN: we have not affected the other headers' defaults
assert session_headers[invariant_header_name] == invariant_header_value


def test_token_auth(cl_admin: jira.client.JIRA):
"""Tests the Personal Access Token authentication works."""
# GIVEN: We have a PAT token created by a user.
pat_token_request = {
"name": "my_new_token",
"expirationDuration": 1,
}
base_url = cl_admin.server_url
pat_token_response = cl_admin._session.post(
f"{base_url}/rest/pat/latest/tokens", json=pat_token_request
).json()
new_token = pat_token_response["rawToken"]

# WHEN: A new client is authenticated with this token
new_jira_client = jira.client.JIRA(token_auth=new_token)

# THEN: The reported authenticated user of the token
# matches the original token creator user.
assert cl_admin.myself() == new_jira_client.myself()