diff --git a/src/python/dm/common/client/__init__.py b/src/python/dm/common/client/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/src/python/dm/common/client/dmExceptionMapper.py b/src/python/dm/common/client/dmExceptionMapper.py new file mode 100755 index 0000000000000000000000000000000000000000..4fc48c6ff77c68cdf41753861d2c9ec2cc4ea4dc --- /dev/null +++ b/src/python/dm/common/client/dmExceptionMapper.py @@ -0,0 +1,30 @@ +#!/usr/bin/env python + +from dm.common.constants import dmStatus +from dm.common.exceptions import dmExceptionMap +from dm.common.exceptions.dmException import DmException + +class DmExceptionMapper: + + @classmethod + def checkStatus(cls, httpHeaders): + """ Map dm status code into appropriate exception. """ + code = httpHeaders.get('Dm-Status-Code', None) + msg = httpHeaders.get('Dm-Status-Message', 'Internal Error') + if code is None or code == str(dmStatus.DM_OK): + return + elif dmExceptionMap.DM_EXCEPTION_MAP.has_key(int(code)): + # Exception string is value of the form 'x.y.z' + # where 'x.y' is dm module, and 'z' class in that module + exStr = dmExceptionMap.DM_EXCEPTION_MAP.get(int(code)) + exClass = exStr.split('.')[-1] # 'z' in 'x.y.z' + exModule = '.'.join(exStr.split('.')[:-1]) # 'x.y' in 'x.y.z' + exec 'from dm.common.exceptions.%s import %s' % (exModule, exClass) + exec 'ex = %s(msg)' % (exClass) + raise ex + else: + raise DmException(msg) + +# Testing. +if __name__ == '__main__': + pass diff --git a/src/python/dm/common/client/dmHttpsConnection.py b/src/python/dm/common/client/dmHttpsConnection.py new file mode 100755 index 0000000000000000000000000000000000000000..3a7b7df2f53dd9042fd34ada6128950496002d6b --- /dev/null +++ b/src/python/dm/common/client/dmHttpsConnection.py @@ -0,0 +1,54 @@ +#!/usr/bin/env python + +import socket +import httplib +import ssl + +from dm.common.utility.configurationManager import ConfigurationManager + +class DmHttpsConnection(httplib.HTTPSConnection): + + def __init__(self, hostPort, timeout): + cm = ConfigurationManager.getInstance() + args = hostPort.split(':') + host = args[0] + if len(args) > 1: + port = int(args[1]) + else: + port = cm.getServicePort() + keyFile = cm.getSslKeyFile() + certFile = cm.getSslCertFile() + caCertFile = cm.getSslCaCertFile() + certChain = None + strict = True + httplib.HTTPSConnection.__init__(self, host, port, keyFile, certFile, strict, timeout) + context = self.getContext(keyFile, certFile, caCertFile, certChain) + sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + self.sock = context.wrap_socket(sock) + self.connect() + + def connect(self): + self.sock.connect((self.host,self.port)) + + def getContext(self, keyFile, certFile, caCertFile=None, certChain=None): + """Return SSL Context from self attributes.""" + #context = ssl.SSLContext(ssl.PROTOCOL_SSLv23) + context = ssl.SSLContext(ssl.PROTOCOL_TLSv1_2) + if caCertFile is not None: + context.verify_mode = ssl.CERT_REQUIRED + context.load_verify_locations(caCertFile) + else: + context.verify_mode = ssl.CERT_NONE + if certFile is not None and keyFile is not None: + context.load_cert_chain(certFile, keyFile) + if certChain: + context.load_verify_locations(certChain) + return context + +####################################################################### +# Testing. + +if __name__ == '__main__': + pass + diff --git a/src/python/dm/common/client/dmHttpsHandler.py b/src/python/dm/common/client/dmHttpsHandler.py new file mode 100755 index 0000000000000000000000000000000000000000..076e0ece9c9899db90e412cfd20ad946801a3a71 --- /dev/null +++ b/src/python/dm/common/client/dmHttpsHandler.py @@ -0,0 +1,32 @@ +#!/usr/bin/env python + +import urllib2 +import ssl +from dm.common.client.dmHttpsConnection import DmHttpsConnection + +class DmHttpsHandler(urllib2.HTTPSHandler): + + def https_open(self, req): + return self.do_open(DmHttpsConnection,req) + +####################################################################### +# Testing. + +if __name__ == '__main__': + from dm.common.utility.configurationManager import ConfigurationManager + cm = ConfigurationManager.getInstance() + cm.setSslCaCertFile("/home/sveseli/Work/DM/etc/ssl/cacert.pem") + + print "Installing opener" + opener = urllib2.build_opener(DmHttpsHandler) + urllib2.install_opener(opener) + + url = "https://zagreb.svdev.net:10232/dm/directory/list?path=/tmp" + print "Opening URL: ", url + #context = ssl.create_default_context(cafile="/home/sveseli/Work/DM/etc/ssl/cacert.pem") + #ssl._create_default_https_context = ssl._create_unverified_context + #f = urllib2.urlopen(url, context=context) + f = urllib2.urlopen(url) + print f.code + print f.read() + diff --git a/src/python/dm/common/client/sessionManager.py b/src/python/dm/common/client/sessionManager.py new file mode 100755 index 0000000000000000000000000000000000000000..e5c14d9fa6569c935002974bd1eb529075611d34 --- /dev/null +++ b/src/python/dm/common/client/sessionManager.py @@ -0,0 +1,221 @@ +#!/usr/bin/env python + +import urllib +import urllib2 +import urlparse +import time +import getpass +import os +import stat +import ssl +import types + +from dm.common.constants import dmServiceConstants +from dm.common.exceptions.configurationError import ConfigurationError +from dm.common.exceptions.authorizationError import AuthorizationError +from dm.common.exceptions.urlError import UrlError +from dm.common.utility.loggingManager import LoggingManager +from dm.common.utility.configurationManager import ConfigurationManager +from dm.common.utility.osUtility import OsUtility +from dm.common.client.dmExceptionMapper import DmExceptionMapper +from dm.common.client.dmHttpsHandler import DmHttpsHandler + +class SessionManager: + """ Class for session management. """ + + def __init__(self): + self.sessionCookie = None + self.host = None + self.logger = LoggingManager.getInstance().getLogger(self.__class__.__name__) + self.username = '' + self.password = '' + self.urlOpener = None + cm = ConfigurationManager.getInstance() + self.sessionCacheFile = cm.getSessionCacheFile() + self.requireSessionCredentials = cm.getRequireSessionCredentials() + + def setHost(self, host): + self.host = host + + def hasSession(self): + """ Return true if we have session established. """ + if self.sessionCookie is not None: + return True + return False + + def establishSession(self, url, username, password, selector='/dm/login'): + self.host = url + self.username = username + self.password = password + self.sessionCookie = self.loadSession() + if self.sessionCookie is not None: + return + + # Could not load session. + try: + # User will be asked for username/password if they are not + # provided. + data = { 'username' : self.getUsername(username), 'password' : self.getPassword(password) } + self.logger.debug('Establishing session for user %s @ %s)' % (username, url)) + (response,responseData) = self.sendRequest(url='%s%s' % (url, selector), method='POST', contentType='application/x-www-form-urlencoded', data=urllib.urlencode(data)) + except urllib2.URLError, ex: + self.logger.exception('%s' % ex) + raise UrlError(exception=ex) + + #self.logger.debug('Got headers: %s' % response.headers) + # This will save session cookie. + self.sessionCookie = self.checkResponseHeadersForErrorsAndSaveSession(response.headers) + self.logger.debug('User %s session cookie: %s' % (username, self.sessionCookie)) + + def getUsername(self, username): + if not len(username) and self.requireSessionCredentials: + return self.askForUsername() + return username + + def askForUsername(self): + defaultUsername = getpass.getuser() + username = raw_input('Username [%s]: ' % defaultUsername) + username = username.strip() + if not len(username): + username = defaultUsername + return username + + def getPassword(self, password): + if not len(password) and self.requireSessionCredentials: + return self.askForPassword() + return password + + def askForPassword(self): + password = getpass.getpass() + password = password.strip() + if not len(password): + raise AuthorizationError('Empty password provided.') + return password + + def clearSessionFile(self): + if self.sessionCacheFile is None: + return + try: + self.logger.debug('Clearing session cache: %s' % (self.sessionCacheFile)) + OsUtility.removeFile(self.sessionCacheFile) + except Exception, ex: + # ignore errors. + self.logger.warn('Could not clear session cache: %s' % (ex)) + pass + + def saveSession(self, sessionCookie): + if self.sessionCacheFile is None: + return + if sessionCookie is None: + return + try: + f = open(self.sessionCacheFile, 'w') + f.write('%s' % sessionCookie) + f.close() + os.chmod(self.sessionCacheFile, stat.S_IRUSR|stat.S_IWUSR|stat.S_IXUSR) + except Exception, ex: + self.logger.warn('Could not save session: %s' % (ex)) + + def loadSession(self): + if self.sessionCacheFile is None: + return None + try: + f = open(self.sessionCacheFile, 'r') + session = f.read() + expires = session.split(';')[1].split('=')[-1] + t = time.mktime(time.strptime(expires, '%a, %d %b %Y %H:%M:%S %Z')) + now = time.time() + if t < now: + return None + else: + self.logger.debug('Loaded session from %s: %s' % (self.sessionCacheFile, session)) + return session + except Exception, ex: + self.logger.warn('Could not load session: %s' % (ex)) + return None + + def getUrlOpener(self, protocol): + if not self.urlOpener: + if protocol == dmServiceConstants.DM_SERVICE_PROTOCOL_HTTPS: + # HTTPS, use custom https handler, which + # should work even if any of cert/key/cacert files is None + cm = ConfigurationManager.getInstance() + keyFile = cm.getSslKeyFile() + certFile = cm.getSslCertFile() + self.urlOpener = urllib2.build_opener(DmHttpsHandler) + #self.logger.debug('Using Dm HTTPS Handler') + else: + # HTTP, use standard http handler + self.urlOpener = urllib2.build_opener(urllib2.HTTPHandler) + # Install opener before returning it. + urllib2.install_opener(self.urlOpener) + return self.urlOpener + + def sendRequest(self, url, method, contentType='html', data={}): + """ Send http request without cookies. """ + if url.find('://') < 0: + url = '%s%s' % (self.host, url) + parsedUrl = urlparse.urlparse(url) + protocol = parsedUrl[0] + path = parsedUrl[2] + self.logger.debug('Sending request: %s' % url) + encodedData = '' + if data is not None: + if type(data) == types.DictType and len(data): + encodedData=urllib.urlencode(data) + contentType='application/x-www-form-urlencoded' + elif type(data) == types.StringType: + encodedData = data + request = urllib2.Request(url, data=encodedData) + request.get_method = lambda: method + request.add_header('Content-Type', contentType) + request.add_header('Content-Length', str(len(data))) + if self.sessionCookie != None: + request.add_header('Cookie', self.sessionCookie) + try: + opener = self.getUrlOpener(protocol) + response = opener.open(request) + except urllib2.HTTPError, ex: + # If we see dm headers, dm exception will be thrown, + # otherwise we'll throw UrlError + self.checkResponseHeadersForErrors(ex.hdrs) + self.logger.exception('%s' % ex) + raise UrlError(exception=ex) + except urllib2.URLError, ex: + self.logger.exception('%s' % ex) + raise UrlError(exception=ex) + + # Check headers for errors and update session cookie + sessionCookie = self.checkResponseHeadersForErrorsAndSaveSession(response.headers) + if sessionCookie != None: + self.sessionCookie = sessionCookie + responseData = response.read() + return (response, responseData) + + def sendSessionRequest(self, url, method, contentType='html', data={}): + """ Send session request. """ + if self.sessionCookie is None: + self.establishSession(self.host, self.username, self.password) + return self.sendRequest(url, method, contentType, data) + + def checkResponseHeadersForErrorsAndSaveSession(self, responseHeaders): + try: + DmExceptionMapper.checkStatus(responseHeaders) + sessionCookie = responseHeaders.get('Set-Cookie') + self.saveSession(sessionCookie) + return sessionCookie + except AuthorizationError, ex: + self.clearSessionFile() + raise + + def checkResponseHeadersForErrors(self, responseHeaders): + try: + DmExceptionMapper.checkStatus(responseHeaders) + except AuthorizationError, ex: + self.clearSessionFile() + raise + +####################################################################### +# Testing. +if __name__ == '__main__': + sm = SessionManager.createSession()