From 01b5cf0cb0afc74c3b7921aecb5c612fca23ba51 Mon Sep 17 00:00:00 2001 From: Kristjan Date: Wed, 29 Jun 2022 15:31:17 +0300 Subject: [PATCH] 1. OPDM 4.0 support (older versions not supported) 2. Auth from SAML token to wsse username and password 3. Automatic version number update 4. Mutable defaults removed form function calls --- OPDM/OPDM_SOAP_API.py | 97 +++++++++++-------------------------------- 1 file changed, 24 insertions(+), 73 deletions(-) diff --git a/OPDM/OPDM_SOAP_API.py b/OPDM/OPDM_SOAP_API.py index 8c14667..a24de8c 100644 --- a/OPDM/OPDM_SOAP_API.py +++ b/OPDM/OPDM_SOAP_API.py @@ -15,22 +15,17 @@ from zeep.plugins import HistoryPlugin from lxml import etree -from lxml.builder import ElementMaker -import random import os import uuid -import json import xmltodict -from datetime import datetime, timezone -import aniso8601 - import urllib3 -import logging +from OPDM import __version__ as package_version +import logging logger = logging.getLogger(__name__) @@ -71,10 +66,9 @@ def __init__(self, server, username="", password="", debug=False, verify=False): self.debug = debug self.history = HistoryPlugin() - self.API_VERSION = "0.0.11" # TODO - Get the version from versioneer + self.API_VERSION = package_version service_wsdl = '{}/cxf/OPDMSoapInterface?wsdl'.format(server) - auth_wsdl = '{}/cxf/OPDMSoapInterface/SoapAuthentication?wsdl'.format(server) ruleset_wsdl = '{}/cxf/OPDMSoapInterface/RuleSetManagementService?wsdl'.format(server) session = Session() @@ -85,27 +79,13 @@ def __init__(self, server, username="", password="", debug=False, verify=False): session.verify = False # Set up client + self.client = Client(service_wsdl, transport=Transport(session=session), wsse=UsernameToken(username, password=password)) + self.ruleset_client = Client(ruleset_wsdl, transport=Transport(session=session), wsse=UsernameToken(username, password=password)) + if debug: - self.client = Client(service_wsdl, transport=Transport(session=session), plugins=[self.history]) - self.ruleset_client = Client(ruleset_wsdl, transport=Transport(session=session), plugins=[self.history]) + self.client.plugins = [self.history] + self.ruleset_client.plugins = [self.history] logging.basicConfig(format='%(asctime)s | %(name)s | %(levelname)s | %(message)s', level=logging.DEBUG) - else: - self.client = Client(service_wsdl, transport=Transport(session=session)) - self.ruleset_client = Client(ruleset_wsdl, transport=Transport(session=session)) - - # Set up auth - if username == "": - self.auth = False - elif debug: - self.auth = True - self.auth_client = Client(auth_wsdl, transport=Transport(session=session), wsse=UsernameToken(username, password=password), plugins=[self.history]) - self.auth_valid_until = datetime.now(timezone.utc) - self.token = None - else: - self.auth = True - self.auth_client = Client(auth_wsdl, transport=Transport(session=session), wsse=UsernameToken(username, password=password)) - self.auth_valid_until = datetime.now(timezone.utc) - self.token = None def _print_last_message_exchange(self): """Prints out last sent and received SOAP messages""" @@ -225,40 +205,9 @@ class Operations: """ - def request_security_token(self, pretty_print=False): - """Return the token as string and it's validity time""" - token_string = self.auth_client.service.RequestSecurityToken() - token = etree.fromstring(token_string) - valid_unitl = aniso8601.parse_datetime(token.find(".//{*}Conditions").attrib['NotOnOrAfter']) - - if pretty_print: - print(json.dumps(xmltodict.parse(token_string), indent=4)) - - return token, valid_unitl - - def check_token(self): - """Check if token is due to expire and renew if needed""" - utc_now = datetime.now(timezone.utc) - - if utc_now > self.auth_valid_until - aniso8601.parse_duration("PT5S"): - self.token, self.auth_valid_until = self.request_security_token() - logger.debug("Requesting new Auth Token") - - elif self.debug: - logger.debug(f"Auth Token still valid for {self.auth_valid_until - utc_now}") - - def create_saml_header(self): - """Create SOAP SAML authentication header element for zeep""" - if self.auth: - self.check_token() - WSSE = "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd" - return [ElementMaker(namespace=WSSE).Security(self.token)] - else: - return [] - def execute_operation(self, operation_xml): """ExecuteOperation(payload: xsd:base64Binary) -> return: ns0:resultDto""" - response = self.client.service.ExecuteOperation(operation_xml, _soapheaders=self.create_saml_header()) + response = self.client.service.ExecuteOperation(operation_xml) return response def publication_request(self, file_path_or_file_object, content_type="CGMES"): @@ -278,11 +227,11 @@ def publication_request(self, file_path_or_file_object, content_type="CGMES"): payload = {"id": file_name, "type": content_type, "content": file_string} - response = self.client.service.PublicationRequest(payload, _soapheaders=self.create_saml_header()) + response = self.client.service.PublicationRequest(payload) return response - def query_object(self, object_type="IGM", metadata_dict="", components=[], dependencies=[]): + def query_object(self, object_type="IGM", metadata_dict=None, components=None, dependencies=None): """ object_type ->IGM, CGM, BDS metadata_dict_example = {'pmd:cgmesProfile': 'SV', 'pmd:scenarioDate': '2018-12-07T00:30:00+01:00', 'pmd:timeHorizon': '1D'} @@ -293,14 +242,16 @@ def query_object(self, object_type="IGM", metadata_dict="", components=[], depen _QueryObject = self.Operations.QueryObject.format(query_id=query_id, object_type=object_type).encode() - if metadata_dict != "": + if metadata_dict: _QueryObject = add_xml_elements(_QueryObject, ".//opdm:OPDMObject", metadata_dict) - for component in components: - _QueryObject = add_xml_elements(_QueryObject, ".//opde:Components", component) + if components: + for component in components: + _QueryObject = add_xml_elements(_QueryObject, ".//opde:Components", component) - for dependency in dependencies: - _QueryObject = add_xml_elements(_QueryObject, ".//opde:Dependencies", dependency) + if dependencies: + for dependency in dependencies: + _QueryObject = add_xml_elements(_QueryObject, ".//opde:Dependencies", dependency) logger.debug(_QueryObject.decode()) @@ -346,6 +297,7 @@ def get_content(self, content_id, return_payload=False, object_type="file"): result = xmltodict.parse(etree.tostring(self.execute_operation(get_content_result.encode())), xml_attribs=False) + # TODO - add better error handling, in case error message was returned if type(result['sm:GetContentResult']['sm:part']) == list: logger.info("File downloaded") #logger.debug(result['sm:GetContentResult']['sm:part'][1]['opdm:Profile']['opde:Content']) @@ -373,8 +325,7 @@ def publication_subscribe(self, object_type="BDS", subscription_id="", publicati # Get available publications available_publications = self.publication_list() - - object_types = {item['opde:messageType']["@v"].split("-")[-1]:item['opde:publicationID']["@v"] for item in available_publications['sm:PublicationsSubscriptionListResult']['sm:part']['opdm:PublicationsList']['opdm:Publication']} + object_types = {item['opde:messageType']["@v"].split("-")[-1]: item['opde:publicationID']["@v"] for item in available_publications['sm:PublicationsSubscriptionListResult']['sm:part']['opdm:PublicationsList']['opdm:Publication']} if object_type not in object_types.keys(): logger.warning(f"ObjectType '{object_type}' not supported, supported types are: {object_types}") @@ -437,19 +388,19 @@ def publication_cancel_subscription(self, subscription_id): def get_installed_ruleset_version(self): """Retrurns a string with the latest ruleset version""" - return self.ruleset_client.service.GetInstalledRuleSetVersion(_soapheaders=self.create_saml_header()) + return self.ruleset_client.service.GetInstalledRuleSetVersion() def list_available_rulesets(self): """Returns a list of available rulesets""" - return self.ruleset_client.service.ListAvailableRuleSets(_soapheaders=self.create_saml_header()) + return self.ruleset_client.service.ListAvailableRuleSets() def install_ruleset(self, version=None): """Install ruleset library by providing the library version as a string. To get available ruleset libraries use list_available_rulesets()""" - return self.ruleset_client.service.Install(Version=version, _soapheaders=self.create_saml_header()) + return self.ruleset_client.service.Install(Version=version) def reset_ruleset(self): """Reset ruleset library""" - return self.ruleset_client.service.Reset(_soapheaders=self.create_saml_header()) + return self.ruleset_client.service.Reset() if __name__ == '__main__':