def db_generate_password_reset_token(session, user): """ Transaction for issuing password reset tokens :param session: An ORM session :param user: The user for which issuing a password reset token """ user.reset_password_token = generateRandomKey() user.reset_password_date = datetime_now() if user.last_login > datetime_null(): template = 'password_reset_validation' else: template = 'account_activation' user_desc = user_serialize_user(session, user, user.language) template_vars = { 'type': template, 'user': user_desc, 'reset_token': user.reset_password_token, 'node': db_admin_serialize_node(session, user.tid, user.language), 'notification': db_get_notification(session, user.tid, user.language) } State.format_and_send_mail(session, user.tid, user_desc, template_vars)
def post(self): request = self.validate_message(self.request.content.read(), requests.AuthDesc) tid = int(request['tid']) if tid == 0: tid = self.request.tid yield login_delay(tid) self.state.tokens.use(request['token']) session = yield login(tid, request['username'], request['password'], request['authcode'], self.request.client_using_tor, self.request.client_ip) State.log(tid=tid, type='login', user_id=session.user_id) if tid != self.request.tid: returnValue({ 'redirect': 'https://%s/#/login?token=%s' % (State.tenant_cache[tid].hostname, session.id) }) returnValue(session.serialize())
def mail_exception_handler(etype, value, tback): """ Formats traceback and exception data and emails the error, This would be enabled only in the testing phase and testing release, not in production release. """ if isinstance(value, (GeneratorExit, defer.AlreadyCalledError, SMTPError)) or \ (etype == AssertionError and value.message == "Request closed"): # we need to bypass email notification for some exception that: # 1) raise frequently or lie in a twisted bug; # 2) lack of useful stacktraces; # 3) can be cause of email storm amplification # # this kind of exception can be simply logged error logs. log.err( "exception mail suppressed for exception (%s) [reason: special exception]", str(etype)) return # collection of the stacktrace info exc_type = re.sub("(<(type|class ')|'exceptions.|'>|__main__.)", "", str(etype)) error_message = "%s %s" % (exc_type.strip(), etype.__doc__) traceinfo = '\n'.join(traceback.format_exception(etype, value, tback)) mail_body = error_message + "\n\n" + traceinfo log.err("Unhandled exception raised:") log.err(mail_body) from globaleaks.state import State State.schedule_exception_email(mail_body)
def login(session, tid, username, password, authcode, client_using_tor, client_ip): """ login returns a session """ user = None users = session.query(User).filter(User.username == username, User.state != u'disabled', UserTenant.user_id == User.id, UserTenant.tenant_id == tid).distinct() for u in users: if GCE.check_password(u.hash_alg, password, u.salt, u.password): user = u break if user is None: log.debug("Login: Invalid credentials") Settings.failed_login_attempts += 1 raise errors.InvalidAuthentication connection_check(client_ip, tid, user.role, client_using_tor) if State.tenant_cache[ 1].two_factor_auth and user.last_login != datetime_null(): token = TwoFactorTokens.get(user.id) if token is not None and authcode != '': if token.token == authcode: TwoFactorTokens.revoke(user.id) else: raise errors.InvalidTwoFactorAuthCode elif token is None and authcode == '': token = TwoFactorTokens.new(user.id) data = {'type': '2fa', 'authcode': str(token.token)} data['node'] = db_admin_serialize_node(session, tid, user.language) data['notification'] = db_get_notification(session, tid, user.language) subject, body = Templating().get_mail_subject_and_body(data) State.sendmail(1, user.mail_address, subject, body) raise errors.TwoFactorAuthCodeRequired else: raise errors.TwoFactorAuthCodeRequired user.last_login = datetime_now() crypto_prv_key = '' if State.tenant_cache[1].encryption and user.crypto_prv_key: user_key = GCE.derive_key(password.encode('utf-8'), user.salt) crypto_prv_key = GCE.symmetric_decrypt(user_key, user.crypto_prv_key) return Sessions.new(tid, user.id, user.role, user.password_change_needed, crypto_prv_key)
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 = models.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 login_failure(tid, role, whistleblower=False): Settings.failed_login_attempts[tid] = Settings.failed_login_attempts.get( tid, 0) + 1 State.log(tid=tid, type='whistleblower_login_failure' if whistleblower else 'login_failure') raise errors.InvalidAuthentication
def delete(self): """ Logout """ if self.current_user.user_role == 'whistleblower': State.log(tid=self.current_user.tid, type='whistleblower_logout') else: State.log(tid=self.current_user.tid, type='logout', user_id=self.current_user.user_id) del Sessions[self.current_user.id]
def process_mail_creation(self, store, data): user_id = data['user']['id'] # Do not spool emails if the receiver has opted out of ntfns for this tip. if not data['tip']['enable_notifications']: log.debug("Discarding emails for %s due to receiver's preference.", user_id) return # https://github.com/globaleaks/GlobaLeaks/issues/798 # TODO: the current solution is global and configurable only by the admin sent_emails = State.get_mail_counter(user_id) if sent_emails >= State.tenant_cache[ 1].notif.notification_threshold_per_hour: log.debug( "Discarding emails for receiver %s due to threshold already exceeded for the current hour", user_id) return State.increment_mail_counter(user_id) if sent_emails >= State.tenant_cache[ 1].notif.notification_threshold_per_hour: log.info( "Reached threshold of %d emails with limit of %d for receiver %s", sent_emails, State.tenant_cache[1].notif.notification_threshold_per_hour, user_id) # simply changing the type of the notification causes # to send the notification_limit_reached data['type'] = u'receiver_notification_limit_reached' data['notification'] = self.serialize_config(store, 'notification', data['user']['language']) data['node'] = self.serialize_config(store, 'node', data['user']['language']) if not data['node']['allow_unencrypted'] and len( data['user']['pgp_key_public']) == 0: return subject, body = Templating().get_mail_subject_and_body(data) # If the receiver has encryption enabled encrypt the mail body if data['user']['pgp_key_public']: body = encrypt_message(data['user']['pgp_key_public'], body) store.add( models.Mail({ 'address': data['user']['mail_address'], 'subject': subject, 'body': body }))
def reset_submissions(session, tid, user_id): """ Transaction to reset the submissions of the specified tenant :param session: An ORM session :param tid: A tenant ID """ session.query(Config).filter(Config.tid == tid, Config.var_name == 'counter_submissions').update({'value': 0}) db_del(session, InternalTip, InternalTip.tid==tid) State.log(tid=tid, type='reset_reports', user_id=user_id)
def post(self): request = self.validate_message(self.request.content.read(), requests.ReceiptAuthDesc) yield login_delay(self.request.tid) self.state.tokens.use(request['token']) connection_check(self.request.tid, self.request.client_ip, 'whistleblower', self.request.client_using_tor) session = yield login_whistleblower(self.request.tid, request['receipt']) State.log(tid=session.tid, type='whistleblower_login') returnValue(session.serialize())
def set_user_password(tid, user, password, cc): # Regenerate the password hash only if different from the best choice on the platform if user.hash_alg != 'ARGON2': user.hash_alg = 'ARGON2' user.salt = GCE.generate_salt() password_hash = GCE.hash_password(password, user.salt) # Check that the new password is different form the current password if user.password == password_hash: raise errors.PasswordReuseError user.password = password_hash user.password_change_date = datetime_now() State.log(tid=tid, type='change_password', user_id=user.id, object_id=user.id) if not State.tenant_cache[tid].encryption and cc == '': return None enc_key = GCE.derive_key(password.encode(), user.salt) if not cc: # The first password change triggers the generation # of the user encryption private key and its backup cc, user.crypto_pub_key = GCE.generate_keypair() user.crypto_bkp_key, user.crypto_rec_key = GCE.generate_recovery_key( cc) user.crypto_prv_key = Base64Encoder.encode( GCE.symmetric_encrypt(enc_key, cc)) if State.tenant_cache[1].crypto_escrow_pub_key: user.crypto_escrow_bkp1_key = Base64Encoder.encode( GCE.asymmetric_encrypt(State.tenant_cache[1].crypto_escrow_pub_key, cc)) if State.tenant_cache[tid].crypto_escrow_pub_key: user.crypto_escrow_bkp2_key = Base64Encoder.encode( GCE.asymmetric_encrypt( State.tenant_cache[tid].crypto_escrow_pub_key, cc)) return cc
def db_admin_update_user(session, tid, user_session, user_id, request, language): """ Transaction for updating an existing user :param session: An ORM session :param tid: A tenant ID :param user_session: The current user session :param user_id: The ID of the user to update :param request: The request data :param language: The language of the request :return: The serialized descriptor of the updated object """ fill_localized_keys(request, models.User.localized_keys, language) user = db_get_user(session, tid, user_id) user.update(request) password = request['password'] if password and (not user.crypto_pub_key or user_session.ek): if user.crypto_pub_key and user_session.ek: enc_key = GCE.derive_key(password.encode(), user.salt) crypto_escrow_prv_key = GCE.asymmetric_decrypt(user_session.cc, Base64Encoder.decode(user_session.ek)) if user_session.user_tid == 1: user_cc = GCE.asymmetric_decrypt(crypto_escrow_prv_key, Base64Encoder.decode(user.crypto_escrow_bkp1_key)) else: user_cc = GCE.asymmetric_decrypt(crypto_escrow_prv_key, Base64Encoder.decode(user.crypto_escrow_bkp2_key)) user.crypto_prv_key = Base64Encoder.encode(GCE.symmetric_encrypt(enc_key, user_cc)) if user.hash_alg != 'ARGON2': user.hash_alg = 'ARGON2' user.salt = GCE.generate_salt() user.password = GCE.hash_password(password, user.salt) user.password_change_date = datetime_now() State.log(tid=tid, type='change_password', user_id=user_session.user_id, object_id=user_id) # The various options related in manage PGP keys are used here. parse_pgp_options(user, request) return user_serialize_user(session, user, language)
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: itip_ids = [itip.id for itip in itips] for itip_id in itip_ids: State.log(tid=tid, type='delete_report', user_id=receiver_id, object_id=itip_id) db_del(session, models.InternalTip, models.InternalTip.id.in_(itip_ids)) else: raise errors.ForbiddenOperation
def init_state(): Settings.set_devel_mode() Settings.disable_notifications = True Settings.failed_login_attempts.clear() Settings.working_path = os.path.abspath('./working_path') Settings.eval_paths() if os.path.exists(Settings.working_path): shutil.rmtree(Settings.working_path) orm.set_thread_pool(FakeThreadPool()) State.settings.enable_api_cache = False State.tenant_cache[1] = ObjectDict() State.tenant_cache[1].hostname = 'www.globaleaks.org' State.tenant_cache[1].encryption = True State.init_environment() Sessions.clear()
def init_state(): Settings.testing = True Settings.set_devel_mode() Settings.logging = None Settings.failed_login_attempts = 0 Settings.working_path = './working_path' Settings.eval_paths() if os.path.exists(Settings.working_path): dir_util.remove_tree(Settings.working_path, 0) orm.set_thread_pool(FakeThreadPool()) State.settings.enable_api_cache = False State.tenant_cache[1] = ObjectDict() State.tenant_cache[1].hostname = 'www.globaleaks.org' State.init_environment() Sessions.clear()
def init_state(): Settings.testing = True Settings.set_devel_mode() Settings.logging = None Settings.failed_login_attempts = 0 Settings.working_path = os.path.abspath('./working_path') Settings.eval_paths() if os.path.exists(Settings.working_path): dir_util.remove_tree(Settings.working_path, 0) orm.set_thread_pool(FakeThreadPool()) State.settings.enable_api_cache = False State.tenant_cache[1] = ObjectDict() State.tenant_cache[1].hostname = 'www.globaleaks.org' State.tenant_cache[1].encryption = True State.init_environment() Sessions.clear()
def setUp(self): test_config.skipCase(self) self.test_reactor = task.Clock() jobs.base.reactor = self.test_reactor tempdict.reactor = self.test_reactor token.TokenList.reactor = self.test_reactor Sessions.reactor = self.test_reactor init_glsettings_for_unit_tests() self.setUp_dummy() if self.initialize_test_database_using_archived_db: shutil.copy( os.path.join(TEST_DIR, 'db', 'empty', Settings.db_file_name), os.path.join(Settings.working_path, 'db', Settings.db_file_name)) else: yield db.init_db() allow_unencrypted = self.encryption_scenario in ['PLAINTEXT', 'MIXED'] yield update_node_setting(u'allow_unencrypted', allow_unencrypted) yield db.refresh_memory_variables() sup = ProcessSupervisor([], '127.0.0.1', 8082) State.process_supervisor = sup Alarm.reset() event.EventTrackQueue.clear() State.reset_hourly() Settings.submission_minimum_delay = 0 self.internationalized_text = load_appdata( )['node']['whistleblowing_button']
def operation(self): # ------- BEGIN Anomalies section ------- anomalies_to_save = get_anomalies() if anomalies_to_save: save_anomalies(anomalies_to_save) log.debug("Stored %d anomalies collected during the last hour", len(anomalies_to_save)) # ------- END Anomalies section --------- # ------- BEGIN Stats section ----------- current_time = datetime_now() statistic_summary = get_statistics() if statistic_summary: save_statistics(State.stats_collection_start_time, current_time, statistic_summary) log.debug("Stored statistics %s collected from %s to %s", statistic_summary, State.stats_collection_start_time, current_time) # ------- END Stats section ------------- # Hourly Resets State.reset_hourly()
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 db_create_submission(session, tid, request, token, client_using_tor): encryption = db_get( session, models.Config, (models.Config.tid == tid, models.Config.var_name == 'encryption')) crypto_is_available = encryption.value 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)) answers = request['answers'] steps = db_get_questionnaire(session, tid, questionnaire.id, None)['steps'] questionnaire_hash = db_archive_questionnaire_schema(session, steps) crypto_tip_pub_key = '' receivers = [] for r in session.query(models.User).filter( models.User.id.in_(request['receivers'])): if crypto_is_available: if r.crypto_pub_key: # This is the regular condition of systems setup on Globaleaks 4 # Since this version, encryption is enabled by default and # users need to perform their first access before they # could receive reports. receivers.append(r) elif encryption.update_date != datetime_null(): # This is the exceptional condition of systems setup when # encryption was implemented via PGP. # For continuity reason of those production systems # encryption could not be enforced. receivers.append(r) crypto_is_available = False else: receivers.append(r) if not 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") if crypto_is_available: crypto_tip_prv_key, crypto_tip_pub_key = GCE.generate_keypair() 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) 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) if crypto_is_available: answers = base64.b64encode( GCE.asymmetric_encrypt( itip.crypto_tip_pub_key, json.dumps(answers, cls=JSONEncoder).encode())).decode() 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 receivers: if crypto_is_available: _tip_key = GCE.asymmetric_encrypt(user.crypto_pub_key, crypto_tip_prv_key) else: _tip_key = b'' db_create_receivertip(session, user, itip, can_access_whistleblower_identity, _tip_key) State.log(tid=tid, type='whistleblower_new_report') return {'receipt': receipt, 'score': itip.total_score}
def signup(session, request, language): """ Transact handling the registration of a new signup :param session: An ORM session :param request: A user request :param language: A language of the request """ config = ConfigFactory(session, 1) if not config.get_val('enable_signup'): raise errors.ForbiddenOperation request['activation_token'] = generateRandomKey(32) request['language'] = language # Delete the tenants created for the same subdomain that have still not been activated # Ticket reference: https://github.com/globaleaks/GlobaLeaks/issues/2640 subquery = session.query(models.Tenant.id) \ .filter(models.Signup.subdomain == request['subdomain'], not_(models.Signup.activation_token.is_(None)), models.Tenant.id == models.Signup.tid, models.Tenant.subdomain == models.Signup.subdomain) \ .subquery() session.query(models.Tenant).filter( models.Tenant.id.in_(subquery)).delete(synchronize_session=False) tenant_id = db_preallocate_tenant(session, { 'label': request['subdomain'], 'subdomain': request['subdomain'] }).id signup = models.Signup(request) signup.tid = tenant_id session.add(signup) # We need to send two emails # # The first one is sent to the platform owner with the activation email. # # The second goes to the instance administrators notifying them that a new # platform has been added. signup_dict = serialize_signup(signup) # Email 1 - Activation Link template_vars = { 'type': 'signup', 'node': db_admin_serialize_node(session, 1, language), 'notification': db_get_notification(session, 1, language), 'signup': signup_dict } State.format_and_send_mail(session, 1, {'mail_address': signup.email}, template_vars) # Email 2 - Admin Notification for user_desc in db_get_users(session, 1, 'admin'): template_vars = { 'type': 'admin_signup_alert', 'node': db_admin_serialize_node(session, 1, user_desc['language']), 'notification': db_get_notification(session, 1, user_desc['language']), 'user': user_desc, 'signup': signup_dict } State.format_and_send_mail(session, 1, user_desc, template_vars)
def signup_activation(session, token, hostname, language): """ Transaction registering the activation of a platform registered via signup :param session: An ORM session :param token: A activation token :param language: A language of the request """ config = ConfigFactory(session, 1) if not config.get_val('enable_signup'): raise errors.ForbiddenOperation ret = session.query(models.Signup, models.Tenant) \ .filter(models.Signup.activation_token == token, models.Tenant.id == models.Signup.tid).one_or_none() if ret is None: return {} signup, tenant = ret[0], ret[1] signup.activation_token = None mode = config.get_val('mode') db_initialize_tenant(session, tenant, mode) password_admin = generateRandomKey(16) password_receiver = generateRandomKey(16) node_name = signup.organization_name if signup.organization_name else signup.subdomain wizard = { 'node_language': signup.language, 'node_name': node_name, 'admin_username': '******', 'admin_name': signup.name + ' ' + signup.surname, 'admin_password': password_admin, 'admin_mail_address': signup.email, 'receiver_username': '******', 'receiver_name': signup.name + ' ' + signup.surname, 'receiver_password': password_receiver, 'receiver_mail_address': signup.email, 'profile': 'default', 'skip_recipient_account_creation': False, 'enable_developers_exception_notification': True } db_wizard(session, signup.tid, hostname, wizard) template_vars = { 'type': 'activation', 'node': db_admin_serialize_node(session, 1, language), 'notification': db_get_notification(session, 1, language), 'signup': serialize_signup(signup), 'password_admin': wizard['admin_password'], 'password_recipient': wizard['receiver_password'] } State.format_and_send_mail(session, 1, {'mail_address': signup.email}, template_vars) db_refresh_memory_variables(session, [signup.tid])
def login(session, tid, username, password, authcode, client_using_tor, client_ip): """ login returns a session """ user = None users = session.query(User).filter(User.username == username, User.state != u'disabled', UserTenant.user_id == User.id, UserTenant.tenant_id == tid).distinct() for u in users: if GCE.check_password(u.hash_alg, password, u.salt, u.password): user = u break # Fix for issue: https://github.com/globaleaks/GlobaLeaks/issues/2563 if State.tenant_cache[1].creation_date < datetime.timestamp(datetime(2019, 5, 3, 0, 0)): u_password = '******'' + u.password + '\'' if GCE.check_password(u.hash_alg, password, u.salt, u_password): user = u break if user is None: log.debug("Login: Invalid credentials") Settings.failed_login_attempts += 1 raise errors.InvalidAuthentication connection_check(client_ip, tid, user.role, client_using_tor) if State.tenant_cache[1].two_factor_auth and user.last_login != datetime_null(): token = TwoFactorTokens.get(user.id) if token is not None and authcode != '': if token.token == authcode: TwoFactorTokens.revoke(user.id) else: raise errors.InvalidTwoFactorAuthCode elif token is None and authcode == '': token = TwoFactorTokens.new(user.id) data = { 'type': '2fa', 'authcode': str(token.token) } data['node'] = db_admin_serialize_node(session, tid, user.language) data['notification'] = db_get_notification(session, tid, user.language) subject, body = Templating().get_mail_subject_and_body(data) State.sendmail(1, user.mail_address, subject, body) raise errors.TwoFactorAuthCodeRequired else: raise errors.TwoFactorAuthCodeRequired user.last_login = datetime_now() crypto_prv_key = '' if State.tenant_cache[1].encryption and user.crypto_prv_key: user_key = GCE.derive_key(password.encode('utf-8'), user.salt) crypto_prv_key = GCE.symmetric_decrypt(user_key, user.crypto_prv_key) return Sessions.new(tid, user.id, user.role, user.password_change_needed, crypto_prv_key)
def login(session, tid, username, password, receiver_second_login, receiver_auth_code, client_using_tor, client_ip, token=''): """ login returns a tuple (user_id, state, role, pcn, authCodePrepared) """ if token: user = session.query(User).filter(User.auth_token == token, \ User.state != u'disabled', \ User.tid == tid).one_or_none() else: user = session.query(User).filter(User.username == username, \ User.state != u'disabled', \ User.tid == tid).one_or_none() if user is None or (not token and not security.check_password( password, user.salt, user.password)): log.debug("Login: Invalid credentials") Settings.failed_login_attempts += 1 raise errors.InvalidAuthentication if not client_using_tor and not mystate.tenant_cache[tid]['https_' + user.role]: log.err("Denied login request over Web for role '%s'" % user.role) raise errors.TorNetworkRequired # Check if we're doing IP address checks today if mystate.tenant_cache[tid]['ip_filter_authenticated_enable']: ip_networks = parse_csv_ip_ranges_to_ip_networks( mystate.tenant_cache[tid]['ip_filter_authenticated']) client_ip = text_type(client_ip) client_ip_obj = ipaddress.ip_address(client_ip) # Safety check, we always allow localhost to log in success = False if client_ip_obj.is_loopback is True: success = True for ip_network in ip_networks: if client_ip_obj in ip_network: success = True if success is not True: raise errors.AccessLocationInvalid # se sono arrivato qui il primo login è andato a buon fine # il login (username, password) per un ricevente viene rieseguito anche al secondo passaggio # per motivi di sicurezza # A QUESTO PUNTO: # SE receiver2ndStepLoginState = 'N': # 1 - genero il codice # 2 - memorizzo record in ReceiverAuthCode # 3 - invio mail # ELSE: # verifico la correttenza del secondo codice if user.role == 'receiver': receiver = session.query(Receiver).filter( Receiver.id == user.id).one_or_none() if receiver.two_step_login_enabled and not user.password_change_needed: if receiver_second_login == 'first_login_to_complete': yyyy = str(datetime_now().year) mm = str(datetime_now().month).zfill(2) dd = str(datetime_now().day).zfill(2) result_query = session.query(ReceiverAuthCode).filter( ReceiverAuthCode.receiver_id == user.id, func.strftime("%Y-%m-%d", ReceiverAuthCode.creation_date) == yyyy + '-' + mm + '-' + dd).all() # genero il codice #chiamo la funzione generate_authcode_password che genera la password con l'utilizzo della funzione os.urandom #randnum = ''.join(["%s" % SystemRandom().randint(0, 9) for num in range(0, 12)]) randnum = generate_authcode_password(12) #print randnum log.debug(randnum[0:4] + ' ' + randnum[4:8] + ' ' + randnum[8:12]) # inserisco il record nel db newAuthCode = models.ReceiverAuthCode() newAuthCode.receiver_id = receiver.id newAuthCode.salt = security.generateRandomSalt() newAuthCode.auth_code = security.hash_password( randnum, newAuthCode.salt) session.add(newAuthCode) session.flush() # invio i tre pezzi del codice alle tre mail specificate nel profilo del ricevente email_prg = str(len(result_query) + 1) day = dd + '/' + mm + '/' + yyyy mystate.sendmail( 1, receiver.control_mail_1, "Receiver Auth Code #" + email_prg + " - " + day, randnum[0:4]) mystate.sendmail( 1, receiver.control_mail_2, "Receiver Auth Code #" + email_prg + " - " + day, randnum[4:8]) mystate.sendmail( 1, receiver.control_mail_3, "Receiver Auth Code #" + email_prg + " - " + day, randnum[8:12]) log.debug("Invio delle mail effettuato con successo") receiver_second_login = '******' elif receiver_second_login == 'second_login_to_complete': auth_code_item = session.query(ReceiverAuthCode).filter(ReceiverAuthCode.receiver_id == user.id) \ .order_by(ReceiverAuthCode.creation_date.desc()).first() # se non sono passati TOT minuti dall'ultimo codice emesso si può controllare la validità del codice if auth_code_item is not None and auth_code_item.is_valid and not is_expired( auth_code_item.creation_date, 0, AUTH_CODE_EXPIRATION_IN_MINUTES, 0, 0): # qui devo verificare che il codice inviato dall'utente sia uguale a una delle permutazioni dei tre blocchi # da quattro cifre che si ottengono dal codice salvato sul db firstBlock = receiver_auth_code[0:4] secondBlock = receiver_auth_code[4:8] thirdBlock = receiver_auth_code[8:12] combList = [] combList.insert(0, firstBlock + secondBlock + thirdBlock) combList.insert(1, firstBlock + thirdBlock + secondBlock) combList.insert(2, secondBlock + firstBlock + thirdBlock) combList.insert(3, secondBlock + thirdBlock + firstBlock) combList.insert(4, thirdBlock + firstBlock + secondBlock) combList.insert(5, thirdBlock + secondBlock + firstBlock) auth_code_match = False for authCode in combList: if security.check_password(authCode, auth_code_item.salt, auth_code_item.auth_code): auth_code_match = True # POSSO ANCHE FARE UNA UPDATE del campo is_valid PER IL RECORD UTILIZZATO NELLA VALIDAZIONE auth_code_item.is_valid = False session.add(auth_code_item) session.flush() #objs = session.query(ReceiverAuthCode).filter(ReceiverAuthCode.receiver_id == auth_code_item.receiver_id) \ # .order_by(ReceiverAuthCode.creation_date.desc(), ReceiverAuthCode.daily_prg.desc()) #for obj in objs: # session.delete(obj) # session.flush() #session.delete(auth_code_item) receiver_second_login = '******' break if not auth_code_match: log.debug("Login: Invalid authentication code") Settings.failed_login_attempts += 1 raise errors.InvalidAuthentication else: log.debug( "Login: authentication code is expired. Please repeat login" ) Settings.failed_login_attempts += 1 raise errors.InvalidAuthentication else: log.debug( "receiver_auth_code diverso da first_login_to_complete e second_login_to_complete" ) receiver_second_login = '******' else: receiver_second_login = '******' #da tenere sotto controllo else: receiver_second_login = '******' # da tenere sotto controllo log.debug("Login: Success (%s)" % user.role) user.last_login = datetime_now() return user.id, user.state, user.role, user.password_change_needed, receiver_second_login
def db_user_update_user(session, tid, user_session, request): """ Updates the specified user. This version of the function is specific for users that with comparison with admins can change only few things: - real name - email address - preferred language - the password (with old password check) - pgp key raises: globaleaks.errors.ResourceNotFound` if the receiver does not exist. """ from globaleaks.handlers.admin.notification import db_get_notification from globaleaks.handlers.admin.node import db_admin_serialize_node user = models.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'] new_password = request['password'] old_password = request['old_password'] if new_password: if user.password_change_needed: user.password_change_needed = False else: if not GCE.check_password(user.hash_alg, old_password, user.salt, user.password): raise errors.InvalidOldPassword # Regenerate the password hash only if different from the best choice on the platform if user.hash_alg != GCE.HASH: user.hash_alg = GCE.HASH user.salt = GCE.generate_salt() password_hash = GCE.hash_password(new_password, user.salt) # Check that the new password is different form the current password if user.password == password_hash: raise errors.PasswordReuseError user.password = password_hash user.password_change_date = datetime_now() if State.tenant_cache[tid].encryption: enc_key = GCE.derive_key(request['password'].encode(), user.salt) if not user_session.cc: # Th First first password change triggers the generation # of the user encryption private key and its backup user_session.cc, user.crypto_pub_key = GCE.generate_keypair() user.crypto_bkp_key, user.crypto_rec_key = GCE.generate_recovery_key( user_session.cc) # If the user had already enabled two factor before encryption was not enable # encrypt the two factor secret if user.two_factor_secret: user.two_factor_secret = GCE.asymmetric_encrypt( user.crypto_pub_key, user.two_factor_secret) user.crypto_prv_key = GCE.symmetric_encrypt( enc_key, 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(32) 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) # If the platform allows users to change PGP keys, process it if State.tenant_cache[tid]['enable_user_pgp_key_upload'] is True: parse_pgp_options(user, request) return user
def db_user_update_user(session, tid, user_session, request): """ Updates the specified user. This version of the function is specific for users that with comparison with admins can change only few things: - real name - email address - preferred language - the password (with old password check) - pgp key raises: globaleaks.errors.ResourceNotFound` if the receiver does not exist. """ from globaleaks.handlers.admin.notification import db_get_notification from globaleaks.handlers.admin.node import db_admin_serialize_node user = models.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'] new_password = request['password'] old_password = request['old_password'] if new_password: if user.password_change_needed: user.password_change_needed = False else: if not GCE.check_password(user.hash_alg, old_password, user.salt, user.password): raise errors.InvalidOldPassword user.hash_alg = GCE.HASH user.salt = GCE.generate_salt() user.password = GCE.hash_password(new_password, user.salt) user.password_change_date = datetime_now() if State.tenant_cache[1].encryption: enc_key = GCE.derive_key(request['password'].encode(), user.salt) if not user_session.cc: user_session.cc, user.crypto_pub_key = GCE.generate_keypair() user.crypto_prv_key = GCE.symmetric_encrypt(enc_key, 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(32) user_desc = user_serialize_user(session, user, user.language) 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, 1, user.language), 'notification': db_get_notification(session, tid, user.language) } State.format_and_send_mail(session, tid, user_desc, template_vars) # If the platform allows users to change PGP keys, process it if State.tenant_cache[tid]['enable_user_pgp_key_upload'] is True: parse_pgp_options(user, request) return user