Ejemplo n.º 1
0
def auth(view, **kwargs):
    """
    This plugin allow user to login to application

    kwargs:
        - signin_view
        - signout_view
        - template_dir
        - menu:
            - name
            - group_name
            - ...

        @plugin(user.login, model=model.User)
        class MyAccount(Webmaster):
            pass

    """

    endpoint_namespace = view.__name__ + ":%s"
    view_name = view.__name__
    UserModel = kwargs.pop("model")
    User = UserModel.User


    login_view = endpoint_namespace % "login"
    on_signin_view = kwargs.get("signin_view", "Index:index")
    on_signout_view = kwargs.get("signout_view", "Index:index")
    template_dir = kwargs.get("template_dir", "Webmaster/Package/User/Account")
    template_page = template_dir + "/%s.html"

    login_manager = LoginManager()
    login_manager.login_view = login_view
    login_manager.login_message_category = "error"
    init_app(login_manager.init_app)

    menu_context = view
    _menu = kwargs.get("menu", {})
    if _menu:
        @menu(**_menu)
        class UserAccountMenu(object): pass
        menu_context = UserAccountMenu

    @login_manager.user_loader
    def load_user(userid):
        return User.get(userid)

    class Auth(object):
        decorators = view.decorators + [login_required]

        SESSION_KEY_SET_EMAIL_DATA = "set_email_tmp_data"
        TEMP_DATA_KEY = "login_tmp_data"

        @property
        def _package_conf(self):
            return self.get_config("USER_AUTH", {})

        @property
        def tmp_data(self):
            return session[self.TEMP_DATA_KEY]

        @tmp_data.setter
        def tmp_data(self, data):
            session[self.TEMP_DATA_KEY] = data

        def _login_enabled(self):
            if self._package_conf.get("enable_login") is not True:
                abort("UserLoginDisabledError")

        def _signup_enabled(self):
            if self._package_conf.get("enable_signup") is not True:
                abort("UserSignupDisabledError")

        def _oauth_enabled(self):
            if self._package_conf.get("enable_oauth") is not True:
                abort("UserOAuthDisabledError")

        @classmethod
        def login_user(cls, user):
            login_user(user)
            now = datetime.datetime.now()
            user.update(last_login=now, last_visited=now)

        @menu("Login",
              endpoint=endpoint_namespace % "login",
              visible=user_not_authenticated,
              extends=menu_context)
        @template(template_page % "login",
                  endpoint_namespace=endpoint_namespace)
        @route("login/",
               methods=["GET", "POST"],
               endpoint=endpoint_namespace % "login")
        @no_login_required
        def login(self):
            """ Login page """

            self._login_enabled()
            logout_user()
            self.tmp_data = None
            self.meta_tags(title="Login")

            if request.method == "POST":
                email = request.form.get("email").strip()
                password = request.form.get("password").strip()

                if not email or not password:
                    flash("Email or Password is empty", "error")
                    return redirect(url_for(login_view, next=request.form.get("next")))

                user = User.get_by_email(email)
                if user and user.password_hash and user.password_matched(password):
                    self.login_user(user)
                    return redirect(request.form.get("next") or url_for(on_signin_view))
                else:
                    flash("Email or Password is invalid", "error")
                    return redirect(url_for(login_view, next=request.form.get("next")))

            return dict(login_url_next=request.args.get("next", ""),
                        login_url_default=url_for(on_signin_view),
                        signup_enabled=self._package_conf.get("enable_signup"),
                        oauth_enabled=self._package_conf.get("enable_oauth_login"))

        @menu("Logout",
              endpoint=endpoint_namespace % "logout",
              visible=user_authenticated,
              order=100,
              extends=menu_context)
        @route("logout/",
               endpoint=endpoint_namespace % "logout")
        @no_login_required
        def logout(self):
            logout_user()
            return redirect(url_for(on_signout_view or login_view))

        @menu("Signup",
              endpoint=endpoint_namespace % "signup",
              visible=[user_not_authenticated],
              extends=menu_context)
        @template(template_page % "signup",
                  endpoint_namespace=endpoint_namespace)
        @route("signup/",
               methods=["GET", "POST"],
               endpoint=endpoint_namespace % "signup")
        @no_login_required
        def signup(self):
            """
            For Email Signup
            :return:
            """
            self._login_enabled()
            self._signup_enabled()
            self.meta_tags(title="Signup")

            if request.method == "POST":
                # reCaptcha
                if not recaptcha.verify():
                    flash("Invalid Security code", "error")
                    return redirect(url_for(endpoint_namespace % "signup",
                                            next=request.form.get("next")))
                try:
                    name = request.form.get("name")
                    email = request.form.get("email")
                    password = request.form.get("password")
                    password2 = request.form.get("password2")
                    profile_image_url = request.form.get("profile_image_url", None)

                    if not name:
                        raise UserError("Name is required")
                    elif not utils.is_valid_email(email):
                        raise UserError("Invalid email address '%s'" % email)
                    elif not password.strip() or password.strip() != password2.strip():
                        raise UserError("Passwords don't match")
                    elif not utils.is_valid_password(password):
                        raise UserError("Invalid password")
                    else:
                        new_account = User.new(email=email,
                                        password=password.strip(),
                                        first_name=name,
                                        profile_image_url=profile_image_url,
                                        signup_method="email")

                        self.login_user(new_account)
                        return redirect(request.form.get("next") or url_for(on_signin_view))
                except ApplicationError as ex:
                    flash(ex.message, "error")
                return redirect(url_for(endpoint_namespace % "signup",
                                        next=request.form.get("next")))

            logout_user()
            return dict(login_url_next=request.args.get("next", ""))

        @route("lost-password/",
               methods=["GET", "POST"],
               endpoint=endpoint_namespace % "lost_password")
        @template(template_page % "lost_password",
                  endpoint_namespace=endpoint_namespace)
        @no_login_required
        def lost_password(self):
            self._login_enabled()
            logout_user()

            self.meta_tags(title="Lost Password")

            if request.method == "POST":
                email = request.form.get("email")
                user = User.get_by_email(email)
                if user:
                    delivery = self._package_conf.get("password_reset_method")

                    new_password = None
                    if delivery.upper() == "TOKEN":
                        token = user.set_temp_login()
                        url = url_for(endpoint_namespace % "reset_password",
                                      token=token,
                                      _external=True)
                    else:
                        new_password = user.set_password(password=None, random=True)
                        url = url_for(endpoint_namespace % "login", _external=True)

                    mailer.send_template("reset-password.txt",
                                         method_=delivery,
                                         to=user.email,
                                         name=user.email,
                                         url=url,
                                         new_password=new_password)

                    flash("A new password has been sent to '%s'" % email, "success")
                else:
                    flash("Invalid email address", "error")
                return redirect(url_for(login_view))
            else:
                return {}


        @menu("Account Settings",
              endpoint=endpoint_namespace % "account_settings",
              order=99,
              visible=user_authenticated,
              extends=menu_context)
        @template(template_page % "account_settings",
                  endpoint_namespace=endpoint_namespace)
        @route("account-settings",
               methods=["GET", "POST"],
               endpoint=endpoint_namespace % "account_settings")
        @fresh_login_required
        def account_settings(self):
            self.meta_tags(title="Account Settings")

            if request.method == "POST":
                action = request.form.get("action")
                try:
                    action = action.lower()
                    #
                    if action == "info":
                        first_name = request.form.get("first_name").strip()
                        last_name = request.form.get("last_name", "").strip()

                        data = {
                            "first_name": first_name,
                            "last_name": last_name
                        }
                        current_user.update(**data)
                        flash("Account info updated successfully!", "success")
                    #
                    elif action == "login":
                        confirm_password = request.form.get("confirm-password").strip()
                        if current_user.password_matched(confirm_password):
                            self.change_login_handler()
                            flash("Login Info updated successfully!", "success")
                        else:
                            flash("Invalid password", "error")
                    #
                    elif action == "password":
                        confirm_password = request.form.get("confirm-password").strip()
                        if current_user.password_matched(confirm_password):
                            self.change_password_handler()
                            flash("Password updated successfully!", "success")
                        else:
                            flash("Invalid password", "error")

                    elif action == "profile-photo":
                        file = request.files.get("file")
                        if file:
                            prefix = "profile-photos/%s/" % current_user.id
                            extensions = ["jpg", "jpeg", "png", "gif"]
                            my_photo = storage.upload(file,
                                                      prefix=prefix,
                                                      allowed_extensions=extensions)
                            if my_photo:
                                url = my_photo.url
                                current_user.update(profile_image_url=url)
                                flash("Profile Image updated successfully!", "success")
                    else:
                        raise UserError("Invalid action")

                except Exception as e:
                    flash(e.message, "error")

                return redirect(url_for(endpoint_namespace % "account_settings"))

            return {}

        @classmethod
        def change_login_handler(cls, user_context=None, email=None):
            if not user_context:
                user_context = current_user
            if not email:
                email = request.form.get("email").strip()

            if not utils.is_valid_email(email):
                raise UserWarning("Invalid email address '%s'" % email)
            else:
                if email != user_context.email and User.get_by_email(email):
                    raise UserWarning("Email exists already '%s'" % email)
                elif email != user_context.email:
                    user_context.update(email=email)
                    return True
            return False

        @classmethod
        def change_password_handler(cls, user_context=None, password=None,
                                    password2=None):
            if not user_context:
                user_context = current_user
            if not password:
                password = request.form.get("password").strip()
            if not password2:
                password2 = request.form.get("password2").strip()

            if password:
                if password != password2:
                    raise UserWarning("Password don't match")
                elif not utils.is_valid_password(password):
                    raise UserWarning("Invalid password")
                else:
                    user_context.set_password(password)
                    return True
            else:
                raise UserWarning("Password is empty")


        # OAUTH Login
        @route("oauth-login/<provider>",
               methods=["GET", "POST"],
               endpoint=endpoint_namespace % "oauth_login")
        @template(template_page % "oauth_login",
                  endpoint_namespace=endpoint_namespace)
        @no_login_required
        def oauth_login(self, provider):
            """ Login via oauth providers """

            self._login_enabled()
            self._oauth_enabled()

            provider = provider.lower()
            result = oauth.login(provider)
            response = oauth.response
            popup_js_custom = {
                "action": "",
                "url": ""
            }

            if result:
                if result.error:
                    pass

                elif result.user:
                    result.user.update()

                    oauth_user = result.user
                    user = User.get_by_oauth(provider=provider,
                                             provider_user_id=oauth_user.id)
                    if not user:
                        if oauth_user.email and User.get_by_email(oauth_user.email):
                            flash("Account already exists with this email '%s'. "
                                        "Try to login or retrieve your password " % oauth_user.email, "error")

                            popup_js_custom.update({
                                "action": "redirect",
                                "url": url_for(login_view, next=request.form.get("next"))
                            })

                        else:
                            tmp_data = {
                                "is_oauth": True,
                                "provider": provider,
                                "id": oauth_user.id,
                                "name": oauth_user.name,
                                "picture": oauth_user.picture,
                                "first_name": oauth_user.first_name,
                                "last_name": oauth_user.last_name,
                                "email": oauth_user.email,
                                "link": oauth_user.link
                            }
                            if not oauth_user.email:
                                self.tmp_data = tmp_data

                                popup_js_custom.update({
                                    "action": "redirect",
                                    "url": url_for(endpoint_namespace % "setup_login")
                                })

                            else:
                                try:
                                    picture = oauth_user.picture
                                    user = User.new(email=oauth_user.email,
                                                    name=oauth_user.name,
                                                    signup_method=provider,
                                                    profile_image_url=picture
                                                    )
                                    user.add_oauth(provider,
                                                   oauth_user.provider_id,
                                                   name=oauth_user.name,
                                                   email=oauth_user.email,
                                                   profile_image_url=oauth_user.picture,
                                                   link=oauth_user.link)
                                except ModelError as e:
                                    flash(e.message, "error")
                                    popup_js_custom.update({
                                        "action": "redirect",
                                        "url": url_for(endpoint_namespace % "login")
                                    })
                    if user:
                        self.login_user(user)

                    return dict(popup_js=result.popup_js(custom=popup_js_custom),
                                template_=template_page % "oauth_login")
            return response

        @template(template_page % "setup_login",
                  endpoint_namespace=endpoint_namespace)
        @route("setup-login/", methods=["GET", "POST"], 
               endpoint=endpoint_namespace % "setup_login")
        def setup_login(self):
            """
            Allows to setup a email password if it's not provided specially
            coming from oauth-login
            :return:
            """
            self._login_enabled()
            self.meta_tags(title="Setup  Login")

            # Only user without email can set email
            if current_user.is_authenticated() and current_user.email:
                return redirect(url_for(endpoint_namespace % "account_settings"))

            if self.tmp_data:
                if request.method == "POST":
                    if not self.tmp_data["is_oauth"]:
                        return redirect(endpoint_namespace % "login")

                    try:
                        email = request.form.get("email")
                        password = request.form.get("password")
                        password2 = request.form.get("password2")

                        if not utils.is_valid_email(email):
                            raise UserError("Invalid email address '%s'" % email)
                        elif User.get_by_email(email):
                            raise UserError("An account exists already with this email address '%s' " % email)
                        elif not password.strip() or password.strip() != password2.strip():
                            raise UserError("Passwords don't match")
                        elif not utils.is_valid_password(password):
                            raise UserError("Invalid password")
                        else:
                            user = User.new(email=email,
                                            password=password.strip(),
                                            name=self.tmp_data["name"],
                                            profile_image_url=self.tmp_data["picture"],
                                            signup_method=self.tmp_data["provider"])

                            user.add_oauth(self.tmp_data["provider"],
                                           self.tmp_data["id"],
                                           name=self.tmp_data["name"],
                                           email=email,
                                           profile_image_url=self.tmp_data["picture"],
                                           link=self.tmp_data["link"])

                            self.login_user(user)
                            self.tmp_data = None

                        return redirect(request.form.get("next") or url_for(on_signin_view))
                    except ApplicationError as ex:
                        flash(ex.message, "error")
                        return redirect(url_for(endpoint_namespace % "login"))

                return dict(provider=self.tmp_data)

            else:
                return redirect(url_for(endpoint_namespace % "login"))

        @route("reset-password/<token>",
               methods=["GET", "POST"],
               endpoint=endpoint_namespace % "reset_password")
        @template(template_page % "reset_password",
                  endpoint_namespace=endpoint_namespace)
        @no_login_required
        def reset_password(self, token):
            self._login_enabled()
            logout_user()

            self.meta_tags(title="Reset Password")
            user = User.get_by_temp_login(token)
            if user:
                if not user.has_temp_login:
                    return redirect(url_for(on_signin_view))
                if request.method == "POST":
                    try:
                        self.change_password_handler(user_context=user)
                        user.clear_temp_login()
                        flash("Password updated successfully!", "success")
                        return redirect(url_for(on_signin_view))
                    except Exception as ex:
                        flash("Error: %s" % ex.message, "error")
                        return redirect(url_for(endpoint_namespace % "reset_password",
                                                token=token))
                else:
                    return dict(token=token)
            else:
                abort(404, "Invalid token")

        @route("oauth-connect", methods=["POST"], 
               endpoint="%s:oauth_connect" % endpoint_namespace)
        def oauth_connect(self):
            """ To login via social """
            email = request.form.get("email").strip()
            name = request.form.get("name").strip()
            provider = request.form.get("provider").strip()
            provider_user_id = request.form.get("provider_user_id").strip()
            image_url = request.form.get("image_url").strip()
            next = request.form.get("next", "")
            try:
                current_user.oauth_connect(provider=provider,
                                         provider_user_id=provider_user_id,
                                         email=email,
                                         name=name,
                                         image_url=image_url)
            except Exception as ex:
                flash("Unable to link your account", "error")

            return redirect(url_for(endpoint_namespace % "account_settings"))

    return Auth