1+ #!/usr/bin/env python3
2+ # encoding: utf-8
3+
4+ import json
5+ import requests
6+ from cortexutils .responder import Responder
7+
8+ class Slack (Responder ):
9+
10+ def __init__ (self ):
11+ Responder .__init__ (self )
12+ self .slack_token = self .get_param (
13+ "config.slack_token" , None , "Missing Slack bot token" )
14+ self .participants = self .get_param (
15+ "config.participants" , [], "Missing Slack participant emails (list)" )
16+ self .channel_prefix = self .get_param (
17+ "config.channel_prefix" , "case-" , "Missing channel prefix" )
18+ self .visibility = self .get_param (
19+ "config.visibility" , "private" ) # 'private' or 'public'
20+ self .thehive_base_url = self .get_param ("config.thehive_base_url" , None )
21+ self .post_summary = self .get_param ("config.post_summary" , True )
22+ self .post_description = self .get_param ("config.post_description" , False )
23+
24+ self .service = self .get_param ("config.service" )
25+
26+ def find_existing_channel (self , name , headers ):
27+ url = "https://slack.com/api/conversations.list"
28+ cursor = None
29+ while True :
30+ params = {
31+ "limit" : 200 ,
32+ "exclude_archived" : True ,
33+ "types" : "public_channel,private_channel"
34+ }
35+ if cursor :
36+ params ["cursor" ] = cursor
37+ resp = requests .get (url , headers = headers , params = params ).json ()
38+ for ch in resp .get ("channels" , []):
39+ if ch ["name" ] == name :
40+ return ch ["id" ]
41+ cursor = resp .get ("response_metadata" , {}).get ("next_cursor" )
42+ if not cursor :
43+ break
44+ return None
45+
46+ def run (self ):
47+ Responder .run (self )
48+ if self .service == "createchannel" :
49+ # Gather data from TheHive case
50+ case_id = self .get_param ("data.caseId" )
51+ case_unique_id = self .get_param ("data.id" )
52+ title = self .get_param ("data.title" , "" )
53+ owner = self .get_param ("data.owner" , "" )
54+ description = self .get_param ("data.description" , "" )
55+
56+ # Slack channel name must be lowercase, <=80 chars, no spaces, no periods, no commas
57+ channel_name = f"{ self .channel_prefix } { case_id } " .replace (" " , "-" ).replace ("." , "" ).replace ("," , "" ).lower ()
58+ channel_name = channel_name [:80 ]
59+
60+ headers = {
61+ "Authorization" : f"Bearer { self .slack_token } " ,
62+ "Content-Type" : "application/json"
63+ }
64+
65+ # 1. validate participant emails BEFORE creating the channel
66+ user_ids = []
67+ for email in self .participants :
68+ lookup_url = "https://slack.com/api/users.lookupByEmail"
69+ params = {"email" : email }
70+ lookup_resp = requests .get (lookup_url , headers = headers , params = params )
71+ user_data = lookup_resp .json ()
72+ if user_data .get ("ok" ):
73+ user_ids .append (user_data ["user" ]["id" ])
74+ else :
75+ print (f"[WARNING] Could not resolve Slack user for email { email } : { user_data .get ('error' )} " )
76+ if not user_ids :
77+ self .error ("No valid Slack users found from participant emails. Aborting channel creation." )
78+
79+ # 2. create channel only after we have valid users
80+ channel_id = self .find_existing_channel (channel_name , headers )
81+ if channel_id :
82+ print (f"Channel '{ channel_name } ' already exists. Reusing." )
83+ else :
84+ create_url = "https://slack.com/api/conversations.create"
85+ payload = {
86+ "name" : channel_name ,
87+ "is_private" : self .visibility == "private"
88+ }
89+ create_resp = requests .post (create_url , headers = headers , json = payload )
90+ create_data = create_resp .json ()
91+ if not create_data .get ("ok" ):
92+ self .error (f"Slack channel creation failed: { create_data .get ('error' )} " )
93+ channel_id = create_data ["channel" ]["id" ]
94+
95+
96+ # 3. invite users
97+ if user_ids :
98+ invite_url = "https://slack.com/api/conversations.invite"
99+ invite_payload = {
100+ "channel" : channel_id ,
101+ "users" : "," .join (user_ids ),
102+ "force" : True
103+
104+ }
105+ invite_resp = requests .post (invite_url , headers = headers , json = invite_payload )
106+ invite_data = invite_resp .json ()
107+ if not invite_data .get ("ok" ):
108+ self .error (f"Error inviting users: { invite_data .get ('error' )} " )
109+
110+ # 4. post a summary message
111+ if self .post_summary :
112+ post_url = "https://slack.com/api/chat.postMessage"
113+
114+ def format_severity (score ):
115+ return {1 : "LOW" , 2 : "MEDIUM" , 3 : "HIGH" , 4 : "CRITICAL" }.get (score , "UNKNOWN" )
116+
117+ def format_tlp (level ):
118+ return {
119+ - 1 : "UNKNOWN" ,
120+ 0 : "WHITE" ,
121+ 1 : "GREEN" ,
122+ 2 : "AMBER" ,
123+ 3 : "RED"
124+ }.get (level , "UNKNOWN" )
125+
126+ severity = format_severity (self .get_param ("data.severity" , 2 ))
127+ tlp = format_tlp (self .get_param ("data.tlp" , 2 ))
128+ status = self .get_param ("data.extendedStatus" , "New" )
129+
130+ case_link_text = ""
131+ if self .thehive_base_url :
132+ case_web_link = f"{ self .thehive_base_url } /cases/{ case_unique_id } /details"
133+ case_link_text = f"\n 🔗 <{ case_web_link } |Open Case in TheHive>"
134+
135+ summary_lines = [
136+ "*🚨 New Slack Channel created from TheHive*" ,
137+ f"*Case ID:* { case_id } — *{ title } *" ,
138+ f"*Owner:* { owner } " ,
139+ f"*Severity:* { severity } | *TLP:* { tlp } | *Status:* { status } " ,
140+ ]
141+
142+ if self .post_description and description .strip ():
143+ summary_lines .append (f"*Description:*\n { description } " )
144+
145+ if case_link_text :
146+ summary_lines .append (case_link_text )
147+
148+ summary = "\n " .join (summary_lines )
149+
150+ post_payload = {
151+ "channel" : channel_id ,
152+ "text" : summary
153+ }
154+ post_resp = requests .post (post_url , headers = headers , json = post_payload )
155+ post_data = post_resp .json ()
156+ if not post_data .get ("ok" ):
157+ self .error (f"[ERROR] Failed to post summary message: { post_data .get ('error' )} " )
158+
159+ self .report ({
160+ "channel_name" : channel_name ,
161+ "channel_id" : channel_id ,
162+ "invited_users" : user_ids ,
163+ "message" : f"Slack channel `{ channel_name } ` created and users invited."
164+ })
165+
166+ if __name__ == "__main__" :
167+ Slack ().run ()
0 commit comments