Skip to content

Commit aede16d

Browse files
committed
Update tests
1 parent d717c59 commit aede16d

16 files changed

Lines changed: 420 additions & 134 deletions

Pipfile.lock

Lines changed: 40 additions & 40 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pybot/__main__.py

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010

1111
from pybot.endpoints import handle_health_check
1212
from . import endpoints
13-
from .plugins import AirtablePlugin
13+
from .plugins import AirtablePlugin, APIPlugin
1414
from pybot.endpoints.slack.utils import PORT, HOST
1515
from pybot.endpoints.slack.utils import slack_configs
1616

@@ -50,10 +50,19 @@ def make_sentry_logger():
5050
endpoints.slack.create_endpoints(slack)
5151
bot.load_plugin(slack)
5252

53+
admin_configs = dict(**slack_configs)
54+
admin_configs["token"] = os.environ.get("APP_ADMIN_OAUTH_TOKEN")
55+
admin_slack = SlackPlugin(**admin_configs)
56+
bot.load_plugin(admin_slack, name="admin_slack")
57+
5358
airtable = AirtablePlugin()
5459
endpoints.airtable.create_endpoints(airtable)
5560
bot.load_plugin(airtable)
5661

62+
api_plugin = APIPlugin()
63+
endpoints.api.create_endpoints(api_plugin)
64+
bot.load_plugin(api_plugin)
65+
5766
# Add route to respond to AWS health check
5867
bot.router.add_get("/health", handle_health_check)
5968
logging.getLogger("aiohttp.access").setLevel(logging.WARNING)

pybot/endpoints/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
from aiohttp.web_response import Response
22

3-
from . import slack, airtable
3+
from . import slack, airtable, api
44

55

66
async def handle_health_check(request):

pybot/endpoints/api/__init__.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
from . import slack_api
2+
3+
4+
def create_endpoints(plugin):
5+
slack_api.create_endpoints(plugin)

pybot/endpoints/api/slack_api.py

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
import logging
2+
from sirbot import SirBot
3+
from slack import ROOT_URL
4+
from slack.exceptions import SlackAPIError
5+
from aiohttp.web_request import Request
6+
7+
from pybot.endpoints.api.utils import _slack_info_from_email
8+
9+
logger = logging.getLogger(__name__)
10+
11+
12+
def create_endpoints(plugin):
13+
plugin.on_get("verify", verify, wait=True)
14+
plugin.on_get("invite", invite, wait=True)
15+
16+
17+
async def verify(request: Request, app: SirBot) -> any:
18+
"""
19+
Verifies whether a user exists in the configured slack group with
20+
the given email
21+
22+
:return: The user's slack id and displayName if they exist
23+
"""
24+
slack = app.plugins["slack"].api
25+
email = request.query["email"]
26+
27+
user = await _slack_info_from_email(email, slack)
28+
if user:
29+
return {"exists": True, "id": user["id"], "displayName": user["name"]}
30+
return {"exists": False}
31+
32+
33+
async def invite(request: Request, app: SirBot):
34+
"""
35+
Pulls an email out of the querystring and sends it an invite
36+
to the slack team
37+
38+
:return: The request response from slack
39+
"""
40+
try:
41+
slack = app.plugins["admin_slack"].api
42+
email = request.query["email"]
43+
44+
response = await slack.query(
45+
url=ROOT_URL + "users.admin.invite", data={"email": email}
46+
)
47+
# logger.info("Response from slack: ", response)
48+
return response
49+
50+
except SlackAPIError as e:
51+
logger.info(e)
52+
return e.data

pybot/endpoints/api/utils.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
from typing import Optional
2+
3+
from slack import ROOT_URL
4+
from slack.exceptions import SlackAPIError
5+
from slack.io.abc import SlackAPI
6+
7+
8+
async def _slack_info_from_email(
9+
email: str, slack: SlackAPI, fallback: Optional[str] = None
10+
):
11+
try:
12+
response = await slack.query(
13+
url=ROOT_URL + "users.lookupByEmail", data={"email": email}
14+
)
15+
return response["user"]
16+
except SlackAPIError:
17+
return fallback

pybot/plugins/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
11
from .airtable import AirtablePlugin
2+
from .api import APIPlugin

pybot/plugins/api/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
from .plugin import APIPlugin

pybot/plugins/api/endpoints.py

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
import asyncio
2+
import json
3+
import logging
4+
5+
from aiohttp.web_response import Response
6+
7+
from pybot.plugins.api.request import SlackApiRequest
8+
9+
logger = logging.getLogger(__name__)
10+
11+
12+
async def slack_api(request):
13+
api_plugin = request.app.plugins["api"]
14+
slack_request = SlackApiRequest.from_request(request)
15+
16+
if not slack_request.authorized:
17+
logger.info(
18+
f"Received unauthorized request Request: {slack_request} Token: {slack_request.token}"
19+
)
20+
return Response(status=403)
21+
22+
futures = list(_dispatch(api_plugin.routers["slack"], slack_request, request.app))
23+
24+
if futures:
25+
return await _wait_and_check_result(futures)
26+
return Response(status=200)
27+
28+
29+
def _dispatch(router, event, app):
30+
for handler, configuration in router.dispatch(event):
31+
f = asyncio.ensure_future(handler(event, app))
32+
yield f
33+
34+
35+
async def _wait_and_check_result(futures):
36+
dones, _ = await asyncio.wait(futures, return_when=asyncio.ALL_COMPLETED)
37+
try:
38+
results = [done.result() for done in dones]
39+
except Exception as e:
40+
logger.exception(e)
41+
return Response(status=500)
42+
43+
if len(results) > 1:
44+
logger.warning("Multiple web.Response for handler, returning none")
45+
46+
elif results:
47+
result = (
48+
results[0]
49+
if isinstance(results[0], Response)
50+
else Response(body=json.dumps(results[0]))
51+
)
52+
53+
return result

pybot/plugins/api/plugin.py

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
import asyncio
2+
from collections import defaultdict
3+
import logging
4+
5+
from pybot.plugins.api import endpoints
6+
7+
logger = logging.getLogger(__name__)
8+
9+
10+
class APIPlugin:
11+
__name__ = "api"
12+
13+
def __init__(self):
14+
self.session = None
15+
self.routers = {"slack": SlackAPIRequestRouter()}
16+
17+
def load(self, sirbot):
18+
self.session = sirbot.http_session
19+
20+
sirbot.router.add_route("GET", "/api/v1/slack/{resource}", endpoints.slack_api)
21+
22+
def on_get(self, request, handler, **kwargs):
23+
if not asyncio.iscoroutinefunction(handler):
24+
handler = asyncio.coroutine(handler)
25+
options = {**kwargs, "wait": False}
26+
self.routers["slack"].register(request, (handler, options))
27+
28+
29+
class SlackAPIRequestRouter:
30+
def __init__(self):
31+
self._routes = defaultdict(list)
32+
33+
def register(self, resource, handler, **detail):
34+
logger.info(f"Registering {resource}, {detail} to {handler}")
35+
self._routes[resource].append(handler)
36+
37+
def dispatch(self, request):
38+
resource = request.resource
39+
logger.debug(f"Dispatching request {resource}")
40+
if resource in self._routes:
41+
for handler in self._routes.get(resource):
42+
yield handler
43+
else:
44+
return

0 commit comments

Comments
 (0)