text: # coding: utf-8
  
  from __future__ import absolute_import
  
  import functools
  import re
  
  from flask_oauthlib import client as oauth
  from google.appengine.ext import ndb
  import flask
  import flask_login
  import flask_wtf
  import unidecode
  import wtforms
  
  import cache
  import config
  import model
  import task
  import util
  
  from main import app
  
  _signals = flask.signals.Namespace()
  
  
  ###############################################################################
  # Flask Login
  ###############################################################################
  login_manager = flask_login.LoginManager()
  
  
  class AnonymousUser(flask_login.AnonymousUserMixin):
    id = 0
    admin = False
    name = 'Anonymous'
    user_db = None
  
    def key(self):
      return None
  
    def has_permission(self, permission):
      return False
  
  
  login_manager.anonymous_user = AnonymousUser
  
  
  class FlaskUser(AnonymousUser):
    def __init__(self, user_db):
      self.user_db = user_db
      self.id = user_db.key.id()
      self.name = user_db.name
      self.admin = user_db.admin
  
    def key(self):
      return self.user_db.key.urlsafe()
  
    def get_id(self):
      return self.user_db.key.urlsafe()
  
    def is_authenticated(self):
      return True
  
    def is_active(self):
      return self.user_db.active
  
    def is_anonymous(self):
      return False
  
    def has_permission(self, permission):
      return self.user_db.has_permission(permission)
  
  
  @login_manager.user_loader
  def load_user(key):
    user_db = ndb.Key(urlsafe=key).get()
    if user_db:
      return FlaskUser(user_db)
    return None
  
  
  login_manager.init_app(app)
  
  
  def current_user_id():
    return flask_login.current_user.id
  
  
  def current_user_key():
    return flask_login.current_user.user_db.key if flask_login.current_user.user_db else None
  
  
  def current_user_db():
    return flask_login.current_user.user_db
  
  
  def is_logged_in():
    return flask_login.current_user.id != 0
  
  
  ###############################################################################
  # Decorators
  ###############################################################################
  def login_required(f):
    decorator_order_guard(f, 'auth.login_required')
  
    @functools.wraps(f)
    def decorated_function(*args, **kwargs):
      if is_logged_in():
        return f(*args, **kwargs)
      if flask.request.path.startswith('/api/'):
        return flask.abort(401)
      return flask.redirect(flask.url_for('signin', next=flask.request.url))
  
    return decorated_function
  
  
  def admin_required(f):
    decorator_order_guard(f, 'auth.admin_required')
  
    @functools.wraps(f)
    def decorated_function(*args, **kwargs):
      if is_logged_in() and current_user_db().admin:
        return f(*args, **kwargs)
      if not is_logged_in() and flask.request.path.startswith('/api/'):
        return flask.abort(401)
      if not is_logged_in():
        return flask.redirect(flask.url_for('signin', next=flask.request.url))
      return flask.abort(403)
  
    return decorated_function
  
  
  def cron_required(f):
    decorator_order_guard(f, 'auth.cron_required')
  
    @functools.wraps(f)
    def decorated_function(*args, **kwargs):
      if 'X-Appengine-Cron' in flask.request.headers:
        return f(*args, **kwargs)
      if is_logged_in() and current_user_db().admin:
        return f(*args, **kwargs)
      if not is_logged_in():
        return flask.redirect(flask.url_for('signin', next=flask.request.url))
      return flask.abort(403)
  
    return decorated_function
  
  
  permission_registered = _signals.signal('permission-registered')
  
  
  def permission_required(permission=None, methods=None):
    def permission_decorator(f):
      decorator_order_guard(f, 'auth.permission_required')
  
      # default to decorated function name as permission
      perm = permission or f.func_name
      meths = [m.upper() for m in methods] if methods else None
  
      permission_registered.send(f, permission=perm)
  
      @functools.wraps(f)
      def decorated_function(*args, **kwargs):
        if meths and flask.request.method.upper() not in meths:
          return f(*args, **kwargs)
        if is_logged_in() and current_user_db().has_permission(perm):
          return f(*args, **kwargs)
        if not is_logged_in():
          if flask.request.path.startswith('/api/'):
            return flask.abort(401)
          return flask.redirect(flask.url_for('signin', next=flask.request.url))
        return flask.abort(403)
  
      return decorated_function
  
    return permission_decorator
  
  
  ###############################################################################
  # Sign in stuff
  ###############################################################################
  class SignInForm(flask_wtf.FlaskForm):
    email = wtforms.StringField(
      'Email',
      [wtforms.validators.required()],
      filters=[util.email_filter],
    )
    password = wtforms.StringField(
      'Password',
      [wtforms.validators.required()],
    )
    remember = wtforms.BooleanField(
      'Keep me signed in',
      [wtforms.validators.optional()],
    )
    recaptcha = flask_wtf.RecaptchaField()
    next_url = wtforms.HiddenField()
  
  
  @app.route('/signin/', methods=['GET', 'POST'])
  def signin():
    next_url = util.get_next_url()
    form = None
    if config.CONFIG_DB.has_email_authentication:
      form = form_with_recaptcha(SignInForm())
      save_request_params()
      if form.validate_on_submit():
        result = get_user_db_from_email(form.email.data, form.password.data)
        if result:
          cache.reset_auth_attempt()
          return signin_user_db(result)
        if result is None:
          form.email.errors.append('Email or Password do not match')
        if result is False:
          return flask.redirect(flask.url_for('welcome'))
      if not form.errors:
        form.next_url.data = next_url
  
    if form and form.errors:
      cache.bump_auth_attempt()
  
    return flask.render_template(
      'auth/auth.html',
      title='Sign in',
      html_class='auth',
      next_url=next_url,
      form=form,
      form_type='signin' if config.CONFIG_DB.has_email_authentication else '',
      **urls_for_oauth(next_url)
    )
  
  
  ###############################################################################
  # Sign up stuff
  ###############################################################################
  class SignUpForm(flask_wtf.FlaskForm):
    email = wtforms.StringField(
      'Email',
      [wtforms.validators.required(), wtforms.validators.email()],
      filters=[util.email_filter],
    )
    recaptcha = flask_wtf.RecaptchaField()
  
  @app.route('/signup/', methods=['GET', 'POST'])
  
  def signup():
    next_url = util.get_next_url()
    form = None
    if config.CONFIG_DB.has_email_authentication:
      form = form_with_recaptcha(SignUpForm())
      save_request_params()
      if form.validate_on_submit():
        user_db = model.User.get_by('email', form.email.data)
        if user_db:
          form.email.errors.append('This email is already taken.')
  
        if not form.errors:
          user_db = create_user_db(
            None,
            util.create_name_from_email(form.email.data),
            form.email.data,
            form.email.data,
          )
          user_db.put()
          task.activate_user_notification(user_db)
          cache.bump_auth_attempt()
          return flask.redirect(flask.url_for('welcome'))
  
    if form and form.errors:
      cache.bump_auth_attempt()
  
    title = 'Sign up' if config.CONFIG_DB.has_email_authentication else 'Sign in'
    return flask.render_template(
      'auth/auth.html',
      title=title,
      html_class='auth',
      next_url=next_url,
      form=form,
      **urls_for_oauth(next_url)
    )
  
  
  ###############################################################################
  # Sign out stuff
  ###############################################################################
  @app.route('/signout/')
  def signout():
    flask_login.logout_user()
    return flask.redirect(util.param('next') or flask.url_for('signin'))
  
  
  ###############################################################################
  # Helpers
  ###############################################################################
  def url_for_signin(service_name, next_url):
    return flask.url_for('signin_%s' % service_name, next=next_url)
  
  
  def urls_for_oauth(next_url):
    return {
      'azure_ad_signin_url': url_for_signin('azure_ad', next_url),
      'bitbucket_signin_url': url_for_signin('bitbucket', next_url),
      'dropbox_signin_url': url_for_signin('dropbox', next_url),
      'facebook_signin_url': url_for_signin('facebook', next_url),
      'github_signin_url': url_for_signin('github', next_url),
      'google_signin_url': url_for_signin('google', next_url),
      'gae_signin_url': url_for_signin('gae', next_url),
      'instagram_signin_url': url_for_signin('instagram', next_url),
      'linkedin_signin_url': url_for_signin('linkedin', next_url),
      'mailru_signin_url': url_for_signin('mailru', next_url),
      'microsoft_signin_url': url_for_signin('microsoft', next_url),
      'reddit_signin_url': url_for_signin('reddit', next_url),
      'twitter_signin_url': url_for_signin('twitter', next_url),
      'vk_signin_url': url_for_signin('vk', next_url),
      'yahoo_signin_url': url_for_signin('yahoo', next_url),
    }
  
  
  def create_oauth_app(service_config, name):
    upper_name = name.upper()
    app.config[upper_name] = service_config
    service_oauth = oauth.OAuth()
    service_app = service_oauth.remote_app(name, app_key=upper_name)
    service_oauth.init_app(app)
    return service_app
  
  
  def decorator_order_guard(f, decorator_name):
    if f in app.view_functions.values():
      raise SyntaxError(
        'Do not use %s above app.route decorators as it would not be checked. '
        'Instead move the line below the app.route lines.' % decorator_name
      )
  
  
  def save_request_params():
    flask.session['auth-params'] = {
      'next': util.get_next_url(),
      'remember': util.param('remember'),
    }
  
  
  def signin_oauth(oauth_app, scheme=None):
    try:
      flask.session.pop('oauth_token', None)
      save_request_params()
      return oauth_app.authorize(callback=flask.url_for(
        '%s_authorized' % oauth_app.name, _external=True, _scheme=scheme
      ))
    except oauth.OAuthException:
      flask.flash(
        'Something went wrong with sign in. Please try again.',
        category='danger',
      )
      return flask.redirect(flask.url_for('signin', next=util.get_next_url()))
  
  
  def form_with_recaptcha(form):
    should_have_recaptcha = cache.get_auth_attempt() >= config.RECAPTCHA_LIMIT
    if not (should_have_recaptcha and config.CONFIG_DB.has_recaptcha):
      del form.recaptcha
    return form
  
  
  ###############################################################################
  # User related stuff
  ###############################################################################
  def create_user_db(auth_id, name, username, email='', verified=False, **props):
    email = email.lower() if email else ''
    if verified and email:
      user_dbs, cursors = model.User.get_dbs(email=email, verified=True, limit=2)
      if len(user_dbs) == 1:
        user_db = user_dbs[0]
        user_db.auth_ids.append(auth_id)
        user_db.put()
        task.new_user_notification(user_db)
        return user_db
  
    if isinstance(username, str):
      username = username.decode('utf-8')
    username = unidecode.unidecode(username.split('@')[0].lower()).strip()
    username = re.sub(r'[\W_]+', '.', username)
    new_username = username
    n = 1
    while not model.User.is_username_available(new_username):
      new_username = '%s%d' % (username, n)
      n += 1
  
    user_db = model.User(
      name=name,
      email=email,
      username=new_username,
      auth_ids=[auth_id] if auth_id else [],
      verified=verified,
      token=util.uuid(),
      **props
    )
    user_db.put()
    task.new_user_notification(user_db)
    return user_db
  
  
  @ndb.toplevel
  def signin_user_db(user_db):
    if not user_db:
      return flask.redirect(flask.url_for('signin'))
    flask_user_db = FlaskUser(user_db)
    auth_params = flask.session.get('auth-params', {
      'next': flask.url_for('welcome'),
      'remember': False,
    })
    flask.session.pop('auth-params', None)
    if flask_login.login_user(flask_user_db, remember=auth_params['remember']):
      user_db.put_async()
      return flask.redirect(util.get_next_url(auth_params['next']))
    flask.flash('Sorry, but you could not sign in.', category='danger')
    return flask.redirect(flask.url_for('signin'))
  
  
  def get_user_db_from_email(email, password):
    user_dbs, cursors = model.User.get_dbs(email=email, active=True, limit=2)
    if not user_dbs:
      return None
    if len(user_dbs) > 1:
      flask.flash('''We are sorry but it looks like there is a conflict with
          your account. Our support team has been informed and we will get
          back to you as soon as possible.''', category='danger')
      task.email_conflict_notification(email)
      return False
  
    user_db = user_dbs[0]
    if user_db.password_hash == util.password_hash(user_db, password):
      return user_db
    return None
  