def add_call(): '''API: add a call record to the DB via Twilio Status Callback''' twilio_client_key = request.values.get( 'key', None) # used to auth requests coming from Twilio if twilio_server_key != twilio_client_key: if not flask_login.current_user.is_authenticated: # block unwanted people from adding records return app.login_manager.unauthorized() call_sid = request.values.get('CallSid', None) from_phone = request.values.get('From', None) to_phone = request.values.get('To', None) duration = request.values.get('CallDuration', None) direction = request.values.get('Direction', None) status = request.values.get('CallStatus', None) caller_id = request.values.get('CallerName', None) time_now = datetime.now(pytz.timezone('US/Eastern')) subtime = timedelta( seconds=int(duration)) if duration != None else timedelta(seconds=5) starttime = (time_now - subtime).strftime('%-m/%-d/%Y %H:%M:%S') call = Call(call_sid=call_sid, from_phone=from_phone, to_phone=to_phone, time=starttime, duration=duration, direction=direction, status=status, caller_id=caller_id) # commit record to DB db_session.add(call) db_session.commit() return "Call record added successfully."
def googleOAuthTokenVerify(): # authenticate with Google for Icahn accounts '''from https://developers.google.com/identity/sign-in/web/backend-auth''' token = request.values.get('idtoken', None) try: idinfo = gauthclient.verify_id_token(token, vm_client_id) # If multiple clients access the backend server: if idinfo['aud'] not in [vm_client_id]: raise crypt.AppIdentityError("Unrecognized client.") if idinfo['iss'] not in ['accounts.google.com', 'https://accounts.google.com']: raise crypt.AppIdentityError("Wrong issuer.") except crypt.AppIdentityError: # Invalid token sys.stderr.write("Bad token from client.\n") return None # okay, now we're logged in. yay! userid = idinfo['sub'] useremail = idinfo['email'] sys.stderr.write("Token sign in user: "******", ".join([useremail, userid]) + "\n") user = User.query.get(useremail) if user: # if user has been here before user.authenticated=True # log them in in DB db_session.add(user) db_session.commit() flask_login.login_user(user, remember=True) # log them in in their browser else: if ('@icahn.mssm.edu' not in useremail) & ('@mssm.edu' not in useremail): # not ISMMS account return 'Unauthorized e-mail address. You must be a MSSM affiliate with an @icahn.mssm.edu or @mssm.edu address!' else: user = User(email = useremail, google_token=userid) # create new user in DB user.authenticated=True # log them in in DB db_session.add(user) db_session.commit() flask_login.login_user(user, remember=False) # log them in in their browser return useremail # return logged in email to user
def secure_message_setpass(remind_id): # second step in secure message ''' sets the passcode from the last step and asks for a reminder time ''' if request.values.get('AccountSid', None) != twilio_AccountSID: return "Not Authorized", 403 #requests must come from Twilio resp = twilio.twiml.Response() passcode = request.values.get("Digits", None) try: # find the record in the DB that corresponds to this call record = Reminder.query.get(remind_id) except: resp.play('/assets/audio/vmsystemfail.mp3') return str(resp) # set a passcode and update the DB record.passcode = passcode db_session.add(record) db_session.commit() # ask for a reminder time with resp.gather(numDigits=1, action='/secure_message/settime/' + str(remind_id), method='POST') as g: g.play('/assets/audio/youdialed.mp3') g.say(' '.join(passcode) + ".", voice="alice") g.play('/assets/audio/settime.mp3') return str(resp)
def add_call(): '''API: add a call record to the DB via Twilio Status Callback''' twilio_client_key = request.values.get('key', None) # used to auth requests coming from Twilio if twilio_server_key != twilio_client_key: if not flask_login.current_user.is_authenticated: # block unwanted people from adding records return app.login_manager.unauthorized() call_sid = request.values.get('CallSid', None) from_phone = request.values.get('From', None) to_phone = request.values.get('To', None) duration = request.values.get('CallDuration', None) direction = request.values.get('Direction', None) status = request.values.get('CallStatus', None) time_now = datetime.now(pytz.timezone('US/Eastern')) subtime=timedelta(seconds=int(duration)) if duration != None else timedelta(seconds=5) starttime = (time_now-subtime).strftime('%-m/%-d/%Y %H:%M:%S') call = Call(call_sid = call_sid, from_phone = from_phone, to_phone = to_phone, time = starttime, duration = duration, direction = direction, status = status) # commit record to DB db_session.add(call) db_session.commit() return "Call record added successfully."
def secure_message_setmessage( remind_id): # fourth step in secure message - record the message '''record secure message''' if request.values.get('AccountSid', None) != twilio_AccountSID: return "Not Authorized", 403 #requests must come from Twilio resp = twilio.twiml.Response() recording_url = request.values.get("RecordingUrl", None) try: # find the record in the DB that corresponds to this call record = Reminder.query.get(remind_id) except: resp.play('/assets/audio/vmsystemfail.mp3') return str(resp) # set a new save file name randomly save_name = randomword(128) + ".mp3" # set the secure recording URL location new_recording_url = 'https://twilio.ehhapp.org/play_recording?filename=' + save_name # set the message url and update the db record.message = new_recording_url db_session.add(record) db_session.commit() # download and save the message (async) save_secure_message.apply_async(args=[recording_url, save_name]) with resp.gather(numDigits=1, action='/secure_message/send/' + str(remind_id), method='POST') as g: g.play('/assets/audio/confirmsecuremessage.mp3') return str(resp)
def secure_message_playback(remind_id): '''play the message for the patient''' if request.values.get('AccountSid', None) != twilio_AccountSID: return "Not Authorized", 403 #requests must come from Twilio resp = twilio.twiml.Response() passcode = request.values.get("Digits", None) # find the record in the DB that corresponds to this call record = Reminder.query.get(remind_id) if record.passcode != passcode: with resp.gather(numDigits=6, action='/secure_message/playback/' + str(remind_id), method='POST') as g: g.play('/assets/audio/passcodeincorrect.mp3') return str(resp) else: record.delivered = True db_session.add(record) db_session.commit() #deliver_callback.apply_async(args=[remind_id, record.from_phone]) resp.play('/assets/audio/pleasewaittohearmessage.mp3') resp.play(record.message + "&key=" + twilio_server_key, mimetype="audio/mpeg") resp.play('/assets/audio/messagewillrepeat.mp3') resp.play(record.message + "&key=" + twilio_server_key, loop=5, mimetype="audio/mpeg") resp.play('/assets/audio/goodbye.mp3') return str(resp)
def secure_message_setnum(): # start secure message function ''' creates a row in the database with a secure message ID and pt phone num still need to set the following: reminder time (now, one day from now, or specify date) reminder frequency (default = one day if they dont pick up) message recorded name spanish? passcode ''' if request.values.get('AccountSid', None) != twilio_AccountSID: return "Not Authorized", 403 #requests must come from Twilio resp = twilio.twiml.Response() number = request.values.get("Digits", None) from_phone = request.values.get("From", None) # add phone number to reminders, move to next step reminder = Reminder(to_phone=number, from_phone=from_phone) db_session.add(reminder) db_session.commit() remind_id = reminder.id with resp.gather(numDigits=6, action='/secure_message/setpass/' + str(remind_id), method='POST') as g: g.play('/assets/audio/youdialed.mp3') g.say(' '.join(number) + '. ', voice='alice') g.play('/assets/audio/setpass.mp3') return str(resp)
def logout(): """Logout the current user.""" user = flask_login.current_user user.authenticated = False # log out in db db_session.add(user) db_session.commit() flask_login.logout_user() # delete browser cookie return "Logged out. You can close this window now."
def delete_reminder(remind_id): '''GUI: delete a secure message from the DB''' record = Reminder.query.get(remind_id) if record == None: flash('Could not find reminder in database.') return redirect(url_for('serve_vm_admin')) else: db_session.delete(record) db_session.commit() return redirect(url_for('serve_vm_admin'))
def add_assignment(): '''GUI: add an assignment to the DB''' form = AssignmentForm(request.form) if request.method == 'POST' and form.validate(): assignment = Assignment(form.from_phone.data, form.recipients.data) db_session.add(assignment) db_session.commit() flash('Assignment added.') return redirect(url_for('serve_assignment_admin')) return render_template("intent_form.html", action="Add", data_type="an assignment", form=form)
def play_vm_recording(): '''serve the voicemail or secure message for playback''' twilio_client_key = request.values.get( 'key', None) # used when playing back messages over phone if twilio_server_key != twilio_client_key: if not flask_login.current_user.is_authenticated: # if accessing VM through GUI return app.login_manager.unauthorized() ''' plays a voicemail recording from the Box server''' filename = request.values.get('filename', None) # check that filename attribute was set, else return None if filename == None: return "Need to specify a filename.", 400 # sterilize filename to prevent attacks safe_char = re.compile(r'[^\w.]+') # only alphanumeric and periods filename = safe_char.sub('', filename) def get_file( filename ): # how do we 'stream' the file from Box to browser? using a callback! class VMFile: # this will store the VM message as a def __init__( self ): # memory object instead of in a file (+ deleted after execution) self.data = "" def __call__(self, s): self.data += s #v = VMFile() #session = FTP_TLS('ftp.box.com', box_username, box_password) # open Box #session.retrbinary('RETR recordings/' + filename, v) # add each chunk of data to memory from Box #session.close() # close Box v = box_api.download_file(source_folder_name="recordings", source_file_name=filename, destination=None) return v # return the data put back together again to be sent to browser vm_info = Voicemail.query.filter_by( message=filename).first() # get VM record if vm_info != None: if vm_info.view_count != None: vm_info.view_count += 1 else: vm_info.view_count = 1 db_session.add(vm_info) db_session.commit() # serve file if filename[-3:] == "mp3": return Response(get_file(filename), mimetype="audio/mpeg", status=200) elif filename[-3:] == "wav": return Response(get_file(filename), mimetype="audio/wav", status=200) else: return Response(get_file(filename), status=200, mimetype="audio/mpeg")
def add_intent(): '''GUI: add an intent/route to the DB''' form = IntentForm(request.form) if request.method == 'POST' and form.validate(): intent = Intent(form.digit.data, form.description.data, form.required_recipients.data, form.distributed_recipients.data) db_session.add(intent) db_session.commit() flash('Intent added.') return redirect(url_for('serve_intent_admin')) return render_template("intent_form.html", action="Add", data_type="an intent", form=form)
def delete_intent(intent_id): '''GUI: delete an intent from the DB''' record = Intent.query.get(intent_id) if record == None: flash('Could not find route in database.') return redirect(url_for('serve_intent_admin')) else: flash('Route removed.') db_session.delete(record) db_session.commit() return redirect(url_for('serve_intent_admin'))
def delete_assignment(assign_id): '''GUI: delete an assignment from the DB''' record = Assignment.query.get(assign_id) if record == None: flash('Could not find assignment in database.') return redirect(url_for('serve_assignment_admin')) else: flash('Assignment removed.') db_session.delete(record) db_session.commit() return redirect(url_for('serve_assignment_admin'))
def edit_voicemail_assignment(record_id): '''GUI: edit a VM assignment''' vm = Voicemail.query.get(record_id) form = VoicemailForm(request.form, obj=vm) if request.method == 'POST' and form.validate(): vm.assigns = form.assigns.data db_session.add(vm) db_session.commit() flash('VM assignment edited.') return redirect(url_for('serve_vm_admin')) return render_template("intent_form.html", action="Edit", data_type="assignment", form=form)
def add_voicemail(recording_name, ani=None, intent=None, requireds=None, assigns=None): '''Internal function to accept incoming VMs and store them in DB''' time_now = datetime.now(pytz.timezone('US/Eastern')) record = Voicemail(intent=intent, from_phone=ani, time=time_now.strftime('%-m/%-d/%Y %H:%M:%S'), message=recording_name, requireds=requireds, assigns=assigns) db_session.add(record) db_session.commit() return record.id
def delete_call(call_id): '''GUI: delete a call record from the DB''' if app.debug == False: return "Delete not allowed in production.", 403 record = Call.query.get(call_id) if record == None: flash('Could not find call record in database.') return redirect(url_for('serve_call_admin')) else: flash('Call record removed.') db_session.delete(record) db_session.commit() return redirect(url_for('serve_call_admin'))
def edit_assignment(assign_id): '''GUI: edit an assignment in the DB''' assign = Assignment.query.get(assign_id) formout = AssignmentForm(obj=assign) form = AssignmentForm(request.form) if request.method == 'POST' and form.validate(): assign.from_phone = form.from_phone.data assign.recipients = form.recipients.data db_session.add(assign) db_session.commit() flash('Assignment edited.') return redirect(url_for('serve_assignment_admin')) return render_template("intent_form.html", action="Edit", data_type=assign.from_phone, form=formout)
def delete_vm(record_id): '''GUI: delete a voicemail from the DB''' if app.debug == False: return "Delete not allowed in production.", 403 record = Voicemail.query.get(record_id) if record == None: flash('Could not find VM record in database.') return redirect(url_for('serve_vm_admin')) else: flash('Voicemail removed.') db_session.delete(record) db_session.commit() return redirect(url_for('serve_vm_admin'))
def edit_intent(intent_id): '''GUI: edit an intent in the DB''' intent = Intent.query.get(intent_id) formout = IntentForm(obj=intent) form = IntentForm(request.form) if request.method == 'POST' and form.validate(): intent.digit = form.digit.data intent.description = form.description.data intent.required_recipients = form.required_recipients.data intent.distributed_recipients = form.distributed_recipients.data db_session.add(intent) db_session.commit() flash('Intent edited.') return redirect(url_for('serve_intent_admin')) return render_template("intent_form.html", action="Edit", data_type=intent.digit, form=formout)
def edit_assignment(assign_id): '''GUI: edit an assignment in the DB''' assign = Assignment.query.get(assign_id) formout = AssignmentForm(obj=assign) form = AssignmentForm(request.form) if request.method == 'POST' and form.validate(): assign.from_phone = form.from_phone.data assign.recipients = form.recipients.data assign.intent = form.intent.data db_session.add(assign) db_session.commit() flash('Assignment edited.') return redirect(url_for('serve_assignment_admin')) return render_template("intent_form.html", action="Edit", data_type=assign.from_phone, form=formout)
def add_voicemail(recording_name, ani=None, intent=None, requireds=None, assigns=None, caller_id=None): '''Internal function to accept incoming VMs and store them in DB''' time_now = datetime.now(pytz.timezone('US/Eastern')) record = Voicemail(intent=intent, from_phone=ani, time=time_now.strftime('%-m/%-d/%Y %H:%M:%S'), message=recording_name, requireds=requireds, assigns=assigns, caller_id=caller_id) db_session.add(record) db_session.commit() return record.id
def googleOAuthTokenVerify(): # authenticate with Google for Icahn accounts '''from https://developers.google.com/identity/sign-in/web/backend-auth''' token = request.values.get('idtoken', None) try: idinfo = gauthclient.verify_id_token(token, vm_client_id) # If multiple clients access the backend server: if idinfo['aud'] not in [vm_client_id]: raise crypt.AppIdentityError("Unrecognized client.") if idinfo['iss'] not in [ 'accounts.google.com', 'https://accounts.google.com' ]: raise crypt.AppIdentityError("Wrong issuer.") except crypt.AppIdentityError: # Invalid token sys.stderr.write("Bad token from client.\n") return None # okay, now we're logged in. yay! userid = idinfo['sub'] useremail = idinfo['email'] sys.stderr.write("Token sign in user: "******", ".join([useremail, userid]) + "\n") user = User.query.get(useremail) if user: # if user has been here before user.authenticated = True # log them in in DB db_session.add(user) db_session.commit() flask_login.login_user(user, remember=True) # log them in in their browser else: if ('@icahn.mssm.edu' not in useremail) & ( '@mssm.edu' not in useremail): # not ISMMS account return 'Unauthorized e-mail address. You must be a MSSM affiliate with an @icahn.mssm.edu or @mssm.edu address!' else: user = User(email=useremail, google_token=userid) # create new user in DB user.authenticated = True # log them in in DB db_session.add(user) db_session.commit() flask_login.login_user( user, remember=False) # log them in in their browser return useremail # return logged in email to user
def secure_message_settime(remind_id): # third step in secure message ''' sets the reminder time and asks for the message ''' if request.values.get('AccountSid', None) != twilio_AccountSID: return "Not Authorized", 403 #requests must come from Twilio resp = twilio.twiml.Response() choice = request.values.get("Digits", None) try: # find the record in the DB that corresponds to this call record = Reminder.query.get(remind_id) except: resp.play('/assets/audio/vmsystemfail.mp3') return str(resp) # set the reminder time delta nowtime = datetime.now() time_set = None if choice == '2': # set the time for tomorrow time_set = datetime.combine(nowtime.date() + timedelta(1), time(10, 0, 0, 1)) else: # set the time as now time_set = nowtime # set the reminder time and update the DB record.time = str(time_set) db_session.add(record) db_session.commit() # now we need to record the message resp.play('/assets/audio/leavesecuremessage.mp3') resp.record(maxLength=300, action="/secure_message/setmessage/" + str(remind_id), method='POST') return str(resp)
def update_vm_status(record_id, input_status): '''GUI: edit a VM assignment''' user = flask_login.current_user vm = Voicemail.query.get(record_id) escaped_status = input_status.replace("-", " ") vm.status = escaped_status status_dict = {"users": {}} if vm.last_updated_by != None: try: status_dict = json.loads(vm.last_updated_by) except: pass status_dict["users"][len(status_dict["users"])] = { "time": str(datetime.now()), "email": user.email, "status": escaped_status } vm.last_updated_by = json.dumps(status_dict) db_session.add(vm) db_session.commit() flash("Updated status") return redirect(url_for('serve_vm_admin'))
Requires: twilio_AccountSID, twilio_AuthToken """ ## assignment autoclear on tuesday nights # get day of week (0 is Monday and 6 is Sunday) day_of_week = datetime.now(pytz.timezone('US/Eastern')).weekday() if day_of_week == 1: # if it's a Tuesday (and this runs at 11:58 PM) assignments = Assignment.query.order_by(Assignment.id.desc()).all() # print "Found %d assignments to delete." % len(assignments) try: for a in assignments: db_session.delete(a) db_session.commit() # print "Deleted %d assignments." % len(assignments) except BaseException as err: print "ERROR: couldn't delete assignments." print err ## record_sids = [] with open("/var/wsgiapps/ehhapp_twilio/download_log.txt", "r") as fp: for line in fp: record_sids.append(line.strip()) client = TwilioRestClient(twilio_AccountSID, twilio_AuthToken) # step 1 - delete recordings (more of a catch-all) recordings = client.recordings.list()
def process_recording(recording_url, intent, ani, requireds=None, assign=None, no_requireds=False, auth_method=auth_combo): '''process_recording(recording_url, intent, ani, requireds=None, assign=None, auth_method=auth_combo) recording_url: URL of recording to download intent: the digit pressed by the caller ani: caller ID requireds: (optional) override the default required recipients assign: (optional) override the default assigned recipients no_requireds: (optional) do not send emails to requireds even if passed auth_method: (optional) specify different twilio REST API credentials ''' satdate = getlastSatDate() # get next clinic date recording_name = save_file(recording_url, auth_method) # save the file playback_url = player_url + recordings_base + recording_name slack_notify('<' + playback_url + '|New voicemail received>') db = EHHOPdb(credentials) # figure out who to send the message to if assign == None: db_assign = Assignment.query.filter_by(from_phone=ani[-10:], intent=intent).all() if db_assign != []: #someone is responsible for that phone number assign = [ a.recipients.replace(" ", "").split(',') for a in db_assign ] assign = [item for sublist in assign for item in sublist] # flatten array of arrays assign = ','.join(assign) else: # no one has the default assignment for that phone number intents = Intent.query.filter_by(digit=intent).first() sys.stderr.write(str(intents)) assigns = intents.distributed_recipients.split(',') sys.stderr.write(str(assigns)) assignlist = [] for a in assigns: sys.stderr.write(str(a) + str(satdate)) interresult = db.lookup_name_in_schedule(a, satdate) sys.stderr.write(str(interresult)) emails = [] for i in interresult: email = db.lookup_email_by_name(i) sys.stderr.write(str(email)) emails.append(email) if email != None else None assignlist.append(emails) sys.stderr.write(str(assignlist)) assign = [] for c in assignlist: c1 = [ Assignment.query.filter_by(recipients=b).count() for b in c ] assign_person = c[c1.index(min(c1))] if c != [] else "" # gets whoever has the lowest load (alphabetical tiebreak) # save the new assignment to the database for later retrieval if assign_person != "": assign.append(assign_person) new_assign = Assignment(from_phone=ani[-10:], recipients=assign_person) db_session.add(new_assign) db_session.commit() assign = ", ".join(assign) if requireds == None and not no_requireds: # if no required recipients are sent and not a direct message db_requireds = Intent.query.filter_by(digit=intent).first() if db_requireds != None: requiredpos = db_requireds.required_recipients.split(',') requirelist = [] for r in requiredpos: r = r.strip(" ") if "schedule:" in r.lower(): a = r.split(":")[1].strip() interresult = db.lookup_name_in_schedule(a, satdate) sys.stderr.write(str(interresult)) for i in interresult: email = db.lookup_email_by_name(i) sys.stderr.write(str(email)) requirelist.append(email) if email != None else None else: lookup = db.lookup_name_by_position(r) if lookup != []: for rr in lookup: requirelist.append(rr[2]) requireds = ','.join(requirelist) requireds = fallback_email if requireds == None else requireds # in case something goes bad if no_requireds: # if a direct VM (dial_extension) requireds = '' with app.app_context( ): # pass the Flask app to the next function (weird rendering quirk) add_voicemail(recording_name, intent=intent, ani=ani, requireds=requireds, assigns=assign) send_email(recording_name, intent, ani, requireds, assign) #delete_file(recording_url) # delete recording from Twilio return recording_name