# -*- coding: utf-8 -*-
## $Id$d$: test_smtp.py 16428 2021-03-23 13:15:55Z henrik $
##
## Copyright (C) 2024 Uli Fouquet & Henrik Bettermann
## This program is free software; you can redistribute it and/or modify
## it under the terms of the GNU General Public License as published by
## the Free Software Foundation; either version 2 of the License, or
## (at your option) any later version.
##
## This program 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 General Public License for more details.
##
## You should have received a copy of the GNU General Public License
## along with this program; if not, write to the Free Software
## Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
##
# Tests for kpasswd hash generator
from __future__ import unicode_literals
import mock
import os
import sys
import tempfile
import unittest
import waeup.kofa.kpasswd

try:
    from StringIO import StringIO  # py2
except ImportError:
    from io import StringIO  # py3


SITE_ZCML_TEMPL = """
<configure xmlns="http://namespaces.zope.org/zope"
           xmlns:browser="http://namespaces.zope.org/browser"
           xmlns:kofa="http://namespaces.waeup.org/kofa"
           i18n_domain="waeup.kofa">

  <include package="waeup.kofa" />
  <include package="waeup.kofa" file="mail.zcml" />
  <!-- install job container

       install a job container for asynchronous jobs. Pick one of the
       two possibilites: 'single' for installation in the one big ZODB
       or 'multidb' for setup with a dedicated ZODB for job handling.
  -->
  <!-- include package="waeup.kofa" file="async_single.zcml" / -->
  <include package="waeup.kofa" file="async_multidb.zcml" />

  <!-- install dispatcher and other needed async components

       install other needed components for asynchronous jobs. Pick one
       of the two possibilities: 'basic_dispatcher' for single ZODB
       setup, 'multidb_dispatcher' for setup with a dedicated ZODB for
       job handling. Make sure the setting matches the setting picked
       above. -->
  <!-- include package="zc.async" file="basic_dispatcher_policy.zcml" / -->
  <include package="zc.async" file="multidb_dispatcher_policy.zcml" />

  <!-- Where should the datacenter reside by default? -->
  <kofa:datacenter
      path="/home/kofa/waeup.kofa/trunk/var/datacenter" />

    <configure i18n_domain="waeup.kofa">


      <unauthenticatedPrincipal id="zope.anybody"
                                title="Unauthenticated User" />
      <unauthenticatedGroup id="zope.Anybody"
                            title="Unauthenticated Users" />
      <authenticatedGroup id="zope.Authenticated"
                        title="Authenticated Users" />
      <everybodyGroup id="zope.Everybody"
                      title="All Users" />
      <principal id="zope.manager"
                 title="Manager"
                 login="grok"
                 password_manager="SHA1"
                 password="321b9cd3739bd029629243732eb682db005a7541f7509622"
                 />

      <!-- XMLRPC user for demo and testing
           Plaintext password is changeme
           Don't use it in real deployment -->
      <principal id="zope.xmlrpcuser1"
                 title="XMLRPC User 1"
                 login="xmlrpcuser1"
                 password="{SSHA}WQ0Mr9Sf7Sc_wxyNaMzjxib3nFFN0Jf0"
                 password_manager="SSHA"
                 />

      <grant role="waeup.xmlrpcusers1"
             principal="zope.xmlrpcuser1" />


      <!-- Replace the following directive if you do not want
           public access -->
      <grant permission="zope.app.dublincore.view"
             principal="zope.Anybody" />
      <grant permission="zope.View"
             principal="zope.Authenticated" />
      <grant permission="zope.app.dublincore.view"
             principal="zope.Authenticated" />
      <grant permission="waeup.Public"
             principal="zope.Everybody" />
      <grant permission="waeup.Anonymous"
             principal="zope.Anybody" />
      <grant permission="waeup.Authenticated"
             principal="zope.Authenticated" />

      <role id="zope.Manager" title="Site Manager" />
      <grantAll role="zope.Manager" />
      <grant role="zope.Manager"
             principal="zope.manager" />
   </configure>

   <!-- session -->
   <browser:beakerSession
        key="waeup.kofa.session.id"
        secret="KofaRocks"
        timeout="3600"
        type="cookie"
        validate_key="thisMightBeChanged"
        />

</configure>
"""


class KPasswordTests(unittest.TestCase):

    def setUp(self):
        self.workdir = tempfile.mkdtemp()
        self.site_zcml = os.path.join(self.workdir, "site.zcml")
        open(self.site_zcml, "w").write(SITE_ZCML_TEMPL)
        pass

    @mock.patch("sys.stderr", new_callable=StringIO)
    def test_parse_args_req_at_least_1_arg(self, mock_err):
        # we require at least one argument (the password)
        with self.assertRaises(SystemExit) as cm:
            waeup.kofa.kpasswd.parse_args(["kpasswd"])
        self.assertEqual(cm.exception.code, 2)
        self.assertTrue("error: too few arguments" in mock_err.getvalue())

    @mock.patch("sys.stdout", new_callable=StringIO)
    def test_main_callable(self, mock_out):
        result = waeup.kofa.kpasswd.main(["kpasswd", "foo"])
        output = mock_out.getvalue()
        self.assertEqual(result, 0)
        self.assertTrue(output.startswith("{SSHA}"))

    @mock.patch("sys.stderr", new_callable=StringIO)
    def test_main_considers_argv(self, mock_err):
        with mock.patch.object(
            sys,
            "argv",
            [
                "kpasswd-mark1",
            ],
        ):
            waeup.kofa.kpasswd.main()
        self.assertTrue(
            "kpasswd-mark1: error: too few arguments" in mock_err.getvalue()
        )

    @mock.patch("sys.stdout", new_callable=StringIO)
    def test_application_hash_password(self, mock_out):
        # we can show an hashed password (SSHA)
        options = waeup.kofa.kpasswd.parse_args(["kpasswd", "mypassword"])
        app = waeup.kofa.kpasswd.Application(options)
        app.run()
        output = mock_out.getvalue()
        self.assertTrue(output.startswith("{SSHA}"))

    @mock.patch("sys.stdout", new_callable=StringIO)
    def test_application_update_site_zcml(self, mock_out):
        # we can update a given site.zcml file
        options = waeup.kofa.kpasswd.parse_args(
            ["kpasswd", "-c", self.site_zcml, "mypassword"]
        )
        app = waeup.kofa.kpasswd.Application(options)
        old_content = open(self.site_zcml).read()
        app.run()
        output = mock_out.getvalue()
        # we get a message, that the file was updated
        self.assertTrue("Updated: %s" % self.site_zcml in output)
        new_content = open(self.site_zcml).read()
        # password hash has changed
        self.assertNotEqual(old_content, new_content)
        self.assertTrue("321b9cd3739bd029629243732eb682" not in new_content)

    @mock.patch("sys.stdout", new_callable=StringIO)
    def test_application_change_non_default_users(self, mock_out):
        # we can change also users not being `grok`
        options = waeup.kofa.kpasswd.parse_args(
            [
                "kpasswd",
                "-c",
                self.site_zcml,
                "--id",
                "zope.xmlrpcuser1",
                "-l",
                "xmlrpcuser1",
                "mypassword",
            ]
        )
        app = waeup.kofa.kpasswd.Application(options)
        old_content = open(self.site_zcml).read()
        app.run()
        output = mock_out.getvalue()
        # we get a message, that the file was updated
        self.assertTrue("Updated: %s" % self.site_zcml in output)
        new_content = open(self.site_zcml).read()
        # password hash has changed
        self.assertNotEqual(old_content, new_content)
        # but this time not the one of the zope.manager...
        self.assertTrue("321b9cd3739bd029629243732eb682" in new_content)
        self.assertTrue("{SSHA}WQ0Mr9Sf7Sc_wxyNaMzjxib3nFFN0Jf0" not in new_content)
