#
#   Copyright © 2021,2022 IsardVDI S.L.
#
#   This file is part of DD
#
#   DD is free software: you can redistribute it and/or modify
#   it under the terms of the GNU Affero General Public License as published by
#   the Free Software Foundation, either version 3 of the License, or (at your
#   option) any later version.
#
#   DD is distributed in the hope that it will be useful, but WITHOUT ANY
#   WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
#   FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
#   details.
#
#   You should have received a copy of the GNU Affero General Public License
#   along with DD. If not, see <https://www.gnu.org/licenses/>.
#
# SPDX-License-Identifier: AGPL-3.0-or-later

import json
import logging as log
import os
import pprint
import random
import string
import time
import traceback
from datetime import datetime, timedelta

import psycopg2
import yaml
from admin.lib.keycloak_client import KeycloakClient
from admin.lib.mysql import Mysql

app = {}
app["config"] = {}

nickname_script="""
var Output = user.getFirstName()+" "+user.getLastName();
Output;
"""


class WordpressSaml:
    def __init__(self):
        self.url = "http://dd-sso-keycloak:8080/auth/"
        self.username = os.environ["KEYCLOAK_USER"]
        self.password = os.environ["KEYCLOAK_PASSWORD"]
        self.realm = "master"
        self.verify = True

        ready = False
        while not ready:
            try:
                self.db = Mysql(
                    "dd-apps-mariadb",
                    "wordpress",
                    os.environ["WORDPRESS_MARIADB_USER"],
                    os.environ["WORDPRESS_MARIADB_PASSWORD"],
                )
                ready = True
            except:
                log.warning("Could not connect to wordpress database. Retrying...")
                time.sleep(2)
        log.info("Connected to wordpress database.")

        basepath = os.path.dirname(__file__)

        ready = False
        while not ready:
            try:
                with open(
                    os.path.abspath(
                        os.path.join(basepath, "../saml_certs/wordpress.crt")
                    ),
                    "r",
                ) as crt:
                    app["config"]["PUBLIC_CERT_RAW"] = crt.read()
                    app["config"]["PUBLIC_CERT"] = self.cert_prepare(
                        app["config"]["PUBLIC_CERT_RAW"]
                    )
                    ready = True
            except IOError:
                log.warning(
                    "Could not get public certificate to be used in wordpress. Retrying..."
                )
                log.warning(
                    " You should generate them: /admin/saml_certs # openssl req  -nodes -new -x509  -keyout private.key -out public.cert"
                )
                time.sleep(2)
            except:
                log.error(traceback.format_exc())
        log.info("Got moodle srt certificate to be used in wordpress.")

        ready = False
        while not ready:
            try:
                with open(
                    os.path.abspath(
                        os.path.join(basepath, "../saml_certs/wordpress.key")
                    ),
                    "r",
                ) as pem:
                    app["config"]["PRIVATE_KEY"] = self.cert_prepare(pem.read())
                    ready = True
            except IOError:
                log.warning(
                    "Could not get private key to be used in wordpress. Retrying..."
                )
                log.warning(
                    " You should generate them: /admin/saml_certs # openssl req  -nodes -new -x509  -keyout private.key -out public.cert"
                )
                time.sleep(2)
        log.info("Got moodle pem certificate to be used in wordpress.")

        # ## This seems related to the fact that the certificate generated the first time does'nt work.
        # ## And when regenerating the certificate de privatekeypass seems not to be used and instead it
        # ## will use always this code as filename: 0f635d0e0f3874fff8b581c132e6c7a7
        # ## As this bug I'm not able to solve, the process is:
        # ## 1.- Bring up moodle and regenerate certificates on saml2 plugin in plugins-authentication
        # ## 2.- Execute this script
        # ## 3.- Cleanup all caches in moodle (Development tab)
        # # with open(os.path.join("./moodledata/saml2/"+os.environ['NEXTCLOUD_SAML_PRIVATEKEYPASS'].replace("moodle."+os.environ['DOMAIN'],'')+'.idp.xml'),"w") as xml:
        # #     xml.write(self.parse_idp_metadata())
        # with open(os.path.join("./moodledata/saml2/0f635d0e0f3874fff8b581c132e6c7a7.idp.xml"),"w") as xml:
        #     xml.write(self.parse_idp_metadata())

        try:
            self.reset_saml()
        except:
            print(traceback.format_exc())
            print("Error resetting saml on wordpress")
        try:
            self.delete_keycloak_wordpress_saml_plugin()
        except:
            print("Error resetting saml on keycloak")

        try:
            self.set_wordpress_saml_plugin()
        except:
            print(traceback.format_exc())
            print("Error adding saml on wordpress")

        try:
            self.add_keycloak_wordpress_saml()
        except:
            print("Error adding saml on keycloak")

        # SAML clients don't work well with composite roles so disabling and adding on realm
        # self.add_client_roles()

    def connect(self):
        self.keycloak = KeycloakClient(
            url=self.url,
            username=self.username,
            password=self.password,
            realm=self.realm,
            verify=self.verify,
        )

    # def activate_saml_plugin(self):
    #     ## After you need to purge moodle caches: /var/www/html # php admin/cli/purge_caches.php
    #     return self.db.update("""UPDATE "mdl_config" SET value = 'email,saml2' WHERE "name" = 'auth'""")

    # def get_privatekey_pass(self):
    #     return self.db.select("""SELECT * FROM "mdl_config" WHERE "name" = 'siteidentifier'""")[0][2]

    def cert_prepare(self, cert):
        return "".join(cert.split("-----")[2].splitlines())

    def parse_idp_cert(self):
        self.connect()
        rsa = self.keycloak.get_server_rsa_key()
        self.keycloak = None
        return rsa["certificate"]

    def set_keycloak_wordpress_saml_plugin(self):
        self.connect()
        self.keycloak.add_wordpress_client()
        self.keycloak = None

    def delete_keycloak_wordpress_saml_plugin(self):
        self.connect()
        self.keycloak.delete_client("630601f8-25d1-4822-8741-c93affd2cd84")
        self.keycloak = None

    def set_wordpress_saml_plugin(self):
        # ('active_plugins',	'a:2:{i:0;s:33:\"edwiser-bridge/edwiser-bridge.php\";i:1;s:17:\"onelogin_saml.php\";}',	'yes'),
        self.db.update(
            """INSERT INTO wp_options (option_name, option_value, autoload) VALUES
('onelogin_saml_enabled',	'on',	'yes'),
('onelogin_saml_idp_entityid',	'Saml Login',	'yes'),
('onelogin_saml_idp_sso',	'https://sso.%s/auth/realms/master/protocol/saml',	'yes'),
('onelogin_saml_idp_slo',	'https://sso.%s/auth/realms/master/protocol/saml',	'yes'),
('onelogin_saml_idp_x509cert',	'%s',	'yes'),
('onelogin_saml_autocreate',	'on',	'yes'),
('onelogin_saml_updateuser',	'on',	'yes'),
('onelogin_saml_forcelogin',	'on',	'yes'),
('onelogin_saml_slo',	'on',	'yes'),
('onelogin_saml_keep_local_login',	'',	'yes'),
('onelogin_saml_alternative_acs',	'',	'yes'),
('onelogin_saml_account_matcher',	'username',	'yes'),
('onelogin_saml_trigger_login_hook',	'',	'yes'),
('onelogin_saml_multirole',	'',	'yes'),
('onelogin_saml_trusted_url_domains',	'',	'yes'),
('onelogin_saml_attr_mapping_username',	'username',	'yes'),
('onelogin_saml_attr_mapping_mail',	'email',	'yes'),
('onelogin_saml_attr_mapping_firstname',	'givenName',	'yes'),
('onelogin_saml_attr_mapping_lastname',	'sn',	'yes'),
('onelogin_saml_attr_mapping_nickname',	'',	'yes'),
('onelogin_saml_attr_mapping_role',	'Role',	'yes'),
('onelogin_saml_attr_mapping_rememberme',	'',	'yes'),
('onelogin_saml_role_mapping_administrator',	'admin',	'yes'),
('onelogin_saml_role_mapping_editor',	'manager',	'yes'),
('onelogin_saml_role_mapping_author',	'coursecreator',	'yes'),
('onelogin_saml_role_mapping_contributor',	'teacher',	'yes'),
('onelogin_saml_role_mapping_subscriber',	'',	'yes'),
('onelogin_saml_role_mapping_multivalued_in_one_attribute_value',	'on',	'yes'),
('onelogin_saml_role_mapping_multivalued_pattern',	'',	'yes'),
('onelogin_saml_role_order_administrator',	'',	'yes'),
('onelogin_saml_role_order_editor',	'',	'yes'),
('onelogin_saml_role_order_author',	'',	'yes'),
('onelogin_saml_role_order_contributor',	'',	'yes'),
('onelogin_saml_role_order_subscriber',	'',	'yes'),
('onelogin_saml_customize_action_prevent_local_login',	'',	'yes'),
('onelogin_saml_customize_action_prevent_reset_password',	'',	'yes'),
('onelogin_saml_customize_action_prevent_change_password',	'',	'yes'),
('onelogin_saml_customize_action_prevent_change_mail',	'',	'yes'),
('onelogin_saml_customize_stay_in_wordpress_after_slo',	'on',	'yes'),
('onelogin_saml_customize_links_user_registration',	'',	'yes'),
('onelogin_saml_customize_links_lost_password',	'',	'yes'),
('onelogin_saml_customize_links_saml_login',	'',	'yes'),
('onelogin_saml_advanced_settings_debug',	'',	'yes'),
('onelogin_saml_advanced_settings_strict_mode',	'',	'yes'),
('onelogin_saml_advanced_settings_sp_entity_id',	'',	'yes'),
('onelogin_saml_advanced_idp_lowercase_url_encoding',	'',	'yes'),
('onelogin_saml_advanced_settings_nameid_encrypted',	'',	'yes'),
('onelogin_saml_advanced_settings_authn_request_signed',	'on',	'yes'),
('onelogin_saml_advanced_settings_logout_request_signed',	'on',	'yes'),
('onelogin_saml_advanced_settings_logout_response_signed',	'on',	'yes'),
('onelogin_saml_advanced_settings_want_message_signed',	'',	'yes'),
('onelogin_saml_advanced_settings_want_assertion_signed',	'',	'yes'),
('onelogin_saml_advanced_settings_want_assertion_encrypted',	'',	'yes'),
('onelogin_saml_advanced_settings_retrieve_parameters_from_server',	'',	'yes'),
('onelogin_saml_advanced_nameidformat',	'unspecified',	'yes'),
('onelogin_saml_advanced_requestedauthncontext',	'',	'yes'),
('onelogin_saml_advanced_settings_sp_x509cert',	'%s',	'yes'),
('onelogin_saml_advanced_settings_sp_privatekey',	'%s',	'yes'),
('onelogin_saml_advanced_signaturealgorithm',	'http://www.w3.org/2000/09/xmldsig#rsa-sha1',	'yes'),
('onelogin_saml_advanced_digestalgorithm',	'http://www.w3.org/2000/09/xmldsig#sha1',	'yes');"""
            % (
                os.environ["DOMAIN"],
                os.environ["DOMAIN"],
                self.parse_idp_cert(),
                app["config"]["PUBLIC_CERT"],
                app["config"]["PRIVATE_KEY"],
            )
        )

    def reset_saml(self):
        self.db.update(
            """DELETE FROM wp_options WHERE option_name LIKE 'onelogin_saml_%'"""
        )

    def add_keycloak_wordpress_saml(self):
        client = {
            "id": "630601f8-25d1-4822-8741-c93affd2cd84",
            "clientId": "php-saml",
            "surrogateAuthRequired": False,
            "enabled": True,
            "alwaysDisplayInConsole": False,
            "clientAuthenticatorType": "client-secret",
            "redirectUris": [
                "/realms/master/account/*",
                f"https://wp.{os.environ['DOMAIN']}/wp-login.php?saml_acs",
                f"https://wp.{os.environ['DOMAIN']}/*",
            ],
            "webOrigins": [f"https://wp.{os.environ['DOMAIN']}"],
            "notBefore": 0,
            "bearerOnly": False,
            "consentRequired": False,
            "standardFlowEnabled": True,
            "implicitFlowEnabled": False,
            "directAccessGrantsEnabled": False,
            "serviceAccountsEnabled": False,
            "publicClient": False,
            "frontchannelLogout": True,
            "protocol": "saml",
            "attributes": {
                "saml.force.post.binding": True,
                "saml_assertion_consumer_url_post": "https://wp."
                + os.environ["DOMAIN"]
                + "/wp-login.php?saml_acs",
                "saml.server.signature": True,
                "saml.server.signature.keyinfo.ext": False,
                "saml.signing.certificate": app["config"]["PUBLIC_CERT_RAW"],
                "saml_single_logout_service_url_redirect": "https://wp."
                + os.environ["DOMAIN"]
                + "/wp-login.php?saml_sls",
                "saml.signature.algorithm": "RSA_SHA256",
                "saml_force_name_id_format": False,
                "saml.client.signature": True,
                "saml.authnstatement": True,
                "saml_name_id_format": "username",
                "saml_signature_canonicalization_method": "http://www.w3.org/2001/10/xml-exc-c14n#",
            },
            "authenticationFlowBindingOverrides": {},
            "fullScopeAllowed": True,
            "nodeReRegistrationTimeout": -1,
            "protocolMappers": [
                {
                    "id": "72c6175e-bd07-4c27-abd6-4e4ae38d834b",
                    "name": "username",
                    "protocol": "saml",
                    "protocolMapper": "saml-user-attribute-mapper",
                    "consentRequired": False,
                    "config": {
                        "attribute.nameformat": "Basic",
                        "user.attribute": "username",
                        "friendly.name": "username",
                        "attribute.name": "username",
                    },
                },
                {
                    "id": "abd6562f-4732-4da9-987f-b1a6ad6605fa",
                    "name": "roles",
                    "protocol": "saml",
                    "protocolMapper": "saml-role-list-mapper",
                    "consentRequired": False,
                    "config": {
                        "single": True,
                        "attribute.nameformat": "Basic",
                        "friendly.name": "Roles",
                        "attribute.name": "Role",
                    },
                },
                {
                    "id": "50aafb71-d91c-4bc7-bb60-e1ae0222aab3",
                    "name": "email",
                    "protocol": "saml",
                    "protocolMapper": "saml-user-property-mapper",
                    "consentRequired": False,
                    "config": {
                        "attribute.nameformat": "Basic",
                        "user.attribute": "email",
                        "friendly.name": "email",
                        "attribute.name": "email",
                    },
                },
                {
                    "id": "b1befc6b-580f-4065-804c-b45d23a1af5d",
                    "name": "nickname",
                    "protocol": "saml",
                    "protocolMapper": "saml-javascript-mapper",
                    "consentRequired": False,
                    "config": {
                        "single": True,
                        "Script": nickname_script,
                        "attribute.nameformat": "Basic",
                        "friendly.name": "nickname",
                        "attribute.name": "nickname",
                    },
                },
            ],
            "defaultClientScopes": [
                "web-origins",
                "role_list",
                "roles",
                "profile",
                "email",
            ],
            "optionalClientScopes": [
                "address",
                "phone",
                "offline_access",
                "microprofile-jwt",
            ],
            "access": {"view": True, "configure": True, "manage": True},
        }
        self.connect()
        self.keycloak.add_client(client)
        self.keycloak = None

    def add_client_roles(self):
        self.connect()
        self.keycloak.add_client_role(
            "630601f8-25d1-4822-8741-c93affd2cd84", "admin", "Wordpress admins"
        )
        self.keycloak.add_client_role(
            "630601f8-25d1-4822-8741-c93affd2cd84", "manager", "Wordpress managers"
        )
        self.keycloak.add_client_role(
            "630601f8-25d1-4822-8741-c93affd2cd84", "teacher", "Wordpress teachers"
        )
        self.keycloak.add_client_role(
            "630601f8-25d1-4822-8741-c93affd2cd84", "student", "Wordpress students"
        )
        self.keycloak = None


nw = WordpressSaml()
