"""
Tests dealing with user management and credetials storage.
"""

#c Copyright 2008-2025, the GAVO project <gavo@ari.uni-heidelberg.de>
#c
#c This program is free software, covered by the GNU GPL.  See the
#c COPYING file in the source distribution.


from gavo.helpers import testhelpers
import tresc

from gavo import base
from gavo import svcs
from gavo.helpers import trialhelpers
from gavo.protocols import creds


class UserManagementTest(testhelpers.VerboseTest):
	resources = [("conn", tresc.dbConnection)]

	def tearDown(self):
		self.conn.rollback()
		creds.delUser(self.conn, "deleteme")

	def testAdding(self):
		creds.addUser(self.conn, "deleteme", "deleteme",
			"Argl!  Stuff spilled out from a unit test.")
		with testhelpers.forceConnection(self.conn):
			self.assertTrue(
				creds.hasCredentials("deleteme", "deleteme", "deleteme"))
			self.assertFalse(
				creds.hasCredentials("deleteme", "deleteme", "admin"))
			self.assertFalse(
				creds.hasCredentials("deleteme", "deletee", "deleteme"))

	def testPasswordCrypting(self):
		creds.addUser(self.conn, "deleteme", "deleteme",
			"Argl!  Stuff spilled out from a unit test.")
		storedForm = next(self.conn.query(
			"select password from dc.users where username='deleteme'"))[0]
		self.assertTrue(storedForm.startswith("scrypt:"))
	
	def testChanging(self):
		creds.addUser(self.conn, "deleteme", "deleteme",
			"Argl!  Stuff spilled out from a unit test.")
		oldStoredForm = next(self.conn.query(
			"select password from dc.users where username='deleteme'"))[0]
		creds.changeUser(self.conn, "deleteme", "changed", "New spill.")
		newStoredForm, newRemark = next(self.conn.query(
			"select password, remarks from dc.users where username='deleteme'"))
		
		self.assertEqual(newRemark, "New spill.")
		self.assertNotEqual(oldStoredForm, newStoredForm)

		creds.changeUser(self.conn, "deleteme", "changedAgain")
		newestStoredForm  = next(self.conn.query(
			"select password from dc.users where username='deleteme'"))

		self.assertNotEqual(newStoredForm, newestStoredForm)

	def testChangingRaises(self):
		# this also serves as a test for delUser, as this will fail if it's
		# not done its work
		self.assertRaisesWithMsg(base.ReportableError,
			"User deleteme does not exist.",
			creds.changeUser,
			(self.conn, "deleteme", "should not work"))


class GroupsMembershipTest(testhelpers.VerboseTest):
	resources = [('querier', tresc.testUsers)]

	def testGroupsForUser(self):
		self.assertEqual(creds.getGroupsForUser("X_test", "wrongpass"),
			set(), "Wrong password should yield empty set but doesn't")
		self.assertEqual(creds.getGroupsForUser("X_test", "megapass"),
			set(["X_test", "Y_test"]))
		self.assertEqual(creds.getGroupsForUser("Y_test", "megapass"),
			set(["Y_test"]))


class AuthUserTest(testhelpers.VerboseTest):
	def testOkAuth(self):
		req = trialhelpers.FakeRequest(user="testing", password="testing")
		self.assertEqual(req.getAuthUser(), "testing")

	def testBytesAuth(self):
		req = trialhelpers.FakeRequest(user=b"testing", password="testing")
		self.assertEqual(req.getAuthUser(), "testing")

	def testBadAuth(self):
		req = trialhelpers.FakeRequest(user="testing", password="not-testing")
		self.assertEqual(req.getAuthUser(), None)

	def testNoAuth(self):
		req = trialhelpers.FakeRequest(password="testing")
		self.assertEqual(req.getAuthUser(), None)


class GavoAdminTest(testhelpers.VerboseTest):
	def testGavoAdminMatches(self):
		self.assertTrue(creds.hasCredentials(
			"gavoadmin", "this is the unittest suite", "sharks"))

	def testGavoAdminGroups(self):
		self.assertTrue("just anything" in
			creds.getGroupsForUser("gavoadmin", "this is the unittest suite"))

	def testGavoAdminBadPW(self):
		self.assertFalse(creds.hasCredentials(
			"gavoadmin", "I made this up", "sharks"))

	def testNoAdminWithoutPW(self):
		creds.getHashedAdminPassword.cache_clear()
		try:
			with testhelpers.tempConfig(("web", "adminpasswd", "")):
				self.assertFalse(creds.hasCredentials(
					"gavoadmin", "this is the unittest suite", "sharks"))
				self.assertFalse(creds.hasCredentials(
					"gavoadmin", "", "sharks"))
				self.assertFalse("just anything" in
					creds.getGroupsForUser("gavoadmin", "this is the unittest suite"))
				self.assertFalse("just anything" in
					creds.getGroupsForUser("gavoadmin", ""))
		finally:
			creds.getHashedAdminPassword.cache_clear()

	def testHashedPW(self):
		creds.getHashedAdminPassword.cache_clear()
		try:
			with testhelpers.tempConfig(("web", "adminpasswd", "scrypt:/FXYR1B7"
					"biWqPmLYMqO1ZzVenR9n58fVKDRczcHInLUHoeOP3XUB63cRNFyMOJqelXOqU1YS6"
					"KzstnfRJxRRg8kpqWeczbfLq5Y3VjIeCQE=")):
				self.assertFalse(creds.hasCredentials(
					"gavoadmin", "this is the unittest suite", "sharks"))
				self.assertTrue(creds.hasCredentials(
					"gavoadmin", "gronk", "sharks"))
				self.assertTrue("just anything" in
					creds.getGroupsForUser("gavoadmin", "gronk"))
		finally:
			creds.getHashedAdminPassword.cache_clear()


class SamplePW(testhelpers.TestResource):
	def make(self, _):
		pw = "Hágebütte"
		storedForm = creds.hashPassword(pw)
		return pw, storedForm


class PasswordHashingTest(testhelpers.VerboseTest):
	resources = [("sample", SamplePW())]

	def testComparison(self):
		pw, storedForm = self.sample
		self.assertTrue(creds.hashMatches(pw, storedForm))

	def testSaltPresent(self):
		pw, storedForm = self.sample
		self.assertTrue(storedForm.startswith("scrypt:"))
		self.assertTrue(storedForm != creds.hashPassword(pw),
			"No salt added to passwords?")

	def testOverlongRejected(self):
		_, hash = self.sample
		self.assertRaisesWithMsg(svcs.ForbiddenURI,
			"You passed in an overlong password."
			"  The server will not even look at it.",
			creds.hashMatches,
			("hack"*30, self.sample))

	def testBadStoredForm(self):
		self.assertRaisesWithMsg(ValueError,
			"Bad hash serialisation: 'bizarre:382901fk'",
			creds.hashMatches,
			("foo", "bizarre:382901fk"))


if __name__=="__main__":
	testhelpers.main(PasswordHashingTest)
