From 75750eaa06fdf7c8fcc18e73f864776aae16f40b Mon Sep 17 00:00:00 2001 From: Ilgar Mashayev Date: Fri, 9 Jul 2010 15:38:32 +0200 Subject: [PATCH] ticket:21 administrative auth --- README | 1 + client/sample/admin_auth.py | 56 ++++++++++++++ client/sample/auth.py | 10 ++- client/sample/get_account.py | 66 ++++++++++++++++ client/sample/get_info.py | 5 +- client/sample/proxied_request.py | 5 +- client/sample/util.py | 12 +-- src/pyzimbra/auth.py | 15 +++- src/pyzimbra/base.py | 6 +- src/pyzimbra/sconstant.py | 3 + src/pyzimbra/soap.py | 17 ++++- src/pyzimbra/soap_auth.py | 35 ++++++++- src/pyzimbra/soap_soappy.py | 97 ++++++++++++++++++++++++ src/pyzimbra/soap_transport.py | 112 +--------------------------- src/pyzimbra/zclient.py | 16 ++++ src/pyzimbra/zconstant.py | 5 ++ test/mock/auth.py | 19 ++++- {src/pyzimbra => test}/pconstant.py | 7 +- test/test.properties | 5 ++ test/tests/auth.py | 40 ++++++++++ test/tests/soap.py | 19 +++++ test/util.py | 6 +- 22 files changed, 421 insertions(+), 136 deletions(-) create mode 100644 client/sample/admin_auth.py create mode 100644 client/sample/get_account.py rename {src/pyzimbra => test}/pconstant.py (90%) diff --git a/README b/README index 99f5e78..a294c95 100644 --- a/README +++ b/README @@ -14,3 +14,4 @@ ticket:31 - add proxy support ticket:27 - error and exception handling ticket:33 - switch to SOAPpy ticket:20 - zimbra client preauthentication +ticket:21 - zimbra client administrative authentication \ No newline at end of file diff --git a/client/sample/admin_auth.py b/client/sample/admin_auth.py new file mode 100644 index 0000000..9738ea1 --- /dev/null +++ b/client/sample/admin_auth.py @@ -0,0 +1,56 @@ +# -*- coding: utf-8 -*- +""" +################################################################################ +# Copyright (c) 2010, Ilgar Mashayev +# +# E-mail: pyzimbra@lab.az +# Website: http://github.com/ilgarm/pyzimbra +################################################################################ +# This file is part of pyzimbra. +# +# Pyzimbra is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Pyzimbra 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 Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with Pyzimbra. If not, see . +################################################################################ + +Authentication samples. + +@author: ilgar +""" +from pyzimbra import soap +from pyzimbra.base import ZimbraClientException +from pyzimbra.soap_auth import SoapAuthenticator +from pyzimbra.soap_transport import SoapTransport +from sample.util import load_properties +import pconstant + + +def authenticate(): + p = load_properties() + + transport = SoapTransport() + transport.debug = 1 + transport.soap_url = soap.admin_soap_url(p[pconstant.ADMIN_HOSTNAME]) + + auth = SoapAuthenticator() + auth_token = auth.authenticate_admin(transport, + p[pconstant.ADMIN_ACCOUNT_NAME], + p[pconstant.ADMIN_PASSWORD]) + + print auth_token.token + + +if __name__ == '__main__': + try: + authenticate() + except ZimbraClientException, e: + e.print_trace() diff --git a/client/sample/auth.py b/client/sample/auth.py index 674ddfa..8210ea2 100644 --- a/client/sample/auth.py +++ b/client/sample/auth.py @@ -26,11 +26,12 @@ @author: ilgar """ -from pyzimbra import pconstant +from pyzimbra import soap from pyzimbra.base import ZimbraClientException from pyzimbra.soap_auth import SoapAuthenticator from pyzimbra.soap_transport import SoapTransport from sample.util import load_properties +import pconstant def authenticate(): @@ -38,7 +39,7 @@ def authenticate(): transport = SoapTransport() transport.debug = 1 - transport.domains = p[pconstant.DOMAINS] + transport.soap_url = soap.soap_url(p[pconstant.HOSTNAME]) auth = SoapAuthenticator() auth_token = auth.authenticate(transport, @@ -53,10 +54,11 @@ def pre_authenticate(): transport = SoapTransport() transport.debug = 1 - transport.domains = p[pconstant.DOMAINS] + transport.soap_url = soap.soap_url(p[pconstant.HOSTNAME]) auth = SoapAuthenticator() - auth_token = auth.authenticate(transport, pconstant.ACCOUNT_NAME) + auth.domains = p[pconstant.DOMAINS] + auth_token = auth.authenticate(transport, p[pconstant.ACCOUNT_NAME]) print auth_token.token diff --git a/client/sample/get_account.py b/client/sample/get_account.py new file mode 100644 index 0000000..cc52838 --- /dev/null +++ b/client/sample/get_account.py @@ -0,0 +1,66 @@ +# -*- coding: utf-8 -*- +""" +################################################################################ +# Copyright (c) 2010, Ilgar Mashayev +# +# E-mail: pyzimbra@lab.az +# Website: http://github.com/ilgarm/pyzimbra +################################################################################ +# This file is part of pyzimbra. +# +# Pyzimbra is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Pyzimbra 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 Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with Pyzimbra. If not, see . +################################################################################ + +Account info samples. + +@author: ilgar +""" +from pyzimbra import sconstant, zconstant, soap +from pyzimbra.soap_auth import SoapAuthenticator +from pyzimbra.soap_transport import SoapTransport +from pyzimbra.zclient import ZimbraClient +from sample.util import load_properties +import SOAPpy +import pconstant + + +def get_account(): + p = load_properties() + + transport = SoapTransport() + transport.debug = 1 + transport.soap_url = soap.admin_soap_url(p[pconstant.ADMIN_HOSTNAME]) + + auth = SoapAuthenticator() + + zclient = ZimbraClient() + zclient.transport = transport + + zclient.authenticate_admin(auth, + p[pconstant.ADMIN_ACCOUNT_NAME], + p[pconstant.ADMIN_PASSWORD]) + + attrs = {sconstant.A_BY: sconstant.V_NAME} + account = SOAPpy.Types.stringType(data=p[pconstant.ACCOUNT_NAME], attrs=attrs) + + params = {sconstant.E_ACCOUNT: account} + res = zclient.invoke(zconstant.NS_ZIMBRA_ADMIN_URL, + sconstant.GetAccountRequest, + params) + + print res + + +if __name__ == '__main__': + get_account() diff --git a/client/sample/get_info.py b/client/sample/get_info.py index 2b6cfff..a65e1bd 100644 --- a/client/sample/get_info.py +++ b/client/sample/get_info.py @@ -26,11 +26,12 @@ @author: ilgar """ -from pyzimbra import sconstant, zconstant, pconstant +from pyzimbra import sconstant, zconstant, soap from pyzimbra.soap_auth import SoapAuthenticator from pyzimbra.soap_transport import SoapTransport from pyzimbra.zclient import ZimbraClient from sample.util import load_properties +import pconstant def get_info(): @@ -38,7 +39,7 @@ def get_info(): transport = SoapTransport() transport.debug = 1 - transport.domains = p[pconstant.DOMAINS] + transport.soap_url = soap.soap_url(p[pconstant.HOSTNAME]) auth = SoapAuthenticator() diff --git a/client/sample/proxied_request.py b/client/sample/proxied_request.py index 18c652b..b7fb79d 100644 --- a/client/sample/proxied_request.py +++ b/client/sample/proxied_request.py @@ -26,11 +26,12 @@ @author: ilgar """ -from pyzimbra import soap, sconstant, zconstant, pconstant +from pyzimbra import soap, sconstant, zconstant from pyzimbra.soap_auth import SoapAuthenticator from pyzimbra.soap_transport import SoapTransport from pyzimbra.zclient import ZimbraClient from sample.util import load_properties +import pconstant def get_proxied_info(): @@ -38,7 +39,7 @@ def get_proxied_info(): transport = SoapTransport() transport.debug = 1 - transport.domains = p[pconstant.DOMAINS] + transport.soap_url = soap.soap_url(p[pconstant.HOSTNAME]) transport.proxy_url = soap.proxy_url(p[pconstant.PROXY_HOSTNAME], p[pconstant.PROXY_USERNAME], p[pconstant.PROXY_PASSWORD], diff --git a/client/sample/util.py b/client/sample/util.py index a1ccf83..078132a 100644 --- a/client/sample/util.py +++ b/client/sample/util.py @@ -27,7 +27,7 @@ @author: ilgar """ from ConfigParser import ConfigParser -from pyzimbra import pconstant +import pconstant def load_properties(): @@ -36,16 +36,18 @@ def load_properties(): cfg.read(properties) p = {} + p[pconstant.ADMIN_HOSTNAME] = cfg.get(pconstant.ADMIN, pconstant.HOST) + p[pconstant.ADMIN_ACCOUNT_NAME] = cfg.get(pconstant.ADMIN, pconstant.USERNAME) + p[pconstant.ADMIN_PASSWORD] = cfg.get(pconstant.ADMIN, pconstant.PASSWORD) + p[pconstant.DOMAIN] = cfg.get(pconstant.DOMAIN, pconstant.NAME) p[pconstant.HOSTNAME] = cfg.get(pconstant.DOMAIN, pconstant.HOST) - p[pconstant.DOMAIN_KEY] = cfg.get(pconstant.DOMAIN, pconstant.KEY) p[pconstant.DOMAINS] = {p[pconstant.DOMAIN]: - {pconstant.HOSTNAME: p[pconstant.HOSTNAME], - pconstant.KEY: p[pconstant.DOMAIN_KEY]}} + cfg.get(pconstant.DOMAIN, pconstant.KEY)} p[pconstant.PROXY_SCHEME] = cfg.get(pconstant.PROXY, pconstant.SCHEME) p[pconstant.PROXY_HOSTNAME] = cfg.get(pconstant.PROXY, pconstant.HOST) - p[pconstant.PROXY_PORT] = cfg.get(pconstant.PROXY, pconstant.SCHEME) + p[pconstant.PROXY_PORT] = cfg.get(pconstant.PROXY, pconstant.PORT) p[pconstant.PROXY_USERNAME] = cfg.get(pconstant.PROXY, pconstant.USERNAME) p[pconstant.PROXY_PASSWORD] = cfg.get(pconstant.PROXY, pconstant.PASSWORD) diff --git a/src/pyzimbra/auth.py b/src/pyzimbra/auth.py index 49c01a9..6f29fb9 100644 --- a/src/pyzimbra/auth.py +++ b/src/pyzimbra/auth.py @@ -68,12 +68,25 @@ class Authenticator(object): __metaclass__ = abc.ABCMeta # --------------------------------------------------------------- properties + domains = property(lambda self: self._domains, + lambda self, v: setattr(self, '_domains', v)) # -------------------------------------------------------------------- bound def __init__(self): - pass + self.domains = {} # ------------------------------------------------------------------ unbound + @abc.abstractmethod + def authenticate_admin(self, transport, account_name, password): + """ + Authenticates administrator using username and password. + @param transport: transport to use for method calls + @param account_name: account name + @param password: account password + @return: AuthToken if authentication succeeded + @raise AuthException: if authentication fails + """ + @abc.abstractmethod def authenticate(self, transport, account_name, password): """ diff --git a/src/pyzimbra/base.py b/src/pyzimbra/base.py index 55fc3a9..76ef452 100644 --- a/src/pyzimbra/base.py +++ b/src/pyzimbra/base.py @@ -77,8 +77,8 @@ class ZimbraClientTransport(object): __metaclass__ = abc.ABCMeta # --------------------------------------------------------------- properties - domains = property(lambda self: self._domains, - lambda self, v: setattr(self, '_domains', v)) + soap_url = property(lambda self: self._soap_url, + lambda self, v: setattr(self, '_soap_url', v)) proxy_url = property(lambda self: self._proxy_url, lambda self, v: setattr(self, '_proxy_url', v)) debug = property(lambda self: self._debug, @@ -86,7 +86,7 @@ class ZimbraClientTransport(object): # -------------------------------------------------------------------- bound def __init__(self): - self.domains = {} + self.soap_url = None self.proxy_url = None self.debug = 0 diff --git a/src/pyzimbra/sconstant.py b/src/pyzimbra/sconstant.py index f41cdf6..56f4375 100644 --- a/src/pyzimbra/sconstant.py +++ b/src/pyzimbra/sconstant.py @@ -40,6 +40,9 @@ GetInfoRequest = 'GetInfoRequest' GetInfoResponse = 'GetInfoResponse' +GetAccountRequest = 'GetAccountRequest' +GetAccountResponse = 'GetAccountResponse' + E_CODE = 'Code' E_TRACE = 'Trace' diff --git a/src/pyzimbra/soap.py b/src/pyzimbra/soap.py index 4edddd1..2aa6b94 100644 --- a/src/pyzimbra/soap.py +++ b/src/pyzimbra/soap.py @@ -31,13 +31,24 @@ import urllib2 +def admin_soap_url(hostname): + """ + @return: absolute zimbra administrative soap endpoint url + @raise SoapException: if hostname is empty + """ + if util.empty(hostname): + raise SoapException('Empty hostname') + + return 'https://%s%s' % (hostname, zconstant.SOAP_ADMIN_URL) + + def soap_url(hostname): """ @return: absolute zimbra soap endpoint url @raise SoapException: if hostname is empty """ if util.empty(hostname): - raise SoapException('Empty hostname') + raise SoapException('Empty hostname') return 'http://%s%s' % (hostname, zconstant.SOAP_URL) @@ -48,10 +59,10 @@ def proxy_url(hostname, username=None, password=None, port=0, scheme='http'): @raise SoapException: if scheme or hostname is empty """ if util.empty(hostname): - raise SoapException('Empty hostname') + raise SoapException('Empty hostname') if util.empty(scheme): - raise SoapException('Empty scheme') + raise SoapException('Empty scheme') hostport = hostname if port > 0 and (scheme != 'http' or port != 80): diff --git a/src/pyzimbra/soap_auth.py b/src/pyzimbra/soap_auth.py index 5b2aa99..38ccb4d 100644 --- a/src/pyzimbra/soap_auth.py +++ b/src/pyzimbra/soap_auth.py @@ -26,7 +26,7 @@ @author: ilgar """ -from pyzimbra import zconstant, sconstant, util, pconstant +from pyzimbra import zconstant, sconstant, util from pyzimbra.auth import AuthException, AuthToken, Authenticator from pyzimbra.soap import SoapException from time import time @@ -45,6 +45,31 @@ def __init__(self): Authenticator.__init__(self) # ------------------------------------------------------------------ unbound + def authenticate_admin(self, transport, account_name, password): + """ + Authenticates administrator using username and password. + """ + Authenticator.authenticate_admin(self, transport, account_name, password) + + auth_token = AuthToken() + auth_token.account_name = account_name + + params = {sconstant.E_NAME: account_name, + sconstant.E_PASSWORD: password} + try: + res = transport.invoke(zconstant.NS_ZIMBRA_ADMIN_URL, + sconstant.AuthRequest, + params, + auth_token) + except SoapException as exc: + raise AuthException(unicode(exc), exc) + + auth_token.token = res.authToken + auth_token.session_id = res.sessionId + + return auth_token + + def authenticate(self, transport, account_name, password=None): """ Authenticates account using soap method. @@ -78,7 +103,9 @@ def auth(self, transport, account_name, password): raise AuthException(unicode(exc), exc) auth_token.token = res.authToken - auth_token.session_id = res.sessionId + + if hasattr(res, 'sessionId'): + auth_token.session_id = res.sessionId return auth_token @@ -94,8 +121,8 @@ def pre_auth(self, transport, account_name): if domain == None: raise AuthException('Invalid auth token account') - if domain in transport.domains and pconstant.KEY in transport.domains[domain]: - domain_key = transport.domains[domain][pconstant.KEY] + if domain in self.domains: + domain_key = self.domains[domain] else: domain_key = None diff --git a/src/pyzimbra/soap_soappy.py b/src/pyzimbra/soap_soappy.py index 73f0cac..94d85f4 100644 --- a/src/pyzimbra/soap_soappy.py +++ b/src/pyzimbra/soap_soappy.py @@ -26,7 +26,10 @@ @author: ilgar """ +from pyzimbra import zconstant, util +from pyzimbra.soap import SoapException import SOAPpy +import urllib2 import xml.sax @@ -75,3 +78,97 @@ def parseSOAP(xml_str, rules = None): raise e return t + + +class SoapHttpTransport(SOAPpy.Client.HTTPTransport): + """ + Http transport using urllib2, with support for proxy authentication and more. + """ + # --------------------------------------------------------------- properties + transport = property(lambda self: self._transport, + lambda self, v: setattr(self, '_transport', v)) + + + # ------------------------------------------------------------------ unbound + def call(self, addr, data, namespace, soapaction = None, encoding = None, + http_proxy = None, config = SOAPpy.Config): + + if not isinstance(addr, SOAPpy.Client.SOAPAddress): + addr = SOAPpy.Client.SOAPAddress(addr, config) + + url = addr.proto + "://" + addr.host + addr.path + + headers = {'User-Agent': zconstant.USER_AGENT} + request = urllib2.Request(url, data, headers) + + if self.transport.debug == 1: + print 'Request url: ', url + print 'Request headers: ' + print request.headers + print 'Request data: ' + print data + + try: + opener = self.build_opener() + response = opener.open(request) + data = response.read() + + if self.transport.debug == 1: + print 'Response headers: ' + print response.headers + print 'Response data: ' + print data + + except urllib2.URLError as exc: + raise self.init_soap_exception(exc) + + # get the new namespace + if namespace is None: + new_ns = None + else: + new_ns = self.getNS(namespace, data) + + return data, new_ns + + + def build_opener(self): + """ + Builds url opener, initializing proxy. + @return: OpenerDirector + """ + http_handler = urllib2.HTTPHandler() # debuglevel=self.transport.debug + + if util.empty(self.transport.proxy_url): + return urllib2.build_opener(http_handler) + + proxy_handler = urllib2.ProxyHandler( + {self.transport.proxy_url[:4]: self.transport.proxy_url}) + + return urllib2.build_opener(http_handler, proxy_handler) + + + def init_soap_exception(self, exc): + """ + Initializes exception based on soap error response. + @param exc: URLError + @return: SoapException + """ + if not isinstance(exc, urllib2.HTTPError): + return SoapException(unicode(exc), exc) + + if isinstance(exc, urllib2.HTTPError): + try: + data = exc.read() + if self.transport.debug == 1: + print data + + t = SOAPpy.Parser.parseSOAP(data) + message = '%s:%s' % (t.Fault.faultcode, t.Fault.faultstring) + e = SoapException(message, exc) + e.code = t.Fault.detail.Error.Code + e.trace = t.Fault.detail.Error.Trace + return e + except: + return SoapException(unicode(exc), exc) + + return SoapException(exc.reason, exc) diff --git a/src/pyzimbra/soap_transport.py b/src/pyzimbra/soap_transport.py index 382fad7..d0fb762 100644 --- a/src/pyzimbra/soap_transport.py +++ b/src/pyzimbra/soap_transport.py @@ -26,106 +26,10 @@ @author: ilgar """ -from pyzimbra import zconstant, sconstant, util, soap, pconstant +from pyzimbra import zconstant, sconstant from pyzimbra.base import ZimbraClientTransport -from pyzimbra.soap import SoapException -from pyzimbra.soap_soappy import parseSOAP +from pyzimbra.soap_soappy import parseSOAP, SoapHttpTransport import SOAPpy -import urllib2 - - -class SoapHttpTransport(SOAPpy.Client.HTTPTransport): - """ - Http transport using urllib2, with support for proxy authentication and more. - """ - # --------------------------------------------------------------- properties - transport = property(lambda self: self._transport, - lambda self, v: setattr(self, '_transport', v)) - - - # ------------------------------------------------------------------ unbound - def call(self, addr, data, namespace, soapaction = None, encoding = None, - http_proxy = None, config = SOAPpy.Config): - - if not isinstance(addr, SOAPpy.Client.SOAPAddress): - addr = SOAPpy.Client.SOAPAddress(addr, config) - - url = addr.proto + "://" + addr.host + addr.path - - headers = {'User-Agent': zconstant.USER_AGENT} - request = urllib2.Request(url, data, headers) - - if self.transport.debug == 1: - print 'Request url: ', url - print 'Request headers: ' - print request.headers - print 'Request data: ' - print data - - try: - opener = self.build_opener() - response = opener.open(request) - data = response.read() - - if self.transport.debug == 1: - print 'Response headers: ' - print response.headers - print 'Response data: ' - print data - - except urllib2.URLError as exc: - raise self.init_soap_exception(exc) - - # get the new namespace - if namespace is None: - new_ns = None - else: - new_ns = self.getNS(namespace, data) - - return data, new_ns - - - def build_opener(self): - """ - Builds url opener, initializing proxy. - @return: OpenerDirector - """ - http_handler = urllib2.HTTPHandler() # debuglevel=self.transport.debug - - if util.empty(self.transport.proxy_url): - return urllib2.build_opener(http_handler) - - proxy_handler = urllib2.ProxyHandler( - {self.transport.proxy_url[:4]: self.transport.proxy_url}) - - return urllib2.build_opener(http_handler, proxy_handler) - - - def init_soap_exception(self, exc): - """ - Initializes exception based on soap error response. - @param exc: URLError - @return: SoapException - """ - if not isinstance(exc, urllib2.HTTPError): - return SoapException(unicode(exc), exc) - - if isinstance(exc, urllib2.HTTPError): - try: - data = exc.read() - if self.transport.debug == 1: - print data - - t = SOAPpy.Parser.parseSOAP(data) - message = '%s:%s' % (t.Fault.faultcode, t.Fault.faultstring) - e = SoapException(message, exc) - e.code = t.Fault.detail.Error.Code - e.trace = t.Fault.detail.Error.Trace - return e - except: - return SoapException(unicode(exc), exc) - - return SoapException(exc.reason, exc) class SoapTransport(ZimbraClientTransport): @@ -151,16 +55,6 @@ def invoke(self, ns, request_name, params, auth_token): headers = SOAPpy.Types.headerType() - domain = util.get_domain(auth_token.account_name) - if domain == None: - raise SoapException('Invalid auth token account') - - if domain in self.domains and pconstant.HOSTNAME in self.domains[domain]: - hostname = self.domains[domain][pconstant.HOSTNAME] - else: - hostname = domain - url = soap.soap_url(hostname) - if auth_token.token != None: data={sconstant.E_AUTH_TOKEN: auth_token.token, sconstant.E_SESSION_ID: auth_token.session_id} @@ -169,7 +63,7 @@ def invoke(self, ns, request_name, params, auth_token): context._ns = (zconstant.SOAP_DEFAULT_PREFIX, zconstant.NS_ZIMBRA_URL) headers.context = context - proxy = SOAPpy.SOAPProxy(url, ns, header=headers, noroot=1) + proxy = SOAPpy.SOAPProxy(self.soap_url, ns, header=headers, noroot=1) proxy.config.debug = self.debug proxy.config.strictNamespaces = 0 proxy.config.buildWithNamespacePrefix = 0 diff --git a/src/pyzimbra/zclient.py b/src/pyzimbra/zclient.py index 8e695c1..8c2f575 100644 --- a/src/pyzimbra/zclient.py +++ b/src/pyzimbra/zclient.py @@ -47,6 +47,22 @@ def __init__(self): self.transport = None # ------------------------------------------------------------------ unbound + def authenticate_admin(self, authenticator, account_name, password): + """ + Authenticates zimbra administrative account. + @param authenticator: authenticator to use + @param account_name: account email address + @param password: account password + @raise AuthException: if authentication fails + @raise SoapException: if soap communication fails + """ + if self.transport == None: + raise ZimbraClientException('Invalid transport') + + self._auth_token = authenticator.authenticate_admin(self.transport, + account_name, + password) + def authenticate(self, authenticator, account_name, password): """ Authenticates zimbra account. diff --git a/src/pyzimbra/zconstant.py b/src/pyzimbra/zconstant.py index db85b9e..4d32bde 100644 --- a/src/pyzimbra/zconstant.py +++ b/src/pyzimbra/zconstant.py @@ -29,6 +29,7 @@ USER_AGENT = "pyzimbra/%s" % __version__ SOAP_URL = '/service/soap' +SOAP_ADMIN_URL = '/service/admin/soap' SOAP_DEFAULT_PREFIX = 'ns1' @@ -44,3 +45,7 @@ NS_ZIMBRA_ACC_URL = 'urn:zimbraAccount' NS_ZIMBRA_ACC_MAP = {None: NS_ZIMBRA_ACC_URL} NS_ZIMBRA_ACC = '{%s}' % NS_ZIMBRA_ACC_URL + +NS_ZIMBRA_ADMIN_URL = 'urn:zimbraAdmin' +NS_ZIMBRA_ACC_MAP = {None: NS_ZIMBRA_ADMIN_URL} +NS_ZIMBRA_ACC = '{%s}' % NS_ZIMBRA_ADMIN_URL diff --git a/test/mock/auth.py b/test/mock/auth.py index 784f57f..20405fc 100644 --- a/test/mock/auth.py +++ b/test/mock/auth.py @@ -26,7 +26,7 @@ @author: ilgar """ -from pyzimbra.auth import AuthException, AuthToken +from pyzimbra.auth import AuthException, AuthToken, Authenticator from pyzimbra.soap_auth import SoapAuthenticator from util import load_test_properties @@ -44,6 +44,23 @@ def __init__(self): load_test_properties(self) # ------------------------------------------------------------------ unbound + def authenticate_admin(self, transport, account_name, password): + Authenticator.authenticate_admin(self, transport, account_name, password) + + if not account_name == self.admin_account_name: + raise AuthException('Invalid username') + + if not password == self.admin_password: + raise AuthException('Invalid password') + + token = AuthToken() + token.account_name = self.admin_account_name + token.token = self.token + token.session_id = self.session_id + + return token + + def auth(self, transport, account_name, password): if not account_name == self.account_name: raise AuthException('Invalid username') diff --git a/src/pyzimbra/pconstant.py b/test/pconstant.py similarity index 90% rename from src/pyzimbra/pconstant.py rename to test/pconstant.py index 23deab4..6fb3d8c 100644 --- a/src/pyzimbra/pconstant.py +++ b/test/pconstant.py @@ -39,6 +39,11 @@ DOMAIN_KEY = 'domain_key' DOMAINS = 'domains' +ADMIN = 'admin' +ADMIN_HOSTNAME = 'admin_hostname' +ADMIN_ACCOUNT_NAME = 'admin_account_name' +ADMIN_PASSWORD = 'admin_password' + PROXY = 'proxy' PROXY_SCHEME = 'proxy_scheme' PROXY_HOSTNAME = 'proxy_hostname' @@ -50,4 +55,4 @@ ID = 'id' ACCOUNT_NAME = 'account_name' TOKEN = 'token' -SESSION_ID = 'session_id' +SESSION_ID = 'session_id' \ No newline at end of file diff --git a/test/test.properties b/test/test.properties index a586ad3..33ed20a 100644 --- a/test/test.properties +++ b/test/test.properties @@ -3,6 +3,11 @@ name=mocked.com host=mail.mocked.com key=abcdefghijklmnopqrstuvwxyz +[admin] +host=localhost +username=mocked_admin@mocked.com +password=mocked_admin + [proxy] scheme=http host=mocked.com diff --git a/test/tests/auth.py b/test/tests/auth.py index 52670e6..bdec53b 100644 --- a/test/tests/auth.py +++ b/test/tests/auth.py @@ -64,6 +64,46 @@ def testAuthTokenNotEmpty(self): self.assertFalse(util.empty(auth_token.session_id)) + def testAdminAuthEmptyTransport(self): + a = MockAuthenticator() + + self.assertRaises(ZimbraClientException, a.authenticate_admin, None, "", "") + + + def testAdminAuthEmptyCredentials(self): + a = MockAuthenticator() + transport = MockTransport() + + self.assertRaises(AuthException, + a.authenticate_admin, transport, "", self.password) + self.assertRaises(AuthException, + a.authenticate_admin, transport, self.username, "") + self.assertRaises(AuthException, + a.authenticate_admin, transport, "", "") + + + def testAdminAuthValidCredentials(self): + a = MockAuthenticator() + transport = MockTransport() + + auth_token = a.authenticate(transport, self.account_name, self.password) + + self.assertTrue(auth_token != None) + self.assertEquals(self.account_name, auth_token.account_name) + self.assertEquals(self.token, auth_token.token) + self.assertEquals(self.session_id, auth_token.session_id) + + + def testAdminAuthInvalidCredentials(self): + a = MockAuthenticator() + transport = MockTransport() + + self.assertRaises(AuthException, + a.authenticate, transport, "wrong", self.password) + self.assertRaises(AuthException, + a.authenticate, transport, self.account_name, "wrong") + + def testAuthEmptyTransport(self): a = MockAuthenticator() diff --git a/test/tests/soap.py b/test/tests/soap.py index 360503c..e5cbb94 100644 --- a/test/tests/soap.py +++ b/test/tests/soap.py @@ -51,6 +51,25 @@ def testNotEmptyHostname(self): self.assertEqual("http://localhost/service/soap", result) + def testNotEmptyHostnamePort(self): + result = soap.soap_url("localhost:8080") + self.assertEqual("http://localhost:8080/service/soap", result) + + + def testEmptyAdminHostname(self): + self.assertRaises(SoapException, soap.admin_soap_url, "") + + + def testNotEmptyAdminHostname(self): + result = soap.admin_soap_url("localhost") + self.assertEqual("https://localhost/service/admin/soap", result) + + + def testNotEmptyAdminHostnamePort(self): + result = soap.admin_soap_url("localhost:8080") + self.assertEqual("https://localhost:8080/service/admin/soap", result) + + def testEmptyProxyHostname(self): self.assertRaises(SoapException, soap.proxy_url, "") diff --git a/test/util.py b/test/util.py index 0c9aa6a..8d59526 100644 --- a/test/util.py +++ b/test/util.py @@ -27,7 +27,7 @@ @author: ilgar """ from ConfigParser import ConfigParser -from pyzimbra import pconstant +import pconstant def load_test_properties(test): @@ -35,6 +35,10 @@ def load_test_properties(test): if len(cfg.read("test.properties")) == 0: cfg.read("../test.properties") + test.admin_hostname = cfg.get(pconstant.ADMIN, pconstant.HOST) + test.admin_account_name = cfg.get(pconstant.ADMIN, pconstant.USERNAME) + test.admin_password = cfg.get(pconstant.ADMIN, pconstant.PASSWORD) + test.domain = cfg.get(pconstant.DOMAIN, pconstant.NAME) test.hostname = cfg.get(pconstant.DOMAIN, pconstant.HOST) test.domain_key = cfg.get(pconstant.DOMAIN, pconstant.KEY)