Skip to content

Commit c5c944d

Browse files
author
rohitcoder
committed
Added latest time support for slack DLP
1 parent 501322a commit c5c944d

File tree

2 files changed

+67
-20
lines changed

2 files changed

+67
-20
lines changed

connection.yml.sample

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,5 +138,6 @@ sources:
138138
token: xoxp-XXXXXXXXXXXXXXXXXXXXXXXXX
139139
archived_channels: True ## By default False, set to True if you want to scan archived channels also
140140
limit_mins: 15 ## By default 60 mins
141+
limit_from: last_message ## By default current Unix timestamp, available options - UNIX Timestamp (e..g: 1737354387), last_message
141142
channel_ids:
142143
- XXXXXXXX

hawk_scanner/commands/slack.py

Lines changed: 66 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -22,24 +22,11 @@ def connect_slack(args, token):
2222
system.print_error(args, f"Failed to connect to Slack with error: {e.response['error']}")
2323
return None
2424

25-
26-
def check_slack_messages(args, client, patterns, profile_name, channel_types, channel_ids=None, limit_mins=60, archived_channels=False):
25+
def check_slack_messages(args, client, patterns, profile_name, channel_types, read_from, channel_ids=None, limit_mins=60, archived_channels=False):
2726
results = []
2827
try:
2928
team_info = client.team_info()
3029
workspace_url = team_info["team"]["url"].rstrip('/')
31-
32-
# Get the Unix timestamp for 'limit_mins' minutes ago
33-
current_time = time.time()
34-
oldest_time = current_time - (limit_mins * 60)
35-
36-
# Convert to human-readable time for debugging
37-
current_time_readable = datetime.fromtimestamp(current_time).strftime('%Y-%m-%d %H:%M:%S')
38-
oldest_time_readable = datetime.fromtimestamp(oldest_time).strftime('%Y-%m-%d %H:%M:%S')
39-
40-
system.print_info(args, f"Current Time: {current_time_readable}")
41-
system.print_info(args, f"Fetching messages from the last {limit_mins} minutes (Oldest Time: {oldest_time_readable}, Unix: {int(oldest_time)})")
42-
4330
# Helper function to handle rate limits
4431
hawk_args = args
4532
def rate_limit_retry(func, *args, **kwargs):
@@ -77,7 +64,6 @@ def rate_limit_retry(func, *args, **kwargs):
7764
exclude_archived=not archived_channels
7865
)
7966
channels.extend(response.get("channels", []))
80-
8167
# Update the cursor for the next batch
8268
cursor = response.get("response_metadata", {}).get("next_cursor")
8369

@@ -106,10 +92,32 @@ def rate_limit_retry(func, *args, **kwargs):
10692
for channel in channels:
10793
channel_name = channel["name"]
10894
channel_id = channel["id"]
109-
95+
latest_time = int(time.time())
96+
97+
if read_from == 'last_message':
98+
system.print_info(args, "Fetching messages from the last message in the channel")
99+
last_msg = get_last_msg(args, client, channel_id)
100+
if last_msg:
101+
latest_time = float(last_msg['timestamp'])
102+
# Add 1 second to the latest time to get latest message along with it
103+
latest_time += 1
104+
elif read_from:
105+
try:
106+
read_from = int(read_from)
107+
latest_time = read_from
108+
# Add 1 second to the latest time to get latest message along with it
109+
latest_time += 1
110+
except ValueError:
111+
system.print_error(args, "Invalid value for read_from in Slack configuration. It should be either 'last_message' or a valid Unix timestamp")
112+
exit(1)
113+
else:
114+
latest_time = int(time.time())
115+
oldest_time = latest_time - (limit_mins * 60)
110116
# Get messages from the channel within the time range
111117
system.print_info(args, f"Checking messages in channel {channel_name} ({channel_id})")
112-
messages = rate_limit_retry(client.conversations_history, channel=channel_id, oldest=oldest_time)["messages"]
118+
system.print_info(args, f"Fetching messages from {time.strftime('%Y-%m-%d %H:%M:%S', time.gmtime(oldest_time))} to {time.strftime('%Y-%m-%d %H:%M:%S', time.gmtime(latest_time))}")
119+
messages = rate_limit_retry(client.conversations_history, channel=channel_id, oldest=oldest_time, latest=latest_time)["messages"]
120+
print(f"Found {len(messages)} messages in channel {channel_name} ({channel_id})")
113121
for message in messages:
114122
user = message.get("user", "")
115123
text = message.get("text")
@@ -205,8 +213,6 @@ def rate_limit_retry(func, *args, **kwargs):
205213
return results
206214

207215

208-
209-
210216
def download_file(args, client, file_info, folder_path) -> str:
211217
try:
212218
# Ensure the folder exists
@@ -241,6 +247,44 @@ def download_file(args, client, file_info, folder_path) -> str:
241247
system.print_error(args, f"An unexpected error occurred: {str(e)}")
242248
return None
243249

250+
def get_last_msg(args, client, channel_id):
251+
"""
252+
Fetches the last message from the specified Slack channel.
253+
Handles rate limits and retries if necessary.
254+
"""
255+
try:
256+
def rate_limit_retry(func, *args, **kwargs):
257+
while True:
258+
try:
259+
return func(*args, **kwargs)
260+
except SlackApiError as e:
261+
if e.response["error"] == "ratelimited":
262+
retry_after = int(e.response.headers.get("Retry-After", 1))
263+
system.print_info(args, f"Rate limited. Retrying after {retry_after} seconds...")
264+
time.sleep(retry_after)
265+
else:
266+
raise
267+
268+
system.print_info(args, f"Fetching last message from channel {channel_id}")
269+
response = rate_limit_retry(client.conversations_history, channel=channel_id, limit=1)
270+
messages = response.get("messages", [])
271+
272+
if messages:
273+
last_message = messages[0] # Get the latest message
274+
return {
275+
'user': last_message.get("user", "Unknown"),
276+
'text': last_message.get("text", ""),
277+
'timestamp': last_message.get("ts", ""),
278+
'message_link': f"https://slack.com/archives/{channel_id}/p{last_message.get('ts', '').replace('.', '')}",
279+
}
280+
else:
281+
system.print_info(args, f"No messages found in channel {channel_id}")
282+
return None
283+
284+
except SlackApiError as e:
285+
system.print_error(args, f"Failed to fetch last message from channel {channel_id} with error: {e.response['error']}")
286+
return None
287+
244288

245289
def execute(args):
246290
results = []
@@ -255,6 +299,8 @@ def execute(args):
255299
patterns = system.get_fingerprint_file(args)
256300

257301
for key, config in slack_config.items():
302+
current_unix_timestamp = int(time.time())
303+
read_from = config.get('read_from', current_unix_timestamp)
258304
token = config.get('token')
259305
channel_types = config.get('channel_types', "public_channel,private_channel")
260306
channel_ids = config.get('channel_ids', [])
@@ -269,7 +315,7 @@ def execute(args):
269315

270316
client = connect_slack(args, token)
271317
if client:
272-
results += check_slack_messages(args, client, patterns, key, channel_types, channel_ids, limit_mins, archived_channels)
318+
results += check_slack_messages(args, client, patterns, key, channel_types, read_from, channel_ids, limit_mins, archived_channels)
273319
else:
274320
system.print_error(args, "No Slack connection details found in connection.yml")
275321
else:

0 commit comments

Comments
 (0)