2121from authlib .integrations .flask_client import OAuth
2222from authlib .integrations .requests_client import OAuth2Session
2323
24+ from celery import Celery , Task , shared_task
25+
2426from flask import Flask , g , render_template , request , session
2527from flask .helpers import get_debug_flag , redirect , url_for
2628
@@ -77,6 +79,29 @@ def traces_sampler(sampling_context: Dict[str, Dict[str, str]]) -> bool:
7779 return request_uri != "/ping"
7880
7981
82+ def init_celery (flask : Flask ) -> Celery :
83+ """
84+ Initialize Celery
85+ """
86+
87+ class FlaskTask (Task ): # type: ignore # pylint: disable=abstract-method
88+ """
89+ Extend default Task class to have Flask context available
90+
91+ https://flask.palletsprojects.com/en/stable/patterns/celery/
92+ """
93+
94+ def __call__ (self , * args , ** kwargs ): # type: ignore
95+ with flask .app_context ():
96+ return self .run (* args , ** kwargs )
97+
98+ new_celery_app = Celery ("checkpoint" , task_cls = FlaskTask )
99+ new_celery_app .config_from_object (flask .config , namespace = "CELERY" )
100+ new_celery_app .set_default ()
101+ flask .extensions ["celery" ] = new_celery_app
102+ return new_celery_app # type: ignore
103+
104+
80105sentry_sdk .init (
81106 debug = get_debug_flag (),
82107 integrations = [
@@ -95,6 +120,8 @@ def traces_sampler(sampling_context: Dict[str, Dict[str, str]]) -> bool:
95120app = Flask (__name__ )
96121app .config .from_prefixed_env ()
97122
123+ celery_app = init_celery (app )
124+
98125oauth = OAuth (app ) # type: ignore
99126oauth .register ( # type: ignore
100127 name = "keycloak" ,
@@ -3561,35 +3588,12 @@ def handle_event_callback(body: Dict[str, Any]) -> Dict[str, str]:
35613588 return {"status" : "ok" }
35623589
35633590
3564- @app . post ( "/slack" )
3565- def handle_slack_event ( ) -> Dict [ str , str ] :
3591+ @cache . memoize ( )
3592+ def slack_user_has_access ( user_id : str ) -> bool :
35663593 """
3567- Handle an event from Slack
3594+ Check if a Slack user has access to Checkpoint
35683595 """
3569- verifier = SignatureVerifier (app .config ["SLACK_SIGNING_SECRET" ])
3570-
3571- if not verifier .is_valid_request (request .get_data (), request .headers ): # type: ignore
3572- raise Unauthorized ("Slack signature verification failed" )
3573-
3574- if request .content_type is not None and "application/json" in request .content_type :
3575- body = request .get_json ()
3576- if body is not None and body .get ("type" ) == "url_verification" :
3577- return {"challenge" : body ["challenge" ]}
3578-
3579- if request .content_type is not None and "application/json" in request .content_type :
3580- body = request .get_json ()
3581- if body is not None and body .get ("type" ) == "event_callback" :
3582- return handle_event_callback (body )
3583-
3584- payload = loads (request .form .get ("payload" )) # type: ignore
3585-
3586- if payload .get ("type" ) == "block_actions" :
3587- return {"status" : "ok" }
3588-
3589- if payload .get ("type" ) != "message_action" :
3590- raise BadRequest ("Unsupported payload type" )
3591-
3592- triggering_user_info = slack .users_info (user = payload ["user" ]["id" ])
3596+ triggering_user_info = slack .users_info (user = user_id )
35933597
35943598 triggering_user_email : Union [str , None ] = None
35953599
@@ -3598,69 +3602,21 @@ def handle_slack_event() -> Dict[str, str]:
35983602 triggering_user_email = triggering_user_profile .get ("profile" , {}).get ("email" )
35993603
36003604 if triggering_user_email is None :
3601- slack .views_open (
3602- trigger_id = payload ["trigger_id" ],
3603- view = {
3604- "type" : "modal" ,
3605- "title" : {"type" : "plain_text" , "text" : "Checkpoint" },
3606- "blocks" : [
3607- {
3608- "type" : "section" ,
3609- "text" : {
3610- "type" : "mrkdwn" ,
3611- "text" : "Could not determine your email address from Slack." ,
3612- },
3613- }
3614- ],
3615- },
3616- )
3617- return {"status" : "ok" }
3605+ return False
36183606
36193607 triggering_user_results = search_by_email (
36203608 Address (addr_spec = triggering_user_email ), with_gted = False , with_title_and_organization = False
36213609 )
36223610
36233611 if len (triggering_user_results ["results" ]) == 0 :
3624- slack .views_open (
3625- trigger_id = payload ["trigger_id" ],
3626- view = {
3627- "type" : "modal" ,
3628- "title" : {"type" : "plain_text" , "text" : "Checkpoint" },
3629- "blocks" : [
3630- {
3631- "type" : "section" ,
3632- "text" : {
3633- "type" : "mrkdwn" ,
3634- "text" : "No Georgia Tech account found for your email address." ,
3635- },
3636- }
3637- ],
3638- },
3639- )
3640- return {"status" : "ok" }
3612+ return False
36413613
36423614 keycloak_account = get_keycloak_account (
36433615 triggering_user_results ["results" ][0 ]["directoryId" ], is_frontend_request = False
36443616 )
36453617
36463618 if keycloak_account is None or "id" not in keycloak_account or keycloak_account ["id" ] is None :
3647- slack .views_open (
3648- trigger_id = payload ["trigger_id" ],
3649- view = {
3650- "type" : "modal" ,
3651- "title" : {"type" : "plain_text" , "text" : "Checkpoint" },
3652- "blocks" : [
3653- {
3654- "type" : "section" ,
3655- "text" : {
3656- "type" : "mrkdwn" ,
3657- "text" : "Could not find your Keycloak account." ,
3658- },
3659- }
3660- ],
3661- },
3662- )
3663- return {"status" : "ok" }
3619+ return False
36643620
36653621 keycloak_user_id = keycloak_account ["id" ]
36663622
@@ -3700,10 +3656,17 @@ def handle_slack_event() -> Dict[str, str]:
37003656 "mappings"
37013657 ]:
37023658 if mapping .get ("name" ) == "access" :
3703- has_access = True
3704- break
3659+ return True
37053660
3706- if not has_access :
3661+ return False
3662+
3663+
3664+ @shared_task
3665+ def handle_slack_search_request (payload : Dict [str , Any ]) -> None :
3666+ """
3667+ Handle a Slack interaction event
3668+ """
3669+ if slack_user_has_access (payload ["user" ]["id" ]) is False :
37073670 slack .views_open (
37083671 trigger_id = payload ["trigger_id" ],
37093672 view = {
@@ -3720,7 +3683,6 @@ def handle_slack_event() -> Dict[str, str]:
37203683 ],
37213684 },
37223685 )
3723- return {"status" : "ok" }
37243686
37253687 mentioned_users = re .findall (r"<@(U[A-Z0-9]+)>" , payload ["message" ].get ("text" , "" ))
37263688
@@ -3751,7 +3713,6 @@ def handle_slack_event() -> Dict[str, str]:
37513713 ],
37523714 },
37533715 )
3754- return {"status" : "ok" }
37553716
37563717 lookup_results = search_by_email (Address (addr_spec = lookup_email ))
37573718
@@ -3764,9 +3725,53 @@ def handle_slack_event() -> Dict[str, str]:
37643725 },
37653726 )
37663727
3728+
3729+ @app .post ("/slack/interaction" )
3730+ def handle_slack_interaction () -> Dict [str , str ]:
3731+ """
3732+ Handle a Slack interaction event
3733+ """
3734+ verifier = SignatureVerifier (app .config ["SLACK_SIGNING_SECRET" ])
3735+
3736+ if not verifier .is_valid_request (request .get_data (), request .headers ): # type: ignore
3737+ raise Unauthorized ("Slack signature verification failed" )
3738+
3739+ payload = loads (request .form .get ("payload" )) # type: ignore
3740+
3741+ if payload .get ("type" ) == "block_actions" :
3742+ return {"status" : "ok" }
3743+
3744+ if payload .get ("type" ) != "message_action" :
3745+ raise BadRequest ("Unsupported payload type" )
3746+
3747+ handle_slack_search_request .delay (payload )
3748+
37673749 return {"status" : "ok" }
37683750
37693751
3752+ @app .post ("/slack" )
3753+ def handle_slack_event () -> Dict [str , str ]:
3754+ """
3755+ Handle an event from Slack
3756+ """
3757+ verifier = SignatureVerifier (app .config ["SLACK_SIGNING_SECRET" ])
3758+
3759+ if not verifier .is_valid_request (request .get_data (), request .headers ): # type: ignore
3760+ raise Unauthorized ("Slack signature verification failed" )
3761+
3762+ if request .content_type is not None and "application/json" in request .content_type :
3763+ body = request .get_json ()
3764+ if body is not None and body .get ("type" ) == "url_verification" :
3765+ return {"challenge" : body ["challenge" ]}
3766+
3767+ if request .content_type is not None and "application/json" in request .content_type :
3768+ body = request .get_json ()
3769+ if body is not None and body .get ("type" ) == "event_callback" :
3770+ return handle_event_callback (body )
3771+
3772+ raise BadRequest ("Unsupported payload type" )
3773+
3774+
37703775@app .get ("/ping" )
37713776def ping () -> Dict [str , str ]:
37723777 """
0 commit comments