Advertisement
Guest User

Untitled

a guest
Jun 8th, 2018
653
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 8.77 KB | None | 0 0
  1. # coding=utf-8
  2. import hashlib
  3. from functools import partial, wraps
  4.  
  5. try:
  6.     import ujson as json
  7. except Exception:
  8.     import json
  9.  
  10. import libuuid
  11. import six
  12. from abc import ABCMeta, abstractmethod
  13. from flask import request, url_for
  14. from flask_login import login_user
  15. from flask_restful import Resource
  16. from flask_restful.reqparse import RequestParser
  17. from sqlalchemy.exc import SQLAlchemyError
  18. from werkzeug.exceptions import BadRequest
  19.  
  20. from hasoffers.core.model import Session, User, LoginHistory
  21. from hasoffers.core.util.config import ConfigReader
  22. from hasoffers.core.util.helpers.redis_actions import (
  23.     ACCOUNT_CHANGES,
  24.     RedisSession,
  25. )
  26. from hasoffers.core.util.ip import get_current_ip_from_request
  27. from hasoffers.core.util.mailsender import MailSender
  28. from hasoffers.core.util.redis_session import RedisSessionInterface
  29. from hasoffers.core.util.structlogger import get_logger
  30. from hasoffers.core.util.traffichelper import TrafficHelper
  31. from hasoffers.partner_plus.controllers.auth import Auth
  32.  
  33. logger = get_logger()
  34. cfg = ConfigReader.get_config('hasoffers.partner_plus', keep_letters_case=True)
  35.  
  36.  
  37. def check_if_result_exists(f=None, exc_message=None):
  38.     """
  39.    Проверка, что функция возвращает не None,
  40.        иначе рейзится BadRequest
  41.    :param f: оборачиваемая функция
  42.    :param exc_message: сообщение, отправляемое на клиент при ошибке
  43.    """
  44.     if f is None:
  45.         return partial(check_if_result_exists, exc_message)
  46.     exc_message = exc_message or 'Some errors occurred'
  47.  
  48.     @wraps
  49.     def wrapper(*args, **kwargs):
  50.         res = f(*args, **kwargs)
  51.         if not res:
  52.             raise BadRequest(exc_message)
  53.         return res
  54.     return wrapper
  55.  
  56.  
  57. class LoginMixin(object):
  58.     @staticmethod
  59.     def make_login_history(user_id):
  60.         lh = LoginHistory()
  61.         traffic_helper = TrafficHelper()
  62.         lh.user_id = user_id
  63.         lh.ip = get_current_ip_from_request()
  64.         lh.domain = request.host
  65.         lh.country = traffic_helper.country.name or ''
  66.         lh.user_agent = traffic_helper.bsh.user_agent or ''
  67.         lh.device = traffic_helper.bsh._uap.device.family or ''
  68.         lh.os = traffic_helper.bsh.family or ''
  69.         lh.browser = traffic_helper.bsh._uap.browser.family or ''
  70.         return lh
  71.  
  72.     @staticmethod
  73.     def login_user_or_raise_bad_request(user):
  74.         try:
  75.             login_user(user, False, force=True)
  76.         except Exception:
  77.             logger.exception(
  78.                 'Error while user logging in (resetting password)')
  79.             raise BadRequest('Error: user cannot be logged')
  80.         else:
  81.             # Разлогинить пользователя со всех клиентов
  82.             RedisSessionInterface.delete_user_sessions(user.id)
  83.         return
  84.  
  85.  
  86. @six.with_metaclass(ABCMeta)
  87. class CredentialsResetResource(Resource):
  88.     REDIS = RedisSession(db_id=ACCOUNT_CHANGES)
  89.     PWD_RECOVERY_TTL = cfg.getint('flask', 'password_recovery_ttl')
  90.     TWOFA_RECOVERY_TTL = cfg.getint('flask', 'twofa_recovery_ttl')
  91.     CREDENTIAL_TYPE = None
  92.  
  93.     get_parser = RequestParser()
  94.     get_parser.add_argument('key', type=six.text_type, required=True)
  95.  
  96.     @abstractmethod
  97.     def get(self):
  98.         pass
  99.  
  100.     @abstractmethod
  101.     def post(self):
  102.         pass
  103.  
  104.     @staticmethod
  105.     def _get_hash_from_text(text):
  106.         return hashlib.md5(text.encode('utf8')).hexdigest()
  107.  
  108.     @staticmethod
  109.     @check_if_result_exists('Error. There is no user with such email address')
  110.     def _get_user_from_email_or_raise_exception(email):
  111.         return Session.query(User).filter(User.email == email).first()
  112.  
  113.     @check_if_result_exists('Error: the link is obsolete')
  114.     def _get_data_from_redis_or_raise_exception(self, key):
  115.         return self.REDIS.get(key)
  116.  
  117.     @staticmethod
  118.     def _make_response(user):
  119.         res = Auth.user_to_dict(user)
  120.         res['message'] = 'Password changed.'
  121.         res['redirect_to'] = url_for(
  122.             endpoint='PartnerIndex.index',
  123.             _external=True,
  124.             _scheme='',
  125.         )
  126.         res['redirect'] = True
  127.         return res
  128.  
  129.     @staticmethod
  130.     def _get_mail_ctx_from_key_and_user(key, user):
  131.         return {
  132.             'token': key,
  133.             'username': user.name,
  134.             'email': user.email,
  135.         }
  136.  
  137.     def _generate_token_from_params(self, *params):
  138.         return self._get_hash_from_text(''.join(params))
  139.  
  140.  
  141. class ResetPasswordResource(LoginMixin, CredentialsResetResource):
  142.     CREDENTIAL_TYPE = 'reset_password'
  143.  
  144.     SET_PASSWORD = 'set_password'
  145.     RESET_PASSWORD = 'reset_password'
  146.     ACTION_CHOICES = (SET_PASSWORD, RESET_PASSWORD,)
  147.     SUCCESS_RESET_MSG = 'An email notification has been sent with ' \
  148.                         'additional information. Please check your mailbox.'
  149.  
  150.     post_parser = RequestParser()
  151.     post_parser.add_argument('email', type=six.text_type, required=True)
  152.     post_parser.add_argument('action', type=six.text_type, choices=ACTION_CHOICES, required=True)
  153.  
  154.     def get(self):
  155.         args = self.get_parser.parse_args()
  156.         data = self._get_data_from_redis_or_raise_exception(args.key)
  157.  
  158.         user = User.Get(data['user_id'])
  159.         self.login_user_or_raise_bad_request(user)
  160.  
  161.         if not user.exist_flag(User.Flags.entered):
  162.             user.set_flag(User.Flags.entered)
  163.  
  164.         res = self._make_response(user)
  165.         lh = self.make_login_history(user.id)
  166.         Session.add(lh)
  167.         try:
  168.             Session.commit()
  169.         except SQLAlchemyError:
  170.             Session.rollback()
  171.             logger.exception('Error while saving login history')
  172.         return res
  173.  
  174.     def post(self):
  175.         context = self.post_parser.parse_args()
  176.         user = self._get_user_from_email_or_raise_exception(context.email)
  177.         hashed_password = self._generate_token_from_params(context.password)
  178.  
  179.         context.update({
  180.             'user': user,
  181.             'hashed_password': hashed_password,
  182.         })
  183.         return self.execute_action(context)
  184.  
  185.     def execute_action(self, context):
  186.         return getattr(self, context['action'])(context)
  187.  
  188.     @staticmethod
  189.     def set_password(context):
  190.         user = context['user']
  191.         user.password = context['hashed_password']
  192.         try:
  193.             Session.commit()
  194.         except SQLAlchemyError:
  195.             Session.rollback()
  196.             logger.exception(
  197.                 'Error while resetting password!\n '
  198.                 'User: [{0}] {1}'.format(user.id, user.name)
  199.             )
  200.             raise BadRequest('Error while resetting password')
  201.         else:
  202.             return {'message': 'Success. Password was changed.'}
  203.  
  204.     def reset_password(self, context):
  205.         user = context['user']
  206.         password = context['password']
  207.         key = self._generate_token_from_params(user.id, password)
  208.         self.REDIS.setex(
  209.             name=key,
  210.             time=self.PWD_RECOVERY_TTL,
  211.             value=json.dumps({
  212.                 'user_id': user.id,
  213.                 'password': context['hashed_password'],
  214.             })
  215.         )
  216.         mail_template_ctx = self._get_mail_ctx_from_key_and_user(key, user)
  217.  
  218.         MailSender.send_service_message(
  219.             reason=self.CREDENTIAL_TYPE,
  220.             context=mail_template_ctx,
  221.             emails=[(user.email, user.name)],
  222.             important=True,
  223.         )
  224.         return {
  225.             'message': self.SUCCESS_RESET_MSG
  226.         }
  227.  
  228.  
  229. class ResetTwoFAResource(CredentialsResetResource):
  230.     CREDENTIAL_TYPE = 'reset_two_fa'
  231.  
  232.     post_parser = RequestParser()
  233.     post_parser.add_argument('email', type=six.text_type, required=True)
  234.  
  235.     def get(self):
  236.         args = self.get_parser.parse_args()
  237.         data = self._get_data_from_redis_or_raise_exception(args.key)
  238.         user = User.Get(data['user_id'])
  239.         user.otp_secret = ''
  240.         try:
  241.             Session.commit()
  242.         except SQLAlchemyError:
  243.             Session.rollback()
  244.             logger.exception('Error while saving login history')
  245.         return {'message': 'OTP disabled'}
  246.  
  247.     def post(self):
  248.         args = self.post_parser.parse_args()
  249.         user = self._get_user_from_email_or_raise_exception(args.email)
  250.         key = libuuid.uuid4().hex
  251.         self.REDIS.setex(
  252.             name='otp_{}'.format(key),
  253.             time=self.TWOFA_RECOVERY_TTL,
  254.             value=user.id
  255.         )
  256.  
  257.         mail_template_ctx = self._get_mail_ctx_from_key_and_user(key, user)
  258.         MailSender.send_service_message(
  259.             reason='otp_disable',
  260.             context=mail_template_ctx,
  261.             emails=[(user.email, user.name)],
  262.             important=True,
  263.         )
  264.         return {'message': self.SUCCESS_RESET_MSG}
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement