gatekeeper.py (304 lines of code) (raw):
from gevent import monkey
monkey.patch_all() # flake8: noqa
from gunicorn_wrapper import StandaloneApplication
from runner import Runner, NO_SUSPEND_ACTIONS
from sse import ServerSentEvent
from forms import OffboardForm, LostAssetForm, FilesOwnershipTransferForm
from helper_functions import HelperFunctions
from ldap_client import LDAPClient, LdapSyncThread
import twitter.common.app as app
import twitter.common.log as log
import gevent
from gevent.queue import Queue
from flask import Flask, flash, redirect, render_template, request, Response, url_for
from datetime import timedelta
import json
import os
import platform
app.set_name = "gatekeeper"
app.add_option("-p", "--port", type="int", default=5000)
config = HelperFunctions().read_config_from_yaml()
PLATFORM = platform.system()
GOOGLE_ADMIN_ACTIONS = list(k for k,v in config["actions"]["google_admin"].items() if v is True)
GOOGLE_GMAIL_ACTIONS = list(k for k,v in config["actions"]["google_gmail"].items() if v is True)
GOOGLE_CALENDAR_ACTIONS = list(k for k,v in config["actions"]["google_calendar"].items() if v is True)
GOOGLE_DRIVE_ACTIONS = list(k for k,v in config["actions"]["google_drive"].items() if v is True)
PAGERDUTY_ACTIONS = list(k for k,v in config["actions"]["pagerduty"].items() if v is True)
DUO_ACTIONS = list(k for k,v in config["actions"]["duo"].items() if v is True)
OFFBOARD_ACTIONS = list(k for k,v in config["action_groups"]["offboard"].items() if v is True)
LOST_ASSET_ACTIONS = config["action_groups"]["lost_asset"]
SET_DEBUG = config["defaults"]["debug"]
BASE_DIR = os.path.abspath(config["defaults"]["base_dir"])
TEMPLATE_DIR = os.path.join(BASE_DIR, "templates")
STATIC_DIR = os.path.join(BASE_DIR, "static")
# Flask config
webapp = Flask(__name__, template_folder=TEMPLATE_DIR, static_folder=STATIC_DIR)
webapp.config["SECRET_KEY"] = os.urandom(20)
webapp.config["PERMANENT_SESSION_LIFETIME"] = timedelta(seconds=120)
# LDAP Connection
ldap_client = LDAPClient(config=config["ldap"])
# thread is scheduled to sync every 6 hours
t = LdapSyncThread(21600, ldap_client.sync_users)
t.start()
if config["ldap"]["queries"]["all_users"] == "":
ldap_users = ["harry", "paul", "dave", "adam", "larry"]
else:
ldap_users = ldap_client.sync_users()
# This is the global hash map where we will store all queues
subscriptions = {}
# TODO(): flash a message if oauth token is invalid.
@webapp.route("/offboard/<string:session_id>", methods=["POST"])
def offboard_post(session_id):
log.info("publish: %s" % session_id)
lost_asset = False
admin_api_actions = []
gmail_api_actions = []
gcal_api_actions = []
pagerduty_actions = []
duo_actions = []
user_id = request.form.get("USER_ID")
for (k, v) in request.form.items():
if k in GOOGLE_ADMIN_ACTIONS:
admin_api_actions.append(k.lower())
elif k in GOOGLE_GMAIL_ACTIONS:
gmail_api_actions.append(k.lower())
elif k in GOOGLE_CALENDAR_ACTIONS:
gcal_api_actions.append(k.lower())
elif k in PAGERDUTY_ACTIONS:
pagerduty_actions.append(k.lower())
elif k in DUO_ACTIONS:
duo_actions.append(k.lower())
elif k == "LOST_ASSET":
lost_asset = True
def run(runner, connector, action, kwargs):
if session_id not in subscriptions:
subscriptions[session_id] = Queue()
log.info("adding new queue to map: %s" % subscriptions)
sub = subscriptions[session_id]
params = {"runner": runner, "connector": connector, "action": action, "params": kwargs}
sub.put(params)
log.info("adding to queue: %s" % params)
# TODO(): move this logic on the runner.
def change_events_ownership(runner, grantee_user_id):
if session_id not in subscriptions:
subscriptions[session_id] = Queue()
log.info("adding new queue to map: %s" % subscriptions)
sub = subscriptions[session_id]
runner_grantee = Runner(user=grantee_user_id)
if runner_grantee.is_suspended_user:
runner_grantee.suspend_user(False)
moved_events = {}
calendar_id = runner.calendar_api.get_calendar_id()
grantee_calendar_id = runner_grantee.calendar_api.get_calendar_id()
if calendar_id is not None and grantee_calendar_id is not None:
runner.calendar_api.move_calendar_ownership(grantee_calendar_id)
runner_grantee.calendar_api.move_calendar_ownership(calendar_id)
events = runner.calendar_api.list_events(calendar_id)
if events:
for event in events:
result = runner.calendar_api.move_event(event, grantee_calendar_id)
if result is True:
value = "<span class='text-success'>%s</span>" % result
elif v is False:
value = "<span class='text-danger'>%s</span>" % result
else:
value = result
moved_events[event] = value
sub.put({"change_events_ownership": moved_events})
log.info("adding to queue: %s" % moved_events)
runner = Runner(user=user_id)
if lost_asset:
runner.suspend_user(True)
runner.suspend_user(False)
log.info("lost_asset - resetting sign-in cookies")
gevent.spawn(run, runner, "admin_api", "lost_asset", {})
for action in admin_api_actions:
gevent.spawn(run, runner, "admin_api", action, {})
log.info("spawned action: %s" % action)
for action in gmail_api_actions:
if action == "set_ooo_msg":
ooo_msg_text = request.form.get("OOO_MSG_TEXT")
params = {"text": ooo_msg_text}
gevent.spawn(run, runner, "gmail_api", action, params)
else:
gevent.spawn(run, runner, "gmail_api", action, {})
log.info("spawned action: %s" % action)
for action in gcal_api_actions:
if action == "change_events_ownership":
grantee_user_id = request.form.get("GCAL_NEW_OWNER")
if grantee_user_id:
grantee_user_id_is_valid = ldap_client.is_valid_user(grantee_user_id)
if grantee_user_id_is_valid:
gevent.spawn(change_events_ownership(runner, grantee_user_id))
else:
gevent.spawn(run, runner, "calendar_api", action, {})
log.info("spawned action: %s" % action)
for action in pagerduty_actions:
gevent.spawn(run, runner, "pagerduty_api", action, {})
log.info("spawned action: %s" % action)
for action in duo_actions:
gevent.spawn(run, runner, "duo_api", action, {})
log.info("spawned action: %s" % action)
return "OK"
@webapp.route("/offboard/<string:session_id>", methods=["GET"])
def offboard_get(session_id):
log.info("stream: %s" % session_id)
if session_id in subscriptions:
def gen(session_id):
q = subscriptions[session_id]
q_size = len(q)
log.info("pulling queue: %s" % q)
runner = None
suspend_user = True
while not q.empty():
r = q.get()
log.info("pulling from queue: %s" % r)
if "action" in r.keys():
# do not suspend if we are only performing non-google apps actions
if r["action"] in NO_SUSPEND_ACTIONS and q_size == 1:
suspend_user = False
# do not suspend the user if we are setting an ooo msg
if r["action"] == "set_ooo_msg":
suspend_user = False
# do not suspend the user if we are resetting due to lost asset
if r["action"] == "lost_asset":
suspend_user = False
# handle output for calendar events ownership
if "change_events_ownership" in r.keys():
value = r["change_events_ownership"]
if value:
result = "<p>CHANGE EVENTS OWNERSHIP: %s</p>" % value
else:
result = "<p>CHANGE EVENTS OWNERSHIP: <span class='text-success'>SUCCESS</span></p>"
elif r["action"] != "lost_asset":
runner = r["runner"]
result = runner.perform_action(r["connector"], r["action"], r["params"])
else:
result = "<p>RESET SIGN-IN COOKIES: <span class='text-success'>SUCCESS</span></p>"
log.info("result: %s" % result)
ev = ServerSentEvent(str(result))
yield ev.encode()
if runner is not None:
suspend = runner.suspend_user(True)
log.info("suspend: %s" % suspend)
if not suspend_user:
suspend = runner.suspend_user(False)
log.info("un-suspend: %s" % suspend)
del subscriptions[session_id]
log.info("removing queue: %s" % q)
return Response(gen(session_id), mimetype="text/event-stream")
else:
def error():
err = ""
ev = ServerSentEvent(err)
yield ev.encode()
return Response(error(), mimetype="text/event-stream")
@webapp.route("/", methods=["GET", "POST"])
def index():
# TODO(): implement authentication mechanism
user = "GateKeeper"
log.info("Accessed by: %s" % user)
form = OffboardForm()
user_info = None
if request.method == "POST":
user_id = form.data["USER_ID"]
try:
user_is_valid = ldap_client.is_valid_user(user_id)
if user_is_valid:
user_info = ldap_client.get_user_info(user_id)
user_info["active"] = ldap_client.is_active_user(user_id)
default_ooo_msg = ("Hello. I no longer work here. Please resend your message to %s. "
"Thank you" % (user_info["manager"]))
form.OOO_MSG_TEXT.data = default_ooo_msg
else:
flash("WARNING: %s is not a valid LDAP user." % user_id)
return redirect(url_for('index'))
except AttributeError:
flash("WARNING: %s is not a valid LDAP user." % user_id)
return redirect(url_for('index'))
return render_template("index.html",
form=form,
users=json.dumps(ldap_users),
user=user,
user_info=user_info,
ldap_fields=config["ldap"]["fields"],
google_admin_actions=GOOGLE_ADMIN_ACTIONS,
google_gmail_actions=GOOGLE_GMAIL_ACTIONS,
google_calendar_actions=GOOGLE_CALENDAR_ACTIONS,
pagerduty_actions=PAGERDUTY_ACTIONS,
duo_actions=DUO_ACTIONS)
@webapp.route("/lost_asset", methods=["GET", "POST"])
def lost_asset():
try:
cookie = request.elfowl_cookie
user = cookie.user
except AttributeError:
user = "demo"
log.info("Accessed by: %s" % user)
form = LostAssetForm()
user_info = None
if request.method == "POST":
user_id = form.data["USER_ID"]
try:
user_is_valid = ldap_client.is_valid_user(user_id)
if user_is_valid:
user_info = ldap_client.get_user_info(user_id)
user_info["active"] = ldap_client.is_active_user(user_id)
else:
flash("WARNING: %s is not a valid LDAP user." % user_id)
return redirect(url_for('lost_asset'))
except AttributeError:
flash("WARNING: %s is not a valid LDAP user." % user_id)
return redirect(url_for('lost_asset'))
return render_template("lost_asset.html",
form=form,
users=json.dumps(ldap_users),
user=user,
user_info=user_info,
ldap_fields=config["ldap"]["fields"],
actions=LOST_ASSET_ACTIONS)
@webapp.route("/gdrive", methods=["GET", "POST"])
def gdrive():
try:
cookie = request.elfowl_cookie
user = cookie.user
except AttributeError:
user = "demo"
log.info("Accessed by: %s" % user)
form = FilesOwnershipTransferForm()
user_info = None
files = None
if request.method == "POST":
user_id = form.data["USER_ID"]
user_is_valid = ldap_client.is_valid_user(user_id)
if user_is_valid:
user_info = ldap_client.get_user_info(user_id)
file_search = form.data["FILE_SEARCH"]
new_owner = form.data["NEW_OWNER"]
files = []
runner = Runner(user=user_id)
if new_owner:
new_owner_is_valid_user = ldap_client.is_valid_user(new_owner)
if new_owner_is_valid_user:
files = runner.drive_api.search_files_list(drive_query=file_search)
runner_new_owner = Runner(user=new_owner)
runner.suspend_user(False)
runner_new_owner.suspend_user(False)
for file in files:
if file["id"] in request.form.keys():
result = str(runner
.drive_api
.transfer_file_owner(file["id"], runner_new_owner.user_email))
file["chown"] = result
log.info("File Transfer: %s" % (file["chown"]))
runner.suspend_user(True)
else:
flash("WARNING: %s is not a valid LDAP user." % user_id)
return redirect(url_for('gdrive'))
elif file_search:
files = runner.drive_api.search_files_list(drive_query=file_search)
else:
flash("WARNING: %s is not a valid LDAP user." % user_id)
return redirect(url_for('gdrive'))
return render_template("gdrive.html",
form=form,
users=json.dumps(ldap_users),
user=user,
user_info=user_info,
ldap_fields=config["ldap"]["fields"],
files=files)
def main(args, options):
if PLATFORM == "Linux":
StandaloneApplication(wsgi_app=webapp, port=options.port, debug=SET_DEBUG).run()
elif PLATFORM == "Darwin":
webapp.run(host="0.0.0.0", port=options.port, debug=SET_DEBUG, threaded=True)
app.main()