Skip to content
Open
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
6 changes: 5 additions & 1 deletion .github/workflows/pythonapp.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@ jobs:
matrix:
os: [ ubuntu-latest, macos-latest, windows-latest ]
python-version: [ 3.8, 3.12 ]
# test with robot without and with Secret support? Not sure if
# it is worth it?
robot-version: [ 7.3.2, 7.4 ]
steps:
- uses: actions/checkout@v4
- name: Set up Python
Expand All @@ -23,6 +26,7 @@ jobs:
- name: Install dependencies
run: |
python -m pip install -e .[test]
python -m pip install robotframework==${{ matrix.robot-version }}
- name: Lint with flake8
run: |
# stop the build if there are Python syntax errors or undefined names
Expand Down Expand Up @@ -59,5 +63,5 @@ jobs:
if: ${{ always() }}
uses: actions/upload-artifact@v4
with:
name: rf-tests-report-${{ matrix.os }}-${{ matrix.python-version }}
name: rf-tests-report-${{ matrix.os }}-${{ matrix.python-version }}-${{ matrix.robot-version }}
path: ./tests-report
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -34,3 +34,4 @@ env/*

# ignore http server log
atests/http_server/http_server.log
.claude/
7 changes: 7 additions & 0 deletions atests/secretvar.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# inject secret into robot suite.. doing this via python
# to ensure this can also run in older robot versions
try:
from robot.api.types import Secret
SECRET_PASSWORD = Secret("passwd")
except (ImportError, ModuleNotFoundError):
SECRET_PASSWORD = "not-supported"
39 changes: 36 additions & 3 deletions atests/test_authentication.robot
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
Library RequestsLibrary
Library customAuthenticator.py
Resource res_setup.robot

Variables secretvar.py

*** Test Cases ***
Get With Auth
Expand All @@ -23,12 +23,45 @@ Get With Custom Auth

Get With Digest Auth
[Tags] get get-cert
${auth}= Create List user pass
${auth}= Create List user passwd
Create Digest Session
... authsession
... ${HTTP_LOCAL_SERVER}
... auth=${auth}
... debug=3
${resp}= GET On Session authsession /digest-auth/auth/user/passwd
Should Be Equal As Strings ${resp.status_code} 200
Should Be Equal As Strings ${resp.json()['authenticated']} True

Get With Auth with Robot Secrets
[Tags] robot-74 get get-cert
Skip If $SECRET_PASSWORD == "not-supported"
... msg=robot version does not support secrets
${auth}= Create List user ${SECRET_PASSWORD}
Create Session authsession ${HTTP_LOCAL_SERVER} auth=${auth}
${resp}= GET On Session authsession /basic-auth/user/passwd
Should Be Equal As Strings ${resp.status_code} 200
Should Be Equal As Strings ${resp.json()['authenticated']} True

Get With Digest Auth with Robot Secrets
[Tags] robot-74 get get-cert
Skip If $SECRET_PASSWORD == "not-supported"
... msg=robot version does not support secrets
${auth}= Create List user ${SECRET_PASSWORD}
Create Digest Session
... authsession
... ${HTTP_LOCAL_SERVER}
... auth=${auth}
... debug=3
${resp}= GET On Session authsession /digest-auth/auth/user/pass
${resp}= GET On Session authsession /digest-auth/auth/user/passwd
Should Be Equal As Strings ${resp.status_code} 200
Should Be Equal As Strings ${resp.json()['authenticated']} True

Session-less GET With Auth with Robot Secrets
[Tags] robot-74 get get-cert session-less
Skip If $SECRET_PASSWORD == "not-supported"
... msg=robot version does not support secrets
${auth}= Create List user ${SECRET_PASSWORD}
${resp}= GET ${HTTP_LOCAL_SERVER}/basic-auth/user/passwd auth=${auth}
Should Be Equal As Strings ${resp.status_code} 200
Should Be Equal As Strings ${resp.json()['authenticated']} True
12 changes: 9 additions & 3 deletions src/RequestsLibrary/RequestsKeywords.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
from RequestsLibrary.utils import (
is_list_or_tuple,
is_file_descriptor,
process_secrets,
warn_if_equal_symbol_in_url_session_less,
)

Expand All @@ -30,6 +31,11 @@ def _common_request(self, method, session, uri, **kwargs):
else:
request_function = getattr(requests, "request")

# Process robot's Secret types included in auth
auth = kwargs.get("auth")
if auth is not None and isinstance(auth, (list, tuple)):
kwargs["auth"] = process_secrets(auth)

self._capture_output()

resp = request_function(
Expand Down Expand Up @@ -59,7 +65,7 @@ def _close_file_descriptors(files, data):
"""
Helper method that closes any open file descriptors.
"""

if is_list_or_tuple(files):
files_descriptor_to_close = filter(
is_file_descriptor, [file[1][1] for file in files] + [data]
Expand All @@ -68,10 +74,10 @@ def _close_file_descriptors(files, data):
files_descriptor_to_close = filter(
is_file_descriptor, list(files.values()) + [data]
)

for file_descriptor in files_descriptor_to_close:
file_descriptor.close()

@staticmethod
def _merge_url(session, uri):
"""
Expand Down
9 changes: 5 additions & 4 deletions src/RequestsLibrary/SessionKeywords.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
from RequestsLibrary import utils
from RequestsLibrary.compat import RetryAdapter, httplib
from RequestsLibrary.exceptions import InvalidExpectedStatus, InvalidResponse
from RequestsLibrary.utils import is_string_type
from RequestsLibrary.utils import is_string_type, process_secrets

from .RequestsKeywords import RequestsKeywords

Expand Down Expand Up @@ -172,7 +172,7 @@ def create_session(
Note that max_retries must be greater than 0.

"""
auth = requests.auth.HTTPBasicAuth(*auth) if auth else None
auth = requests.auth.HTTPBasicAuth(*process_secrets(auth)) if auth else None

logger.info(
"Creating Session using : alias=%s, url=%s, headers=%s, \
Expand Down Expand Up @@ -262,7 +262,7 @@ def create_client_cert_session(
eg. set to [502, 503] to retry requests if those status are returned.
Note that max_retries must be greater than 0.
"""
auth = requests.auth.HTTPBasicAuth(*auth) if auth else None
auth = requests.auth.HTTPBasicAuth(*process_secrets(auth)) if auth else None

logger.info(
"Creating Session using : alias=%s, url=%s, headers=%s, \
Expand Down Expand Up @@ -452,7 +452,7 @@ def create_digest_session(
eg. set to [502, 503] to retry requests if those status are returned.
Note that max_retries must be greater than 0.
"""
digest_auth = requests.auth.HTTPDigestAuth(*auth) if auth else None
digest_auth = requests.auth.HTTPDigestAuth(*process_secrets(auth)) if auth else None

return self._create_session(
alias=alias,
Expand Down Expand Up @@ -543,6 +543,7 @@ def create_ntlm_session(
" - expected 3, got {}".format(len(auth))
)
else:
auth = process_secrets(auth)
ntlm_auth = HttpNtlmAuth("{}\\{}".format(auth[0], auth[1]), auth[2])
logger.info(
"Creating NTLM Session using : alias=%s, url=%s, \
Expand Down
21 changes: 21 additions & 0 deletions src/RequestsLibrary/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,11 @@
from requests.status_codes import codes
from requests.structures import CaseInsensitiveDict
from robot.api import logger
try:
from robot.api.types import Secret
robot_has_secret = True
except (ImportError, ModuleNotFoundError):
robot_has_secret = False

from RequestsLibrary.compat import urlencode
from RequestsLibrary.exceptions import UnknownStatusError
Expand Down Expand Up @@ -74,9 +79,25 @@ def is_string_type(data):
def is_file_descriptor(fd):
return isinstance(fd, io.IOBase)


def is_list_or_tuple(data):
return isinstance(data, (list, tuple))


def process_secrets(auth):
"""
Process robot's Secret types in auth tuples by extracting their values.
"""
if robot_has_secret:
new_auth = tuple(
a.value if isinstance(a, Secret) else a
for a in auth
)
else:
new_auth = auth
return new_auth


def utf8_urlencode(data):
if is_string_type(data):
return data.encode("utf-8")
Expand Down
34 changes: 33 additions & 1 deletion utests/test_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,16 @@
from requests import Session

from RequestsLibrary import RequestsLibrary
from RequestsLibrary.utils import is_file_descriptor, merge_headers
from RequestsLibrary.utils import is_file_descriptor, merge_headers, process_secrets
from utests import SCRIPT_DIR
from utests import mock

try:
from robot.api.types import Secret
secret_type_supported = True
except (ImportError, ModuleNotFoundError):
secret_type_supported = False


def test_none():
assert is_file_descriptor(None) is False
Expand Down Expand Up @@ -72,3 +78,29 @@ def test_warn_that_url_is_missing(mocked_logger, mocked_keywords):
except TypeError:
pass
mocked_logger.warn.assert_called()


def test_process_secrets_with_no_secrets():
auth = ('user', 'password')
result = process_secrets(auth)
assert result == ('user', 'password')


@pytest.mark.skipif(not secret_type_supported, reason="Running on pre-7.4 robot")
def test_process_secrets_with_secrets():
secret_password = Secret('mypassword')
auth = ('user', secret_password)
result = process_secrets(auth)
assert result == ('user', 'mypassword')
assert not isinstance(result[1], Secret)


@pytest.mark.skipif(not secret_type_supported, reason="Running on pre-7.4 robot")
def test_process_secrets_with_mixed_secrets():
secret_user = Secret('myuser')
secret_password = Secret('mypassword')
auth = (secret_user, secret_password)
result = process_secrets(auth)
assert result == ('myuser', 'mypassword')
assert not isinstance(result[0], Secret)
assert not isinstance(result[1], Secret)
Loading