def authenticate_and_call(*args, **kwargs): if not is_logged_in(): #check for regular login requirement return redirect("/") admin_name = session["admin_username"] admin = Admin(admin_name) #check proper syntax usage. if "survey_id" not in kwargs and "study_id" not in kwargs: raise ArgumentMissingException() #We want the survey_id check to execute first if both args are supplied. if "survey_id" in kwargs: #turn the survey_id into a bson ObjectId. survey_id = ObjectId(kwargs["survey_id"]) kwargs['survey_id'] = survey_id #MongoDB checks both equality and contains when you pass it a value. study = Study(surveys=survey_id) if not study: #study does not exist. return abort(404) #check admin is allowed, allow system admins. if not admin.system_admin: if admin['_id'] not in study.admins: return abort(403) if "study_id" in kwargs: study_id = ObjectId(kwargs['study_id']) kwargs['study_id'] = study_id study = Study(study_id) if not study: return abort(404) if not admin.system_admin: if admin['_id'] not in study.admins: return abort(403) return some_function(*args, **kwargs)
def completely_purge_study(study_id, actually_delete=False): if not isinstance(study_id, ObjectId): study_id = ObjectId(study_id) study = Study(study_id) surveys = study["surveys"] device_settings = study["device_settings"] users = Users(study_id=study_id) chunks = ChunksRegistry(study_id=study_id) files_to_process = FilesToProcess(study_id=study_id) if not actually_delete: print "if you actually delete this you will not be able to decrypt anything " \ "from this study. Don't do it unless you know what you are doing." print study.name # print len(study) # print len(device_settings) print len(surveys) print len(users) print len(chunks) print len(files_to_process) else: StudyDeviceSettings(device_settings).remove() [Survey(s).remove() for s in surveys] [User(u).remove() for u in users] [ChunkRegistry(c).remove() for c in chunks] [FileToProcess(f).remove() for f in files_to_process] study.remove()
def delete_study(study_id=None): """ This functionality has been disabled pending testing and feature change.""" if request.form.get('confirmation') == 'true': study = Study(study_id) study.update({'admins': [], 'deleted': True}) flash("Deleted study '%s'" % study['name'], 'success') return "success"
def delete_survey(survey_id=None): survey = Survey(survey_id) if not survey: return abort(404) study = Study(surveys=survey_id) study.remove_survey(survey) survey.remove() return redirect('/view_study/' + str(study._id))
def view_study(study_id=None): study = Study(study_id) tracking_survey_ids = study.get_survey_ids_for_study('tracking_survey') audio_survey_ids = study.get_survey_ids_for_study('audio_survey') return render_template('view_study.html', study=study, patients=Users( study_id = study_id ), audio_survey_ids=audio_survey_ids, tracking_survey_ids=tracking_survey_ids, study_name=study.name, allowed_studies=get_admins_allowed_studies(), system_admin=admin_is_system_admin())
def do_upload(file_paths_and_contents, data_type=None, forcibly_overwrite=False): if data_type == None: raise Exception("DATA TYPE!") upload_stream_map = { "survey_answers":("surveyAnswers", "csv"), "audio":("voiceRecording", "mp4") } data_stream_string, file_extension = upload_stream_map[data_type] for timings_path, contents_and_timestamp in file_paths_and_contents.items(): contents, timestamp = contents_and_timestamp study_id_string, user_id, _, survey_id, _ = timings_path.split("/") try: timestamp_string = str( int( mktime( timestamp.timetuple( ) ) ) ) + "000" except AttributeError: print "PROBLEM WITH TIMESTAMP FROM: %s" % timings_path continue if len(timestamp_string) != 13: raise Exception("LOL! No.") study_obj_id = Study(ObjectId(study_id_string))._id s3_file_path = "%s/%s/%s/%s/%s.%s" % (study_id_string, user_id, data_stream_string, survey_id, timestamp_string, file_extension) if len(s3_list_files(s3_file_path)) != 0: print "ALREADY_EXISTS: %s, %s" % (timings_path, s3_file_path) if forcibly_overwrite == False: continue else: print "yay!: ", s3_file_path contents = contents.encode("utf8") #maybe make this unicode-16? s3_upload(s3_file_path, contents, study_obj_id, raw_path=True) FileToProcess.append_file_for_processing( s3_file_path, study_obj_id, user_id )
def get_and_validate_study_id(): """ Checks for a valid study id. No study id causes a 400 (bad request) error. Study id malformed (not 24 characters) causes 400 error. Study id otherwise invalid causes 400 error. Study id does not exist in our database causes _404_ error.""" if "study_id" not in request.values: return abort(400) if len(request.values["study_id"]) != 24: # Don't proceed with large sized input. print "Received invalid length objectid as study_id in the data access API." return abort(400) try: # If the ID is of some invalid form, we catch that and return a 400 study_id = ObjectId(request.values["study_id"]) except InvalidId: return abort(400) study_obj = Study(study_id) # Study object will be None if there is no matching study id. if not study_obj: # additional case: if study object exists but is empty return abort(404) return study_obj
def edit_study(study_id=None): return render_template('edit_study.html', study=Study(study_id), all_admins=sorted(Admins(), key=lambda x: x._id.lower()), allowed_studies=get_admins_allowed_studies(), system_admin=admin_is_system_admin())
def count_study_chunks(): chunked_data = s3_list_files("CHUNKED_DATA") study_prefixes = { f[:38] for f in chunked_data } study_prefix_to_id = { study_prefix: ObjectId(study_prefix.split("/")[-2]) for study_prefix in study_prefixes } study_prefix_to_name= { study_prefix:Study(_id=study_id).name for study_prefix, study_id in study_prefix_to_id.items() } study_count = { study_prefix_to_name[study_prefix]: len([f for f in chunked_data if f[:38] == study_prefix]) for study_prefix in study_prefixes } return study_count
def device_settings(study_id=None): study = Study(study_id) readonly = not admin_is_system_admin() if request.method == 'GET': settings = study.get_study_device_settings() return render_template("device_settings.html", study=study, settings=settings, readonly=not admin_is_system_admin(), system_admin=admin_is_system_admin()) if readonly: abort(403) settings = study.get_study_device_settings() params = combined_multi_dict_to_dict(request.values) params = checkbox_to_boolean(CHECKBOX_TOGGLES, params) params = string_to_int(TIMER_VALUES, params) settings.update(**params) return redirect('/edit_study/' + str(study._id))
def get_users_in_study(): try: study_id = ObjectId(request.values["study_id"]) except InvalidId: study_id = None study_obj = Study(study_id) if not study_obj: return abort(404) _ = get_and_validate_admin(study_obj) return json.dumps([str(user._id) for user in Users(study_id=study_id)])
def export_study_settings_file(study_id): study = Study(ObjectId(study_id)) filename = study['name'].replace(' ', '_') + "_surveys_and_settings.json" output = {'surveys': []} output['device_settings'] = StudyDeviceSettings(study['device_settings']) for survey_id in study['surveys']: output['surveys'].append(Survey(survey_id)) return Response( dumps(output), mimetype="application/json", headers={'Content-Disposition': 'attachment;filename=' + filename})
def import_study_settings_file(study_id): study = Study(ObjectId(study_id)) file = request.files['upload'] if not allowed_filename(file.filename): flash("You can only upload .json files!", 'danger') return redirect(request.referrer) study_data = loads(file.read()) msg = update_device_settings(study_data['device_settings'], study, file.filename) msg += " \n" + add_new_surveys(study_data['surveys'], study, file.filename) flash(msg, 'success') return redirect('/edit_study/' + str(study_id))
def fetch_graph(): """ Fetches the patient's answers to the most recent survey, marked by survey ID. The results are dumped into a jinja template and pushed to the device. """ patient_id = request.values['patient_id'] user = User(patient_id) #see docs in config manipulations for details. study_id = user['study_id'] study = Study(study_id) surveys = study['surveys'] data = [] for survey_id in surveys: data.append(get_survey_results(study_id, patient_id, survey_id, 7)) return render_template("phone_graphs.html", data=data)
def create_study(): if request.method == 'GET': return render_template('create_study.html', studies=Studies.get_all_studies(), allowed_studies=get_admins_allowed_studies(), system_admin=admin_is_system_admin()) name = request.form.get('name') encryption_key = request.form.get('encryption_key') try: study = Study.create_default_study(name, encryption_key) flash("Successfully created a new study.", 'success') copy_existing_study_if_asked_to(study) return redirect('/device_settings/' + str(study._id)) except (InvalidEncryptionKeyError, StudyAlreadyExistsError) as e: flash(e.message, 'danger') return redirect('/create_study')
def create_new_survey(study_id=None, survey_type='tracking_survey'): study = Study(study_id) new_survey = Survey.create_default_survey(survey_type) study.add_survey(new_survey) return redirect('edit_survey/' + str(new_survey._id))
def register_user(OS_API=""): """ Checks that the patient id has been granted, and that there is no device registered with that id. If the patient id has no device registered it registers this device and logs the bluetooth mac address. Check the documentation in user_authentication to ensure you have provided the proper credentials. Returns the encryption key for this patient/user. """ #CASE: If the id and password combination do not match, the decorator returns a 403 error. #the following parameter values are required. patient_id = request.values['patient_id'] phone_number = request.values['phone_number'] device_id = request.values['device_id'] #These values may not be returned by earlier versions of the beiwe app try: device_os = request.values['device_os'] except BadRequestKeyError: device_os = "none" try: os_version = request.values['os_version'] except BadRequestKeyError: os_version = "none" try: product = request.values["product"] except BadRequestKeyError: product = "none" try: brand = request.values["brand"] except BadRequestKeyError: brand = "none" try: hardware_id = request.values["hardware_id"] except BadRequestKeyError: hardware_id = "none" try: manufacturer = request.values["manufacturer"] except BadRequestKeyError: manufacturer = "none" try: model = request.values["model"] except BadRequestKeyError: model = "none" try: beiwe_version = request.values["beiwe_version"] except BadRequestKeyError: beiwe_version = "none" #This value may not be returned by later versions of the beiwe app. try: mac_address = request.values['bluetooth_id'] except BadRequestKeyError: mac_address = "none" user = User(patient_id) study_id = user['study_id'] if user['device_id'] is not None and user['device_id'] != request.values[ 'device_id']: # CASE: this patient has a registered a device already and it does not match this device. # They need to contact the study and unregister their their other device. The device # will receive a 405 error and should alert the user accordingly. # Provided a user does not completely reset their device (which resets the device's # unique identifier) they user CAN reregister an existing device, the unlock key they # need to enter to at registration is their old password. # KG: 405 is good for IOS and Android, no need to check OS_API return abort(405) if user['os_type'] is not None and user['os_type'] != OS_API: # CASE: this patient has registered, but the user was previously registered with a # different device type. To keep the CSV munging code sane and data consistent (don't # cross the iOS and Android data streams!) we disallow it. return abort(400) # At this point the device has been checked for validity and will be registered successfully. # Any errors after this point will be server errors and return 500 codes. the final return # will be the encryption key associated with this user. #Upload the user's various identifiers. unix_time = str(calendar.timegm(time.gmtime())) file_name = patient_id + '/identifiers_' + unix_time + ".csv" #construct a manual csv of the device attributes file_contents = (DEVICE_IDENTIFIERS_HEADER + "%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s" % (patient_id, mac_address, phone_number, device_id, device_os, os_version, product, brand, hardware_id, manufacturer, model, beiwe_version)) # print file_contents, "\n" s3_upload(file_name, file_contents, study_id) FileToProcess.append_file_for_processing(file_name, user['study_id'], patient_id) # set up device. user.set_device(device_id) user.set_os_type(OS_API) User(patient_id).set_password(request.values['new_password']) device_settings = Study(study_id).get_study_device_settings() device_settings.pop('_id', None) return_obj = { 'client_public_key': get_client_public_key_string(patient_id, study_id), 'device_settings': device_settings } return json.dumps(return_obj), 200
def get_latest_surveys(OS_API=""): user = User(request.values['patient_id']) study = Study(user.study_id) return json.dumps(study.get_surveys_for_study())
def decrypt_server(data, study_id): """ Decrypts config encrypted by the encrypt_for_server function.""" encryption_key = Study(study_id)['encryption_key'] iv = data[:16] data = data[16:] return AES.new( encryption_key, AES.MODE_CFB, segment_size=8, IV=iv ).decrypt( data )
def encrypt_for_server(input_string, study_id): """ encrypts config using the ENCRYPTION_KEY, prepends the generated initialization vector. Use this function on an entire file (as a string). """ encryption_key = Study(study_id)['encryption_key'] iv = urandom(16) return iv + AES.new( encryption_key, AES.MODE_CFB, segment_size=8, IV=iv ).encrypt( input_string )
if not studies: raise Exception("you need to provide some studies") def do_email(study): email_system_administrators( study.name + " blew up while reindexing", "Go check on reindex operation.", source_email="*****@*****.**") for study_id in studies: if isinstance(study_id, (str, unicode)): study_id = ObjectId(study_id) study = Study(study_id) print "=============================================================" print "=============================================================" print "=============================================================" print "starting on %s, study id: %s" % (study.name, str(study_id)) print "=============================================================" print "=============================================================" print "=============================================================" study_id = ObjectId(study_id) try: reindex_study(study_id) except Exception as e: process_file_chunks( ) #will raise an error if things fail on second attempt