def can_access_api(self): """ :return: True when we can access the REST API """ try: version_dict = self.get_version() except Exception as e: msg = 'An exception was raised when connecting to REST API: "%s"' raise APIException(msg % e) else: """ This is an example response from the REST API { "branch": "develop", "dirty": "Yes", "revision": "f1cae98161 - 24 Jun 2015 16:29", "version": "1.7.2" } """ if 'version' in version_dict: # Yup, this looks like a w3af REST API return True msg = 'Unexpected HTTP response when connecting to REST API' raise APIException(msg)
def get_findings(self): code, data = self.conn.send_request('/kb/', method='GET') if code != 200: raise APIException('Failed to retrieve findings') findings = data.get('items', None) if findings is None: raise APIException('Failed to retrieve findings') return [Finding(self.conn, f['id']) for f in findings]
def get_exceptions(self): url = '/scans/%s/exceptions/' % self.scan_id code, data = self.conn.send_request(url, method='GET') if code != 200: raise APIException('Failed to retrieve exceptions') exceptions = data.get('items', None) if exceptions is None: raise APIException('Failed to retrieve exceptions') return [ScannerException(self.conn, e['href']) for e in exceptions]
def get_urls(self): url = '/scans/%s/urls/' % self.scan_id code, data = self.conn.send_request(url, method='GET') if code != 200: raise APIException('Failed to retrieve urls') urls = data.get('items', None) if urls is None: raise APIException('Failed to retrieve urls') return urls
def get_fuzzable_requests(self): url = '/scans/%s/fuzzable-requests/' % self.scan_id code, data = self.conn.send_request(url, method='GET') if code != 200: raise APIException('Failed to retrieve fuzzable requests') encoded_fuzzable_requests = data.get('items', None) if encoded_fuzzable_requests is None: raise APIException('Failed to retrieve fuzzable requests') return [base64.b64decode(fr) for fr in encoded_fuzzable_requests]
def send_request(self, path, json_data=None, method='GET'): full_url = urlparse.urljoin(self.api_url, path) if method == 'GET': response = self.session.get(full_url, timeout=self.timeout) elif method == 'DELETE': response = self.session.delete(full_url, timeout=self.timeout) elif method == 'POST': data = json.dumps(json_data) response = self.session.post(full_url, data=data, timeout=self.timeout) else: raise ValueError('Invalid HTTP method: "%s"' % method) try: json_data = response.json() except ValueError: msg = ('REST API service did not return JSON, if this issue' ' persists please create an issue in the w3af framework' ' repository at %s. The response body starts with: "%s"') raise APIException(msg % (ISSUE_URL, response.content[:20])) pretty_json = json.dumps(json_data, indent=4) msg = 'Received %s HTTP response from the wire:\n%s' api_logger.debug(msg % (response.status_code, pretty_json)) # # Error handling # if response.status_code in API_EXCEPTIONS: error = json_data.get('message', None) exception_klass = API_EXCEPTIONS.get(response.status_code) if error is not None: raise exception_klass(error) else: msg = ('REST API service did not return the expected "message"' ' attribute for the %s response. Please create a new' ' issue in the w3af framework repository at %s with' ' this JSON data:\n\n%s') dump = json.dumps(json_data, indent=4) args = (response.status_code, ISSUE_URL, dump) raise APIException(msg % args) return response.status_code, json_data
def get_page(self, page_number): url = '/scans/%s/log?page=%s' % (self.scan_id, page_number) code, page = self.conn.send_request(url, method='GET') if code != 200: raise APIException('Could not retrieve log entry list') entries = page.get('entries', None) if entries is None: raise APIException('Could not retrieve log entries attribute') for entry_dict in entries: yield LogEntry(entry_dict['type'], entry_dict['message'], entry_dict['time'], entry_dict['severity'])
def get_exceptions(self): url = '/scans/%s/exceptions/' % self.scan_id code, data = self.conn.send_request(url, method='GET') if code != 200: message = data.get('message', 'None') args = (code, message) raise APIException('Failed to retrieve exceptions. Received HTTP' ' response code %s. Message: "%s"' % args) exceptions = data.get('items', None) if exceptions is None: raise APIException('Failed to retrieve exceptions') return [ScannerException(self.conn, e['href']) for e in exceptions]
def update(self): code, data = self.conn.send_request(self.resource_href, method='GET') if code != 200: msg = 'Could not retrieve resource detail "%s"' raise APIException(msg % self.resource_href) self._data = data return self._data
def can_access_api(self): """ :return: True when we can access the REST API """ try: version_dict = self.get_version() except Exception, e: msg = 'An exception was raised when connecting to REST API: "%s"' raise APIException(msg % e)
def send_request(self, path, json_data=None, method='GET'): full_url = urlparse.urljoin(self.api_url, path) if method == 'GET': response = self.session.get(full_url, timeout=self.timeout) elif method == 'DELETE': response = self.session.delete(full_url, timeout=self.timeout) elif method == 'POST': data = json.dumps(json_data) response = self.session.post(full_url, data=data, timeout=self.timeout) else: raise ValueError('Invalid HTTP method: "%s"' % method) try: json_data = response.json() except ValueError: msg = ('REST API service did not return JSON, if this issue' ' persists please create an issue in the w3af framework' ' repository at %s') raise APIException(msg % ISSUE_URL) pretty_json = json.dumps(json_data, indent=4) msg = 'Received %s HTTP response from the wire:\n%s' api_logger.debug(msg % (response.status_code, pretty_json)) # # Error handling # if response.status_code in (400, 403, 404): error = json_data.get('error', None) if error is not None: raise APIException(error) else: msg = ('REST API service did not return the expected "error"' ' attribute for the %s response. Please create a new' ' issue in the w3af framework repository at %s') raise APIException(msg % (response.status_code, ISSUE_URL)) return response.status_code, json_data
def get_page(self, page_number): """ :yield: Log entries for the given page number """ url = '/scans/%s/log?page=%s' % (self.scan_id, page_number) code, page = self.conn.send_request(url, method='GET') if code != 200: message = page.get('message', 'None') args = (code, message) raise APIException('Failed to retrieve scan log. Received HTTP' ' response code %s. Message: "%s"' % args) entries = page.get('entries', None) if entries is None: raise APIException('Could not retrieve log entries attribute') for entry_dict in entries: yield LogEntry.from_entry_dict(entry_dict)
def get_status(self): assert self.scan_id is not None, 'No scan_id has been set' code, data = self.conn.send_request('/scans/%s/status' % self.scan_id, method='GET') if code != 200: raise APIException('Failed to retrieve scan status. Received HTTP' ' response code %s' % code) return data
def start(self, scan_profile, target_urls): data = {'scan_profile': scan_profile, 'target_urls': target_urls} code, data = self.conn.send_request('/scans/', json_data=data, method='POST') if code != 201: raise APIException('Failed to start the new scan') api_logger.debug('Scan successfully started using REST API') self.scan_id = data['id']
def get_by_start_id(self, start_id): """ :yield: Log entries starting from :start_id: and ending 200 entries after. In most cases easier to call than the paginate one because there is no need to keep track of the already read entries in a specific page. """ url = '/scans/%s/log?id=%s' % (self.scan_id, start_id) code, page = self.conn.send_request(url, method='GET') if code != 200: raise APIException('Could not retrieve log entry list') entries = page.get('entries', None) if entries is None: raise APIException('Could not retrieve log entries attribute') for entry_dict in entries: yield LogEntry(entry_dict['type'], entry_dict['message'], entry_dict['time'], entry_dict['severity'], entry_dict['id'])
def get_status(self): assert self.scan_id is not None, 'No scan_id has been set' code, data = self.conn.send_request('/scans/%s/status' % self.scan_id, method='GET') if code != 200: message = data.get('message', 'None') args = (code, message) raise APIException('Failed to retrieve scan status. Received HTTP' ' response code %s. Message: "%s"' % args) return data
def get_scans(self): """ :return: A list with all the Scan instances available in the remote API """ code, data = self.send_request('/scans/', method='GET') if code != 200: msg = 'Failed to retrieve scans. Unexpected code %s' raise APIException(msg % code) scans = data.get('items', None) if scans is None: raise APIException('Failed to retrieve scans, no "items" in JSON.') scan_instances = [] for scan_json in scans: scan_id = scan_json['id'] scan_status = scan_json['status'] scan = Scan(self, scan_id=scan_id, status=scan_status) scan_instances.append(scan) return scan_instances
def finding_data(self): """ Cached access to the KB so a piece of code that accesses this finding doesn't perform one HTTP request to the REST API for each attribute :return: The JSON data """ code, data = self.conn.send_request('/kb/%s' % self.finding_id, method='GET') if code != 200: raise APIException('Could not retrieve finding detail') return data
def start(self, scan_profile, target_urls): data = {'scan_profile': scan_profile, 'target_urls': target_urls} code, data = self.conn.send_request('/scans/', json_data=data, method='POST') if code != 201: message = data.get('message', 'None') args = (code, message) raise APIException('Failed to start the new scan. Received HTTP' ' response code %s. Message: "%s"' % args) api_logger.debug('Scan successfully started using REST API') self.scan_id = data['id']
def get_by_start_id(self, start_id): """ :yield: Log entries starting from :start_id: and ending 200 entries after. In most cases easier to call than the paginate one because there is no need to keep track of the already read entries in a specific page. """ url = '/scans/%s/log?id=%s' % (self.scan_id, start_id) code, page = self.conn.send_request(url, method='GET') if code != 200: message = page.get('message', 'None') args = (code, message) raise APIException('Failed to retrieve scan log. Received HTTP' ' response code %s. Message: "%s"' % args) entries = page.get('entries', None) if entries is None: raise APIException('Could not retrieve log entries attribute') for entry_dict in entries: yield LogEntry.from_entry_dict(entry_dict)
def from_entry_dict(cls, entry_dict): """ This is a "constructor" for the LogEntry class. :param entry_dict: A dict we get from the REST API :return: An instance of LogEntry. """ # Debug helper # https://circleci.com/gh/andresriancho/w3af-api-docker/30 try: _type = entry_dict['type'] _id = entry_dict['id'] _time = entry_dict['time'] message = entry_dict['message'] severity = entry_dict['severity'] except KeyError: msg = ('Missing expected log entry attribute. Log entry' ' object is:\n\n%s') raise APIException(msg % json.dumps(entry_dict, indent=4)) return cls(_type, message, _time, severity, _id)
else: """ This is an example response from the REST API { "branch": "develop", "dirty": "Yes", "revision": "f1cae98161 - 24 Jun 2015 16:29", "version": "1.7.2" } """ if 'version' in version_dict: # Yup, this looks like a w3af REST API return True msg = 'Unexpected HTTP response when connecting to REST API' raise APIException(msg) def get_version(self): code, version_dict = self.send_request('/version') return version_dict def set_verbose(self, verbose): # Get level based on verbose boolean level = logging.DEBUG if verbose else logging.CRITICAL # Configure my own logger api_logger.setLevel(level=level) ch = logging.StreamHandler() ch.setLevel(level)