def test_verify_callback(self): json_from_post = "{\"result-data\":{\"gw\":{\"gateway-transaction-id\":\"8d77f986-de7f-4d47-97ef-9de7f8561684\",\"status-code\":7,\"status-text\":\"SUCCESS\"}," \ "\"error\":{},\"acquirer-details\":{\"eci-sli\":\"503\",\"terminal-mid\":\"3201210\",\"transaction-id\":\"7146311464333929\"," \ "\"result-code\":\"000\",\"status-text\":\"Approved\",\"status-description\":\"Approved\"},\"warnings\":" \ "[\"Soon counters will be exceeded for the merchant\",\"Soon counters will be exceeded for the account\"," \ "\"Soon counters will be exceeded for the terminal group\",\"Soon counters will be exceeded for the terminal\"]}}" sign_from_post = "Digest username=bc501eda-e2a1-4e63-9a1e-7a7f6ff4813b, uri=\"/v3.0/sms\", algorithm=SHA-256, " \ "cnonce=\"MTU5MTg2OTQ3OTpbmPfGQxVAh5z7MdWnRjF1cavfwKyxiLVrX4p7IHNwWA==\", " \ "snonce=\"MTU5MTg2OTQ4MTqfPxash/0hfNpI/gHuaoSiV+6PwVKYEawxchE0nxHTkA==\", qop=auth-int, " \ "response=\"87bd753875e28da54dfcb5e61614e10a7120aba9a3f8bed0e6eaa9acb85aa9f9\"" response_digest = ResponseDigest(sign_from_post) response_digest.set_original_uri("/v3.0/sms") response_digest.set_original_cnonce( base64.b64decode( "MTU5MTg2OTQ3OTpbmPfGQxVAh5z7MdWnRjF1cavfwKyxiLVrX4p7IHNwWA==") ) response_digest.set_body(json_from_post) response_digest.verify("bc501eda-e2a1-4e63-9a1e-7a7f6ff4813b", "tPMOogw7YBumh6RpXxi2nvGW0C9lJq3L") parsed_result = CallbackResult(json.loads(json_from_post)) self.assertEqual("8d77f986-de7f-4d47-97ef-9de7f8561684", parsed_result.gw.gateway_transaction_id)
def test_verify_success_full_checks(self): body = "{\"acquirer-details\":{},\"error\":{},\"gw\":{\"gateway-transaction-id\":\"37b88436-b69c-45f3-ad26-b945153ad9a8\"," \ "\"redirect-url\":\"http://api.local/4f1f647d10e8296a2ed4d21e3639f1ee\",\"status-code\":30,\"status-text\":" \ "\"INSIDE FORM URL SENT\"},\"warnings\":[\"Soon counters will be exceeded for the merchant\",\"Soon counters will be exceeded " \ "for the account\"]}" header = "Digest username=bc501eda-e2a1-4e63-9a1e-7a7f6ff4813b, uri=\"/v3.0/sms\", algorithm=SHA-256, " \ "cnonce=\"MTU5MTg2NjU3Mzo38zMeHvu4qcbhR8X158atP/BB4dDb5DbOMRT656yS7Q==\", " \ "snonce=\"MTU5MTg2NjU3MzpvnttqUse7hfrkUHtPS8tWE1jl0D0G/DgMmEFwbk5/jw==\", qop=auth-int, " \ "response=\"dda7026eebbeeee19fda191fd951d470b2064e3e1bc416365835abc775352552\"" response_digest = ResponseDigest(header) response_digest.set_original_uri("/v3.0/sms") response_digest.set_original_cnonce( base64.b64decode( "MTU5MTg2NjU3Mzo38zMeHvu4qcbhR8X158atP/BB4dDb5DbOMRT656yS7Q==") ) response_digest.set_body(body) response_digest.verify("bc501eda-e2a1-4e63-9a1e-7a7f6ff4813b", "tPMOogw7YBumh6RpXxi2nvGW0C9lJq3L")
def test_parse_errors(self): nonce = str(base64.b64encode(bytes("1:q", "utf-8")), "utf-8") no_ts_nonce = str(base64.b64encode(bytes("qqq", "utf-8")), "utf-8") wrong_ts_nonce = str(base64.b64encode(bytes("qqq:www", "utf-8")), "utf-8") cases = [ ('', DigestMissingError, 'Digest is missing'), ("Digest uri=b, algorithm=SHA-256, cnonce=%s, snonce=%s, qop=auth-int, response=e" % (nonce, nonce), DigestMismatchError, 'Digest mismatch: empty value for username'), ("Digest username=a, algorithm=SHA-256, cnonce=%s, snonce=%s, qop=auth-int, response=e" % (nonce, nonce), DigestMismatchError, "Digest mismatch: empty value for uri"), ("Digest username=a, uri=b, cnonce=%s, snonce=%s, qop=auth-int, response=e" % (nonce, nonce), DigestMismatchError, "Digest mismatch: empty value for algorithm"), ("Digest username=a, uri=b, algorithm=SHA-256, snonce=%s, qop=auth-int, response=e" % nonce, DigestMismatchError, "Digest mismatch: empty value for cnonce"), ("Digest username=a, uri=b, algorithm=SHA-256, cnonce=%s, qop=auth-int, response=e" % nonce, DigestMismatchError, "Digest mismatch: empty value for snonce"), ("Digest username=a, uri=b, algorithm=SHA-256, cnonce=%s, snonce=%s, response=e" % (nonce, nonce), DigestMismatchError, "Digest mismatch: empty value for qop"), ("Digest username=a, uri=b, algorithm=SHA-256, cnonce=%s, snonce=%s, qop=auth" % (nonce, nonce), DigestMismatchError, "Digest mismatch: empty value for response"), ("Digest username=a, uri=b, algorithm=SHA-256, cnonce=%s, snonce=%s, qop=aaa, response=x" % (nonce, nonce), DigestMismatchError, "Digest mismatch: format error: unknown QOP value"), ("Digest username=a, uri=b, algorithm=aaa, cnonce=%s, snonce=%s, qop=auth, response=x" % (nonce, nonce), DigestMismatchError, "Digest mismatch: format error: unknown algorithm"), ("Digest username=a, uri=b, algorithm=SHA-256, cnonce=%s, snonce=%s, qop=auth, response=x" % (nonce, no_ts_nonce), DigestMismatchError, "Digest mismatch: corrupted value for snonce (missing timestamp)" ), ("Digest username=a, uri=b, algorithm=SHA-256, cnonce=%s, snonce=%s, qop=auth, response=x" % (nonce, wrong_ts_nonce), DigestMismatchError, "Digest mismatch: corrupted value for snonce (unexpected timestamp value)" ), ] for i, test_case in enumerate(cases): [header, expected_error, expected_message] = test_case with self.subTest("#%d" % i): with self.assertRaises(expected_error) as cm: ResponseDigest(header) self.assertEqual(expected_message, str(cm.exception))
def test_verify_errors(self): valid_cnonce = base64.b64decode( "MTU5MTg2NjU3Mzo38zMeHvu4qcbhR8X158atP/BB4dDb5DbOMRT656yS7Q==") invalid_cnonce = base64.b64decode( "MTU5MTg2NjU3MzpvnttqUse7hfrkUHtPS8tWE1jl0D0G/DgMmEFwbk5/jw==") cases = [ ("wrong-guid", "/v3.0/sms", valid_cnonce, "Digest mismatch: username mismatch"), ("bc501eda-e2a1-4e63-9a1e-7a7f6ff4813b", "http://another.local", valid_cnonce, "Digest mismatch: uri mismatch"), ("bc501eda-e2a1-4e63-9a1e-7a7f6ff4813b", "/v3.0/sms", invalid_cnonce, "Digest mismatch: cnonce mismatch"), ("bc501eda-e2a1-4e63-9a1e-7a7f6ff4813b", "/v3.0/sms", valid_cnonce, "Digest mismatch") ] body = "{\"acquirer-details\":{},\"error\":{},\"gw\":{\"gateway-transaction-id\":\"37b88436-b69c-45f3-ad26-b945153ad9a8\"," \ "\"redirect-url\":\"http://api.local/4f1f647d10e8296a2ed4d21e3639f1ee\",\"status-code\":30,\"status-text\":" \ "\"INSIDE FORM URL SENT\"},\"warnings\":[\"Soon counters will be exceeded for the merchant\",\"Soon counters will be exceeded " \ "for the account\"]}" header = "Digest username=bc501eda-e2a1-4e63-9a1e-7a7f6ff4813b, uri=\"/v3.0/sms\", algorithm=SHA-256, " \ "cnonce=\"MTU5MTg2NjU3Mzo38zMeHvu4qcbhR8X158atP/BB4dDb5DbOMRT656yS7Q==\", " \ "snonce=\"MTU5MTg2NjU3MzpvnttqUse7hfrkUHtPS8tWE1jl0D0G/DgMmEFwbk5/jw==\", qop=auth-int, " \ "response=\"624478f45d33bbadc7cf0ae9b34462efd7b9736111f295e6330fe0bc3b20acda\"" for i, test_case in enumerate(cases): with self.subTest("#%d" % i): response_digest = ResponseDigest(header) response_digest.set_original_uri(test_case[1]) response_digest.set_original_cnonce(test_case[2]) response_digest.set_body(body) with self.assertRaises(DigestMismatchError) as cm: response_digest.verify(test_case[0], "something wrong") self.assertEqual(test_case[3], str(cm.exception))
def test_parse_successful(self): header = "Digest username=bc501eda-e2a1-4e63-9a1e-7a7f6ff4813b, uri=\"/v3.0/sms\", algorithm=SHA-256, " \ "cnonce=\"MTU5MTYyNTA2MzqydV+lpoF4ZtfSAifxoUretZdAzGaZa97iRogrQ8K/yg==\", " \ "snonce=\"MTU5MTYyNDgwNzoUte6YsXIJmUo1EsA4yrYDCVbPrvCrEtqGq6CHTMhImg==\", qop=auth-int, " \ "response=\"a21df219fd9bb2efb71554eb9ebb47f6a7a61769a289f9ab4fcbe41d7544e28d\"" digest = ResponseDigest(header) self.assertEqual("bc501eda-e2a1-4e63-9a1e-7a7f6ff4813b", digest.get_username()) self.assertEqual("/v3.0/sms", digest.get_uri()) self.assertEqual(Algorithm.SHA256, digest.get_algorithm()) self.assertEqual(QOP.AUTH_INT, digest.get_qop()) self.assertEqual( "a21df219fd9bb2efb71554eb9ebb47f6a7a61769a289f9ab4fcbe41d7544e28d", digest.get_response()) self.assertEqual(1591624807, digest.get_timestamp()) self.assertEqual( base64.b64decode( "MTU5MTYyNTA2MzqydV+lpoF4ZtfSAifxoUretZdAzGaZa97iRogrQ8K/yg==" ), digest.get_cnonce()) self.assertEqual( base64.b64decode( "MTU5MTYyNDgwNzoUte6YsXIJmUo1EsA4yrYDCVbPrvCrEtqGq6CHTMhImg==" ), digest.get_snonce())
def make_request(self, request_data=None) -> GatewayResponse: """ Make HTTP request via Transact Pro HttpTransport Args: request_data (dict): Transact Pro request structure Response tuple (HTTP Content, HTTP Status code, HTTP Headers) Returns: GatewayResponse """ if self.__client_operations['current'] == '/report': req_url = gateway.API_BASE_URL.rstrip( '/') + self.__client_operations['current'] elif str(self.__client_operations['current']).startswith('http'): req_url = self.__client_operations['current'] else: req_url = gateway.API_BASE_URL + gateway.API_VERSION + self.__client_operations[ 'current'] if req_url is None: raise RuntimeError('Transact PRO API URL Empty!') if self.__client_operations['method'] != HTTP_GET: if request_data is None or len(request_data) < 1: raise RuntimeError("Request data invalid, is empty") if type(request_data) is not dict: raise RuntimeError('Request data invalid, must be dict') guid = AuthorizationBuilder.get_object_guid( self.__dict_of_auth_data_set) secret = self.__dict_of_auth_data_set[ RequestParameters.AUTH_DATA_SECRET_KEY] digest = RequestDigest(username=guid, secret=secret, full_url=req_url) digest.set_body(json.dumps(request_data)) # Setup config for HTTP transport # And make request via HTTP implemented Client [content, status_code, headers] = new_http_client( cli_name=gateway.HTTP_TRANSPORT_IMPLEMENTATION, verify_ssl=gateway.HTTP_VERIFY_SSL_CERTS, proxy=gateway.HTTP_PROXY, timeout=gateway.HTTP_TIME_OUT).request( http_method=self.__client_operations['method'], http_url=req_url, authorization_header=digest.create_header(), request_data=request_data) gw_response = GatewayResponse(status_code, content, headers) if gw_response.is_successful(): response_digest = ResponseDigest( gw_response.headers.get('authorization')) gw_response.digest = response_digest response_digest.set_original_uri(digest.get_uri()) response_digest.set_original_cnonce(digest.get_cnonce()) response_digest.set_body(gw_response.payload) response_digest.verify(guid, secret) return gw_response