############################################################################
##
## Copyright (c) 2000-2001 BalaBit IT Ltd, Budapest, Hungary
## All rights reserved.
##
## $Id: Auth.py,v 1.17.2.3 2001/09/19 19:22:17 bazsi Exp $
##
## Author  : Bazsi
## Auditor : kisza
## Last version: 1.3
## Notes:
##
############################################################################

import Zorp
from Zorp import *

authorization_dbs = {}

def getCommonMethods(method1, method2):
	"""Function picking up the common methods from method1 and method2.
	
	This function is used to get the intersection of methods in method1
	and method2.
	
	Arguments
	
	  method1 -- first set
	  
	  method2 -- second set
	
	"""
	method = []
	for x in method1:
		if x in method2:
			method.append(x)
	return method

class AbstractAuthorization:
	"""Class encapsulating an authorization database service.
	
	This is an abstract class to encapsulate an authorization service,
	which is responsible for checking authentication credentials.
	
	You shouldn't need to instantiate this class, use a derived one
	instead.
	
	Attributes
	
	  methods  -- set of allowed methods set by the administrator
	"""
	def __init__(self, name, methods=()):
		"""Constructor to initialize an 'AbstractAuthorization' instance.
		
		This constructor initializes an 'AbstractAuthorization' instance
		by storing its methods argument as an attribute.
		
		Arguments
		
		  self -- this instance
		
		  methods  -- set of allowed methods set by the administrator
		"""
		if authorization_dbs.has_key(name):
			raise AuthException, "Duplicate authorization db"
		authorization_dbs[name] = self
		self.methods = methods
	
	def startSession(self, session_id):
		"""Method to be called when an authentication session starts.
		
		This method is called when an authentication session
		identified by 'session_id' starts. 'session_id' can be used
		to associate data with this session, as each subsequent
		calls to AbstractAuthorization methods will get this value.
		
		Arguments
		
		  self -- this instance
		  
		  session_id -- session identifier represented as a string
		"""
		pass
	
	def stopSession(self, session_id):
		"""Method to be called when an authentication session ends.
		
		This method is called when an authentication session is ended.
		It's a placeholder for freeing up any resources associated to
		a given session.
		"""
		pass
	
	def getMethods(self, session_id, entity):
		"""Function returning the allowed set of methods.
		
		This function calculates and returns the set of allowed methods
		a user is allowed to authenticate with. We return an empty
		set here, overridden methods should return something more
		interesting.
		
		Arguments
		
		  self -- this instance
		  
		  session_id -- authentication session id
		  
		  entity -- username
		  
		Returns
		
		  a sequence containing identifiers of auth methods
		"""
		return ()
	
	def getChallenge(self, session_id, entity, method):
		"""Function returning a challenge for the given entity using the given authentication method.
		
		This function should return a challenge for a given entity
		using the given method, or None if challenge is not
		applicable for the given method.
		
		Arguments
		
		  self -- this instance
		  
		  session_id -- authentication session id
		  
		  entity -- username
		  
		  method -- authentication method
		  
		Returns
		
		  a string containing the challenge, or None if not applicable
		"""
		raise NotImplementedError
	
	def checkCredentials(self, session_id, entity, method, challenge, credentials):
		"""Function checking the presented credentials of an entity.
		
		This function is called to check the credentials presented
		by the client for validity. It should return either TRUE, if
		the credentials for the given challenge & method & username
		are valid.
		
		Arguments
		
		  self -- this instance
		  
		  session_id -- authentication session identifier
		  
		  entity -- username
		  
		  challenge -- a previously issued challenge (might be None or an empty string)
		  
		  credentials -- response for the given challenge
		"""
		raise NotImplementedError

class TISAuthorization(AbstractAuthorization):
	"""Class encapsulating a TIS authserv authorization service.
	
	This class is responsible for authenticating users against
	authentication servers using the TIS authserv protocol.
	"""
	def __init__(self, name, serveraddr):
		"""Constructor to initialize a TISAuthorization service.
		
		This constructor initializes a TISAuthorization service.
		
		Arguments
		
		  self -- this instance
		  
		  serveraddr -- a SockAddr instance containing authserv address.
		"""
		AbstractAuthorization.__init__(self, name)
		self.tis = Zorp.ZorpTISAuthorization(serveraddr, 0)

	def startSession(self, session_id):
		return self.tis.startSession(session_id)

	def stopSession(self, session_id):
		return self.tis.stopSession(session_id)

	def getMethods(self, session_id, entity):
		return self.tis.getMethods(session_id, entity)

	def getChallenge(self, session_id, entity, method):
		return self.tis.getChallenge(session_id, entity, method)

	def checkCredentials(self, session_id, entity, method, challenge, credential):
		return self.tis.checkCredentials(session_id, entity, method, challenge, credential)

class ZASAuthorization(TISAuthorization):
	def __init__(self, name, serveraddr):
		AbstractAuthorization.__init__(self, name)
		self.tis = Zorp.ZorpTISAuthorization(serveraddr, 1)

class AbstractAuthentication:
	"""Abstract class encapsulating interface different authentication means.
	
	This class encapsulates interfaces for inband and outband
	authentication procedures. You should not use this class directly,
	use 'InbandAuthentication' or 'SatyrAuthentication'.
	
	Attributes
	
	  authorization -- authorization service to authenticate against
	"""
	def __init__(self, authorization_name):
		"""Constructor to initialize an AbstractAuthentication instance.
		
		This constructor initializes an 'AbstractAuthentication' instance
		by storing arguments as attributes.
		
		Arguments
		
		  self -- this instance
		  
		  authorization_name -- authorization service to authenticate against
		"""
		try:
			self.authorization = authorization_dbs[authorization_name]
		except KeyError:
			raise AuthException, 'Authorization db not found.'

	def performAuth(self, session):
		raise AuthException, 'Authentication not implemented'
		
		
	def performOutbandAuth(self, session):
		"""Function called to perform outband authentication before the session is started.
		
		This function is called to perform outband authentication
		before a session is started. It should raise AuthException
		if the authentication was not successful.
		
		Arguments
		
		  self -- this instance
		
		  session -- the session object which is to be started
		"""
		raise AuthException, 'Outband authentication not implemented'
		
	def performInbandAuth(self, proxy):
		"""Function called to enable inband authentication in a session.
		
		This function is called when a proxy was started, and it should
		enable inband authentication in the proxy, given it has support
		to perform it.
		
		Arguments
		
		  self -- this instance
		  
		  proxy -- proxy instance to enable authentication in
		"""
		raise AuthException, 'Inband authentication not implemented'


class InbandAuthentication(AbstractAuthentication):
	"""Class encapsulating inband authentication.
	
	This class encapsulates inband authentication. Instances
	of this class can be used as the 'auth' parameter for 
	Service definitions.
	
	Example
	
	  r = TISAuthorization(SockAddrInet('192.168.1.1', 7777))
	  Service("intra_http", HttpProxy,
	          auth=InbandAuthentication(r))
	
	"""

	def performAuth(self, proxy):
		return self.performInbandAuth(proxy)

	def performInbandAuth(self, proxy):
		try:
			if proxy.auth_inband_supported:
				proxy.auth = self.authorization
				self.authorization.startSession(proxy.session.session_id)
		except AttributeError:
			raise AuthException, 'Inband authentication not supported by the underlying proxy'

class SatyrAuthentication(AbstractAuthentication):
	"""Class encapsulating outband authentication using Satyr.
	
	This class encapsulates outband authentication using the Satyr
	architecture. The satyr architecture comprises a satyrd component
	running on the client's computer and this class cooperating in
	a manner that permits authentication outside the protocol to be
	used by the current session. This way any protocol, even those
	without the necessary elements can be authenticated.
	"""
	def __init__(self, authorization, cert_file=None, key_file=None, port=4556, timeout=60000):
		"""Constructor to initialize a SatyrAuthentication instance.
		
		This constructor initializes a 'SatyrAuthentication' instance
		by calling the inherited __init__ method and creating
		a low level Satyr instance.
		"""
		AbstractAuthentication.__init__(self, authorization)
		if cert_file and key_file:
			use_ssl = 1
		else:
			use_ssl = 0
		if cert_file == None:
			cert_file = ''
		if key_file == None:
			key_file = ''
		self.satyr = Zorp.ZorpSatyr(port, timeout, use_ssl, cert_file, key_file)

	def performAuth(self, proxy):
		return self.performOutbandAuth(proxy)

	def performOutbandAuth(self, proxy):
		"""Function performing outband authentication.
		
		This function performs outband authentication using the
		satyr architecture.
		
		Arguments
		
		  self -- this instance
		  
		  proxy -- session to be authenticated
		"""
		try:
			session = proxy.session
			entity = self.satyr.authenticate(self.authorization, session.session_id, session.client_address, session.service.auth_name)
		except IOError:
			entity = None
		if entity:
			proxy.userAuthenticated(entity)
			return
		raise AuthException, 'Satyr authentication failed.'

