"""Custom exceptions and errors"""
from difflib import get_close_matches
from requests import HTTPError
[docs]class SwimlaneException(Exception):
"""Base exception for Swimlane errors"""
[docs]class UnknownField(SwimlaneException, KeyError):
"""Raised anytime access is attempted to a field that does not exist on an App or Record
Attributes:
app (App): App with the unknown field requested
field_name (str): Name of the field that was requested
similar_field_names (list(str)): List of strings of fields on app that are potentially similar to field_name
"""
def __init__(self, app, field_name, field_pool):
self.app = app
self.field_name = field_name
self.similar_field_names = get_close_matches(self.field_name, field_pool, 3)
message = "{!r} has no field '{}'".format(self.app, self.field_name)
if self.similar_field_names:
message += '. Similar fields: ' + ', '.join([repr(f) for f in self.similar_field_names])
super(UnknownField, self).__init__(message)
[docs]class ValidationError(SwimlaneException, ValueError):
"""Raised when record's field data is invalid
Attributes:
record (Record): Record in context of validation failure
failure (str): Reason for record failure
"""
def __init__(self, record, failure):
self.record = record
self.failure = failure
super(ValidationError, self).__init__(
'Validation failed for {!r}. Reason: {}'.format(self.record, self.failure)
)
class _InvalidSwimlaneVersion(SwimlaneException, NotImplementedError):
"""Base class raised when connecting to unsupported versions of Swimlane
Attributes:
swimlane (Swimlane): Swimlane client failing the version check
min_version (str): Minimum version specified on version range
max_version (str): Maximum version specified on version range
"""
def __init__(self, swimlane, min_version, max_version):
self.swimlane = swimlane
self.min_version = min_version
self.max_version = max_version
super(_InvalidSwimlaneVersion, self).__init__(self._get_message())
def _get_range_string(self):
if self.min_version and self.max_version:
range_string = '>= {}, < {}'.format(self.min_version, self.max_version)
elif self.min_version:
range_string = '>= {}'.format(self.min_version)
else:
range_string = '<= {}'.format(self.max_version)
return range_string
def _get_message(self):
return 'Swimlane version {}, must be {}'.format(
self.swimlane.version,
self._get_range_string()
)
[docs]class InvalidSwimlaneBuildVersion(_InvalidSwimlaneVersion):
"""Raised when method connected to Swimlane with build version outside a required range"""
def _get_message(self):
return 'Swimlane build version {}, must be {}'.format(
self.swimlane.build_version,
self._get_range_string()
)
[docs]class InvalidSwimlaneProductVersion(_InvalidSwimlaneVersion):
"""Raised when method connected to Swimlane with product version outside a required range"""
def _get_message(self):
return 'Swimlane product version {}, must be {}'.format(
self.swimlane.product_version,
self._get_range_string()
)
[docs]class SwimlaneHTTP400Error(SwimlaneException, HTTPError):
"""Exception raised when receiving a 400 response with additional context
Attributes:
code (int): Swimlane error code
name (str): Human-readable Swimlane error name
argument (str): Optional argument included with error or None
http_error (HTTPError): Source requests.HTTPError caught and used to generate this exception
"""
codes = {
-1: 'Unknown',
1000: 'PasswordExpired',
1001: 'DuplicateUserName',
1002: 'InvalidUserNameOrPassword',
1003: 'ConfirmPasswordDoesNotMatch',
1004: 'PasswordDoesNotMeetComplexityRequirements',
1005: 'PasswordResetRequired',
1006: 'NewPasswordCannotMatchCurrent',
1007: 'InvalidUser',
1051: 'DuplicateGroupName',
1061: 'DuplicateRoleName',
2000: 'DuplicateFieldName',
2001: 'FieldNameEmpty',
2002: 'InvalidApplicationExportFile',
2003: 'ApplicationNotFound',
2004: 'InvalidCalculation',
2005: 'DuplicateApplicationName',
2006: 'DuplicateAppletName',
2007: 'DuplicateAppletAcronym',
2008: 'DuplicateApplicationAcronym',
2011: 'SectionNameTooLong',
3000: 'DuplicateFieldValue',
3001: 'InvalidDateField',
3002: 'RecordNotFound',
3003: 'FieldNotFound',
3006: 'MaxAttachmentSize',
4000: 'BadStatsGroup',
4001: 'BadFilter',
5000: 'AppLimitExceeded',
5001: 'UserLimitExceeded',
5002: 'NewServerInstall',
5003: 'UnableToConnectToActiveDirectory',
5004: 'UnableToRetrieveStoredValue',
5005: 'UnableToConnectToMongoDb',
5006: 'UnableToConnectToSmtp',
5007: 'SwimlaneAlreadyInitialized',
5008: 'ModelValidationError',
5009: 'UpgradeInProcess',
5010: 'RequiredFieldMissing',
5011: 'UnableToRetrieveEncryptionKey',
5012: 'PathNotFound',
5013: 'WrongType',
5014: 'ModificationError',
5015: 'DatabaseError',
5016: 'NetworkError',
5017: 'InvalidOnThisOS',
6000: 'ConnectionDataNotProvided',
7000: 'RegexNotDefined',
7001: 'AssetNotFound',
9000: 'BadThreatIntelConnector',
9001: 'NoThreatIntel',
9002: 'ThreatIntelTypeNotSupportedByThisProvider',
10000: 'DuplicateTaskName',
10001: 'TaskNotFound',
17001: 'DuplicateAssetName',
19001: 'HangfireError'
}
def __init__(self, http_error):
self.http_error = http_error
try:
error_content = self.http_error.response.json()
except ValueError:
error_content = {'Argument': None, 'ErrorCode': '-1'}
self.code = int(error_content.get('ErrorCode', -1))
self.argument = error_content.get('Argument')
self.name = self.codes.get(self.code, self.codes[-1])
message = '{}:{}'.format(self.name, self.code)
if self.argument is not None:
message = '{message} ({argument})'.format(message=message, argument=self.argument)
super(SwimlaneHTTP400Error, self).__init__(
'{message}: Bad Request for url: {url}'.format(message=message, url=self.http_error.response.url)
)