Skip to content

Commit

Permalink
Merge pull request #487 from DrDroidLab/slack_bot_demo
Browse files Browse the repository at this point in the history
Slack bot demo
  • Loading branch information
droid-mohit authored Oct 1, 2024
2 parents e339a38 + b52880f commit d47174f
Show file tree
Hide file tree
Showing 5 changed files with 85 additions and 10 deletions.
10 changes: 10 additions & 0 deletions connectors/authentication.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
from accounts.authentication import AccountApiTokenAuthentication
from accounts.cache import GLOBAL_ACCOUNT_API_TOKEN_CACHE
from accounts.models import AccountApiTokenUser


class SlackBotApiTokenAuthentication(AccountApiTokenAuthentication):
def authenticate(self, request):
key = request.GET.get('token')
token = GLOBAL_ACCOUNT_API_TOKEN_CACHE.get(api_key=key)
return AccountApiTokenUser(token.account_id, token.created_by.email), token
9 changes: 8 additions & 1 deletion connectors/handlers/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,20 @@
from . import views as handler_views

urlpatterns = [
# Slack event subscription webhook
# Slack bot webhooks
path('slack_bot/handle_callback_events', handler_views.slack_bot_handle_callback_events),
path('slack_bot/app_manifest_create', handler_views.slack_manifest_create),
path('slack_bot/command_execute_playbook', handler_views.slack_bot_command_execute_playbook),

# Pagerduty bot webhooks
path('pagerduty/handle_incidents', handler_views.pagerduty_handle_incidents),
path('pagerduty/generate/webhook', handler_views.pagerduty_generate_webhook),

# Rootly bot webhooks
path('rootly/handle_incidents', handler_views.rootly_handle_incidents),
path('rootly/generate/webhook', handler_views.rootly_generate_webhook),

# Zen duty bot webhooks
path('zenduty/generate/webhook', handler_views.zenduty_generate_webhook),
path('zenduty/handle_incidents', handler_views.zenuty_handle_incidents),
]
65 changes: 60 additions & 5 deletions connectors/handlers/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,16 +9,24 @@

from accounts.authentication import AccountApiTokenAuthentication
from accounts.models import get_request_account, Account, User, get_request_user, AccountApiToken
from connectors.authentication import SlackBotApiTokenAuthentication
from connectors.handlers.bots.pager_duty_handler import handle_pd_incident
from connectors.handlers.bots.rootly_handler import handle_rootly_incident
from connectors.handlers.bots.zenduty_handler import handle_zd_incident
from connectors.handlers.bots.slack_bot_handler import handle_slack_event_callback
from connectors.models import Site
from playbooks.utils.decorators import web_api, account_post_api, api_auth_check
from playbooks.utils.decorators import web_api, api_auth_check
from utils.proto_utils import proto_to_dict
from utils.time_utils import current_epoch_timestamp
from protos.connectors.api_pb2 import GetSlackAppManifestResponse, GetSlackAppManifestRequest, \
GetPagerDutyWebhookRequest, GetPagerDutyWebhookResponse, GetRootlyWebhookRequest, GetRootlyWebhookResponse, GetPagerDutyWebhookRequest, GetPagerDutyWebhookResponse, GetZendutyWebhookRequest, GetZendutyWebhookResponse
GetRootlyWebhookRequest, GetPagerDutyWebhookRequest, GetZendutyWebhookRequest
from utils.uri_utils import build_absolute_uri, construct_curl
from executor.crud.playbooks_crud import get_db_playbooks
from executor.workflows.tasks import test_workflow_notification
from protos.playbooks.workflow_actions.slack_message_pb2 import SlackMessageWorkflowAction
from protos.playbooks.workflow_schedules.one_off_schedule_pb2 import OneOffSchedule
from protos.playbooks.workflow_pb2 import Workflow, WorkflowAction as WorkflowActionProto, WorkflowSchedule, \
WorkflowEntryPoint, WorkflowConfiguration

logger = logging.getLogger(__name__)

Expand All @@ -27,17 +35,24 @@
def slack_manifest_create(request_message: GetSlackAppManifestRequest) -> \
Union[GetSlackAppManifestResponse, HttpResponse]:
account: Account = get_request_account()
user: User = get_request_user()

# read sample_manifest file string
sample_manifest = """
display_information:
name: MyDroid
name: MyFirstDroidApp
description: App for Automating Investigation & Actions
background_color: "#1f2126"
features:
bot_user:
display_name: MyDroid
display_name: MyFirstDroidApp
always_online: true
slash_commands:
- command: /execute_playbook
url: HOST_NAME/connectors/handlers/slack_bot/command_execute_playbook?token=TOKEN_VALUE
description: Executes Playbooks
usage_hint: "[which playbook to launch]"
should_escape: false
oauth_config:
scopes:
bot:
Expand All @@ -55,6 +70,7 @@ def slack_manifest_create(request_message: GetSlackAppManifestRequest) -> \
- mpim:read
- im:read
- groups:history
- commands
settings:
event_subscriptions:
request_url: HOST_NAME/connectors/handlers/slack_bot/handle_callback_events
Expand All @@ -76,8 +92,11 @@ def slack_manifest_create(request_message: GetSlackAppManifestRequest) -> \
app_manifest=StringValue(
value="Host name not found for generating Manifest"))

api_token = AccountApiToken(account=account, created_by=user)
api_token.save()
manifest_hostname = host_name.protocol + '://' + host_name.domain
app_manifest = sample_manifest.replace("HOST_NAME", manifest_hostname)
app_manifest = app_manifest.replace("TOKEN_VALUE", api_token.key)

return GetSlackAppManifestResponse(success=BoolValue(value=True), app_manifest=StringValue(value=app_manifest))

Expand Down Expand Up @@ -123,6 +142,38 @@ def slack_bot_handle_callback_events(request_message: HttpRequest) -> JsonRespon
return JsonResponse({'success': False, 'message': 'Slack Event Callback Handling failed'}, status=400)


@csrf_exempt
@api_view(['POST'])
@authentication_classes([SlackBotApiTokenAuthentication])
@api_auth_check
def slack_bot_command_execute_playbook(request_message: HttpRequest) -> JsonResponse:
channel_id = request_message.data.get('channel_id', None)
name = request_message.data.get('text', None)
if not name:
return JsonResponse({'success': False, 'message': 'Missing Playbook Name'}, status=400)
account: Account = get_request_account()
playbooks = get_db_playbooks(account=account, is_active=True, playbook_name=name)
if not playbooks:
return JsonResponse({'success': False, 'message': 'Playbook not found'}, status=404)
playbook = playbooks.first()
if not playbook:
return JsonResponse({'success': False, 'message': 'Playbook not found'}, status=404)
pb_proto = playbook.proto
workflow = Workflow(playbooks=[pb_proto],
actions=[WorkflowActionProto(type=WorkflowActionProto.Type.SLACK_MESSAGE,
slack_message=SlackMessageWorkflowAction(
slack_channel_id=StringValue(value=channel_id),
))],
configuration=WorkflowConfiguration(generate_summary=BoolValue(value=True)),
type=Workflow.Type.STANDARD,
entry_points=[WorkflowEntryPoint(type=WorkflowEntryPoint.Type.API, api={})],
schedule=WorkflowSchedule(one_off=OneOffSchedule()),
)
test_workflow_notification.delay(workflow_json=proto_to_dict(workflow), account_id=account.id,
message_type=WorkflowActionProto.Type.SLACK_MESSAGE, created_by='SLACK_COMMAND')
return JsonResponse({'success': True, 'message': "Handling successfull"}, status=200)


@web_api(GetPagerDutyWebhookRequest)
def pagerduty_generate_webhook(request_message: GetPagerDutyWebhookRequest) -> HttpResponse:
account: Account = get_request_account()
Expand All @@ -145,6 +196,7 @@ def pagerduty_generate_webhook(request_message: GetPagerDutyWebhookRequest) -> H
curl = construct_curl('POST', uri, headers=headers, payload=None)
return HttpResponse(curl, content_type="text/plain", status=200)


@web_api(GetZendutyWebhookRequest)
def zenduty_generate_webhook(request_message: GetZendutyWebhookRequest) -> HttpResponse:
account: Account = get_request_account()
Expand All @@ -167,6 +219,7 @@ def zenduty_generate_webhook(request_message: GetZendutyWebhookRequest) -> HttpR
curl = construct_curl('POST', uri, headers=headers, payload=None)
return HttpResponse(curl, content_type="text/plain", status=200)


@web_api(GetRootlyWebhookRequest)
def rootly_generate_webhook(request_message: GetRootlyWebhookRequest) -> HttpResponse:
account: Account = get_request_account()
Expand Down Expand Up @@ -203,6 +256,7 @@ def pagerduty_handle_incidents(request_message: HttpRequest) -> JsonResponse:
logger.error(f'Error handling pagerduty incident: {str(e)}')
return JsonResponse({'success': False, 'message': f"pagerduty incident Handling failed"}, status=500)


@csrf_exempt
@api_view(['POST'])
@authentication_classes([AccountApiTokenAuthentication])
Expand All @@ -215,7 +269,8 @@ def rootly_handle_incidents(request_message: HttpRequest) -> JsonResponse:
except Exception as e:
logger.error(f'Error handling rootly incident: {str(e)}')
return JsonResponse({'success': False, 'message': f"rootly incident Handling failed"}, status=500)



@csrf_exempt
@api_view(['POST'])
@authentication_classes([AccountApiTokenAuthentication])
Expand Down
8 changes: 5 additions & 3 deletions executor/workflows/tasks.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@
from protos.playbooks.workflow_pb2 import WorkflowExecutionStatusType, Workflow as WorkflowProto, \
WorkflowAction as WorkflowActionProto, WorkflowConfiguration as WorkflowConfigurationProto, \
WorkflowExecution as WorkflowExecutionProto, WorkflowSchedule as WorkflowScheduleProto, \
WorkflowEntryPoint as WorkflowEntryPointProto, WorkflowConfiguration
WorkflowEntryPoint as WorkflowEntryPointProto, WorkflowConfiguration, Workflow
from protos.base_pb2 import Source
from google.protobuf.wrappers_pb2 import StringValue

Expand Down Expand Up @@ -329,8 +329,10 @@ def workflow_action_execution(account_id, workflow_id, workflow_execution_id, pl
workflow_action_execution_postrun_notifier = publish_post_run_task(workflow_action_execution)


def test_workflow_notification(user, account_id, workflow, message_type):
@shared_task
def test_workflow_notification(created_by, account_id, workflow_json, message_type):
try:
workflow = dict_to_proto(d=workflow_json, proto_clazz=Workflow)
account = Account.objects.get(id=account_id)
except Exception as e:
logger.error(f"Account not found for account_id: {account_id}, error: {e}")
Expand Down Expand Up @@ -380,7 +382,7 @@ def test_workflow_notification(user, account_id, workflow, message_type):
uuid_str = uuid.uuid4().hex
playbook_run_uuid = f'{str(current_time)}_{account.id}_{playbook_id}_pb_run_{uuid_str}'

playbook_execution = create_playbook_execution(account, time_range, playbook_id, playbook_run_uuid, user.email)
playbook_execution = create_playbook_execution(account, time_range, playbook_id, playbook_run_uuid, created_by)

if workflow.configuration.generate_summary and workflow.configuration.generate_summary.value:
execute_playbook(account_id, playbook_id, playbook_execution.id, proto_to_dict(time_range))
Expand Down
3 changes: 2 additions & 1 deletion executor/workflows/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -439,7 +439,8 @@ def test_workflows_notification(request_message: CreateWorkflowRequest) -> Union
message=Message(title="Invalid Request",
description="Select a notification type"))

test_workflow_notification(user, account.id, workflow, workflow.actions[0].type)
workflow_dict = proto_to_dict(workflow)
test_workflow_notification(user.email, account.id, workflow_dict, workflow.actions[0].type)
return CreateWorkflowResponse(success=BoolValue(value=True))


Expand Down

0 comments on commit d47174f

Please sign in to comment.