def create_message(session, tid, wbtip_id, receiver_id, content): wbtip, itip, rtip_id = db_get(session, (models.WhistleblowerTip, models.InternalTip, models.ReceiverTip.id), (models.WhistleblowerTip.id == wbtip_id, models.ReceiverTip.internaltip_id == wbtip_id, models.ReceiverTip.receiver_id == receiver_id, models.InternalTip.id == models.WhistleblowerTip.id, models.InternalTip.enable_two_way_messages.is_(True), models.InternalTip.status != 'closed', models.InternalTip.tid == tid)) itip.update_date = itip.wb_last_access = datetime_now() _content = content if itip.crypto_tip_pub_key: _content = base64.b64encode(GCE.asymmetric_encrypt(itip.crypto_tip_pub_key, content)).decode() msg = models.Message() msg.receivertip_id = rtip_id msg.type = 'whistleblower' msg.content = _content session.add(msg) session.flush() ret = serialize_message(session, msg) ret['content'] = content return ret
def db_update_node(session, tid, user_session, request, language): """ Transaction to update the node configuration :param session: An ORM session :param tid: A tenant ID :param user_session: The current user session :param request: The request data :param language: the language in which to localize data :return: Return the serialized configuration for the specified tenant """ config = ConfigFactory(session, tid) enable_escrow = not config.get_val('escrow') and request.get('escrow', False) disable_escrow = user_session.ek and config.get_val('escrow') and not request.get('escrow', False) config.update('node', request) if request['enable_ricochet_panel'] and not request['ricochet_address']: request['enable_ricochet_panel'] = False # Validate that IP addresses/ranges we're getting are goo if 'ip_filter_admin' in request and request['ip_filter_admin_enable'] and request['ip_filter_admin']: parse_csv_ip_ranges_to_ip_networks(request['ip_filter_admin']) if 'languages_enabled' in request and 'default_language' in request: db_update_enabled_languages(session, tid, request['languages_enabled'], request['default_language']) if language in db_get_languages(session, tid): ConfigL10NFactory(session, tid).update('node', request, language) if enable_escrow: crypto_escrow_prv_key, State.tenant_cache[tid].crypto_escrow_pub_key = GCE.generate_keypair() user = db_get(session, models.User, models.User.id == user_session.user_id) user.crypto_escrow_prv_key = Base64Encoder.encode(GCE.asymmetric_encrypt(user.crypto_pub_key, crypto_escrow_prv_key)) if tid == 1: session.query(models.User).update({'password_change_needed': True}, synchronize_session=False) else: session.query(models.User).filter(models.User.tid == tid).update({'password_change_needed': True}, synchronize_session=False) if disable_escrow: if tid == 1: session.query(models.User).update({'crypto_escrow_bkp1_key': ''}, synchronize_session=False) else: session.query(models.User).update({'crypto_escrow_bkp2_key': ''}, synchronize_session=False) session.query(models.User).filter(models.User.tid == tid).update({'crypto_escrow_prv_key': ''}, synchronize_session=False) config.set_val('crypto_escrow_pub_key', State.tenant_cache[tid].crypto_escrow_pub_key) db_refresh_memory_variables(session, [tid]) if tid == 1: log.setloglevel(config.get_val('log_level')) return db_admin_serialize_node(session, tid, language)
def db_update_step(session, tid, step_id, request, language): """ Transaction for updating a step :param session: An ORM session :param tid: The tenant ID :param step_id: the step_id of the step to update :param request: the step definition dict :param language: the language of the step definition dict :return: a serialization of the object """ step = db_get( session, models.Step, (models.Step.id == step_id, models.Questionnaire.id == models.Step.questionnaire_id, models.Questionnaire.tid == tid)) fill_localized_keys(request, models.Step.localized_keys, language) step.update(request) for child in request['children']: db_update_field(session, tid, child['id'], child, language) db_reset_option_triggers(session, 'step', step.id) for trigger in request.get('triggered_by_options', []): db_create_option_trigger(session, trigger['option'], 'step', step.id, trigger.get('sufficient', True)) return serialize_step(session, tid, step, language)
def create_comment(session, tid, wbtip_id, content): wbtip, itip = db_get(session, (models.WhistleblowerTip, models.InternalTip), (models.WhistleblowerTip.id == wbtip_id, models.InternalTip.id == models.WhistleblowerTip.id, models.InternalTip.enable_two_way_comments.is_(True), models.InternalTip.status != 'closed', models.InternalTip.tid == tid)) itip.update_date = itip.wb_last_access = datetime_now() _content = content if itip.crypto_tip_pub_key: _content = base64.b64encode(GCE.asymmetric_encrypt(itip.crypto_tip_pub_key, content)).decode() comment = models.Comment() comment.internaltip_id = wbtip_id comment.type = 'whistleblower' comment.content = _content session.add(comment) session.flush() ret = serialize_comment(session, comment) ret['content'] = content return ret
def perform_tips_operation(session, tid, receiver_id, operation, rtips_ids): """ Transaction for performing operation on submissions (postpone/delete) :param session: An ORM session :param tid: A tenant ID :param receiver_id: A recipient ID :param operation: An operation command (postpone/delete) :param rtips_ids: The set of submissions on which performing the specified operation """ receiver = db_get(session, models.User, models.User.id == receiver_id) can_postpone_expiration = State.tenant_cache[tid].can_postpone_expiration or receiver.can_postpone_expiration can_delete_submission = State.tenant_cache[tid].can_delete_submission or receiver.can_delete_submission itips = session.query(models.InternalTip) \ .filter(models.ReceiverTip.receiver_id == receiver_id, models.ReceiverTip.id.in_(rtips_ids), models.InternalTip.id == models.ReceiverTip.internaltip_id, models.InternalTip.tid == tid) if operation == 'postpone' and can_postpone_expiration: for itip in itips: db_postpone_expiration(session, itip) elif operation == 'delete' and can_delete_submission: db_delete_itips(session, [itip.id for itip in itips]) else: raise errors.ForbiddenOperation
def db_get_questionnaire(session, tid, questionnaire_id, language, serialize_templates=True): questionnaire = db_get(session, models.Questionnaire, (models.Questionnaire.tid.in_(set([1, tid])), models.Questionnaire.id == questionnaire_id)) return serialize_questionnaire(session, tid, questionnaire, language, serialize_templates=serialize_templates)
def update(session, tid, request): tenant = db_get(session, models.Tenant, models.Tenant.id == tid) tenant.active = request['active'] for var in ['mode', 'name', 'subdomain']: db_set_config_variable(session, tid, var, request[var]) return serialize_tenant(session, tenant)
def db_user_update_user(session, tid, user_session, request): """ Transaction for updating an existing user :param session: An ORM session :param tid: A tenant ID :param user_session: A session of the user invoking the transaction :param request: A user request data :return: A user model """ from globaleaks.handlers.admin.notification import db_get_notification from globaleaks.handlers.admin.node import db_admin_serialize_node user = db_get(session, models.User, models.User.id == user_session.user_id) user.language = request.get('language', State.tenant_cache[tid].default_language) user.name = request['name'] user.public_name = request['public_name'] if request['public_name'] else request['name'] if request['password']: if user.password_change_needed: user.password_change_needed = False else: if not GCE.check_password(user.hash_alg, request['old_password'], user.salt, user.password): raise errors.InvalidOldPassword user_session.cc = set_user_password(tid, user, request['password'], user_session.cc) # If the email address changed, send a validation email if request['mail_address'] != user.mail_address: user.change_email_address = request['mail_address'] user.change_email_date = datetime_now() user.change_email_token = generateRandomKey() user_desc = user_serialize_user(session, user, user.language) user_desc['mail_address'] = request['mail_address'] template_vars = { 'type': 'email_validation', 'user': user_desc, 'new_email_address': request['mail_address'], 'validation_token': user.change_email_token, 'node': db_admin_serialize_node(session, tid, user.language), 'notification': db_get_notification(session, tid, user.language) } State.format_and_send_mail(session, tid, user_desc, template_vars) parse_pgp_options(user, request) return user
def db_get_wbtip(session, itip_id, language): wbtip, itip = db_get(session, (models.WhistleblowerTip, models.InternalTip), (models.WhistleblowerTip.id == models.InternalTip.id, models.InternalTip.id == itip_id)) itip.wb_access_counter += 1 itip.wb_last_access = datetime_now() return serialize_wbtip(session, wbtip, itip, language), base64.b64decode(wbtip.crypto_tip_prv_key)
def db_get_user(session, tid, user_id): """ Transaction for retrieving a user model given an id :param session: An ORM session :param tid: A tenant ID :param user_id: A id of the user to retrieve :return: A retrieved model """ return db_get(session, models.User, (models.User.id == user_id, models.User.tid == tid))
def get_file_id(session, tid, name): """ Transaction returning a file ID given the file name :param session: An ORM session :param tid: A tenant on which performing the lookup :param name: A file name :return: A result model """ return db_get(session, models.File.id, (models.File.tid == tid, models.File.name == name))[0]
def download_wbfile(self, session, tid, file_id): wbfile, wbtip = db_get(session, (models.WhistleblowerFile, models.WhistleblowerTip), (models.WhistleblowerFile.id == file_id, models.WhistleblowerFile.receivertip_id == models.ReceiverTip.id, models.ReceiverTip.internaltip_id == models.WhistleblowerTip.id)) if not self.user_can_access(session, tid, wbfile): raise errors.ResourceNotFound() self.access_wbfile(session, wbfile) return serializers.serialize_wbfile(session, wbfile), base64.b64decode(wbtip.crypto_tip_prv_key)
def db_update_field(session, tid, field_id, request, language): """ Transaction for updating a field :param session: An ORM session :param tid: The tenant ID :param field_id: The ID of the object to be updated :param request: The request data :param language: The language of the request :return: The updated field """ field = db_get(session, models.Field, (models.Field.tid == tid, models.Field.id == field_id)) check_field_association(session, tid, request) fill_localized_keys(request, models.Field.localized_keys, language) if field.instance != 'reference' or field.template_id == 'whistleblower_identity': db_update_fieldattrs(session, field.id, request['attrs'], language) db_reset_option_triggers(session, 'field', field.id) for trigger in request.get('triggered_by_options', []): db_create_option_trigger(session, trigger['option'], 'field', field.id, trigger.get('sufficient', True)) if field.instance != 'reference': db_update_fieldoptions(session, field.id, request['options'], language) # full update field.update(request) else: # partial update field.update({ 'label': request['label'], 'hint': request['hint'], 'description': request['description'], 'placeholder': request['placeholder'], 'template_override_id': request['template_override_id'], 'x': request['x'], 'y': request['y'], 'width': request['width'], 'required': request['required'] }) return field
def db_access_rtip(session, tid, user_id, rtip_id): """ Transaction retrieving an rtip and performing basic access checks :param session: An ORM session :param tid: A tenant ID of the user :param user_id: A user ID :param rtip_id: the requested rtip ID :return: A model requested """ return db_get( session, (models.ReceiverTip, models.InternalTip), (models.ReceiverTip.id == rtip_id, models.ReceiverTip.receiver_id == user_id, models.ReceiverTip.internaltip_id == models.InternalTip.id, models.InternalTip.tid == tid))
def db_get_submission_status(session, tid, status_id, language): """ Transaction for fetching the submission status given its ID :param session: An ORM session :param tid: A tenant ID :param status_id: The ID of the submission status to be retriven :param language: The language to be used in the serialization :return: The serialized descriptor of the indicated submission status """ status = db_get(session, models.SubmissionStatus, (models.SubmissionStatus.tid == tid, models.SubmissionStatus.id == status_id)) return serialize_submission_status(session, status, language)
def update_identity_information(session, tid, tip_id, identity_field_id, wbi, language): itip = db_get(session, models.InternalTip, (models.InternalTip.id == tip_id, models.InternalTip.status != 'closed', models.InternalTip.tid == tid)) if itip.crypto_tip_pub_key: wbi = base64.b64encode(GCE.asymmetric_encrypt(itip.crypto_tip_pub_key, json.dumps(wbi).encode())).decode() db_set_internaltip_data(session, itip.id, 'whistleblower_identity', wbi) now = datetime_now() itip.update_date = now itip.wb_last_access = now
def db_update_submission_status(session, tid, status_id, request, language): """ Transaction for updating a submission status :param session: An ORM session :param tid: The tenant ID :param status_id: The ID of the object to be updated :param request: The request data :param language: The language of the request :return: The serialized descriptor of the updated object """ status = db_get(session, models.SubmissionStatus, (models.SubmissionStatus.tid == tid, models.SubmissionStatus.id == status_id)) db_update_status_model_from_request(status, request, language)
def delete_field(session, tid, field_id): """ Transaction to delete a field :param session: An ORM session :param tid: The tenant ID :param field_id: The id of the field to be deleted """ field = db_get(session, models.Field, (models.Field.tid == tid, models.Field.id == field_id)) if field.instance == 'template' and session.query(models.Field).filter(models.Field.tid == tid, models.Field.template_id == field.id).count(): raise errors.InputValidationError("Cannot remove the field template as it is used by one or more questionnaires") session.delete(field)
def download_rfile(self, session, tid, user_id, file_id): rfile, rtip = db_get( session, (models.ReceiverFile, models.ReceiverTip), (models.ReceiverFile.id == file_id, models.ReceiverFile.receivertip_id == models.ReceiverTip.id, models.ReceiverTip.receiver_id == user_id, models.ReceiverTip.internaltip_id == models.InternalTip.id, models.InternalTip.tid == tid)) log.debug("Download of file %s by receiver %s (%d)" % (rfile.internalfile_id, rtip.receiver_id, rfile.downloads)) rfile.last_access = datetime_now() rfile.downloads += 1 return serializers.serialize_rfile(session, rfile), base64.b64decode( rtip.crypto_tip_prv_key)
def download_wbfile(self, session, tid, file_id): wbfile, wbtip, = db_get( session, (models.WhistleblowerFile, models.WhistleblowerTip), (models.WhistleblowerFile.id == file_id, models.WhistleblowerFile.receivertip_id == models.ReceiverTip.id, models.ReceiverTip.internaltip_id == models.WhistleblowerTip.id)) rtip = session.query(models.ReceiverTip) \ .filter(models.ReceiverTip.receiver_id == self.current_user.user_id, models.ReceiverTip.internaltip_id == wbtip.id).one_or_none() if not rtip: raise errors.ResourceNotFound() self.access_wbfile(session, wbfile) return serializers.serialize_wbfile(session, wbfile), base64.b64decode( rtip.crypto_tip_prv_key)
def update_context(session, tid, context_id, request, language): """ Transaction for updating a context :param session: An ORM session :param tid: The tenant ID :param context_id: The ID of object to be updated :param request: The request data :param language: The request language :return: A serialized descriptor of the context """ context = db_get( session, models.Context, (models.Context.tid == tid, models.Context.id == context_id)) context = db_update_context(session, tid, context, request, language) return admin_serialize_context(session, context, language)
def postpone_expiration(session, tid, user_id, rtip_id): """ Transaction for postponing the expiration of a submission :param session: An ORM session :param tid: A tenant ID of the user performing the operation :param user_id: A user ID of the user performing the operation :param rtip_id: A rtip ID of the submission object of the operation """ rtip, itip = db_access_rtip(session, tid, user_id, rtip_id) receiver = db_get(session, models.User, models.User.id == rtip.receiver_id) if not (State.tenant_cache[tid].can_postpone_expiration or receiver.can_postpone_expiration): raise errors.ForbiddenOperation db_postpone_expiration(session, itip)
def db_access_wbfile(session, tid, user_id, wbfile_id): """ Transaction retrieving an wbfile and performing basic access checks :param session: An ORM session :param tid: A tenant ID of the user :param user_id: A user ID :param wbfile_id: the requested wbfile ID :return: A model requested """ itips_ids = [x[0] for x in session.query(models.InternalTip.id) \ .filter(models.InternalTip.id == models.ReceiverTip.internaltip_id, models.ReceiverTip.receiver_id == user_id, models.InternalTip.tid == tid)] return db_get( session, models.WhistleblowerFile, (models.WhistleblowerFile.id == wbfile_id, models.WhistleblowerFile.receivertip_id == models.ReceiverTip.id, models.ReceiverTip.internaltip_id.in_(itips_ids), models.InternalTip.tid == tid))
def db_update_questionnaire(session, tid, questionnaire_id, request, language): """ Updates the specified questionnaire. If the key receivers is specified we remove the current receivers of the Questionnaire and reset set it to the new specified ones. :param session: An ORM session :param tid: A tenant ID :param questionnaire_id: The ID of the model to be updated :param request: The request data :param language: The language of the request :return: A serialized descriptor of the questionnaire """ questionnaire = db_get(session, models.Questionnaire, (models.Questionnaire.tid == tid, models.Questionnaire.id == questionnaire_id)) fill_localized_keys(request, models.Questionnaire.localized_keys, language) questionnaire.update(request) return serialize_questionnaire(session, tid, questionnaire, language)
def receiver_serialize_wbfile(session, wbfile): """ Transaction returning a serialized descriptor of an wbfile :param session: An ORM session :param wbfile: A model to be serialized :return: A serialized description of the model specified """ rtip = db_get(session, models.ReceiverTip, models.ReceiverTip.id == wbfile.receivertip_id) return { 'id': wbfile.id, 'creation_date': wbfile.creation_date, 'name': wbfile.name, 'filename': wbfile.filename, 'description': wbfile.description, 'size': wbfile.size, 'type': wbfile.content_type, 'downloads': wbfile.downloads, 'author': rtip.receiver_id, 'path': os.path.join(Settings.attachments_path, wbfile.filename) }
def delete_rtip(session, tid, user_id, rtip_id): """ Transaction for deleting a submission :param session: An ORM session :param tid: A tenant ID of the user performing the operation :param user_id: A user ID of the user performing the operation :param rtip_id: A rtip ID of the submission object of the operation """ rtip, itip = db_access_rtip(session, tid, user_id, rtip_id) receiver = db_get(session, models.User, models.User.id == rtip.receiver_id) if not (State.tenant_cache[tid].can_delete_submission or receiver.can_delete_submission): raise errors.ForbiddenOperation State.log(tid=tid, type='delete_report', user_id=user_id, object_id=itip.id) db_delete_itip(session, itip.id)
def set_reset_token(session, user_id, validation_token): user = db_get(session, models.User, models.User.id == user_id) user.change_email_date = datetime_now() user.reset_password_token = validation_token user.reset_password_date = datetime_now()
def get(session, tid): return serialize_tenant( session, db_get(session, models.Tenant, models.Tenant.id == tid))
def set_email_token(session, user_id, validation_token, email): user = db_get(session, models.User, models.User.id == user_id) user.change_email_date = datetime_now() user.change_email_token = validation_token user.change_email_address = email
def db_create_submission(session, tid, request, token, client_using_tor): tenant = db_get(session, models.Tenant, models.Tenant.id == tid) context, questionnaire = db_get( session, (models.Context, models.Questionnaire), (models.Context.id == request['context_id'], models.Questionnaire.id == models.Context.questionnaire_id)) receivers = request['receivers'] answers = request['answers'] steps = db_get_questionnaire(session, tid, questionnaire.id, None)['steps'] questionnaire_hash = db_archive_questionnaire_schema(session, steps) preview = extract_answers_preview(steps, answers) crypto_is_available = State.tenant_cache[tid].encryption crypto_tip_pub_key = '' if crypto_is_available: crypto_tip_prv_key, crypto_tip_pub_key = GCE.generate_keypair() receivers = [r[0] for r in session.query(models.User.id) \ .filter(models.User.id.in_(receivers), models.User.crypto_pub_key != '')] if not request['receivers']: raise errors.InputValidationError( "Unable to deliver the submission to at least one recipient") if 0 < context.maximum_selectable_receivers < len(request['receivers']): raise errors.InputValidationError( "The number of recipients selected exceed the configured limit") itip = models.InternalTip() itip.tid = tid itip.status = 'new' itip.crypto_tip_pub_key = crypto_tip_pub_key itip.progressive = db_assign_submission_progressive(session, tid) itip.additional_questionnaire_id = context.additional_questionnaire_id if context.tip_timetolive > 0: itip.expiration_date = get_expiration(context.tip_timetolive) # Evaluate the score level itip.total_score = request['total_score'] # The status https is used to keep track of the security level adopted by the whistleblower itip.https = not client_using_tor itip.mobile = request['mobile'] itip.context_id = context.id itip.enable_two_way_comments = context.enable_two_way_comments itip.enable_two_way_messages = context.enable_two_way_messages itip.enable_attachments = context.enable_attachments x = session.query(models.Field, models.FieldAttr.value) \ .filter(models.Field.template_id == 'whistleblower_identity', models.Field.step_id == models.Step.id, models.Step.questionnaire_id == context.questionnaire_id, models.FieldAttr.field_id == models.Field.id, models.FieldAttr.name == 'visibility_subject_to_authorization').one_or_none() whistleblower_identity = None can_access_whistleblower_identity = True if x: whistleblower_identity = x[0] can_access_whistleblower_identity = not x[1] itip.enable_whistleblower_identity = whistleblower_identity is not None session.add(itip) session.flush() # Evaluate if the whistleblower tip should be generated if ((not State.tenant_cache[tid].enable_scoring_system) or (context.score_threshold_receipt == 0) or (context.score_threshold_receipt == 1 and itip.total_score >= 2) or (context.score_threshold_receipt == 2 and itip.total_score == 3)): receipt = GCE.generate_receipt() receipt_salt = State.tenant_cache[tid].receipt_salt wbtip = models.WhistleblowerTip() wbtip.id = itip.id wbtip.tid = tid wbtip.hash_alg = 'ARGON2' wbtip.receipt_hash = GCE.hash_password(receipt, receipt_salt) # Evaluate if the whistleblower tip should be encrypted if crypto_is_available: crypto_tip_prv_key, itip.crypto_tip_pub_key = GCE.generate_keypair( ) wb_key = GCE.derive_key(receipt.encode(), receipt_salt) wb_prv_key, wb_pub_key = GCE.generate_keypair() wbtip.crypto_prv_key = Base64Encoder.encode( GCE.symmetric_encrypt(wb_key, wb_prv_key)) wbtip.crypto_pub_key = wb_pub_key wbtip.crypto_tip_prv_key = Base64Encoder.encode( GCE.asymmetric_encrypt(wb_pub_key, crypto_tip_prv_key)) session.add(wbtip) else: receipt = '' # Apply special handling to the whistleblower identity question if itip.enable_whistleblower_identity and request[ 'identity_provided'] and answers[whistleblower_identity.id]: if crypto_is_available: wbi = base64.b64encode( GCE.asymmetric_encrypt( itip.crypto_tip_pub_key, json.dumps(answers[whistleblower_identity.id] [0]).encode())).decode() else: wbi = answers[whistleblower_identity.id][0] answers[whistleblower_identity.id] = '' db_set_internaltip_data(session, itip.id, 'whistleblower_identity', wbi) db_save_plaintext_answers(session, tid, itip.id, answers, crypto_is_available) if crypto_is_available: preview = base64.b64encode( GCE.asymmetric_encrypt( itip.crypto_tip_pub_key, json.dumps(preview, cls=JSONEncoder).encode())).decode() answers = base64.b64encode( GCE.asymmetric_encrypt( itip.crypto_tip_pub_key, json.dumps(answers, cls=JSONEncoder).encode())).decode() itip.preview = preview db_set_internaltip_answers(session, itip.id, questionnaire_hash, answers) for uploaded_file in token.uploaded_files: if not itip.enable_attachments: break if uploaded_file['id'] in request['removed_files']: continue if crypto_is_available: for k in ['name', 'type', 'size']: uploaded_file[k] = base64.b64encode( GCE.asymmetric_encrypt(itip.crypto_tip_pub_key, str(uploaded_file[k]))) new_file = models.InternalFile() new_file.tid = tid new_file.name = uploaded_file['name'] new_file.content_type = uploaded_file['type'] new_file.size = uploaded_file['size'] new_file.internaltip_id = itip.id new_file.filename = uploaded_file['filename'] new_file.submission = uploaded_file['submission'] session.add(new_file) for user in session.query(models.User).filter( models.User.id.in_(request['receivers'])): _tip_key = b'' if crypto_is_available: if not user.crypto_pub_key: continue _tip_key = GCE.asymmetric_encrypt(user.crypto_pub_key, crypto_tip_prv_key) db_create_receivertip(session, user, itip, can_access_whistleblower_identity, _tip_key) return {'receipt': receipt, 'score': itip.total_score}