def submit_answers(eq_id, form_type, collection_id, metadata, answer_store): is_completed, invalid_location = is_survey_completed( answer_store, metadata) if is_completed: path_finder = PathFinder(g.schema_json, answer_store, metadata) message = convert_answers(metadata, g.schema_json, answer_store, path_finder.get_routing_path()) message = current_app.eq['encrypter'].encrypt(message) sent = current_app.eq['submitter'].send_message( message, current_app.config['EQ_RABBITMQ_QUEUE_NAME'], metadata['tx_id']) if not sent: raise SubmissionFailedException() current_app.eq['session_storage'].store_survey_completed_metadata( metadata['tx_id'], metadata['period_str'], metadata['ru_ref']) get_questionnaire_store(current_user.user_id, current_user.user_ik).delete() _remove_survey_session_data() return redirect( url_for('.get_thank_you', eq_id=eq_id, form_type=form_type, collection_id=collection_id)) else: return redirect(invalid_location.url(metadata))
def _submit_data(user): answer_store = get_answer_store(user) if answer_store.answers: metadata = get_metadata(user) schema = load_schema_from_metadata(metadata) routing_path = PathFinder(schema, answer_store, metadata).get_routing_path() message = convert_answers(metadata, schema, answer_store, routing_path, flushed=True) message = current_app.eq['encrypter'].encrypt(message) sent = current_app.eq['submitter'].send_message( message, current_app.config['EQ_RABBITMQ_QUEUE_NAME'], metadata["tx_id"]) if not sent: raise SubmissionFailedException() get_questionnaire_store(user.user_id, user.user_ik).delete() return True else: return False
def _submit_data(user): answer_store = get_answer_store(user) if answer_store.answers: metadata = get_metadata(user) collection_metadata = get_collection_metadata(user) schema = load_schema_from_metadata(metadata) completed_blocks = get_completed_blocks(user) routing_path = PathFinder(schema, answer_store, metadata, completed_blocks).get_full_routing_path() message = convert_answers(metadata, collection_metadata, schema, answer_store, routing_path, flushed=True) encrypted_message = encrypt(message, current_app.eq['key_store'], KEY_PURPOSE_SUBMISSION) sent = current_app.eq['submitter'].send_message( encrypted_message, current_app.config['EQ_RABBITMQ_QUEUE_NAME'], metadata['tx_id']) if not sent: raise SubmissionFailedException() get_questionnaire_store(user.user_id, user.user_ik).delete() return True return False
def submit_answers(schema, questionnaire_store, full_routing_path): answer_store = questionnaire_store.answer_store list_store = questionnaire_store.list_store metadata = questionnaire_store.metadata message = json.dumps( convert_answers(schema, questionnaire_store, full_routing_path), for_json=True ) encrypted_message = encrypt( message, current_app.eq["key_store"], KEY_PURPOSE_SUBMISSION ) sent = current_app.eq["submitter"].send_message( encrypted_message, questionnaire_id=metadata.get("questionnaire_id"), case_id=metadata.get("case_id"), tx_id=metadata.get("tx_id"), ) if not sent: raise SubmissionFailedException() submitted_time = datetime.utcnow() _store_submitted_time_in_session(submitted_time) if is_view_submitted_response_enabled(schema.json): _store_viewable_submission( answer_store.serialise(), list_store.serialise(), metadata, submitted_time ) get_questionnaire_store(current_user.user_id, current_user.user_ik).delete() return redirect(url_for("post_submission.get_thank_you"))
def _submit_data(user): answer_store = get_answer_store(user) if answer_store: questionnaire_store = get_questionnaire_store(user.user_id, user.user_ik) answer_store = questionnaire_store.answer_store metadata = questionnaire_store.metadata response_metadata = questionnaire_store.response_metadata progress_store = questionnaire_store.progress_store list_store = questionnaire_store.list_store submitted_at = datetime.now(timezone.utc) schema = load_schema_from_metadata(metadata) router = Router( schema, answer_store, list_store, progress_store, metadata, response_metadata, ) full_routing_path = router.full_routing_path() message = json_dumps( convert_answers( schema, questionnaire_store, full_routing_path, submitted_at, flushed=True, )) encrypted_message = encrypt(message, current_app.eq["key_store"], KEY_PURPOSE_SUBMISSION) sent = current_app.eq["submitter"].send_message( encrypted_message, tx_id=metadata.get("tx_id"), case_id=metadata["case_id"], ) if not sent: raise SubmissionFailedException() get_questionnaire_store(user.user_id, user.user_ik).delete() logger.info("successfully flushed answers") return True logger.info("no answers found to flush") return False
def store_session(metadata): """ Store new session and metadata :param metadata: metadata parsed from jwt token """ # also clear the secure cookie data cookie_session.clear() # get the hashed user id for eq id_generator = current_app.eq['id_generator'] user_id = id_generator.generate_id(metadata) user_ik = id_generator.generate_ik(metadata) eq_session_id = str(uuid4()) # store the user ik and es_session_id in the cookie cookie_session[USER_IK] = user_ik cookie_session[EQ_SESSION_ID] = eq_session_id session_data = _create_session_data_from_metadata(metadata) create_session_store(eq_session_id, user_id, user_ik, session_data) questionnaire_store = get_questionnaire_store(user_id, user_ik) questionnaire_store.set_metadata(metadata) questionnaire_store.add_or_update() logger.info('user authenticated')
def before_post_submission_request(): if request.method == "OPTIONS": return None metadata = get_metadata(current_user) if not metadata: raise NoQuestionnaireStateException(401) questionnaire_store = get_questionnaire_store( current_user.user_id, current_user.user_ik ) if not questionnaire_store.submitted_at: raise NotFound handle_language() session_store = get_session_store() session_data = session_store.session_data # pylint: disable=assigning-non-slot g.schema = load_schema_from_session_data(session_data) logger.bind(tx_id=session_data.tx_id, schema_name=session_data.schema_name) logger.info( "questionnaire request", method=request.method, url_path=request.full_path )
def store_session(metadata): """ Store new session and metadata :param metadata: metadata parsed from jwt token """ # also clear the secure cookie data cookie_session.clear() # get the hashed user id for eq id_generator = current_app.eq['id_generator'] user_id = id_generator.generate_id(metadata) user_ik = id_generator.generate_ik(metadata) eq_session_id = str(uuid4()) # store the user ik and es_session_id in the cookie cookie_session[USER_IK] = user_ik cookie_session[EQ_SESSION_ID] = eq_session_id session_data = _create_session_data_from_metadata(metadata) create_session_store(eq_session_id, user_id, user_ik, session_data) questionnaire_store = get_questionnaire_store(user_id, user_ik) questionnaire_store.metadata = metadata questionnaire_store.add_or_update() # Set the user in Flask such that anyone calling current_user for the duration of this request doesn't have to # load it from the db. login_user(User(user_id, user_ik)) logger.info('user authenticated')
def before_questionnaire_request(): if request.method == "OPTIONS": return None if cookie_session.get("submitted"): raise PreviouslySubmittedException( "The Questionnaire has been previously submitted" ) metadata = get_metadata(current_user) if not metadata: raise NoQuestionnaireStateException(401) questionnaire_store = get_questionnaire_store( current_user.user_id, current_user.user_ik ) if questionnaire_store.submitted_at: return redirect(url_for("post_submission.get_thank_you")) logger.bind( tx_id=metadata["tx_id"], schema_name=metadata["schema_name"], ce_id=metadata["collection_exercise_sid"], ) logger.info( "questionnaire request", method=request.method, url_path=request.full_path ) handle_language() session_store = get_session_store() # pylint: disable=assigning-non-slot g.schema = load_schema_from_session_data(session_store.session_data)
def jwt_login(self, request): """ Login using a JWT token, this must be an encrypted JWT. :param request: The flask request """ # clear the session entry in the database session_manager.clear() # also clear the secure cookie data session.clear() if request.args.get(EQ_URL_QUERY_STRING_JWT_FIELD_NAME) is None: raise NoTokenException("Please provide a token") token = self._jwt_decrypt(request) # once we've decrypted the token correct # check we have the required user data self._check_user_data(token) # get the hashed user id for eq user_id = UserIDGenerator.generate_id(token) user_ik = UserIDGenerator.generate_ik(token) # store the user id in the session session_manager.store_user_id(user_id) # store the user ik in the cookie session_manager.store_user_ik(user_ik) # store the meta data metadata = parse_metadata(token) questionnaire_store = get_questionnaire_store(user_id, user_ik) questionnaire_store.metadata = metadata questionnaire_store.save() logger.info("User authenticated with tx_id=%s", metadata["tx_id"])
def store_session(metadata: dict[str, Any]) -> None: """ Store new session and metadata :param metadata: metadata parsed from jwt token """ # also clear the secure cookie data cookie_session.clear() # get the hashed user id for eq id_generator = current_app.eq["id_generator"] # type: ignore user_id = id_generator.generate_id(metadata["response_id"]) user_ik = id_generator.generate_ik(metadata["response_id"]) eq_session_id = str(uuid4()) # store the user ik and es_session_id in the cookie cookie_session[USER_IK] = user_ik cookie_session[EQ_SESSION_ID] = eq_session_id session_data = _create_session_data_from_metadata(metadata) create_session_store(eq_session_id, user_id, user_ik, session_data) questionnaire_store = get_questionnaire_store(user_id, user_ik) questionnaire_store.set_metadata(metadata) questionnaire_store.save() logger.info("user authenticated")
def jwt_login(request): """ Login using a JWT token, this must be an encrypted JWT. :param request: The flask request """ # clear the session entry in the database session_storage.clear() # also clear the secure cookie data session.clear() if request.args.get('token') is None: raise NoTokenException("Please provide a token") token = _jwt_decrypt(request) # once we've decrypted the token correct # check we have the required user data _check_user_data(token) # get the hashed user id for eq user_id = UserIDGenerator.generate_id(token) user_ik = UserIDGenerator.generate_ik(token) # store the user id in the session session_storage.store_user_id(user_id) # store the user ik in the cookie session_storage.store_user_ik(user_ik) # store the meta data metadata = parse_metadata(token) logger.bind(tx_id=metadata["tx_id"]) questionnaire_store = get_questionnaire_store(user_id, user_ik) questionnaire_store.metadata = metadata questionnaire_store.add_or_update() logger.info("user authenticated")
def submit_answers(routing_path, eq_id, form_type, schema): metadata = get_metadata(current_user) collection_metadata = get_collection_metadata(current_user) answer_store = get_answer_store(current_user) message = json.dumps( convert_answers( metadata, collection_metadata, schema, answer_store, routing_path, )) encrypted_message = encrypt(message, current_app.eq['key_store'], KEY_PURPOSE_SUBMISSION) sent = current_app.eq['submitter'].send_message( encrypted_message, current_app.config['EQ_RABBITMQ_QUEUE_NAME'], metadata['tx_id'], ) if current_app.config['EQ_PUBSUB_ENABLED']: current_app.eq['pubsub_submitter'].send_message( encrypted_message, current_app.config['EQ_PUBSUB_TOPIC_ID'], metadata['tx_id'], ) if not sent: raise SubmissionFailedException() submitted_time = datetime.utcnow() _store_submitted_time_in_session(submitted_time) if is_view_submitted_response_enabled(schema.json): _store_viewable_submission(list(answer_store), metadata, submitted_time) get_questionnaire_store(current_user.user_id, current_user.user_ik).delete() return redirect( url_for('post_submission.get_thank_you', eq_id=eq_id, form_type=form_type))
def save_questionnaire_store(response): if not current_user.is_anonymous: questionnaire_store = get_questionnaire_store(current_user.user_id, current_user.user_ik) if questionnaire_store.has_changed(): questionnaire_store.add_or_update() return response
def save_questionnaire_store_wrapper(*args, **kwargs): response = func(*args, **kwargs) if not current_user.is_anonymous: questionnaire_store = get_questionnaire_store( current_user.user_id, current_user.user_ik) questionnaire_store.add_or_update() return response
def _set_started_at_metadata_if_required(form, metadata): questionnaire_store = get_questionnaire_store(current_user.user_id, current_user.user_ik) if not questionnaire_store.answer_store.answers and len(form.data) > 1: started_at = datetime.now(timezone.utc).isoformat() logger.info( 'first answer about to be stored. writing started_at time to metadata', started_at=started_at) metadata['started_at'] = started_at
def _remove_repeating_on_household_answers(answer_store, group_id): answer_store.remove(group_id=group_id, block_id='household-composition') questionnaire_store = get_questionnaire_store(current_user.user_id, current_user.user_ik) for answer in SchemaHelper.get_answers_that_repeat_in_block(g.schema_json, 'household-composition'): groups_to_delete = SchemaHelper.get_groups_that_repeat_with_answer_id(g.schema_json, answer['id']) for group in groups_to_delete: answer_store.remove(group_id=group['id']) questionnaire_store.completed_blocks[:] = [b for b in questionnaire_store.completed_blocks if b.group_id != group['id']]
def _update_questionnaire_store(current_location, form): questionnaire_store = get_questionnaire_store(current_user.user_id, current_user.user_ik) if current_location.block_id in [ 'relationships', 'household-relationships' ]: update_questionnaire_store_with_answer_data( questionnaire_store, current_location, form.serialise(current_location)) else: update_questionnaire_store_with_form_data(questionnaire_store, current_location, form.data)
def post_household_composition(eq_id, form_type, collection_id, group_id): # pylint: disable=too-many-locals answer_store = get_answer_store(current_user) if _household_answers_changed(answer_store): _remove_repeating_on_household_answers(answer_store, group_id) error_messages = SchemaHelper.get_messages(g.schema_json) disable_mandatory = any(x in request.form for x in [ 'action[add_answer]', 'action[remove_answer]', 'action[save_sign_out]' ]) current_location = Location(group_id, 0, 'household-composition') block = _render_schema(current_location) form, _ = post_form_for_location(block, current_location, answer_store, request.form, error_messages, disable_mandatory=disable_mandatory) if 'action[add_answer]' in request.form: form.household.append_entry() elif 'action[remove_answer]' in request.form: index_to_remove = int(request.form.get('action[remove_answer]')) form.remove_person(index_to_remove) elif 'action[save_sign_out]' in request.form: response = _save_sign_out(collection_id, eq_id, form_type, current_location, form) remove_empty_household_members_from_answer_store( answer_store, group_id) return response if _is_invalid_form( form ) or 'action[add_answer]' in request.form or 'action[remove_answer]' in request.form: context = {'form': form, 'block': block} return _build_template(current_location, context, template='questionnaire') else: questionnaire_store = get_questionnaire_store(current_user.user_id, current_user.user_ik) update_questionnaire_store_with_answer_data( questionnaire_store, current_location, form.serialise(current_location)) metadata = get_metadata(current_user) path_finder = PathFinder(g.schema_json, get_answer_store(current_user), metadata) next_location = path_finder.get_next_location( current_location=current_location) return redirect(next_location.url(metadata))
def _update_questionnaire_store(current_location, form, schema): questionnaire_store = get_questionnaire_store(current_user.user_id, current_user.user_ik) if schema.block_has_question_type(current_location.block_id, 'Relationship'): update_questionnaire_store_with_answer_data(questionnaire_store, current_location, form.serialise(), schema) else: update_questionnaire_store_with_form_data(questionnaire_store, current_location, form.data, schema)
def check_session(self): """ Checks for the present of the JWT in the users sessions :return: A user object if a JWT token is available in the session """ logger.debug("Checking for session") if session_manager.has_user_id(): user = User(session_manager.get_user_id(), session_manager.get_user_ik()) questionnaire_store = get_questionnaire_store(user.user_id, user.user_ik) metadata = questionnaire_store.metadata logger.info("Session token exists for tx_id=%s", metadata["tx_id"]) return user else: logging.info("Session does not have an authenticated token") return None
def dump_submission(schema, questionnaire_store): router = Router( schema, questionnaire_store.answer_store, questionnaire_store.list_store, questionnaire_store.progress_store, questionnaire_store.metadata, ) routing_path = router.full_routing_path() questionnaire_store = get_questionnaire_store( current_user.user_id, current_user.user_ik ) response = { "submission": convert_answers(schema, questionnaire_store, routing_path) } return json.dumps(response, for_json=True), 200
def check_session(): """ Checks for the present of the JWT in the users sessions :return: A user object if a JWT token is available in the session """ logger.debug("Checking for session") if session_storage.has_user_id(): user = User(session_storage.get_user_id(), session_storage.get_user_ik()) questionnaire_store = get_questionnaire_store(user.user_id, user.user_ik) metadata = questionnaire_store.metadata logger.debug("Session token exists for tx_id=%s", metadata["tx_id"]) return user else: logging.info("Session does not have an authenticated token") return None
def dump_submission(schema, questionnaire_store): router = Router( schema, questionnaire_store.answer_store, questionnaire_store.list_store, questionnaire_store.progress_store, questionnaire_store.metadata, ) routing_path = router.full_routing_path() questionnaire_store = get_questionnaire_store(current_user.user_id, current_user.user_ik) submission_handler = SubmissionHandler(schema, questionnaire_store, routing_path) response = {"submission": submission_handler.get_payload()} return json_dumps(response), 200
def _save_sign_out(collection_id, eq_id, form_type, this_location, form): questionnaire_store = get_questionnaire_store(current_user.user_id, current_user.user_ik) block_json = _render_schema(this_location) # Form will have been created with no mandatory fields if not form.validate(): content = {'block': block_json, 'form': form} return _build_template(this_location, content, template='questionnaire') else: if this_location.block_id != 'household-composition': update_questionnaire_store_with_form_data(questionnaire_store, this_location, form.data) else: update_questionnaire_store_with_answer_data(questionnaire_store, this_location, form.serialise(this_location)) if this_location in questionnaire_store.completed_blocks: questionnaire_store.completed_blocks.remove(this_location) return redirect(url_for('.get_sign_out', eq_id=eq_id, form_type=form_type, collection_id=collection_id))
def load_user(): """ Checks for the present of the JWT in the users sessions :return: A user object if a JWT token is available in the session """ user_id = current_app.eq['session_storage'].get_user_id() if user_id: user = User(user_id, current_app.eq['session_storage'].get_user_ik()) questionnaire_store = get_questionnaire_store(user.user_id, user.user_ik) metadata = questionnaire_store.metadata if metadata: logger.bind(tx_id=metadata["tx_id"]) logger.debug("session token exists") return user else: logger.info("session does not have an authenticated token") return None
def process_incoming_answers(self, location, post_data): logger.debug("Processing post data for %s", location) is_valid = self.validate(location, post_data) # run the validator to update the validation_store if is_valid: # Store answers in QuestionnaireStore questionnaire_store = get_questionnaire_store(current_user.user_id, current_user.user_ik) for answer in self.get_state_answers(location): questionnaire_store.answers[answer.id] = answer.value if location not in questionnaire_store.completed_blocks: questionnaire_store.completed_blocks.append(location) questionnaire_store.save() return is_valid
def _remove_repeating_on_household_answers(answer_store, schema): answer_ids = schema.get_answer_ids_for_block('household-composition') answer_store.remove(answer_ids=answer_ids) questionnaire_store = get_questionnaire_store( current_user.user_id, current_user.user_ik, ) for answer in schema.get_answers_that_repeat_in_block( 'household-composition'): groups_to_delete = schema.get_groups_that_repeat_with_answer_id( answer['id']) for group in groups_to_delete: answer_ids = schema.get_answer_ids_for_group(group['id']) answer_store.remove(answer_ids=answer_ids) questionnaire_store.completed_blocks[:] = [ b for b in questionnaire_store.completed_blocks if b.group_id != group['id'] ]
def post_household_composition(eq_id, form_type, collection_id, group_id): path_finder = PathFinder(g.schema_json, get_answer_store(current_user), get_metadata(current_user)) answer_store = get_answer_store(current_user) questionnaire_store = get_questionnaire_store(current_user.user_id, current_user.user_ik) current_location = Location(group_id, 0, 'household-composition') block = _render_schema(current_location) if _household_answers_changed(answer_store): _remove_repeating_on_household_answers(answer_store, group_id) error_messages = SchemaHelper.get_messages(g.schema_json) if any(x in request.form for x in ['action[add_answer]', 'action[remove_answer]', 'action[save_sign_out]']): disable_mandatory = True else: disable_mandatory = False form, _ = post_form_for_location(block, current_location, answer_store, request.form, error_messages, disable_mandatory=disable_mandatory) if 'action[add_answer]' in request.form: form.household.append_entry() elif 'action[remove_answer]' in request.form: index_to_remove = int(request.form.get('action[remove_answer]')) form.remove_person(index_to_remove) elif 'action[save_sign_out]' in request.form: return _save_sign_out(collection_id, eq_id, form_type, current_location, form) if not form.validate() or 'action[add_answer]' in request.form or 'action[remove_answer]' in request.form: return _render_template({ 'form': form, 'block': block, }, current_location.block_id, current_location=current_location, template='questionnaire') update_questionnaire_store_with_answer_data(questionnaire_store, current_location, form.serialise(current_location)) next_location = path_finder.get_next_location(current_location=current_location) metadata = get_metadata(current_user) return redirect(next_location.url(metadata))
def post_block(eq_id, form_type, collection_id, group_id, group_instance, block_id): path_finder = PathFinder(g.schema_json, get_answer_store(current_user), get_metadata(current_user)) current_location = Location(group_id, group_instance, block_id) valid_location = current_location in path_finder.get_routing_path(group_id, group_instance) block = _render_schema(current_location) error_messages = SchemaHelper.get_messages(g.schema_json) disable_mandatory = 'action[save_sign_out]' in request.form form, _ = post_form_for_location(block, current_location, get_answer_store(current_user), request.form, error_messages, disable_mandatory=disable_mandatory) if 'action[save_sign_out]' in request.form: return _save_sign_out(collection_id, eq_id, form_type, current_location, form) content = { 'form': form, 'block': block, } if not valid_location or not form.validate(): return _build_template(current_location, content, template='questionnaire') else: questionnaire_store = get_questionnaire_store(current_user.user_id, current_user.user_ik) if current_location.block_id in ['relationships', 'household-relationships']: update_questionnaire_store_with_answer_data(questionnaire_store, current_location, form.serialise(current_location)) else: update_questionnaire_store_with_form_data(questionnaire_store, current_location, form.data) next_location = path_finder.get_next_location(current_location=current_location) if next_location is None: raise NotFound metadata = get_metadata(current_user) return redirect(next_location.url(metadata))
def _save_sign_out(collection_id, eq_id, form_type, this_location, form): questionnaire_store = get_questionnaire_store(current_user.user_id, current_user.user_ik) block_json = _render_schema(this_location) if _is_invalid_form(form): content = {'block': block_json, 'form': form} return _build_template(this_location, content, template=block_json['type']) else: _update_questionnaire_store(this_location, form) if this_location in questionnaire_store.completed_blocks: questionnaire_store.completed_blocks.remove(this_location) questionnaire_store.add_or_update() _remove_survey_session_data() return redirect( url_for('.get_sign_out', eq_id=eq_id, form_type=form_type, collection_id=collection_id))
def _save_sign_out(routing_path, current_location, form, schema, answer_store, metadata): questionnaire_store = get_questionnaire_store(current_user.user_id, current_user.user_ik) block = _get_block_json(current_location, schema, answer_store, metadata) if form.validate(): answer_store_updater = AnswerStoreUpdater(current_location, schema, questionnaire_store) answer_store_updater.save_answers(form) questionnaire_store.remove_completed_blocks(location=current_location) questionnaire_store.add_or_update() logout_user() return redirect(url_for('session.get_sign_out')) context = _get_context(routing_path, block, current_location, schema, form) return _render_page(block['type'], context, current_location, schema, answer_store, metadata, routing_path)
def post_interstitial(eq_id, form_type, collection_id, block_id): # pylint: disable=unused-argument path_finder = PathFinder(g.schema_json, get_answer_store(current_user), get_metadata(current_user)) current_location = Location(SchemaHelper.get_first_group_id(g.schema_json), 0, block_id) valid_location = current_location in path_finder.get_location_path() questionnaire_store = get_questionnaire_store(current_user.user_id, current_user.user_ik) update_questionnaire_store_with_form_data(questionnaire_store, current_location, request.form.to_dict()) # Don't care if data is valid because there isn't any for interstitial if not valid_location: block = _render_schema(current_location) return _build_template(current_location=current_location, context={"block": block}, template='questionnaire') next_location = path_finder.get_next_location(current_location=current_location) if next_location is None: raise NotFound metadata = get_metadata(current_user) next_location_url = next_location.url(metadata) logger.debug("redirecting", url=next_location_url) return redirect(next_location_url)
def delete_user_data(): get_questionnaire_store(current_user.user_id, current_user.user_ik).delete() session_manager.clear()