diff --git a/atest/auth.robot b/atest/auth.robot new file mode 100644 index 0000000..bfde934 --- /dev/null +++ b/atest/auth.robot @@ -0,0 +1,53 @@ +*** Settings *** +Library REST + +*** Variables *** +${baseurl} = https://postman-echo.com +${user} = postman +${password}= password + +*** Test Cases *** +GET Basic Auth + ${url} Set Variable ${baseurl}/basic-auth + + Set Client Authentication basic ${user} ${password} + GET ${url} + Integer response status 200 + + Set Client Authentication digest ${user} ${password} + Run Keyword And Expect Error KeyError: 'content-type' + ... GET ${url} + Integer response status 401 + + Set Client Authentication ${NONE} + Run Keyword And Expect Error KeyError: 'content-type' + ... GET ${url} + Integer response status 401 + +GET Digest Auth + ${url} Set Variable ${baseurl}/digest-auth + + Set Client Authentication digest ${user} ${password} + GET ${url} + Integer response status 200 + + Set Client Authentication basic ${user} ${password} + Run Keyword And Expect Error KeyError: 'content-type' + ... GET ${url} + Integer response status 401 + + Set Client Authentication ${NONE} + Run Keyword And Expect Error KeyError: 'content-type' + ... GET ${url} + Integer response status 401 + +Set Auth Invalid Args + ${error} Set Variable TypeError: Argument "auth_type" must be \${NONE}, basic, digest or proxy. + Run Keyword And Expect Error ${error} + ... Set Client Authentication none ${user} ${password} + + Run Keyword And Expect Error ${error} + ... Set Client Authentication doge ${user} ${password} + + Run Keyword And Expect Error ${error} + ... Set Client Authentication ${1} ${user} ${password} diff --git a/src/REST/__init__.py b/src/REST/__init__.py index 3dd2f34..0d75cb6 100644 --- a/src/REST/__init__.py +++ b/src/REST/__init__.py @@ -132,6 +132,7 @@ def __init__( "query": {}, "body": None, "data": None, + "auth" : None, "headers": { "Accept": REST._input_string(accept), "Content-Type": REST._input_string(content_type), diff --git a/src/REST/keywords.py b/src/REST/keywords.py index 0b17cb4..adad430 100644 --- a/src/REST/keywords.py +++ b/src/REST/keywords.py @@ -24,7 +24,7 @@ from tzlocal import get_localzone from collections import OrderedDict -from copy import deepcopy +from copy import deepcopy, copy from datetime import datetime from json import dumps, loads from os import path, getcwd @@ -35,6 +35,7 @@ from jsonschema import validate, FormatChecker from jsonschema.exceptions import SchemaError, ValidationError from requests import request as client +from requests.auth import HTTPDigestAuth, HTTPBasicAuth, HTTPProxyAuth from requests.exceptions import SSLError, Timeout if IS_PYTHON_2: @@ -77,6 +78,44 @@ def set_client_cert(self, cert): self.request["cert"] = self._input_client_cert(cert) return self.request["cert"] + + @keyword(name="Set Client Authentication", tags=("settings",)) + def set_client_authentication(self, auth_type, user=None, password=None): + """*Attaches HTTP basic authentication to the given requests.* + + The API by default will not have authentication enabled. + + The auth_type argument must be ${NONE}, basic, digest or proxy. + In case the user sets the auth_type to ${NONE} the authentication + information + The user and password will be written in plain text. + + *Examples* + + | `Set Client Authentication` | basic | admin | password | + | `Set Client Authentication` | digest | admin | password | + | `Set Client Authentication` | proxy | admin | password | + | `Set Client Authentication` | ${NONE} | | | + """ + error_auth = "Argument \"auth_type\" must be ${NONE}, basic, digest or proxy." + if auth_type != None: + if not isinstance(auth_type, str): + raise TypeError(error_auth) + + auth_type = auth_type.lower() + + if not auth_type in ["basic", "digest","proxy"] : + raise TypeError(error_auth) + + if auth_type == "basic" : + auth_type = HTTPBasicAuth + elif auth_type == "digest" : + auth_type = HTTPDigestAuth + elif auth_type == "proxy" : + auth_type = HTTPProxyAuth + + return self._setauth(auth_type, user, password) + @keyword(name="Set Headers", tags=("settings",)) def set_headers(self, headers): """*Sets new request headers or updates the existing.* @@ -276,7 +315,7 @@ def head( | `HEAD` | /users/1 | timeout=0.5 | """ endpoint = self._input_string(endpoint) - request = deepcopy(self.request) + request = copy(self.request) request["method"] = "HEAD" if allow_redirects is not None: request["allowRedirects"] = self._input_boolean(allow_redirects) @@ -319,7 +358,7 @@ def options( | `OPTIONS` | /users/1 | allow_redirects=false | """ endpoint = self._input_string(endpoint) - request = deepcopy(self.request) + request = copy(self.request) request["method"] = "OPTIONS" if allow_redirects is not None: request["allowRedirects"] = self._input_boolean(allow_redirects) @@ -373,7 +412,7 @@ def get( | `GET` | https://jsonplaceholder.typicode.com/users | headers={ "Authentication": "" } | """ endpoint = self._input_string(endpoint) - request = deepcopy(self.request) + request = copy(self.request) request["method"] = "GET" request["query"] = OrderedDict() query_in_url = OrderedDict(parse_qsl(urlparse(endpoint).query)) @@ -431,7 +470,7 @@ def post( | `POST` | /users | ${CURDIR}/new_user.json | """ endpoint = self._input_string(endpoint) - request = deepcopy(self.request) + request = copy(self.request) request["method"] = "POST" request["body"] = self.input(body) if allow_redirects is not None: @@ -483,7 +522,7 @@ def put( | `PUT` | /users/2 | ${dict} | """ endpoint = self._input_string(endpoint) - request = deepcopy(self.request) + request = copy(self.request) request["method"] = "PUT" request["body"] = self.input(body) if allow_redirects is not None: @@ -535,7 +574,7 @@ def patch( | `PATCH` | /users/4 | ${dict} | """ endpoint = self._input_string(endpoint) - request = deepcopy(self.request) + request = copy(self.request) request["method"] = "PATCH" request["body"] = self.input(body) if allow_redirects is not None: @@ -585,7 +624,7 @@ def delete( | `DELETE` | /users/6/pets | {"name" : "Rex","tagID" : "1234"} | """ endpoint = self._input_string(endpoint) - request = deepcopy(self.request) + request = copy(self.request) request["method"] = "DELETE" request["body"]=self.input(body) if allow_redirects is not None: @@ -1282,6 +1321,7 @@ def rest_instances(self, file_path=None, sort_keys=False): self.instances, ensure_ascii=False, indent=4, + default=vars, separators=(",", ": "), sort_keys=sort_keys, ) @@ -1315,6 +1355,13 @@ def set_ssl_verify(self, ssl_verify=True): ### Internal methods + def _setauth(self, auth_type, user = None, password = None): + if auth_type == None: + self.request["auth"] = None + else : + self.request["auth"] = auth_type(user, password) + return self.request["auth"] + def _request(self, endpoint, request, validate=True): if not endpoint.startswith(("http://", "https://")): base_url = self.request["scheme"] + "://" + self.request["netloc"] @@ -1336,6 +1383,7 @@ def _request(self, endpoint, request, validate=True): headers=request["headers"], proxies=request["proxies"], cert=request["cert"], + auth=request["auth"], timeout=tuple(request["timeout"]), allow_redirects=request["allowRedirects"], verify=request["sslVerify"],