# Copyright (C) 2001-2018 by the Free Software Foundation, Inc.
#
# 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
# USA.

"""Unit tests for Mailman/SecurityManager.py
"""

import os
import unittest
import errno
import Cookie
try:
    import crypt
except ImportError:
    crypt = None
# Don't use cStringIO because we're going to inherit
from StringIO import StringIO
try:
    from Mailman import __init__
except ImportError:
    import paths

from Mailman import mm_cfg
from Mailman import Utils
from Mailman import Errors
from Mailman.Utils import md5_new, sha_new

from TestBase import TestBase



def password(plaintext):
    return sha_new(plaintext).hexdigest()



class TestSecurityManager(TestBase):
    def test_init_vars(self):
        eq = self.assertEqual
        eq(self._mlist.mod_password, None)
        eq(self._mlist.passwords, {})

    def test_auth_context_info_authuser(self):
        mlist = self._mlist
        self.assertRaises(TypeError, mlist.AuthContextInfo, mm_cfg.AuthUser)
        # Add a member
        mlist.addNewMember('aperson@dom.ain', password='xxXXxx')
        self.assertEqual(
            mlist.AuthContextInfo(mm_cfg.AuthUser, 'aperson@dom.ain'),
            ('_xtest+user+aperson--at--dom.ain', 'xxXXxx'))

    def test_auth_context_moderator(self):
        mlist = self._mlist
        mlist.mod_password = 'yyYYyy'
        self.assertEqual(
            mlist.AuthContextInfo(mm_cfg.AuthListModerator),
            ('_xtest+moderator', 'yyYYyy'))

    def test_auth_context_admin(self):
        mlist = self._mlist
        mlist.password = 'zzZZzz'
        self.assertEqual(
            mlist.AuthContextInfo(mm_cfg.AuthListAdmin),
            ('_xtest+admin', 'zzZZzz'))

    def test_auth_context_site(self):
        mlist = self._mlist
        mlist.password = 'aaAAaa'
        self.assertEqual(
            mlist.AuthContextInfo(mm_cfg.AuthSiteAdmin),
            ('_xtest+admin', 'aaAAaa'))

    def test_auth_context_huh(self):
        self.assertEqual(
            self._mlist.AuthContextInfo('foo'),
            (None, None))



class TestAuthenticate(TestBase):
    def setUp(self):
        TestBase.setUp(self)
        Utils.set_global_password('bbBBbb', siteadmin=1)
        Utils.set_global_password('ccCCcc', siteadmin=0)

    def tearDown(self):
        try:
            os.unlink(mm_cfg.SITE_PW_FILE)
        except OSError, e:
            if e.errno <> errno.ENOENT: raise
        try:
            os.unlink(mm_cfg.LISTCREATOR_PW_FILE)
        except OSError, e:
            if e.errno <> errno.ENOENT: raise
        TestBase.tearDown(self)

    def test_auth_creator(self):
        self.assertEqual(self._mlist.Authenticate(
            [mm_cfg.AuthCreator], 'ccCCcc'), mm_cfg.AuthCreator)

    def test_auth_creator_unauth(self):
        self.assertEqual(self._mlist.Authenticate(
            [mm_cfg.AuthCreator], 'xxxxxx'), mm_cfg.UnAuthorized)

    def test_auth_site_admin(self):
        self.assertEqual(self._mlist.Authenticate(
            [mm_cfg.AuthSiteAdmin], 'bbBBbb'), mm_cfg.AuthSiteAdmin)

    def test_auth_site_admin_unauth(self):
        self.assertEqual(self._mlist.Authenticate(
            [mm_cfg.AuthSiteAdmin], 'xxxxxx'), mm_cfg.UnAuthorized)

    def test_list_admin(self):
        self._mlist.password = password('ttTTtt')
        self.assertEqual(self._mlist.Authenticate(
            [mm_cfg.AuthListAdmin], 'ttTTtt'), mm_cfg.AuthListAdmin)

    def test_list_admin_unauth(self):
        self._mlist.password = password('ttTTtt')
        self.assertEqual(self._mlist.Authenticate(
            [mm_cfg.AuthListAdmin], 'xxxxxx'), mm_cfg.UnAuthorized)

    def test_list_admin_upgrade(self):
        eq = self.assertEqual
        mlist = self._mlist
        mlist.password = md5_new('ssSSss').digest()
        eq(mlist.Authenticate(
            [mm_cfg.AuthListAdmin], 'ssSSss'), mm_cfg.AuthListAdmin)
        eq(mlist.password, password('ssSSss'))
        # Test crypt upgrades if crypt is supported
        if crypt:
            mlist.password = crypt.crypt('rrRRrr', 'zc')
            eq(self._mlist.Authenticate(
                [mm_cfg.AuthListAdmin], 'rrRRrr'), mm_cfg.AuthListAdmin)
            eq(mlist.password, password('rrRRrr'))

    def test_list_admin_oldstyle_unauth(self):
        eq = self.assertEqual
        mlist = self._mlist
        mlist.password = md5_new('ssSSss').digest()
        eq(mlist.Authenticate(
            [mm_cfg.AuthListAdmin], 'xxxxxx'), mm_cfg.UnAuthorized)
        eq(mlist.password, md5_new('ssSSss').digest())
        # Test crypt upgrades if crypt is supported
        if crypt:
            mlist.password = crypted = crypt.crypt('rrRRrr', 'zc')
            eq(self._mlist.Authenticate(
                [mm_cfg.AuthListAdmin], 'xxxxxx'), mm_cfg.UnAuthorized)
            eq(mlist.password, crypted)

    def test_list_moderator(self):
        self._mlist.mod_password = password('mmMMmm')
        self.assertEqual(self._mlist.Authenticate(
            [mm_cfg.AuthListModerator], 'mmMMmm'), mm_cfg.AuthListModerator)

    def test_user(self):
        mlist = self._mlist
        mlist.addNewMember('aperson@dom.ain', password='nosrepa')
        self.assertEqual(mlist.Authenticate(
            [mm_cfg.AuthUser], 'nosrepa', 'aperson@dom.ain'), mm_cfg.AuthUser)

    def test_wrong_user(self):
        mlist = self._mlist
        mlist.addNewMember('aperson@dom.ain', password='nosrepa')
        self.assertEqual(mlist.Authenticate(
                [mm_cfg.AuthUser], 'nosrepa', 'bperson@dom.ain'),
                mm_cfg.UnAuthorized)

    def test_no_user(self):
        mlist = self._mlist
        mlist.addNewMember('aperson@dom.ain', password='nosrepa')
        self.assertEqual(mlist.Authenticate(
                [mm_cfg.AuthUser], 'nosrepa'),
                mm_cfg.UnAuthorized)

    def test_user_unauth(self):
        mlist = self._mlist
        mlist.addNewMember('aperson@dom.ain', password='nosrepa')
        self.assertEqual(mlist.Authenticate(
            [mm_cfg.AuthUser], 'xxxxxx', 'aperson@dom.ain'),
                         mm_cfg.UnAuthorized)

    def test_value_error(self):
        self.assertRaises(ValueError, self._mlist.Authenticate,
                          ['spooge'], 'xxxxxx', 'zperson@dom.ain')



class StripperIO(StringIO):
    HEAD = 'Set-Cookie: '
    def write(self, s):
        if s.startswith(self.HEAD):
            s = s[len(self.HEAD):]
        StringIO.write(self, s)


class TestWebAuthenticate(TestBase):
    def setUp(self):
        TestBase.setUp(self)
        Utils.set_global_password('bbBBbb', siteadmin=1)
        Utils.set_global_password('ccCCcc', siteadmin=0)
        mlist = self._mlist
        mlist.mod_password = password('abcdefg')
        mlist.addNewMember('aperson@dom.ain', password='qqQQqq')
        # Set up the cookie data
        sfp = StripperIO()
        print >> sfp, mlist.MakeCookie(mm_cfg.AuthSiteAdmin)
        # AuthCreator isn't handled in AuthContextInfo()
        print >> sfp, mlist.MakeCookie(mm_cfg.AuthListAdmin)
        print >> sfp, mlist.MakeCookie(mm_cfg.AuthListModerator)
        print >> sfp, mlist.MakeCookie(mm_cfg.AuthUser, 'aperson@dom.ain')
        # Strip off the "Set-Cookie: " prefix
        cookie = sfp.getvalue()
        os.environ['HTTP_COOKIE'] = cookie

    def tearDown(self):
        try:
            os.unlink(mm_cfg.SITE_PW_FILE)
        except OSError, e:
            if e.errno <> errno.ENOENT: raise
        try:
            os.unlink(mm_cfg.LISTCREATOR_PW_FILE)
        except OSError, e:
            if e.errno <> errno.ENOENT: raise
        del os.environ['HTTP_COOKIE']
        TestBase.tearDown(self)

    def test_auth_site_admin(self):
        self.assertEqual(self._mlist.WebAuthenticate(
            [mm_cfg.AuthSiteAdmin], 'xxxxxx'), 1)

    def test_list_admin(self):
        self.assertEqual(self._mlist.WebAuthenticate(
            [mm_cfg.AuthListAdmin], 'xxxxxx'), 1)

    def test_list_moderator(self):
        self.assertEqual(self._mlist.WebAuthenticate(
            [mm_cfg.AuthListModerator], 'xxxxxx'), 1)

    def test_user(self):
        self.assertEqual(self._mlist.WebAuthenticate(
            [mm_cfg.AuthUser], 'xxxxxx'), 1)

    def test_not_a_user(self):
        self._mlist.removeMember('aperson@dom.ain')
        self.assertEqual(self._mlist.WebAuthenticate(
            [mm_cfg.AuthUser], 'xxxxxx', 'aperson@dom.ain'), 0)



# TBD: Tests for MakeCookie(), ZapCookie(), CheckCookie() -- although the
# latter is implicitly tested by testing WebAuthenticate() above.



def suite():
    suite = unittest.TestSuite()
    suite.addTest(unittest.makeSuite(TestSecurityManager))
    suite.addTest(unittest.makeSuite(TestAuthenticate))
    suite.addTest(unittest.makeSuite(TestWebAuthenticate))
    return suite



if __name__ == '__main__':
    unittest.main(defaultTest='suite')
