class AnalysisSubmissionResponse(BaseResponse): """The API response domain model for a successful analysis job submision.""" with open(resolve_schema(__file__, "analysis-submission.json")) as sf: schema = json.load(sf) def __init__(self, analysis: Analysis): self.analysis = analysis @classmethod def from_dict(cls, d): """Create the response domain model from a dict. This also validates the dict's schema and raises a :code:`ValidationError` if any required keys are missing or the data is malformed. :param d: The dict to deserialize from :return: The domain model with the data from :code:`d` filled in """ cls.validate(d) return cls(analysis=Analysis.from_dict(d)) def to_dict(self): """Serialize the reponse model to a Python dict. :return: A dict holding the request model data """ d = self.analysis.to_dict() self.validate(d) return d def __getattr__(self, name): return getattr(self.analysis, name)
class VersionResponse(BaseResponse): """The API response domain model for an API version request.""" with open(resolve_schema(__file__, "version.json")) as sf: schema = json.load(sf) def __init__( self, api_version: str, maru_version: str, mythril_version: str, harvey_version: str, hashed_version: str, ): self.api_version = api_version self.maru_version = maru_version self.mythril_version = mythril_version self.harvey_version = harvey_version self.hashed_version = hashed_version @classmethod def from_dict(cls, d: Dict): """Create the response domain model from a dict. This also validates the dict's schema and raises a :code:`ValidationError` if any required keys are missing or the data is malformed. :param d: The dict to deserialize from :return: The domain model with the data from :code:`d` filled in """ cls.validate(d) return cls( api_version=d["api"], maru_version=d["maru"], mythril_version=d["mythril"], harvey_version=d["harvey"], hashed_version=d["hash"], ) def to_dict(self): """Serialize the reponse model to a Python dict. :return: A dict holding the request model data """ d = { "api": self.api_version, "maru": self.maru_version, "mythril": self.mythril_version, "harvey": self.harvey_version, "hash": self.hashed_version, } self.validate(d) return d
class OASResponse(BaseResponse): """The API response domain model for an OpenAPI request.""" with open(resolve_schema(__file__, "openapi.json")) as sf: schema = json.load(sf) def __init__(self, data: str): if not type(data) == str: raise TypeError("Expected data type str but got {}".format( type(data))) self.data = data @classmethod def from_dict(cls, d: Dict) -> "OASResponse": """Create the response domain model from a dict. This also validates the dict's schema and raises a :code:`ValidationError` if any required keys are missing or the data is malformed. :param d: The dict to deserialize from :return: The domain model with the data from :code:`d` filled in """ cls.validate(d) return cls(data=d["data"]) @classmethod def from_json(cls, json_str: str) -> "OASResponse": """ :param json_str: The string to decode from :return: An OASResponse instance """ # overwrite from base response because the API doesn't actually deliver # JSON but raw YAML/HTML return cls.from_dict({"data": json_str}) def to_dict(self) -> Dict: """Serialize the response model to a Python dict. :return: A dict holding the request model data """ d = {"data": self.data} return d def __eq__(self, other: "OASResponse") -> bool: return self.data == other.data
class AuthRefreshResponse(BaseResponse): """The API response domain model for a successful authentication refresh.""" with open(resolve_schema(__file__, "auth.json")) as sf: schema = json.load(sf) def __init__(self, access_token: str, refresh_token: str): self.access_token = access_token self.refresh_token = refresh_token @classmethod def from_dict(cls, d: Dict) -> "AuthRefreshResponse": """Create the response domain model from a dict. This also validates the dict's schema and raises a :code:`ValidationError` if any required keys are missing or the data is malformed. :param d: The dict to deserialize from :return: The domain model with the data from :code:`d` filled in """ cls.validate(d) return cls( access_token=d["jwtTokens"]["access"], refresh_token=d["jwtTokens"]["refresh"], ) def to_dict(self) -> Dict: """Serialize the response model to a Python dict. :return: A dict holding the request model data """ d = {"jwtTokens": {"access": self.access_token, "refresh": self.refresh_token}} self.validate(d) return d def __eq__(self, other: "AuthRefreshResponse") -> bool: return all( ( self.access_token == other.access_token, self.refresh_token == other.refresh_token, ) )
class GroupStatusResponse(BaseResponse): """The API response domain model for a fetching the status of a group.""" with open(resolve_schema(__file__, "group-status.json")) as sf: schema = json.load(sf) def __init__(self, group: Group): self.group = group @classmethod def from_dict(cls, d) -> "GroupStatusResponse": """Create the response domain model from a dict. This also validates the dict's schema and raises a :code:`ValidationError` if any required keys are missing or the data is malformed. :param d: The dict to deserialize from :return: The domain model with the data from :code:`d` filled in """ cls.validate(d) return cls(group=Group.from_dict(d)) def to_dict(self) -> Dict: """Serialize the response model to a Python dict. :return: A dict holding the request model data """ d = self.group.to_dict() self.validate(d) return d def __getattr__(self, name: str) -> Any: return getattr(self.group, name) def __eq__(self, other: "GroupStatusResponse") -> bool: return self.group == other.group
class AuthLogoutRequest(BaseRequest): """Perform an API request that logs out the current user.""" with open(resolve_schema(__file__, "auth-logout.json")) as sf: schema = json.load(sf) def __init__(self, global_: bool = False): self.global_ = global_ @property def endpoint(self): """The API's logout endpoint. :return: A string denoting the logout endpoint without the host prefix """ return "v1/auth/logout" @property def method(self): """The HTTP method to perform. :return: The uppercase HTTP method, e.g. "POST" """ return "POST" @property def parameters(self): """Additional URL parameters :return: A dict (str -> str) instance mapping parameter name to parameter content """ return {} @property def headers(self): """Additional request headers. :return: A dict (str -> str) instance mapping header name to header content """ return {} @property def payload(self): """The request's payload data. :return: A Python dict to be serialized into JSON format and submitted to the endpoint. """ return {} @classmethod def from_dict(cls, d: Dict): """Create the request domain model from a dict. This also validates the dict's schema and raises a :code:`ValidationError` if any required keys are missing or the data is malformed. :param d: The dict to deserialize from :return: The domain model with the data from :code:`d` filled in """ cls.validate(d) return cls(global_=d["global"]) def to_dict(self): """Serialize the request model to a Python dict. :return: A dict holding the request model data """ d = {"global": self.global_} self.validate(d) return d
class DetectedIssuesRequest(AnalysisStatusRequest): """Perform an API request that lists the detected issues of a finished analysis job.""" with open(resolve_schema(__file__, "detected-issues.json")) as sf: schema = json.load(sf) def __init__(self, uuid: str): super().__init__(uuid) self.uuid = uuid @property def endpoint(self): """The API's analysis issue report endpoint. :return: A string denoting the issue report endpoint without the host prefix """ return "v1/analyses/{}/issues".format(self.uuid) @property def method(self): """The HTTP method to perform. :return: The uppercase HTTP method, e.g. "POST" """ return "GET" @property def parameters(self): """Additional URL parameters :return: A dict (str -> str) instance mapping parameter name to parameter content """ return {} @property def headers(self): """Additional request headers. :return: A dict (str -> str) instance mapping header name to header content """ return {} @property def payload(self): """The request's payload data. :return: A Python dict to be serialized into JSON format and submitted to the endpoint. """ return {} @classmethod def from_dict(cls, d: Dict): """Create the request domain model from a dict. This also validates the dict's schema and raises a :code:`ValidationError` if any required keys are missing or the data is malformed. :param d: The dict to deserialize from :return: The domain model with the data from :code:`d` filled in """ cls.validate(d) return cls(uuid=d["uuid"]) def to_dict(self): """Serialize the request model to a Python dict. :return: A dict holding the request model data """ d = {"uuid": self.uuid} self.validate(d) return d
class AuthRefreshRequest(BaseRequest): """Perform an API request that refreshes the logged-in user's access token.""" with open(resolve_schema(__file__, "auth-refresh.json")) as sf: schema = json.load(sf) def __init__(self, access_token: str, refresh_token: str): self.access_token = access_token self.refresh_token = refresh_token @property def endpoint(self): """The API's auth refresh endpoint. :return: A string denoting the refresh endpoint without the host prefix """ return "v1/auth/refresh" @property def method(self): """The HTTP method to perform. :return: The uppercase HTTP method, e.g. "POST" """ return "POST" @property def parameters(self): """Additional URL parameters :return: A dict (str -> str) instance mapping parameter name to parameter content """ return {} @property def headers(self): """Additional request headers. :return: A dict (str -> str) instance mapping header name to header content """ return {} @property def payload(self): """The request's payload data. :return: A Python dict to be serialized into JSON format and submitted to the endpoint. """ return { "jwtTokens": { "access": self.access_token, "refresh": self.refresh_token } } @classmethod def from_dict(cls, d: Dict): """Create the request domain model from a dict. This also validates the dict's schema and raises a :code:`ValidationError` if any required keys are missing or the data is malformed. :param d: The dict to deserialize from :return: The domain model with the data from :code:`d` filled in """ cls.validate(d) return cls( access_token=d["jwtTokens"]["access"], refresh_token=d["jwtTokens"]["refresh"], ) def to_dict(self): """Serialize the request model to a Python dict. :return: A dict holding the request model data """ d = { "jwtTokens": { "access": self.access_token, "refresh": self.refresh_token } } self.validate(d) return d
class GroupCreationRequest(BaseRequest): """Perform an API request creates a new analysis group.""" with open(resolve_schema(__file__, "group-creation.json")) as sf: schema = json.load(sf) def __init__(self, group_name: str = ""): self.group_name = group_name @property def endpoint(self) -> str: """The API's logout endpoint. :return: A string denoting the group endpoint without the host prefix """ return "v1/analysis-groups" @property def method(self) -> str: """The HTTP method to perform. :return: The uppercase HTTP method, e.g. "POST" """ return "POST" @property def parameters(self) -> Dict: """Additional URL parameters. :return: A dict (str -> str) instance mapping parameter name to parameter content """ return {} @property def headers(self) -> Dict: """Additional request headers. :return: A dict (str -> str) instance mapping header name to header content """ return {} @property def payload(self) -> Dict: """The request's payload data. :return: A Python dict to be serialized into JSON format and submitted to the endpoint. """ return {"groupName": self.group_name} @classmethod def from_dict(cls, d: Dict) -> "GroupCreationRequest": """Create the request domain model from a dict. This also validates the dict's schema and raises a :code:`ValidationError` if any required keys are missing or the data is malformed. :param d: The dict to deserialize from :return: The domain model with the data from :code:`d` filled in """ cls.validate(d) return cls(group_name=d["groupName"]) def to_dict(self) -> Dict: """Serialize the request model to a Python dict. :return: A dict holding the request model data """ d = {"groupName": self.group_name} self.validate(d) return d def __eq__(self, other: "GroupCreationRequest") -> bool: return self.group_name == other.group_name
class DetectedIssuesResponse(BaseResponse): """The API response domain model for a report of the detected issues.""" with open(resolve_schema(__file__, "detected-issues.json")) as sf: schema = json.load(sf) def __init__(self, issue_reports: List[IssueReport]) -> None: self.issue_reports = issue_reports @classmethod def from_dict(cls, d: Dict) -> "DetectedIssuesResponse": """Create the response domain model from a dict. This also validates the dict's schema and raises a :code:`ValidationError` if any required keys are missing or the data is malformed. :param d: The List to deserialize from :return: The domain model with the data from :code:`d` filled in """ if type(d) == list: cls.validate(d) d = {"issueReports": d} elif type(d) == dict: if d.get("issueReports") is None: raise ValidationError( "Cannot create DetectedIssuesResponse object from invalid dictionary d: {}" .format(d)) cls.validate(d["issueReports"]) else: raise ValidationError( "Expected list or dict but got {} of type {}".format( d, type(d))) return cls(issue_reports=[ IssueReport.from_dict(i) for i in d["issueReports"] ]) def to_dict(self, as_list=False) -> Dict: """Serialize the response model to a Python dict. :return: A dict holding the request model data """ d = { "issueReports": [report.to_dict() for report in self.issue_reports] } self.validate(d["issueReports"]) return d["issueReports"] if as_list else d def to_json(self) -> str: """Serialize the model to JSON format. Internally, this method is using the :code:`to_dict` method. :return: A JSON string holding the model's data """ return json.dumps([report.to_dict() for report in self.issue_reports]) def __contains__(self, key: str) -> bool: """Check whether a specified SWC ID is contained in any of the reports.""" if not type(key) == str: raise ValueError( "Expected SWC ID of type str but got {} of type {}".format( key, type(key))) for report in self.issue_reports: if key in report: return True return False def __iter__(self) -> Iterator[Issue]: for report in self.issue_reports: for issue in report: yield issue def __len__(self) -> int: """Return the number of issues across all reports.""" total_detected_issues = 0 for report in self.issue_reports: total_detected_issues += len(report) return total_detected_issues def __getitem__(self, key: int) -> IssueReport: """Get an issue report at a specific index.""" return self.issue_reports[key] def __setitem__(self, key: int, value: IssueReport) -> None: """Set an issue report at a specified index.""" self.issue_reports[key] = value def __delitem__(self, key: int) -> None: """Delete an issue report at a specified index.""" del self.issue_reports[key] def __eq__(self, other: "DetectedIssuesResponse") -> bool: return self.issue_reports == other.issue_reports
class AuthLoginRequest(BaseRequest): """Perform an API request that performs a login action with Ethereum address and password.""" with open(resolve_schema(__file__, "auth-login.json")) as sf: schema = json.load(sf) def __init__(self, eth_address: str, password: str): self.eth_address = eth_address self.password = password @property def endpoint(self): """The API's login endpoint. :return: A string denoting the login endpoint without the host prefix """ return "v1/auth/login" @property def method(self): """The HTTP method to perform. :return: The uppercase HTTP method, e.g. "POST" """ return "POST" @property def parameters(self): """Additional URL parameters :return: A dict (str -> str) instance mapping parameter name to parameter content """ return {} @property def headers(self): """Additional request headers. :return: A dict (str -> str) instance mapping header name to header content """ return {} @property def payload(self): """The request's payload data. :return: A Python dict to be serialized into JSON format and submitted to the endpoint. """ return self.to_dict() @classmethod def from_dict(cls, d: Dict[str, str]): """Create the request domain model from a dict. This also validates the dict's schema and raises a :code:`ValidationError` if any required keys are missing or the data is malformed. :param d: The dict to deserialize from :return: The domain model with the data from :code:`d` filled in """ cls.validate(d) return cls(eth_address=d["ethAddress"], password=d["password"]) def to_dict(self): """Serialize the request model to a Python dict. :return: A dict holding the request model data """ d = {"ethAddress": self.eth_address, "password": self.password} self.validate(d) return d
class AnalysisSubmissionRequest(BaseRequest): """Perform an API analysis job submission as a logged in user.""" with open(resolve_schema(__file__, "analysis-submission.json")) as sf: schema = json.load(sf) def __init__( self, contract_name: str = None, bytecode: str = None, source_map: str = None, deployed_bytecode: str = None, deployed_source_map: str = None, main_source: str = None, sources: Dict[str, Dict[str, str]] = None, source_list: List[str] = None, solc_version: str = None, analysis_mode: str = "quick", ): self.contract_name = contract_name self.bytecode = bytecode self.source_map = source_map self.deployed_bytecode = deployed_bytecode self.deployed_source_map = deployed_source_map self.main_source = main_source self.sources = sources self.source_list = source_list self.solc_version = solc_version # set alias for full mode for backwards compatibility - new modes are quick, standard, deep self.analysis_mode = self._get_analysis_mode(analysis_mode) @staticmethod def _get_analysis_mode(mode: str) -> str: return "standard" if mode == "full" else mode @property def endpoint(self) -> str: """The API's analysis submission endpoint. :return: A string denoting the submission endpoint without the host prefix """ return "v1/analyses" @property def method(self) -> str: """The HTTP method to perform. :return: The uppercase HTTP method, e.g. "POST" """ return "POST" @property def parameters(self) -> Dict: """Additional URL parameters. :return: A dict (str -> str) instance mapping parameter name to parameter content """ return {} @property def headers(self) -> Dict: """Additional request headers. :return: A dict (str -> str) instance mapping header name to header content """ return {} @property def payload(self) -> Dict: """The request's payload data. :return: A Python dict to be serialized into JSON format and submitted to the endpoint. """ return {"data": self.to_dict()} @classmethod def from_dict(cls, d: Dict) -> "AnalysisSubmissionRequest": """Create the request domain model from a dict. This also validates the dict's schema and raises a :code:`ValidationError` if any required keys are missing or the data is malformed. :param d: The dict to deserialize from :return: The domain model with the data from :code:`d` filled in """ if d.get("analysisMode"): d["analysisMode"] = cls._get_analysis_mode(d["analysisMode"]) cls.validate(d) return cls( contract_name=d.get("contractName"), bytecode=d.get("bytecode"), source_map=d.get("sourceMap"), deployed_bytecode=d.get("deployedBytecode"), deployed_source_map=d.get("deployedSourceMap"), main_source=d.get("mainSource"), sources=d.get("sources"), source_list=d.get("sourceList"), solc_version=d.get("version"), analysis_mode=d.get("analysisMode"), ) def to_dict(self) -> Dict: """Serialize the request model to a Python dict. :return: A dict holding the request model data """ base_dict = dict_delete_none_fields({ "contractName": self.contract_name, "bytecode": self.bytecode, "sourceMap": self.source_map, "deployedBytecode": self.deployed_bytecode, "deployedSourceMap": self.deployed_source_map, "mainSource": self.main_source, "sources": self.sources if self.sources else None, "sourceList": self.source_list if self.source_list else None, "version": self.solc_version, "analysisMode": self.analysis_mode, }) self.validate(base_dict) return base_dict def __eq__(self, candidate) -> bool: """Perform an equality check on two Request domain models.""" return all([ self.contract_name == candidate.contract_name, self.bytecode == candidate.bytecode, self.source_map == candidate.source_map, self.deployed_bytecode == candidate.deployed_bytecode, self.deployed_source_map == candidate.deployed_source_map, self.main_source == candidate.main_source, self.sources == candidate.sources, self.source_list == candidate.source_list, self.solc_version == candidate.solc_version, self.analysis_mode == candidate.analysis_mode, ])
class AnalysisListResponse(BaseResponse): """The API response domain model for a list of analyses.""" with open(resolve_schema(__file__, "analysis-list.json")) as sf: schema = json.load(sf) def __init__(self, analyses: List[Analysis], total: int) -> None: self.analyses = analyses self.total = total @classmethod def validate(cls, candidate): """Validate the response data structure and add an explicit type check. :param candidate: The Python dict to validate """ super().validate(candidate) if not type(candidate) == dict: raise ValidationError( "Expected type dict but got {}".format(type(candidate)) ) @classmethod def from_dict(cls, d: dict): """Create the response domain model from a dict. This also validates the dict's schema and raises a :code:`ValidationError` if any required keys are missing or the data is malformed. :param d: The dict to deserialize from :return: The domain model with the data from :code:`d` filled in """ cls.validate(d) analyses = [Analysis.from_dict(a) for a in d["analyses"]] return cls(analyses=analyses, total=d["total"]) def to_dict(self): """Serialize the reponse model to a Python dict. :return: A dict holding the request model data """ d = { "analyses": [a.to_dict() for a in self.analyses], "total": len(self.analyses), } self.validate(d) return d def __iter__(self): for analysis in self.analyses: yield analysis def __getitem__(self, idx): try: return self.analyses[idx] except IndexError: raise IndexError(INDEX_ERROR_MSG.format(idx)) def __setitem__(self, idx, value): try: self.analyses[idx] = value except IndexError: raise IndexError(INDEX_ERROR_MSG.format(idx)) def __delitem__(self, idx): try: del self.analyses[idx] self.total -= 1 except IndexError: raise IndexError(INDEX_ERROR_MSG.format(idx)) def __len__(self): return self.total def __reversed__(self): return reversed(self.analyses) def __contains__(self, item): if not type(item) in (Analysis, str): raise ValueError( "Expected type Analysis or str but got {}".format(type(item)) ) uuid = item.uuid if type(item) == Analysis else item return uuid in map(lambda x: x.uuid, self.analyses) def __eq__(self, candidate): return self.total == candidate.total and self.analyses == candidate.analyses
class GroupListResponse(BaseResponse): """The API response domain model for a list of analyses.""" with open(resolve_schema(__file__, "group-list.json")) as sf: schema = json.load(sf) def __init__(self, groups: List[Group], total: int): self.groups = groups self.total = total @classmethod def from_dict(cls, d: Dict) -> "GroupListResponse": """Create the response domain model from a dict. This also validates the dict's schema and raises a :code:`ValidationError` if any required keys are missing or the data is malformed. :param d: The dict to deserialize from :return: The domain model with the data from :code:`d` filled in """ cls.validate(d) groups = [Group.from_dict(a) for a in d["groups"]] return cls(groups=groups, total=d["total"]) def to_dict(self) -> Dict: """Serialize the response model to a Python dict. :return: A dict holding the request model data """ d = { "groups": [a.to_dict() for a in self.groups], "total": len(self.groups) } self.validate(d) return d def __iter__(self) -> Group: """Iterate over all the groups in the list.""" for group in self.groups: yield group def __getitem__(self, idx: int) -> Group: """Get a group at a specific list index.""" try: return self.groups[idx] except IndexError: raise IndexError(INDEX_ERROR_MSG.format(idx)) def __setitem__(self, idx: int, value: Group) -> None: """Set a group at a specific list index.""" try: self.groups[idx] = value except IndexError: raise IndexError(INDEX_ERROR_MSG.format(idx)) def __delitem__(self, idx: int) -> None: """Delete a group at a specified list index.""" try: del self.groups[idx] self.total -= 1 except IndexError: raise IndexError(INDEX_ERROR_MSG.format(idx)) def __len__(self) -> int: """Get the number of total group items in the list.""" return self.total def __reversed__(self) -> Iterator[Group]: """Return the reversed group list.""" return reversed(self.groups) def __contains__(self, item: Group) -> bool: """Check whether a given group is part of the list.""" if not type(item) in (Group, str): raise ValueError("Expected type Group or str but got {}".format( type(item))) identifier = item.identifier if type(item) == Group else item return identifier in map(lambda x: x.identifier, self.groups) def __eq__(self, candidate: "GroupListResponse") -> bool: return all( (self.total == candidate.total, self.groups == candidate.groups))