diff --git a/examples/how_to/workitem/get_workitem.py b/examples/how_to/workitem/get_workitem.py index ef095f8..7064f40 100644 --- a/examples/how_to/workitem/get_workitem.py +++ b/examples/how_to/workitem/get_workitem.py @@ -9,7 +9,7 @@ url = "https://your_domain:9443/jazz" username = "your_username" password = "your_password" - myclient = RTCClient(url, username, password) + myclient = RTCClient(url, username, password) # ends_with_jazz , old_rtc_authentication kwargs # get all workitems # If both projectarea_id and projectarea_name are None, all the workitems diff --git a/poetry.lock b/poetry.lock index 739b595..7f9ad3d 100644 --- a/poetry.lock +++ b/poetry.lock @@ -747,6 +747,18 @@ files = [ {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, ] +[[package]] +name = "toml" +version = "0.10.2" +description = "Python Library for Tom's Obvious, Minimal Language" +category = "dev" +optional = false +python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" +files = [ + {file = "toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b"}, + {file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"}, +] + [[package]] name = "tomli" version = "2.0.1" @@ -932,4 +944,4 @@ testing = ["big-O", "flake8 (<5)", "jaraco.functools", "jaraco.itertools", "more [metadata] lock-version = "2.0" python-versions = ">=3.7.0,<4.0" -content-hash = "e34c2dbb238bb43211cc83b01a6fef54cfed2547b3f034dd4c7e47940cc84f95" +content-hash = "d1b93aabe50ca296f8324af41db0ba3b97b09444730854b7ebc3497f6e70fbdb" diff --git a/pyproject.toml b/pyproject.toml index bd2a89d..7fe23fe 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -53,6 +53,7 @@ ] yapf = "*" tox = "*" + toml = "*" [build-system] diff --git a/rtcclient/client.py b/rtcclient/client.py index aba7afb..5d2be51 100644 --- a/rtcclient/client.py +++ b/rtcclient/client.py @@ -8,7 +8,7 @@ import xmltodict from rtcclient import exception -from rtcclient import urlparse, urlquote, OrderedDict +from rtcclient import urlencode, urlparse, urlquote, OrderedDict from rtcclient.base import RTCBase from rtcclient.models import FiledAgainst, FoundIn, Comment, Action, State # noqa: F401 from rtcclient.models import IncludedInBuild, ChangeSet, Attachment # noqa: F401 @@ -58,7 +58,8 @@ def __init__(self, proxies=None, searchpath=None, ends_with_jazz=True, - verify: Union[bool, str] = False): + verify: Union[bool, str] = False, + old_rtc_authentication=False): """Initialization See params above @@ -68,6 +69,7 @@ def __init__(self, self.password = password self.proxies = proxies self.verify = verify + self.old_rtc_authentication = old_rtc_authentication RTCBase.__init__(self, url) if not isinstance(ends_with_jazz, bool): @@ -99,6 +101,24 @@ def _get_headers(self): proxies=self.proxies, allow_redirects=_allow_redirects) + if self.old_rtc_authentication: + # works with server that needs 0.6.0 version + _headers["Content-Type"] = self.CONTENT_URL_ENCODED + if resp.headers.get("set-cookie") is not None: + _headers["Cookie"] = resp.headers.get("set-cookie") + + credentials = urlencode({ + "j_username": self.username, + "j_password": self.password + }) + + resp = self.post(self.url + "/authenticated/j_security_check", + data=credentials, + verify=False, + headers=_headers, + proxies=self.proxies, + allow_redirects=_allow_redirects) + # authfailed authfailed = resp.headers.get("x-com-ibm-team-repository-web-auth-msg") if authfailed == "authfailed": diff --git a/tests/test_client.py b/tests/test_client.py index ad6309f..49e7fd5 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -2,6 +2,8 @@ import requests import pytest import utils_test +from unittest.mock import call + from rtcclient.project_area import ProjectArea from rtcclient.models import Severity, Priority, FoundIn, FiledAgainst from rtcclient.models import TeamArea, Member, PlannedFor @@ -42,6 +44,254 @@ def test_headers(mocker): assert client.headers == expected_headers +def test_client_rest_calls_new_auth(mocker): + mocked_get = mocker.patch("requests.get") + mocked_post = mocker.patch("requests.post") + + mock_rsp = mocker.MagicMock(spec=requests.Response) + mock_rsp.status_code = 200 + + mocked_get.return_value = mock_rsp + mocked_post.return_value = mock_rsp + + mock_rsp.headers = {"set-cookie": "cookie-id"} + _ = RTCClient(url="http://test.url:9443/jazz", + username="user", + password="password") + + # assert GET calls + assert mocked_get.call_count == 2 + mocked_get.assert_has_calls([ + call('http://test.url:9443/jazz/authenticated/identity', + verify=False, + headers={ + 'Content-Type': 'text/xml', + 'Cookie': 'cookie-id', + 'Accept': 'text/xml' + }, + proxies=None, + timeout=60, + auth=('user', 'password'), + allow_redirects=True), + call('http://test.url:9443/jazz/authenticated/identity', + verify=False, + headers={ + 'Content-Type': 'text/xml', + 'Cookie': 'cookie-id', + 'Accept': 'text/xml' + }, + proxies=None, + timeout=60, + auth=('user', 'password'), + allow_redirects=True) + ]) + + # assert POST calls + assert mocked_post.call_count == 0 + + +def test_client_rest_calls_old_auth(mocker): + mocked_get = mocker.patch("requests.get") + mocked_post = mocker.patch("requests.post") + + mock_rsp = mocker.MagicMock(spec=requests.Response) + mock_rsp.status_code = 200 + + mocked_get.return_value = mock_rsp + mocked_post.return_value = mock_rsp + + mock_rsp.headers = {"set-cookie": "cookie-id"} + _ = RTCClient(url="http://test.url:9443/jazz", + username="user", + password="password", + old_rtc_authentication=True) + + # assert GET calls + assert mocked_get.call_count == 2 + mocked_get.assert_has_calls([ + call('http://test.url:9443/jazz/authenticated/identity', + verify=False, + headers={ + 'Content-Type': 'application/x-www-form-urlencoded', + 'Cookie': 'cookie-id', + 'Accept': 'text/xml' + }, + proxies=None, + timeout=60, + auth=('user', 'password'), + allow_redirects=True), + call('http://test.url:9443/jazz/authenticated/identity', + verify=False, + headers={ + 'Content-Type': 'application/x-www-form-urlencoded', + 'Cookie': 'cookie-id', + 'Accept': 'text/xml' + }, + proxies=None, + timeout=60, + auth=('user', 'password'), + allow_redirects=True) + ]) + + # assert POST calls + assert mocked_post.call_count == 1 + mocked_post.assert_has_calls([ + call('http://test.url:9443/jazz/authenticated/j_security_check', + data='j_username=user&j_password=password', + json=None, + verify=False, + headers={ + 'Content-Type': 'application/x-www-form-urlencoded', + 'Cookie': 'cookie-id', + 'Accept': 'text/xml' + }, + proxies=None, + timeout=60, + allow_redirects=True) + ]) + + +def test_headers_auth_required_new_auth(mocker): + mocked_get = mocker.patch("requests.get") + mocked_post = mocker.patch("requests.post") + + mock_rsp = mocker.MagicMock(spec=requests.Response) + mock_rsp.status_code = 200 + + # auth required + mock_rsp.headers = { + "set-cookie": "cookie-id", + "X-com-ibm-team-repository-web-auth-msg": "authrequired" + } + + mocked_get.return_value = mock_rsp + mocked_post.return_value = mock_rsp + + _ = RTCClient(url="http://test.url:9443/jazz", + username="user", + password="password") + + # assert GET calls + assert mocked_get.call_count == 2 + mocked_get.assert_has_calls([ + call('http://test.url:9443/jazz/authenticated/identity', + verify=False, + headers={ + 'Content-Type': 'text/xml', + 'Cookie': 'cookie-id', + 'Accept': 'text/xml' + }, + proxies=None, + timeout=60, + auth=('user', 'password'), + allow_redirects=True), + call('http://test.url:9443/jazz/authenticated/identity', + verify=False, + headers={ + 'Content-Type': 'text/xml', + 'Cookie': 'cookie-id', + 'Accept': 'text/xml' + }, + proxies=None, + timeout=60, + auth=('user', 'password'), + allow_redirects=True) + ]) + + # assert POST calls + assert mocked_post.call_count == 1 + mocked_post.assert_has_calls([ + call('http://test.url:9443/jazz/authenticated/j_security_check', + data={ + 'j_username': 'user', + 'j_password': 'password' + }, + json=None, + verify=False, + headers={'Content-Type': 'application/x-www-form-urlencoded'}, + proxies=None, + timeout=60, + allow_redirects=True) + ]) + + +def test_headers_auth_required_old_auth(mocker): + mocked_get = mocker.patch("requests.get") + mocked_post = mocker.patch("requests.post") + + mock_rsp = mocker.MagicMock(spec=requests.Response) + mock_rsp.status_code = 200 + + # auth required + mock_rsp.headers = { + "set-cookie": "cookie-id", + "X-com-ibm-team-repository-web-auth-msg": "authrequired" + } + + mocked_get.return_value = mock_rsp + mocked_post.return_value = mock_rsp + + _ = RTCClient(url="http://test.url:9443/jazz", + username="user", + password="password", + old_rtc_authentication=True) + + # assert GET calls + assert mocked_get.call_count == 2 + mocked_get.assert_has_calls([ + call('http://test.url:9443/jazz/authenticated/identity', + verify=False, + headers={ + 'Content-Type': 'application/x-www-form-urlencoded', + 'Cookie': 'cookie-id', + 'Accept': 'text/xml' + }, + proxies=None, + timeout=60, + auth=('user', 'password'), + allow_redirects=True), + call('http://test.url:9443/jazz/authenticated/identity', + verify=False, + headers={ + 'Content-Type': 'application/x-www-form-urlencoded', + 'Cookie': 'cookie-id', + 'Accept': 'text/xml' + }, + proxies=None, + timeout=60, + auth=('user', 'password'), + allow_redirects=True) + ]) + + # assert POST calls + assert mocked_post.call_count == 2 + mocked_post.assert_has_calls([ + call('http://test.url:9443/jazz/authenticated/j_security_check', + data='j_username=user&j_password=password', + json=None, + verify=False, + headers={ + 'Content-Type': 'application/x-www-form-urlencoded', + 'Cookie': 'cookie-id', + 'Accept': 'text/xml' + }, + proxies=None, + timeout=60, + allow_redirects=True), + call('http://test.url:9443/jazz/authenticated/j_security_check', + data={ + 'j_username': 'user', + 'j_password': 'password' + }, + json=None, + verify=False, + headers={'Content-Type': 'application/x-www-form-urlencoded'}, + proxies=None, + timeout=60, + allow_redirects=True) + ]) + + class TestRTCClient: @pytest.fixture(autouse=True)