from AccessControl.class_init import InitializeClass
from plone import api
from ploneintranet import api as pi_api
from Products.PageTemplates.PageTemplateFile import PageTemplateFile
from Products.PluggableAuthService.interfaces.plugins import IAuthenticationPlugin
from Products.PluggableAuthService.plugins.BasePlugin import BasePlugin
from Products.PluggableAuthService.utils import classImplements
from urllib.parse import urlencode


class EmailAuthenticatorPlugin(BasePlugin):
    """
    Email Authenticator PAS Plugin

    This is heavily inspired by the collective.googleauthenticator plugin.

    The goal is to secure the authentication process by asking the user to prove
    he can control is mailbox.

    The 2FA process is as follow:

    1. The user provides his username and password
    2. An email is sent to the user with a token
    3. The user clicks on the link in the email
    4. The user is logged in

    The plugin is only used for membrane users, not for zope users.
    """

    meta_type = "PloneIntranet Email Authenticator PAS Plugin"

    def __init__(self, id, title=None):
        self._setId(id)
        self.title = title

    def _get_user(self, credentials):
        """
        Check if the plugin is able to handle the credentials provided for the user.
        """
        login = credentials.get("login")
        if not login:
            return

        user = pi_api.userprofile.get(login)

        if not user:
            # We are interested only ploneintranet users, not the zope ones
            return

        acl_users = api.portal.get_tool("acl_users")
        membrane_users = acl_users.get("membrane_users")
        if not membrane_users:
            # We can't find the plugin, so we can't authenticate the user
            return

        if not membrane_users.authenticateCredentials(credentials):
            # Plugin failed to authenticate the user
            return

        return user

    @property
    def redirect_uri(self):
        """
        A thank you page that will be shown to the user to inform him that
        he provided the proper credentials and that an email has been sent
        with a link that will finalize the authentication process.
        """
        return f"{api.portal.get().absolute_url()}/@@email-verification-sent"

    def authenticateCredentials(self, credentials):
        """
        This verifies that a membrane user has provided
        a valid login/password combination, i.e. the first authentication factor.
        If the first authentication factor succeeds,
        we send an email to the user with a token that will be used
        as the second authentication factor.
        """
        if credentials.get("extractor", "") == "plone.session":
            # The user already has a valid cookie, so we don't need to do anything
            return

        user = self._get_user(credentials)
        if not user:
            # If the user is not a membrane user, or the credentials are invalid,
            # with pass the authentication task to the next authentication plugin.
            return

        # The first authentication factor is ok.
        # Send an email with a token, it will be the second one.
        api.content.get_view("email-verification-send-token", user).send_email()

        # Inform the system that we are in the 2fa process
        self.REQUEST.set("2fa_in_progress", True)
        # Go to the thank you page
        self.REQUEST.response.redirect(self.redirect_uri, lock=1)


classImplements(EmailAuthenticatorPlugin, IAuthenticationPlugin)
InitializeClass(EmailAuthenticatorPlugin)

manage_addEmailAuthenticatorPluginForm = PageTemplateFile(
    "www/add_email_authenticator_pas_plugin_form",
    globals(),
    __name__="manage_addEmailAuthenticatorPluginForm",
)


def addEmailAuthenticatorAuthenticatorPlugin(context, id, title="", REQUEST=None):
    """
    Action that adds the plugin to a Pluggable Auth Service.
    """
    context._setObject(id, EmailAuthenticatorPlugin(id, title))

    if REQUEST is None:
        return

    params = {"manage_tabs_message": f"{EmailAuthenticatorPlugin.meta_type} added."}
    qs = urlencode(params)

    REQUEST["RESPONSE"].redirect(f"{context.absolute_url()}/manage_main?{qs}")
