Skip to content

Commit de40fd8

Browse files
committed
updated the code with major changes
1 parent 1397c5a commit de40fd8

File tree

2 files changed

+327
-181
lines changed

2 files changed

+327
-181
lines changed

Packs/SilentPush/Integrations/SilentPush/SilentPush.py

Lines changed: 209 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,6 @@
2222
def mock_debug(message):
2323
"""Print debug messages to the XSOAR logs"""
2424
print(f"DEBUG: {message}")
25-
26-
2725
demisto.debug = mock_debug
2826

2927
''' CONSTANTS '''
@@ -105,42 +103,152 @@ def _http_request(self, method: str, url_suffix: str, params: dict = None, data:
105103
except Exception as e:
106104
demisto.error(f'Error in API call: {str(e)}')
107105
raise
108-
109-
def list_domain_information(self, domain: str) -> dict:
106+
107+
def list_domain_information(self, domains: Union[str, List[str]]) -> Dict:
110108
"""
111-
Fetches domain information such as WHOIS data, domain age, and risk scores.
109+
Fetches domain information including WHOIS data and risk scores for multiple domains.
112110
113111
Args:
114-
domain (str): The domain to fetch information for.
115-
112+
domains: Either a single domain string or a list of domain strings
113+
116114
Returns:
117-
dict: A dictionary containing domain information fetched from the API.
115+
Dict: A dictionary containing combined domain information and risk scores
118116
"""
119-
demisto.debug(f'Fetching domain information for domain: {domain}')
120-
url_suffix = f'explore/domain/domaininfo/{domain}'
121-
return self._http_request('GET', url_suffix)
117+
demisto.debug(f'Fetching domain information for: {domains}')
118+
domain_list = [domains] if isinstance(domains, str) else domains
119+
120+
if len(domain_list) > 100:
121+
raise DemistoException("Maximum of 100 domains can be submitted in a single request")
122+
123+
if len(domain_list) == 1:
122124

125+
domain = domain_list[0]
126+
try:
127+
128+
domain_info_response = self._http_request(
129+
method='GET',
130+
url_suffix=f'explore/domain/domaininfo/{domain}'
131+
)
132+
domain_info = domain_info_response.get('response', {}).get('domaininfo', {})
133+
134+
risk_score_response = self._http_request(
135+
method='GET',
136+
url_suffix=f'explore/domain/riskscore/{domain}'
137+
)
138+
risk_info = risk_score_response.get('response', {})
139+
140+
combined_info = {
141+
'domain': domain,
142+
**domain_info,
143+
'sp_risk_score': risk_info.get('sp_risk_score'),
144+
'sp_risk_score_explain': risk_info.get('sp_risk_score_explain')
145+
}
146+
147+
return {'domains': [combined_info]}
148+
149+
except Exception as e:
150+
raise DemistoException(f'Failed to fetch information for domain {domain}: {str(e)}')
151+
152+
else:
153+
154+
try:
155+
156+
domains_data = {'domains': domain_list}
157+
bulk_info_response = self._http_request(
158+
method='POST',
159+
url_suffix='explore/bulk/domaininfo',
160+
data=domains_data
161+
)
162+
163+
bulk_risk_response = self._http_request(
164+
method='POST',
165+
url_suffix='explore/bulk/domain/riskscore',
166+
data=domains_data
167+
)
168+
169+
domain_info_list = bulk_info_response.get('response', {}).get('domaininfo', [])
170+
risk_score_list = bulk_risk_response.get('response', [])
171+
domain_info_dict = {item['domain']: item for item in domain_info_list}
172+
risk_score_dict = {item['domain']: item for item in risk_score_list}
173+
174+
combined_results = []
175+
for domain in domain_list:
176+
domain_data = domain_info_dict.get(domain, {})
177+
risk_data = risk_score_dict.get(domain, {})
178+
179+
combined_results.append({
180+
'domain': domain,
181+
**domain_data,
182+
'sp_risk_score': risk_data.get('sp_risk_score'),
183+
'sp_risk_score_explain': risk_data.get('sp_risk_score_explain')
184+
})
185+
186+
return {'domains': combined_results}
187+
188+
except Exception as e:
189+
raise DemistoException(f'Failed to fetch bulk domain information: {str(e)}')
190+
191+
123192
def get_domain_certificates(self, domain: str) -> dict:
124193
"""
125194
Fetches SSL/TLS certificate data for a given domain.
126-
195+
If the job is not completed, it polls the job status periodically.
196+
127197
Args:
128198
domain (str): The domain to fetch certificate information for.
129-
199+
130200
Returns:
131201
dict: A dictionary containing certificate information fetched from the API.
132202
"""
133203
demisto.debug(f'Fetching certificate information for domain: {domain}')
204+
205+
134206
url_suffix = f'explore/domain/certificates/{domain}'
135-
return self._http_request('GET', url_suffix)
207+
response = self._http_request('GET', url_suffix, params={
208+
'limit': 100,
209+
'skip': 0,
210+
'with_metadata': 0
211+
})
212+
213+
214+
job_status_url = response.get('response', {}).get('job_status', {}).get('get')
215+
if not job_status_url:
216+
demisto.error('Job status URL not found in the response')
217+
return response
218+
219+
220+
job_complete = False
221+
while not job_complete:
222+
demisto.debug(f'Checking job status at {job_status_url}')
223+
224+
225+
job_response = self._http_request('GET', job_status_url)
226+
job_status = job_response.get('response', {}).get('job_status', {}).get('status')
227+
228+
if job_status == 'COMPLETED':
229+
job_complete = True
230+
demisto.debug('Job completed, fetching certificates.')
231+
232+
certificate_data = job_response.get('response', {}).get('domain_certificates', [])
233+
return certificate_data
234+
elif job_status == 'FAILED':
235+
demisto.error('Job failed to complete.')
236+
return {'error': 'Job failed'}
237+
else:
238+
239+
demisto.debug('Job is still in progress. Retrying...')
240+
time.sleep(5)
241+
242+
return {}
243+
136244

137245
def search_domains(self,
138-
query: Optional[str] = None,
139-
start_date: Optional[str] = None,
140-
end_date: Optional[str] = None,
141-
risk_score_min: Optional[int] = None,
142-
risk_score_max: Optional[int] = None,
143-
limit: int = 100) -> dict:
246+
query: Optional[str] = None,
247+
start_date: Optional[str] = None,
248+
end_date: Optional[str] = None,
249+
risk_score_min: Optional[int] = None,
250+
risk_score_max: Optional[int] = None,
251+
limit: int = 100) -> dict:
144252
"""
145253
Search for domains with optional filters.
146254
@@ -157,18 +265,21 @@ def search_domains(self,
157265
"""
158266
demisto.debug(f'Searching domains with query: {query}')
159267
url_suffix = 'explore/domain/search'
160-
268+
161269
params = {k: v for k, v in {
162-
'query': query,
270+
'domain': query,
163271
'start_date': start_date,
164272
'end_date': end_date,
165273
'risk_score_min': risk_score_min,
166274
'risk_score_max': risk_score_max,
167275
'limit': limit
168276
}.items() if v is not None}
169277

170-
return self._http_request('GET', url_suffix, params=params)
171-
278+
try:
279+
return self._http_request('GET', url_suffix, params=params)
280+
except Exception as e:
281+
demisto.error(f"Error in search_domains API request: {str(e)}")
282+
172283
def list_domain_infratags(self, domains: list, cluster: Optional[bool] = False, mode: Optional[str] = 'live', match: Optional[str] = 'self', as_of: Optional[str] = None) -> dict:
173284
"""
174285
Get infratags for multiple domains with optional clustering and additional filtering options.
@@ -185,7 +296,7 @@ def list_domain_infratags(self, domains: list, cluster: Optional[bool] = False,
185296
"""
186297
demisto.debug(f'Fetching infratags for domains: {domains} with cluster={cluster}, mode={mode}, match={match}, as_of={as_of}')
187298

188-
# Loop through the domains to create individual requests
299+
189300
results = {}
190301
for domain in domains:
191302
url = f'https://api.silentpush.com/api/v1/merge-api/explore/domain/infratag/{domain}'
@@ -196,14 +307,17 @@ def list_domain_infratags(self, domains: list, cluster: Optional[bool] = False,
196307
'as_of': as_of
197308
}
198309
try:
199-
response = self._http_request('GET', url, params=data) # Assuming GET method for this endpoint
310+
311+
response = self._http_request('GET', url, params=data)
200312
results[domain] = response
201313
except Exception as e:
202314
demisto.error(f"Error fetching infratags for domain {domain}: {str(e)}")
203315
results[domain] = {"error": str(e)}
204316

205317
return results
206318

319+
320+
207321
def test_module(client: Client) -> str:
208322
"""
209323
Tests connectivity to the SilentPush API and checks the authentication status.
@@ -232,49 +346,75 @@ def test_module(client: Client) -> str:
232346
''' COMMAND FUNCTIONS '''
233347

234348

235-
def list_domain_information_command(client: Client, args: dict) -> CommandResults:
349+
def list_domain_information_command(client: Client, args: Dict[str, Any]) -> CommandResults:
236350
"""
237351
Command handler for fetching domain information.
238352
239-
This function processes the command for 'silentpush-list-domain-information', retrieves the
240-
domain information using the client, and formats it for XSOAR output.
241-
242353
Args:
243-
client (Client): The client instance to fetch the data.
244-
args (dict): The arguments passed to the command, including the domain.
245-
354+
client (Client): The client instance to fetch the data
355+
args (dict): Command arguments
356+
246357
Returns:
247-
CommandResults: The command results containing readable output and the raw response.
358+
CommandResults: XSOAR command results
248359
"""
249-
domain = args.get('domain', 'silentpush.com')
250-
demisto.debug(f'Processing domain: {domain}')
251-
raw_response = client.list_domain_information(domain)
360+
361+
domains_arg = args.get('domains', args.get('domain'))
362+
363+
if not domains_arg:
364+
raise DemistoException('No domains provided. Please provide domains using either the "domain" or "domains" argument.')
365+
366+
367+
if isinstance(domains_arg, str):
368+
domains = [d.strip() for d in domains_arg.split(',')]
369+
else:
370+
domains = domains_arg
371+
372+
demisto.debug(f'Processing domain(s): {domains}')
373+
374+
375+
raw_response = client.list_domain_information(domains)
252376
demisto.debug(f'Response from API: {raw_response}')
253-
254-
readable_output = tableToMarkdown('Domain Information', raw_response)
255-
377+
378+
379+
markdown = []
380+
for domain_info in raw_response.get('domains', []):
381+
markdown.append(f"### Domain: {domain_info.get('domain')}")
382+
markdown.append("#### Domain Information:")
383+
markdown.append(f"- Age: {domain_info.get('age', 'N/A')} days")
384+
markdown.append(f"- Registrar: {domain_info.get('registrar', 'N/A')}")
385+
markdown.append(f"- Created Date: {domain_info.get('whois_created_date', 'N/A')}")
386+
markdown.append(f"- Risk Score: {domain_info.get('sp_risk_score', 'N/A')}")
387+
markdown.append("\n")
388+
389+
readable_output = '\n'.join(markdown)
390+
256391
return CommandResults(
257392
outputs_prefix='SilentPush.Domain',
258393
outputs_key_field='domain',
259-
outputs=raw_response,
394+
outputs=raw_response.get('domains', []),
260395
readable_output=readable_output,
261396
raw_response=raw_response
262397
)
263398

264-
265399
def get_domain_certificates_command(client: Client, args: dict) -> CommandResults:
266400
"""
267401
Command handler for fetching domain certificate information.
268402
"""
269403
domain = args.get('domain', 'silentpush.com')
270404
demisto.debug(f'Processing certificates for domain: {domain}')
271405

272-
273-
demisto.debug('Entering get_domain_certificates_command function')
274-
275406
raw_response = client.get_domain_certificates(domain)
276407
demisto.debug(f'Response from API: {raw_response}')
277408

409+
if 'error' in raw_response:
410+
return CommandResults(
411+
outputs_prefix='SilentPush.Certificates',
412+
outputs_key_field='domain',
413+
outputs=raw_response,
414+
readable_output=f"Error: {raw_response['error']}",
415+
raw_response=raw_response
416+
)
417+
278418
readable_output = tableToMarkdown('Domain Certificates', raw_response)
279419

280420
return CommandResults(
@@ -284,10 +424,9 @@ def get_domain_certificates_command(client: Client, args: dict) -> CommandResult
284424
readable_output=readable_output,
285425
raw_response=raw_response
286426
)
287-
427+
288428

289429
def search_domains_command(client: Client, args: dict) -> CommandResults:
290-
291430
query = args.get('query')
292431
start_date = args.get('start_date')
293432
end_date = args.get('end_date')
@@ -297,16 +436,30 @@ def search_domains_command(client: Client, args: dict) -> CommandResults:
297436

298437
demisto.debug(f'Searching domains with query: {query}')
299438

300-
raw_response = client.search_domains(
301-
query=query,
302-
start_date=start_date,
303-
end_date=end_date,
304-
risk_score_min=risk_score_min,
305-
risk_score_max=risk_score_max,
306-
limit=limit
307-
)
439+
try:
440+
raw_response = client.search_domains(
441+
query=query,
442+
start_date=start_date,
443+
end_date=end_date,
444+
risk_score_min=risk_score_min,
445+
risk_score_max=risk_score_max,
446+
limit=limit
447+
)
448+
except Exception as e:
449+
return CommandResults(
450+
readable_output=f"Error: {str(e)}",
451+
raw_response={},
452+
outputs_prefix='SilentPush.Error',
453+
outputs_key_field='error'
454+
)
308455

309-
readable_output = tableToMarkdown('Domain Search Results', raw_response.get('results', []))
456+
457+
if raw_response.get('response') and 'records' in raw_response['response']:
458+
records = raw_response['response']['records']
459+
else:
460+
records = []
461+
462+
readable_output = tableToMarkdown('Domain Search Results', records)
310463

311464
return CommandResults(
312465
outputs_prefix='SilentPush.SearchResults',

0 commit comments

Comments
 (0)