#!/usr/share/ucs-test/runner python3
## desc: Test roleshares creation
## roles: [domaincontroller_master]
## tags: [apptest,ucsschool,ucsschool_base1]
## exposure: dangerous
## packages:
##    - ucs-school-import
##    - univention-samba | univention-samba4
##    - ucs-school-roleshares

from __future__ import print_function

import subprocess
import sys

from ldap.filter import escape_filter_chars

import univention.testing.ucr as testing_ucr
import univention.testing.ucsschool.ucs_test_school as testing_ucsschool
from ucsschool.lib import roleshares
from ucsschool.lib.models.school import School
from ucsschool.lib.models.utils import add_stream_logger_to_schoollib
from ucsschool.lib.roles import role_pupil
from ucsschool.lib.school_umc_ldap_connection import MACHINE_READ, LDAP_Connection, set_credentials
from univention.admin.uldap import explodeDn
from univention.testing import utils
from univention.testing.codes import TestCodes

try:
    from univention.testing.ucs_samba import wait_for_drs_replication
except ImportError:

    def wait_for_drs_replication(ldap_filter, attrs=None):
        pass


add_stream_logger_to_schoollib()


# Initialize globals
ucr = testing_ucr.UCSTestConfigRegistry()
ucr.load()
TESTS_DOMAINADMIN_ACCOUNT = ucr.get("tests/domainadmin/account")
if TESTS_DOMAINADMIN_ACCOUNT:
    TESTS_DOMAINADMIN = explodeDn(TESTS_DOMAINADMIN_ACCOUNT, 1)[0]
TESTS_DOMAINADMIN_PWD = ucr.get("tests/domainadmin/pwd")
set_credentials(TESTS_DOMAINADMIN_ACCOUNT, TESTS_DOMAINADMIN_PWD)


def run_smbclient(share, authname, password, smbcmd):
    """
    runs "smbclient ${share} -U${authname}%${password} -c ${smbcmd}
    and returns the exitcode.
    """
    return run_cmd(["smbclient", share, "-U%s%%%s" % (authname, password), "-c", smbcmd])


def run_cmd(cmd, shell=False, print_output=True):
    try:
        print("Running %r" % cmd)
        p1 = subprocess.Popen(
            cmd, close_fds=True, shell=shell, stdout=subprocess.PIPE, stderr=subprocess.STDOUT
        )
        (out, err) = p1.communicate()
    except OSError:
        print("Failed to run: %r" % cmd)
        raise
    out = out.decode("UTF-8")
    if print_output:
        print("---[stdout+stderr]---")
        print(out)
        print("---[cut]---")
    print("Exitcode: %s" % (p1.returncode,))
    if p1.returncode == 0 and any(line.startswith("NT_STATUS_") for line in out.splitlines()):
        print('WARNING: "\\nNT_STATUS_" found in output --> raising exitcode to 127')
        return 127
    return p1.returncode


def access_user_dir(
    authname,
    school_id,
    serverfqdn,
    username,
    userfilename,
    password="univention",
    testwrite=True,
    write_access=False,
    read_access=True,
):
    roleshare = "//%s/%s" % (serverfqdn, roleshares.roleshare_name(role_pupil, school_id, ucr))
    print("INFO: Testing access to student roleshare for share %s by user %s" % (roleshare, authname))

    smbcmd = "dir %s" % (username,)
    read_success = run_smbclient(roleshare, authname, password, smbcmd) == 0
    if read_success != read_access:
        utils.fail(
            "ERROR: read access test failed: user=%s  share=%s  expected=%s  result=%s"
            % (authname, roleshare, read_access, read_success)
        )

    if testwrite:
        smbcmd = "del %s\\%s" % (username, userfilename)
        write_success = run_smbclient(roleshare, authname, password, smbcmd) == 0
        if write_success != write_access:
            utils.fail(
                "ERROR: write access test (del) to %s failed: user=%s  share=%s  expected=%s  result=%s"
                % (userfilename, authname, roleshare, write_access, write_success)
            )

        teacherfilename = "teacherfile1"
        smbcmd = "put /etc/hosts %s\\%s" % (username, teacherfilename)
        write_success = run_smbclient(roleshare, authname, password, smbcmd) == 0
        if write_success != write_access:
            utils.fail(
                "ERROR: write access test (put) to %s failed: user=%s  share=%s  expected=%s  result=%s"
                % (teacherfilename, authname, roleshare, write_access, write_success)
            )


def create_testfile_on_homeshare(fqdn, username, password, filename):
    share_unc = "//%s/%s" % (fqdn, username)
    print(
        "INFO: Creating testfile %s on home share %s"
        % (
            filename,
            share_unc,
        )
    )

    smbcmd = "dir"
    if run_smbclient(share_unc, username, password, smbcmd):
        utils.fail("Failed to list home directory")

    smbcmd = "put /etc/hosts %s" % (filename,)
    if run_smbclient(share_unc, username, password, smbcmd):
        utils.fail("Failed to create testfile: %r" % (smbcmd,))


def access_roleshare_student_on_all_school_fileservers(
    school_id,
    name_student,
    name_teacher,
    name_teacherstaff,
    name_staff,
    password="univention",
    testwrite=True,
    write_access=False,
):
    print("INFO: Testing access to student roleshare for school %s" % (school_id,))
    print("INFO: teacher name = %s" % (name_teacher,))
    print("INFO: teacher-staff name = %s" % (name_teacherstaff,))
    print("INFO: staff name = %s" % (name_staff,))
    print("INFO: student name = %s" % (name_student,))
    for fqdn in roleshares.fileservers_for_school(school_id):
        # trigger home creation for stu1 and create a test file
        userfilename = "testfile1"
        create_testfile_on_homeshare(fqdn, name_student, password, userfilename)

        # and test access with teacher
        access_user_dir(
            name_teacher,
            school_id,
            fqdn,
            name_student,
            userfilename,
            testwrite=True,
            write_access=False,
            read_access=True,
        )
        # and test access with teacher and staff
        access_user_dir(
            name_teacherstaff,
            school_id,
            fqdn,
            name_student,
            userfilename,
            testwrite=True,
            write_access=False,
            read_access=True,
        )
        # and test access with staff
        access_user_dir(
            name_staff,
            school_id,
            fqdn,
            name_student,
            userfilename,
            testwrite=True,
            write_access=False,
            read_access=False,
        )
        # and test access with student
        access_user_dir(
            name_student,
            school_id,
            fqdn,
            name_student,
            userfilename,
            testwrite=True,
            write_access=False,
            read_access=False,
        )
        # and test access with Administrator
        access_user_dir(
            TESTS_DOMAINADMIN,
            school_id,
            fqdn,
            name_student,
            userfilename,
            testwrite=True,
            write_access=False,
            read_access=True,
        )


def smb_server_online(fqdn):
    cmd = ["smbclient", "-N", "-L", fqdn]
    return run_cmd(cmd) == 0


def smb_server_offers_roleshare_student(fqdn, school_id):
    share_name = roleshares.roleshare_name("pupil", school_id, ucr)
    share_unc = "//%s/%s" % (fqdn, share_name)
    return run_smbclient(share_unc, TESTS_DOMAINADMIN, TESTS_DOMAINADMIN_PWD, "quit") == 0


def create_roleshare_student(school_id):
    # create roleshares
    cmd = [
        "/usr/share/ucs-school-import/scripts/create_roleshares",
        "--create",
        "student",
        "--school",
        str(school_id),
        "--binddn",
        TESTS_DOMAINADMIN_ACCOUNT,
        "--bindpwd",
        TESTS_DOMAINADMIN_PWD,
    ]
    if run_cmd(cmd):
        utils.fail("Creating student roleshare failed: %r" % (cmd,))


def prepare_fileservers(school_id):
    tested_servers = []
    for fqdn in roleshares.fileservers_for_school(school_id):
        if smb_server_online(fqdn):
            if not smb_server_offers_roleshare_student(fqdn, school_id):
                print("INFO: Manually creating roleshares for %s" % (school_id,))
                create_roleshare_student(school_id)
            tested_servers.append(fqdn)
        else:
            print("WARNING: Server %s is not online" % (fqdn,))
    return tested_servers


@LDAP_Connection(MACHINE_READ)
def main(ldap_machine_read=None):
    status = 100

    if not ucr.is_true("ucsschool/import/roleshare", True):
        utils.fail(
            log_message="ucsschool/import/roleshare has been deactivated, skipping test",
            returncode=TestCodes.REASON_SKIP,
        )

    all_available_servers = []
    with testing_ucsschool.UCSTestSchool() as schoolenv:
        (ou_A_name, ou_A_dn), (ou_B_name, ou_B_dn), (ou_C_name, ou_C_dn) = schoolenv.create_multiple_ous(
            3, name_edudc=ucr.get("hostname")
        )
        for school_id in map(School.get_name_from_dn, [ou_A_dn, ou_B_dn, ou_C_dn]):
            available_servers = prepare_fileservers(school_id)
            if available_servers:
                all_available_servers.extend(available_servers)
            else:
                print("WARNING: No file servers online for school %s" % (school_id,))
                continue

            # create test users
            classes = "{}-1A".format(school_id)
            tea1, teadn1 = schoolenv.create_user(school_id, classes=classes, is_teacher=True)
            teastaff1, teastaffdn1 = schoolenv.create_user(
                school_id, classes=classes, is_teacher=True, is_staff=True
            )
            staff1, staffdn1 = schoolenv.create_user(school_id, classes=classes, is_staff=True)
            stu1, stu1dn = schoolenv.create_user(school_id, classes=classes)

            utils.wait_for_replication_and_postrun()
            print("Waiting for DRS replication...")
            wait_for_drs_replication(
                "(sAMAccountName=%s)" % (escape_filter_chars(stu1),), attrs="objectSid"
            )

            access_roleshare_student_on_all_school_fileservers(school_id, stu1, tea1, teastaff1, staff1)

    if not all_available_servers:
        utils.fail(log_message="No fileserver online", returncode=TestCodes.REASON_FAIL)

    return status


if __name__ == "__main__":
    sys.exit(main())
