from enum import Enum
from dataclasses import dataclass, field
from typing import List, Optional
from lxml import etree
[docs]
class ResponseType(Enum):
"""Enum of valid ResponseTypes expected by HQ"""
LINK_OUT = "LinkOut"
HTML = "RawHTML"
EXCEPTION = "Exception"
[docs]
@dataclass
class UserLevelConfig:
"""Serializes key and value into HQ appropriate shapes"""
key: str #: Must match a value in Q2_ThirdPartyDataElements.Name
value: str #: Will set this in Q2_ThirdPartyData.DataValue
def __post_init__(self):
self.key = f"Session.{self.key.replace('Session.', '')}"
[docs]
@dataclass
class UserLevelConfigList:
"""
Serializes into HQ appropriate return shapes
"""
vendor_id: int #: self.vendor_id from an SSOHandler
#: List of UserLevelConfig instances
configs: List[UserLevelConfig] = field(default_factory=list)
def __post_init__(self):
err_msg = "configs must be a list of UserLevelConfig objects"
assert isinstance(self.configs, list), err_msg
assert all([isinstance(x, UserLevelConfig) for x in self.configs]), err_msg
def to_json(self) -> dict:
vendor_configs = [
{
"VendorID": self.vendor_id,
"DataType": "String",
"ConfigName": x.key,
"ConfigValue": x.value,
}
for x in self.configs
]
return {"Q2_VendorConfigView": vendor_configs}
def to_xml(self) -> etree.Element:
root = etree.Element("AdaptorCfg")
for cfg in self.configs:
parent = etree.SubElement(root, "Q2_VendorConfigView")
etree.SubElement(parent, "VendorID").text = str(self.vendor_id)
etree.SubElement(parent, "DataType").text = "String"
etree.SubElement(parent, "ConfigName").text = cfg.key
etree.SubElement(parent, "ConfigValue").text = str(cfg.value)
return root
[docs]
class Q2SSOResponse:
"""The shape HQ expects back from an SSO call"""
def __init__(
self,
response_type: ResponseType,
response: str,
success: bool = True,
end_user_error_message="",
user_level_config_list: Optional[UserLevelConfigList] = None,
):
"""
:param response_type: From ResponseType enum
:param response: Actual response shown to the end user
:param success: If False, will log failure into HQ
:param end_user_error_message: In conjunction with success=False, customizes the error message
:param user_level_config_list: If provided, will fill the Q2_ThirdPartyData table with user level key/value pairs for specified vendor
"""
self.response_type = response_type
self.response = response
self.success = success
if user_level_config_list is not None:
assert isinstance(user_level_config_list, UserLevelConfigList), (
"user_level_config_list must be UserLevelConfigList instance if supplied"
)
if end_user_error_message:
assert self.success is False, (
"Cannot provide an end_user_error_message when success=True"
)
self.end_user_error_message = end_user_error_message
self.user_level_config_list = user_level_config_list
[docs]
def serialize(self) -> dict:
"""JSON response"""
sso_return = {
"Success": str(self.success),
"ReturnType": self.response_type.value,
"Data": self.response,
}
if self.end_user_error_message:
sso_return["EndUserErrorMessage"] = self.end_user_error_message
if self.user_level_config_list:
sso_return["AdaptorCfg"] = self.user_level_config_list.to_json()
return sso_return
[docs]
def serialize_as_xml(self) -> etree.Element:
"""SOAP response"""
root = etree.Element("LoginToAdapterResult")
etree.SubElement(root, "Success").text = str(self.success).lower()
etree.SubElement(root, "ReturnType").text = self.response_type.value
etree.SubElement(root, "Data").text = self.response
if self.end_user_error_message:
etree.SubElement(
root, "EndUserErrorMessage"
).text = self.end_user_error_message
if self.user_level_config_list:
root.append(self.user_level_config_list.to_xml())
return root