def getDlData(name, url, token): comm = Communication(url) info_request = comm.gen_request(token=token) info_request.add_request('GetDistributionListRequest', {'dl': { '_content': name, 'by': 'name' }}, 'urn:zimbraAdmin') info_response = comm.send_request(info_request) data = {} data['members'] = [] if not info_response.is_fault(): zimbra_data = info_response.get_response( )['GetDistributionListResponse'] data['settings'] = dict.zimbra_to_python(zimbra_data['dl']['a'], key_attribute='n', content_attribute='_content') if 'dlm' in zimbra_data['dl']: for i in zimbra_data['dl']['dlm']: data['members'].append(i['_content']) data['id'] = zimbra_data['dl']['id'] data['name'] = zimbra_data['dl']['name'] return data else: print(info_response.get_fault_message()) sys.exit(1)
def test_fault_non_existing_folder_genrequest_xml(self): """ Request a non existing folder, so we get a fitting fault """ config = get_config() if config.getboolean('fault_test', 'enabled'): comm = Communication(config.get('fault_test', 'url')) token = auth.authenticate(config.get('fault_test', 'url'), config.get('fault_test', 'account'), config.get('fault_test', 'preauthkey'), config.get('fault_test', 'account_by')) request = comm.gen_request(request_type="xml", token=token) request.add_request( "GetFolderRequest", { "folder": { "path": config.get('fault_test', 'folder') } }, "urn:zimbraMail" ) response = comm.send_request(request) self.check_response( response )
def test_genrequest_default(self): """ Create a request only using the Communication-object """ config = get_config() if config.getboolean("genrequest_test", "enabled"): # Run only if enabled comm = Communication(config.get("genrequest_test", "url")) token = authenticate(config.get("genrequest_test", "url"), config.get("genrequest_test", "account"), config.get("genrequest_test", "preauthkey")) self.assertNotEqual(token, None, "Cannot authenticate.") request = comm.gen_request(token=token) request.add_request("NoOpRequest", {}, "urn:zimbraMail") response = comm.send_request(request) if response.is_fault(): self.fail( "Reponse failed: (%s) %s" % (response.get_fault_code(), response.get_fault_message()))
def test_fault_non_existing_folder_genrequest_invalidurl(self): """ Request a non existing folder, so we get a fitting fault """ config = get_config() if config.getboolean('fault_test', 'enabled'): comm = Communication(config.get('fault_test', 'url') + "1234") token = auth.authenticate(config.get('fault_test', 'url'), config.get('fault_test', 'account'), config.get('fault_test', 'preauthkey'), config.get('fault_test', 'account_by')) request = comm.gen_request(token=token) request.add_request( "GetFolderRequest", { "folder": { "path": config.get('fault_test', 'folder') } }, "urn:zimbraMail" ) # A 404 error should by raised as an exception. self.assertRaises( Exception, comm.send_request, request )
def run_request(self, creds, req, req_args={}): token = self.get_token( creds['url'], creds['user'], creds['pwd'], creds['admin_auth']) comm = Communication(creds['url']) zmreq = comm.gen_request(token=token, request_type="xml") zmreq.add_request(req, req_args, creds['urn']) resp = comm.send_request(zmreq) if resp.is_fault(): print(resp.get_fault_code()) ret = resp.get_response() return json.loads(ret['response']['content'])
def test_genrequest_fail(self): """ Create a request only using the Communication-object """ config = get_config() if config.getboolean("genrequest_test", "enabled"): # Run only if enabled comm = Communication(config.get("genrequest_test", "url")) token = authenticate( config.get("genrequest_test", "url"), config.get("genrequest_test", "account"), config.get("genrequest_test", "preauthkey") ) self.assertNotEqual( token, None, "Cannot authenticate." ) self.assertRaises( UnknownRequestType, comm.gen_request, request_type="INVALID", token=token ) request = comm.gen_request(token=token) request.add_request( "NoOpRequest", { }, "urn:zimbraMail" ) # Deliberately break the request request.request_type = "INVALID" self.assertRaises( UnknownRequestType, comm.send_request, request )
def test_genrequest_xml(self): """ Create a request only using the Communication-object """ config = get_config() if config.getboolean("genrequest_test", "enabled"): # Run only if enabled comm = Communication(config.get("genrequest_test", "url")) token = authenticate( config.get("genrequest_test", "url"), config.get("genrequest_test", "account"), config.get("genrequest_test", "preauthkey") ) self.assertNotEqual( token, None, "Cannot authenticate." ) request = comm.gen_request(request_type="xml", token=token) request.add_request( "NoOpRequest", { }, "urn:zimbraMail" ) response = comm.send_request(request) if response.is_fault(): self.fail( "Reponse failed: (%s) %s" % ( response.get_fault_code(), response.get_fault_message() ) ) self.assertEqual( response.response_type, "xml", "Invalid response type %s" % response.response_type )
def test_genrequest_check_response_xml(self): """ Create a request only using the Communication-object, send it and check the response """ config = get_config() if config.getboolean("genrequest_test", "enabled"): # Run only if enabled comm = Communication(config.get("genrequest_test", "url")) token = authenticate( config.get("genrequest_test", "url"), config.get("genrequest_test", "account"), config.get("genrequest_test", "preauthkey") ) self.assertNotEqual( token, None, "Cannot authenticate." ) request = comm.gen_request(request_type="xml", token=token) request.add_request( "GetInfoRequest", { }, "urn:zimbraAccount" ) response = comm.send_request(request) if response.is_fault(): self.fail( "Reponse failed: (%s) %s" % ( response.get_fault_code(), response.get_fault_message() ) ) self.assertEqual( response.get_response()["GetInfoResponse"]["name"], config.get("genrequest_test", "account"), "Request returned unexpected response" )
def delMembers(dlId, member, url, token): if options.verbose: print('deleting member: ', member) comm = Communication(url) info_request = comm.gen_request(token=token) info_request.add_request('RemoveDistributionListMemberRequest', { 'id': dlId, 'dlm': { '_content': member } }, 'urn:zimbraAdmin') info_response = comm.send_request(info_request) if not info_response.is_fault(): zimbra_data = info_response.get_response( )['RemoveDistributionListMemberResponse'] else: print(info_response.get_fault_message()) sys.exit(1)
class GetZimbra(): def __init__(self, hostname, user, password, domain, pathzm, atributo, add_domain, quota, path_archive): self.url = f'https://{hostname}:7071/service/admin/soap' self.usr = user self.__password = password self.domain = domain.split(',') self.__atributo = atributo self.quota = quota self.__add_domain = add_domain self.__path_archive = path_archive self.today = datetime.datetime.now().date() self.colum = ['name'] def connect(self): try: self.comm = Communication(self.url) self.__token = auth.authenticate(self.url, self.usr, self.__password, admin_auth=True) self.request = self.comm.gen_request(token=self.__token, set_batch=True) return 'Conectado' except urllib.error.URLError as E: # catastrophic error. bail. return f'Erro na conexão: {E}' def getAllDomains(self): """Retornar todos os dominios do ambiente""" self.request.add_request( 'GetAllDomainsRequest', { }, 'urn:zimbraAdmin' ) response = self.comm.send_request(self.request) if not response.is_fault(): alldomains = response.get_response(1)['GetAllDomainsResponse']['domain'] return [{k: v for k, v in cos.items() if k == 'name'} for cos in alldomains] # Retornar somente name do dominio else: return f'Error CountAccount: {response.get_fault_message()}' def countAccount(self): """Limite de COS por dominio""" for dom in self.domain: self.request.add_request( 'CountAccountRequest', { "domain": { "_content": dom, "by": "name" } }, 'urn:zimbraAdmin' ) response = self.comm.send_request(self.request) if not response.is_fault(): allcount = response.get_response(1)['CountAccountResponse'] l_domains = [] d = 0 if len(self.domain) > 1: for a in allcount: cos = {} cos.update({'name': self.domain[d]}) d =+ 1 for v in a['cos']: cos.update({v['name']: v['_content']}) if v['name'] not in self.colum: self.colum.append(v['name']) l_domains.append(cos) else: cos = {} cos.update({'name': self.domain[d]}) for a in allcount['cos']: cos.update({a['name']: a['_content']}) if a['name'] not in self.colum: self.colum.append(a['name']) l_domains.append(cos) return l_domains else: return f'Error CountAccount: {response.get_fault_message()}' def getAllAccounts(self): """Consulta todas as contas por dominio""" if self.__add_domain: if 'name' in self.__atributo: self.__atributo.insert(1, 'domain') else: return "Só é possivel adicionar o dominio se adicionar o atributo 'name'" self.request.add_request( "GetAllAccountsRequest", { "domain": { "_content": self.domain[0], "by": "name" } }, "urn:zimbraAdmin" ) self.request.add_request( "GetAllCosRequest", { }, "urn:zimbraAdmin" ) if self.quota: self.request.add_request( "GetQuotaUsageRequest", { "domain": self.domain[0], "allServers": "1" }, "urn:zimbraAdmin" ) response = self.comm.send_request(self.request) if not response.is_fault(): allAccounts = [response.get_response(1)['GetAllAccountsResponse']['account'],response.get_response(2)['GetAllCosResponse']['cos']] if self.quota: allAccounts.append(response.get_response(3)['GetQuotaUsageResponse']['account']) return allAccounts else: return f'Error getAllAccounts: {response.get_fault_message()}' def getFormatted(self, func=None): quota = '' lista_dict = [] for account in func[0]: a_atr = {} a_atr['name'] = account['name'] for atr in account['a']: if atr['n'] in self.__atributo: a_atr[atr['n']] = atr['_content'] lista_dict.append(a_atr) if 'zimbraCOSId' in self.__atributo: allcos = [{k: v for k, v in cos.items() if k == 'name' or k == 'id'} for cos in func[1]] if self.quota: quota = func[2] for account in quota: account['used'] = f"{int(account['used'] / 1024 / 1024)} MB" account['limit'] = f"{int(account['limit'] / 1024 / 1024)} MB" [self.__atributo.append(i) for i in ['used', 'limit']] with open(f'{self.__path_archive}Relatório_{self.domain[0]}.csv', 'w') as archive: data_csv = DictWriter(archive, fieldnames=self.__atributo) data_csv.writeheader() for account in lista_dict: dict_comprehension = {chave: valor for chave, valor in account.items()} if self.__add_domain: dict_comprehension['domain'] = dict_comprehension['name'].split('@')[1] if self.quota: # Configurar quota com as contas for i in quota: if i['name'] == account['name']: dict_comprehension['used'] = i['used'] dict_comprehension['limit'] = i['limit'] if 'zimbraLastLogonTimestamp' in dict_comprehension.keys(): # Configurar data do ultimo login data = dict_comprehension['zimbraLastLogonTimestamp'] date_br = datetime.datetime( int(data[:4]), int(data[4:6]), int(data[6:8]), int(data[8:10]), int(data[10:12]), int(data[12:14]) ).strftime('%d/%m/%Y %H:%M') dict_comprehension['zimbraLastLogonTimestamp'] = date_br if 'zimbraCreateTimestamp' in dict_comprehension.keys(): # Configurar data de criação data1 = dict_comprehension['zimbraCreateTimestamp'] date1_br = datetime.datetime( int(data1[:4]), int(data1[4:6]), int(data1[6:8]), int(data1[8:10]), int(data1[10:12]), int(data1[12:14]) ).strftime('%d/%m/%Y %H:%M') dict_comprehension['zimbraCreateTimestamp'] = date1_br if 'zimbraCOSId' in dict_comprehension.keys(): # Configurar nome do COS cosid = dict_comprehension['zimbraCOSId'] cosname = [cos['name'] for cos in allcos if cosid == cos['id']] dict_comprehension['zimbraCOSId'] = cosname[0] data_csv.writerow(dict_comprehension) return f'Planilha criada com SUCESSO! \nRelatório_{self.today}' def formatedPlan(self, func): list_domains = func with open(f'{self.__path_archive}Relatório_{self.today}.csv', 'w') as archive: data_csv = DictWriter(archive, fieldnames=self.colum) data_csv.writeheader() for domain_count in list_domains: list_domains = {chave: valor for chave, valor in domain_count.items()} data_csv.writerow(list_domains) return f'Planilha criada com SUCESSO! \nRelatório_{self.today}'
class ZimbraRequest(object): def __init__(self, admin_url, admin_user, admin_pass): self.admin_url = admin_url self.admin_user = admin_user self.admin_pass = admin_pass self.admin_account_by = 'name' self.request = None self.token = authenticate( self.admin_url, self.admin_user, self.admin_pass, self.admin_account_by, admin_auth=True, request_type="json" ) self.comm = Communication(self.admin_url) def getAllAdminAccounts(self, domain_name): self.cleanUp() self.request.add_request( request_name = 'GetAllAccountsRequest', request_dict = { "domain": { "_content" : domain_name, "by": "name", }, "a" : { "n" : "zimbraIsAdminAccount", "_content" : "TRUE" } }, namespace = 'urn:zimbraAdmin' ) response = self.comm.send_request(self.request) if response.is_fault(): raise ZimbraRequestError( message = 'Error getting all admin accounts. Error: %s' % response.get_fault_message(), response = response ) return response.get_response() def createDomain(self, domain_name, attrs=list()): """ Create a new domain :param domain_name: The name of the domain :param attrs: List of tuple attributes of domain (zmprov desc domain) """ if not type(attrs) == list: raise TypeError('attrs must be a list') self.cleanUp() request_attrs = [] for attr in attrs: zattr, value = attr request_attrs.append({ 'n' : zattr, '_content' : value }) request_dict = { 'name' : domain_name } if request_attrs: request_dict['a'] = request_attrs self.request.add_request( request_name = 'CreateDomainRequest', request_dict = request_dict, namespace = 'urn:zimbraAdmin' ) response = self.comm.send_request(self.request) if response.is_fault(): raise ZimbraRequestError( message = 'Error creating domain %s Error: %s' % (domain_name, response.get_fault_message()), response = response ) return response.get_response() def deleteDomain(self, domain_id): """ Delete a domain :param domain_id: The zimbraId of the domain """ self.cleanUp() self.request.add_request( request_name = 'DeleteDomainRequest', request_dict = { 'id' : domain_id }, namespace = 'urn:zimbraAdmin' ) response = self.comm.send_request(self.request) if response.is_fault(): raise ZimbraRequestError( message = 'Error deleting domain %s Error: %s' % (domain_id, response.get_fault_message()), response = response ) return response.get_response() def getDomain(self, domain, attrs): """ TODO - Implement list of attributes to get. - Raise exception when an error occours. """ """ Returns the attributes requested in a json format. "GetDomainResponse": { "domain": { "a": [ { "_content": "externalLdapAutoComplete", "n": "zimbraGalAutoCompleteLdapFilter" }, { "_content": "FALSE", "n": "zimbraAdminConsoleDNSCheckEnabled" }, . . . https://files.zimbra.com/docs/soap_api/8.6.0/api-reference/zimbraAdmin/GetDomain.html """ if not type(attrs) == list: raise TypeError('attrs must be a list') self.cleanUp() attrs = ','.join(attrs) self.request.add_request( request_name = "GetDomainRequest", request_dict = { "attrs" : attrs, "domain": { "_content" : domain, "by": "name", }, }, namespace = "urn:zimbraAdmin" ) response = self.comm.send_request(self.request) if response.is_fault(): raise ZimbraRequestError( message = 'Error getting domain %s Error: %s' % (domain, response.get_fault_message()), response = response ) return response.get_response() def modifyDomain(self, domain_id, attrs): """ Modify an attribute of a domain This method is idempotent, it will not change the result executing multiple times :param domain_id: The zimbraID of the domain :param attrs: A list of tuple containing the zimbra attribute with the corresponding value: [(zattr, value), ...] """ self.cleanUp() request_attrs = [] for attr in attrs: zattr, value = attr request_attrs.append({ 'n' : zattr, '_content' : value }) self.request.add_request( request_name = "ModifyDomainRequest", request_dict = { "id": { "_content": domain_id, }, "a": request_attrs }, namespace = "urn:zimbraAdmin" ) response = self.comm.send_request(self.request) if response.is_fault(): raise ZimbraRequestError( message = 'Error modifying domain %s Error: %s' % (domain_id, response.get_fault_message()), response = response ) return response.get_response() def getAccount(self, account, attrs): """ TODO - Implement a list of attributes to get. - Implement raise exception. """ """ Returns a json containing all attributes of an account or an exception: "GetAccountResponse": { "account": { "a": [ { "_content": "FALSE", "n": "zimbraPrefCalendarReminderMobile" }, { "_content": "TRUE", "n": "zimbraPrefIMLogChats" }, . . . https://files.zimbra.com/docs/soap_api/8.6.0/api-reference/zimbraAdmin/GetAccount.html """ if not type(attrs) == list: raise TypeError('attrs must be a list') attrs = ','.join(attrs) self.cleanUp() self.request.add_request( "GetAccountRequest", { "attrs" : attrs, "account": { "_content": account, "by": "name", }, }, "urn:zimbraAdmin" ) response = self.comm.send_request(self.request) if response.is_fault(): raise ZimbraRequestError("Reponse failed: (%s) %s" % (response.get_fault_code(), response.get_fault_message())) return(response.get_response()) def createAccount(self, account, password=None, attrs=list()): """ Create a new account into Zimbra system :param account: The target account :param password: The given for the account :param attrs: A list of tuple containing the zimbra attribute with the corresponding value: [(zattr, value), ...] """ request_attrs = [] for attr in attrs: zattr, value = attr request_attrs.append({ 'n' : zattr, '_content' : value }) self.cleanUp() if not password: password = hmac.new(str(uuid.uuid4), str(uuid.uuid4()), hashlib.md5).hexdigest() self.request.add_request( request_name = "CreateAccountRequest", request_dict = { "name" : account, "password" : password, "a" : request_attrs }, namespace = "urn:zimbraAdmin" ) response = self.comm.send_request(self.request) if response.is_fault(): raise ZimbraRequestError( message = 'Error creating account %s. Error: %s' % (account, response.get_fault_message()), response = response ) return response.get_response() def cleanUp(self): """ Clean up after one step to leave a dedicated result for the other test cases. """ self.setUp() def setUp(self): """ Setup everything required to make a request to zimbra server. """ self.request = RequestJson() self.request = self.comm.gen_request(token=self.token) def getDomainId(self, domain): """ Returns the zimbraId of a domain. Useful to modify a domain with ModifyDomainRequest for instance. domain_id = self.getDomainId(inova.net) equal: domain_id = '4af850c7-7e44-452e-ad25-c70fda58f9bf' """ self.cleanUp() self.request.add_request( "GetDomainInfoRequest", { "domain": { "_content": domain, "by": "name", }, }, "urn:zimbraAdmin" ) response = self.comm.send_request(self.request) if response.is_fault(): raise ZimbraRequestError("Reponse failed: (%s) %s" % (response.get_fault_code(), response.get_fault_message())) return response.get_response()['GetDomainInfoResponse']['domain']['id'] def getDomainQuotaUsage(self,domain): """ Returns quota usage of all users of a specific domain { "GetQuotaUsageResponse": { "searchTotal": 1294, "account": [ { "used": 0, "limit": 0, "name": "*****@*****.**", "id": "63b128d6-b7f2-466d-ac86-7b253e62a7ed" }, { "used": 28, "limit": 26843545600, "name": "*****@*****.**", "id": "5b4832d1-b642-4778-ab7d-3056ebcefada" }, . . . https://files.zimbra.com/docs/soap_api/8.6.0/api-reference/zimbraAdmin/GetQuotaUsage.html """ self.cleanUp() self.request.add_request( "GetQuotaUsageRequest", { "domain": domain, "allServers": "1", "sortBy": "percentUsed", "sortAscending": "1", }, "urn:zimbraAdmin" ) response = self.comm.send_request(self.request) if response.is_fault(): raise ZimbraRequestError("Reponse failed: (%s) %s" % (response.get_fault_code(), response.get_fault_message())) return(response.get_response()) def getCos(self, cos_name): """ Get COS by it's name :param cos_name: The name of the COS """ self.cleanUp() self.request.add_request( request_name = 'GetCosRequest', request_dict = { 'cos' : { 'by' : 'name', '_content' : cos_name } }, namespace = 'urn:zimbraAdmin' ) response = self.comm.send_request(self.request) if response.is_fault(): raise ZimbraRequestError( message = 'Error getting COS %s. Error: %s' % (cos_name, response.get_fault_message()), response = response ) return response.get_response() def createCos(self, cos_name, features=dict()): """ Create a new cos. :param cos_name: The name of the COS :param features: A dict representing the feature->value """ if type(features) is not dict: raise TypeError('Wrong type found for features, must be a dict.') features_req = [] for feature, value in features.items(): features_req.append({ 'n' : feature , '_content' : value }) self.cleanUp() self.request.add_request( request_name = 'CreateCosRequest', request_dict = { 'name' : { '_content' : cos_name }, 'a' : features_req }, namespace = 'urn:zimbraAdmin' ) response = self.comm.send_request(self.request) if response.is_fault(): raise ZimbraRequestError( message = 'Error creating COS %s Error: %s' % (cos_name, response.get_fault_message()), response = response ) return response.get_response() def modifyCos(self, zimbra_cos_id, features): """ Update a cos. :param zimbra_cos_id: The zimbraID of the COS :param features: A dict representing the feature->value """ if type(features) is not dict: raise TypeError('Wrong type found for features, must be a dict') features_req = [] for feature, value in features.items(): features_req.append({ 'n' : feature, '_content' : value }) self.cleanUp() self.request.add_request( request_name = 'ModifyCosRequest', request_dict = { 'id' : { '_content' : zimbra_cos_id }, 'a' : features_req }, namespace = 'urn:zimbraAdmin' ) response = self.comm.send_request(self.request) if response.is_fault(): raise ZimbraRequestError( message = 'Error creating COS %s Error: %s' % (cos_name, response.get_fault_message()), response = response ) return response.get_response() def deleteCos(self, zimbra_cos_id): """ Delete a specific COS :param zimbra_cos_id: The zimbraID of the COS """ self.cleanUp() self.request.add_request( request_name = 'DeleteCosRequest', request_dict = { 'id' : { '_content' : zimbra_cos_id } }, namespace = 'urn:zimbraAdmin' ) response = self.comm.send_request(self.request) if response.is_fault(): raise ZimbraRequestError( message = 'Error creating COS %s Error: %s' % (cos_name, response.get_fault_message()), response = response ) return response.get_response() def getComputeAggregateQuotaUsage(self): """ This method get all quota usage of all domains in a zimbra system. This may take a while depending how many domains and servers you have. Use wisely :P """ self.cleanUp() self.request.add_request( "ComputeAggregateQuotaUsageRequest", { }, "urn:zimbraAdmin" ) response = self.comm.send_request(self.request) if response.is_fault(): raise ZimbraRequestError("Reponse failed: (%s) %s" % (response.get_fault_code(), response.get_fault_message())) return(response.get_response()) def createDistributionList(self, dlist, attrs=list()): """ This method create zimbra distribution list. A list of attributes may be given, we will handle it for you. :param dlist: The target distribution list :param attrs: List of tuple attributes of distribution list """ if not type(attrs) == list: raise TypeError('attrs must be a list') request_attrs = [] for attr in attrs: zattr, value = attr # If it's a list, then it's a multi-value attribute if type(value) == list: for multi_attr in value: request_attrs.append({ 'n' : zattr, '_content' : multi_attr }) else: request_attrs.append({ 'n' : zattr, '_content' : value }) self.cleanUp() self.request.add_request( request_name = "CreateDistributionListRequest", request_dict = { "name": dlist, "a": request_attrs }, namespace = "urn:zimbraAdmin" ) response = self.comm.send_request(self.request) if response.is_fault(): raise ZimbraRequestError( message = 'Error creating DL %s Error: %s' % (dlist, response.get_fault_message()), response = response ) return response.get_response() def getDistributionList(self, dlist): """ Gets information about a distribution list. :param dlist: The target distribution list Obs: Tested with "a" attribute does not have effect on result """ self.cleanUp() self.request.add_request( request_name = "GetDistributionListRequest", request_dict = { "dl": { "_content": dlist, "by": "name", }, }, namespace = "urn:zimbraAdmin" ) response = self.comm.send_request(self.request) if response.is_fault(): raise ZimbraRequestError( message = 'Error getting DL: %s Error: %s' % (dlist, response.get_fault_message()), response = response ) return response.get_response() def deleteDistributionList(self, dlist_zimbra_id): """ Deletes distribution list :param dlist_zimbra_id: Distribution List zimbraID """ self.cleanUp() self.request.add_request( request_name = "DeleteDistributionListRequest", request_dict = { "id" : dlist_zimbra_id }, namespace = "urn:zimbraAdmin" ) response = self.comm.send_request(self.request) if response.is_fault(): raise ZimbraRequestError( message = 'Error deleting DL: %s Error: %s' % (dlist, response.get_fault_message()), response = response ) return response.get_response() def addDistributionListMember(self, dlist_zimbra_id, members): """ This method adds members to a zimbra distribution list. A list of members must be sent. This method is idempotent, it will not change the result executing multiple times :param dlist_zimbra_id: The target distribution list zimbraId :param members: List containing the account members """ if not type(members) == list: raise TypeError('members must be a list') zmembers = [] for member in members: zmembers.append({'_content': member}) self.cleanUp() self.request.add_request( request_name = "AddDistributionListMemberRequest", request_dict = { "id": dlist_zimbra_id, "dlm": zmembers }, namespace = "urn:zimbraAdmin" ) response = self.comm.send_request(self.request) if response.is_fault(): raise ZimbraRequestError( message = 'Error adding members to dlist %s Error: %s' % (dlist_zimbra_id, response.get_fault_message()), response = response ) return response.get_response() def grantRight(self, target_name, target_type, grantee_name, grantee_type, right, deny=0): """ Grant a right on a target to an individual or group grantee. This method is idempotent, it will not change the result executing multiple times :param target_name: The target for applying the right (by name). E.g.: 'inova.net'. External docs: /target :param target_type: The type of the target. E.g.: 'domain'. External docs: /target@type :param grantee_name: Grantee selector. E.g.: 'grp', 'dlist'. External docs: /grantee :param grantee_type: The type of the grantee. E.g.: 'grp', 'dlist'. External docs: /grantee@type :param right: The name of the right. E.g.: getDomainQuotaUsage, domainAdminConsoleRights. External docs: /right :param deny: Either to deny or grant the permission. Default is 0. External docs: /right@deny Ref. Docs: https://files.zimbra.com/docs/soap_api/8.6.0/api-reference/zimbraAdmin/GrantRight.html """ self.cleanUp() self.request.add_request( request_name = "GrantRightRequest", request_dict = { "target": { "type": target_type, "by": "name", "_content": target_name }, "grantee": { "type": grantee_type, "by": "name", "_content": grantee_name }, "right": { "_content": right, "deny": deny } }, namespace = "urn:zimbraAdmin" ) response = self.comm.send_request(self.request) if response.is_fault(): raise ZimbraRequestError( message = 'Error adding grant to target_name %s Error: %s' % (target_name, response.get_fault_message()), response = response ) return response.get_response()
comm = Communication(url) user_comm = Communication(user_url) token = auth.authenticate(url, admin_account, admin_password, admin_auth=True) if token is None: logging.error("Cannot login into zimbra with the supplied credentials.") exit(1) # Search for groupcal dictionaries search_request = comm.gen_request(token=token) search_request.add_request( "SearchDirectoryRequest", {"query": "uid=%s*" % options.prefix, "types": "distributionlists"}, "urn:zimbraAdmin" ) search_response = comm.send_request(search_request) if search_response.is_fault(): raise Exception( "Cannot fetch distribution lists: (%s) %s" % (search_response.get_fault_code(), search_response.get_fault_message()) ) lists = search_response.get_response()["SearchDirectoryResponse"]["dl"]
class ZimbraRequest(object): def __init__(self, admin_url, admin_user, admin_pass): self.admin_url = admin_url self.admin_user = admin_user self.admin_pass = admin_pass self.admin_account_by = 'name' self.request = None self.token = authenticate(self.admin_url, self.admin_user, self.admin_pass, self.admin_account_by, admin_auth=True, request_type="json") self.comm = Communication(self.admin_url) def getAllAdminAccounts(self, domain_name): self.cleanUp() self.request.add_request(request_name='GetAllAccountsRequest', request_dict={ "domain": { "_content": domain_name, "by": "name", }, "a": { "n": "zimbraIsAdminAccount", "_content": "TRUE" } }, namespace='urn:zimbraAdmin') response = self.comm.send_request(self.request) if response.is_fault(): raise ZimbraRequestError( message='Error getting all admin accounts. Error: %s' % response.get_fault_message(), response=response) return response.get_response() def createDomain(self, domain_name, attrs=list()): """ Create a new domain :param domain_name: The name of the domain :param attrs: List of tuple attributes of domain (zmprov desc domain) """ if not type(attrs) == list: raise TypeError('attrs must be a list') self.cleanUp() request_attrs = [] for attr in attrs: zattr, value = attr request_attrs.append({'n': zattr, '_content': value}) request_dict = {'name': domain_name} if request_attrs: request_dict['a'] = request_attrs self.request.add_request(request_name='CreateDomainRequest', request_dict=request_dict, namespace='urn:zimbraAdmin') response = self.comm.send_request(self.request) if response.is_fault(): raise ZimbraRequestError( message='Error creating domain %s Error: %s' % (domain_name, response.get_fault_message()), response=response) return response.get_response() def deleteDomain(self, domain_id): """ Delete a domain :param domain_id: The zimbraId of the domain """ self.cleanUp() self.request.add_request(request_name='DeleteDomainRequest', request_dict={'id': domain_id}, namespace='urn:zimbraAdmin') response = self.comm.send_request(self.request) if response.is_fault(): raise ZimbraRequestError( message='Error deleting domain %s Error: %s' % (domain_id, response.get_fault_message()), response=response) return response.get_response() def getDomain(self, domain, attrs): """ TODO - Implement list of attributes to get. - Raise exception when an error occours. """ """ Returns the attributes requested in a json format. "GetDomainResponse": { "domain": { "a": [ { "_content": "externalLdapAutoComplete", "n": "zimbraGalAutoCompleteLdapFilter" }, { "_content": "FALSE", "n": "zimbraAdminConsoleDNSCheckEnabled" }, . . . https://files.zimbra.com/docs/soap_api/8.6.0/api-reference/zimbraAdmin/GetDomain.html """ if not type(attrs) == list: raise TypeError('attrs must be a list') self.cleanUp() attrs = ','.join(attrs) self.request.add_request(request_name="GetDomainRequest", request_dict={ "attrs": attrs, "domain": { "_content": domain, "by": "name", }, }, namespace="urn:zimbraAdmin") response = self.comm.send_request(self.request) if response.is_fault(): raise ZimbraRequestError( message='Error getting domain %s Error: %s' % (domain, response.get_fault_message()), response=response) return response.get_response() def modifyDomain(self, domain_id, attrs): """ Modify an attribute of a domain This method is idempotent, it will not change the result executing multiple times :param domain_id: The zimbraID of the domain :param attrs: A list of tuple containing the zimbra attribute with the corresponding value: [(zattr, value), ...] """ self.cleanUp() request_attrs = [] for attr in attrs: zattr, value = attr request_attrs.append({'n': zattr, '_content': value}) self.request.add_request(request_name="ModifyDomainRequest", request_dict={ "id": { "_content": domain_id, }, "a": request_attrs }, namespace="urn:zimbraAdmin") response = self.comm.send_request(self.request) if response.is_fault(): raise ZimbraRequestError( message='Error modifying domain %s Error: %s' % (domain_id, response.get_fault_message()), response=response) return response.get_response() def getAccount(self, account, attrs): """ TODO - Implement a list of attributes to get. - Implement raise exception. """ """ Returns a json containing all attributes of an account or an exception: "GetAccountResponse": { "account": { "a": [ { "_content": "FALSE", "n": "zimbraPrefCalendarReminderMobile" }, { "_content": "TRUE", "n": "zimbraPrefIMLogChats" }, . . . https://files.zimbra.com/docs/soap_api/8.6.0/api-reference/zimbraAdmin/GetAccount.html """ if not type(attrs) == list: raise TypeError('attrs must be a list') attrs = ','.join(attrs) self.cleanUp() self.request.add_request("GetAccountRequest", { "attrs": attrs, "account": { "_content": account, "by": "name", }, }, "urn:zimbraAdmin") response = self.comm.send_request(self.request) if response.is_fault(): raise ZimbraRequestError( "Reponse failed: (%s) %s" % (response.get_fault_code(), response.get_fault_message())) return (response.get_response()) def createAccount(self, account, password=None, attrs=list()): """ Create a new account into Zimbra system :param account: The target account :param password: The given for the account :param attrs: A list of tuple containing the zimbra attribute with the corresponding value: [(zattr, value), ...] """ request_attrs = [] for attr in attrs: zattr, value = attr request_attrs.append({'n': zattr, '_content': value}) self.cleanUp() if not password: password = hmac.new(str(uuid.uuid4), str(uuid.uuid4()), hashlib.md5).hexdigest() self.request.add_request(request_name="CreateAccountRequest", request_dict={ "name": account, "password": password, "a": request_attrs }, namespace="urn:zimbraAdmin") response = self.comm.send_request(self.request) if response.is_fault(): raise ZimbraRequestError( message='Error creating account %s. Error: %s' % (account, response.get_fault_message()), response=response) return response.get_response() def cleanUp(self): """ Clean up after one step to leave a dedicated result for the other test cases. """ self.setUp() def setUp(self): """ Setup everything required to make a request to zimbra server. """ self.request = RequestJson() self.request = self.comm.gen_request(token=self.token) def getDomainId(self, domain): """ Returns the zimbraId of a domain. Useful to modify a domain with ModifyDomainRequest for instance. domain_id = self.getDomainId(inova.net) equal: domain_id = '4af850c7-7e44-452e-ad25-c70fda58f9bf' """ self.cleanUp() self.request.add_request("GetDomainInfoRequest", { "domain": { "_content": domain, "by": "name", }, }, "urn:zimbraAdmin") response = self.comm.send_request(self.request) if response.is_fault(): raise ZimbraRequestError( "Reponse failed: (%s) %s" % (response.get_fault_code(), response.get_fault_message())) return response.get_response()['GetDomainInfoResponse']['domain']['id'] def getDomainQuotaUsage(self, domain): """ Returns quota usage of all users of a specific domain { "GetQuotaUsageResponse": { "searchTotal": 1294, "account": [ { "used": 0, "limit": 0, "name": "*****@*****.**", "id": "63b128d6-b7f2-466d-ac86-7b253e62a7ed" }, { "used": 28, "limit": 26843545600, "name": "*****@*****.**", "id": "5b4832d1-b642-4778-ab7d-3056ebcefada" }, . . . https://files.zimbra.com/docs/soap_api/8.6.0/api-reference/zimbraAdmin/GetQuotaUsage.html """ self.cleanUp() self.request.add_request( "GetQuotaUsageRequest", { "domain": domain, "allServers": "1", "sortBy": "percentUsed", "sortAscending": "1", }, "urn:zimbraAdmin") response = self.comm.send_request(self.request) if response.is_fault(): raise ZimbraRequestError( "Reponse failed: (%s) %s" % (response.get_fault_code(), response.get_fault_message())) return (response.get_response()) def getCos(self, cos_name): """ Get COS by it's name :param cos_name: The name of the COS """ self.cleanUp() self.request.add_request( request_name='GetCosRequest', request_dict={'cos': { 'by': 'name', '_content': cos_name }}, namespace='urn:zimbraAdmin') response = self.comm.send_request(self.request) if response.is_fault(): raise ZimbraRequestError( message='Error getting COS %s. Error: %s' % (cos_name, response.get_fault_message()), response=response) return response.get_response() def createCos(self, cos_name, features=dict()): """ Create a new cos. :param cos_name: The name of the COS :param features: A dict representing the feature->value """ if type(features) is not dict: raise TypeError('Wrong type found for features, must be a dict.') features_req = [] for feature, value in features.items(): features_req.append({'n': feature, '_content': value}) self.cleanUp() self.request.add_request(request_name='CreateCosRequest', request_dict={ 'name': { '_content': cos_name }, 'a': features_req }, namespace='urn:zimbraAdmin') response = self.comm.send_request(self.request) if response.is_fault(): raise ZimbraRequestError( message='Error creating COS %s Error: %s' % (cos_name, response.get_fault_message()), response=response) return response.get_response() def modifyCos(self, zimbra_cos_id, features): """ Update a cos. :param zimbra_cos_id: The zimbraID of the COS :param features: A dict representing the feature->value """ if type(features) is not dict: raise TypeError('Wrong type found for features, must be a dict') features_req = [] for feature, value in features.items(): features_req.append({'n': feature, '_content': value}) self.cleanUp() self.request.add_request(request_name='ModifyCosRequest', request_dict={ 'id': { '_content': zimbra_cos_id }, 'a': features_req }, namespace='urn:zimbraAdmin') response = self.comm.send_request(self.request) if response.is_fault(): raise ZimbraRequestError( message='Error creating COS %s Error: %s' % (cos_name, response.get_fault_message()), response=response) return response.get_response() def deleteCos(self, zimbra_cos_id): """ Delete a specific COS :param zimbra_cos_id: The zimbraID of the COS """ self.cleanUp() self.request.add_request( request_name='DeleteCosRequest', request_dict={'id': { '_content': zimbra_cos_id }}, namespace='urn:zimbraAdmin') response = self.comm.send_request(self.request) if response.is_fault(): raise ZimbraRequestError( message='Error creating COS %s Error: %s' % (cos_name, response.get_fault_message()), response=response) return response.get_response() def getComputeAggregateQuotaUsage(self): """ This method get all quota usage of all domains in a zimbra system. This may take a while depending how many domains and servers you have. Use wisely :P """ self.cleanUp() self.request.add_request("ComputeAggregateQuotaUsageRequest", {}, "urn:zimbraAdmin") response = self.comm.send_request(self.request) if response.is_fault(): raise ZimbraRequestError( "Reponse failed: (%s) %s" % (response.get_fault_code(), response.get_fault_message())) return (response.get_response()) def createDistributionList(self, dlist, attrs=list()): """ This method create zimbra distribution list. A list of attributes may be given, we will handle it for you. :param dlist: The target distribution list :param attrs: List of tuple attributes of distribution list """ if not type(attrs) == list: raise TypeError('attrs must be a list') request_attrs = [] for attr in attrs: zattr, value = attr # If it's a list, then it's a multi-value attribute if type(value) == list: for multi_attr in value: request_attrs.append({'n': zattr, '_content': multi_attr}) else: request_attrs.append({'n': zattr, '_content': value}) self.cleanUp() self.request.add_request(request_name="CreateDistributionListRequest", request_dict={ "name": dlist, "a": request_attrs }, namespace="urn:zimbraAdmin") response = self.comm.send_request(self.request) if response.is_fault(): raise ZimbraRequestError(message='Error creating DL %s Error: %s' % (dlist, response.get_fault_message()), response=response) return response.get_response() def getDistributionList(self, dlist): """ Gets information about a distribution list. :param dlist: The target distribution list Obs: Tested with "a" attribute does not have effect on result """ self.cleanUp() self.request.add_request(request_name="GetDistributionListRequest", request_dict={ "dl": { "_content": dlist, "by": "name", }, }, namespace="urn:zimbraAdmin") response = self.comm.send_request(self.request) if response.is_fault(): raise ZimbraRequestError(message='Error getting DL: %s Error: %s' % (dlist, response.get_fault_message()), response=response) return response.get_response() def deleteDistributionList(self, dlist_zimbra_id): """ Deletes distribution list :param dlist_zimbra_id: Distribution List zimbraID """ self.cleanUp() self.request.add_request(request_name="DeleteDistributionListRequest", request_dict={"id": dlist_zimbra_id}, namespace="urn:zimbraAdmin") response = self.comm.send_request(self.request) if response.is_fault(): raise ZimbraRequestError( message='Error deleting DL: %s Error: %s' % (dlist, response.get_fault_message()), response=response) return response.get_response() def addDistributionListMember(self, dlist_zimbra_id, members): """ This method adds members to a zimbra distribution list. A list of members must be sent. This method is idempotent, it will not change the result executing multiple times :param dlist_zimbra_id: The target distribution list zimbraId :param members: List containing the account members """ if not type(members) == list: raise TypeError('members must be a list') zmembers = [] for member in members: zmembers.append({'_content': member}) self.cleanUp() self.request.add_request( request_name="AddDistributionListMemberRequest", request_dict={ "id": dlist_zimbra_id, "dlm": zmembers }, namespace="urn:zimbraAdmin") response = self.comm.send_request(self.request) if response.is_fault(): raise ZimbraRequestError( message='Error adding members to dlist %s Error: %s' % (dlist_zimbra_id, response.get_fault_message()), response=response) return response.get_response() def grantRight(self, target_name, target_type, grantee_name, grantee_type, right, deny=0): """ Grant a right on a target to an individual or group grantee. This method is idempotent, it will not change the result executing multiple times :param target_name: The target for applying the right (by name). E.g.: 'inova.net'. External docs: /target :param target_type: The type of the target. E.g.: 'domain'. External docs: /target@type :param grantee_name: Grantee selector. E.g.: 'grp', 'dlist'. External docs: /grantee :param grantee_type: The type of the grantee. E.g.: 'grp', 'dlist'. External docs: /grantee@type :param right: The name of the right. E.g.: getDomainQuotaUsage, domainAdminConsoleRights. External docs: /right :param deny: Either to deny or grant the permission. Default is 0. External docs: /right@deny Ref. Docs: https://files.zimbra.com/docs/soap_api/8.6.0/api-reference/zimbraAdmin/GrantRight.html """ self.cleanUp() self.request.add_request(request_name="GrantRightRequest", request_dict={ "target": { "type": target_type, "by": "name", "_content": target_name }, "grantee": { "type": grantee_type, "by": "name", "_content": grantee_name }, "right": { "_content": right, "deny": deny } }, namespace="urn:zimbraAdmin") response = self.comm.send_request(self.request) if response.is_fault(): raise ZimbraRequestError( message='Error adding grant to target_name %s Error: %s' % (target_name, response.get_fault_message()), response=response) return response.get_response()
def _get_devices(self, user): self.app.log.debug("Fetching devices of user %s" % user) (local_part, domain_part) = user.split("@") if domain_part not in self.preauth_cache: # No preauth key cached. Fetch one self.app.log.debug("Fetch preauth key for domain %s" % domain_part) comm = Communication(self.url) preauthkey_request = comm.gen_request(token=self.token) preauthkey_request.add_request( "GetDomainRequest", { "domain": { "by": "name", "_content": domain_part } }, "urn:zimbraAdmin" ) preauthkey_response = comm.send_request(preauthkey_request) if preauthkey_response.is_fault(): self.app.log.fatal( "Cannot fetch preauth key for domain %s" % domain_part, preauthkey_response.get_fault_code(), preauthkey_response.get_fault_message(), ) exit(1) preauth = get_value( preauthkey_response.get_response()["GetDomainResponse"][ "domain"]["a"], "zimbraPreAuthKey" ) if preauth is None: self.app.log.fatal( "Domain %s has no preauthkey. Please use zmprov gdpak " "<domain> first." % domain_part ) exit(1) self.preauth_cache[domain_part] = preauth else: preauth = self.preauth_cache[domain_part] user_token = auth.authenticate( self.user_url, user, preauth ) if user_token is None: self.app.log.fatal("Cannot login as user %s" % user) exit(1) user_comm = Communication(self.user_url) get_device_status_request = user_comm.gen_request(token=user_token) get_device_status_request.add_request( "GetDeviceStatusRequest", {}, "urn:zimbraSync" ) get_device_status_response = user_comm.send_request( get_device_status_request) if get_device_status_response.is_fault(): self.app.log.fatal( "Cannot fetch devices for user %s: (%s) %s" % ( user, get_device_status_response.get_fault_code(), get_device_status_response.get_fault_message() ) ) exit(1) devices = [] if "device" in get_device_status_response.get_response()[ "GetDeviceStatusResponse"]: devices = get_device_status_response.get_response()[ "GetDeviceStatusResponse"]["device"] if type(devices) == dict: devices = [devices] return devices
def _get_scope(self): """ Build up a list of users for the specified scope """ self.app.log.debug("Building up user list of scope definition") if self.app.pargs.scope == "": # Set the scope to the admin user's domain (local_part, domain_part) = self.app.pargs.user.split("@") scope_config = "DOMAIN=%s" % domain_part else: scope_config = self.app.pargs.scope (scope, scope_value) = scope_config.split("=") if scope not in ("DOMAIN", "LIST", "USER"): self.app.log.fatal("Scope not correctly configured. Please use " "DOMAIN, LIST or USER") exit(1) userlist = [] if scope == "DOMAIN": # Fetch all users in a domain self.app.log.debug( "Searching for accounts in domain %s" % scope_value ) comm = Communication(self.url) search_account_request = comm.gen_request(token=self.token) search_account_request.add_request( "SearchAccountsRequest", { "query": "", "domain": scope_value }, "urn:zimbraAdmin" ) search_account_response = comm.send_request(search_account_request) if search_account_response.is_fault(): self.app.log.fatal( "Cannot search for accounts in the specified domain %s: " "(%s) %s" % ( scope_value, search_account_response.get_fault_code(), search_account_response.get_fault_message() ) ) for account in search_account_response.get_response()[ "SearchAccountsResponse"]["account"]: userlist.append(account["name"]) elif scope == "LIST": # Fetch all users in a distribution list self.app.log.debug( "Searching for users in distribution list %s" % scope_value ) comm = Communication(self.url) get_distributionlist_request = comm.gen_request(token=self.token) get_distributionlist_request.add_request( "GetDistributionListRequest", { "dl": { "by": "name", "_content": scope_value } }, "urn:zimbraAdmin" ) get_distributionlist_response = comm.send_request( get_distributionlist_request ) if get_distributionlist_response.is_fault(): self.app.log.fatal( "Cannot search for accounts in the specified list %s: " "(%s) %s" % ( scope_value, get_distributionlist_response.get_fault_code(), get_distributionlist_response.get_fault_message() ) ) for member in get_distributionlist_response.get_response()[ "GetDistributionListResponse"]["dl"]["dlm"]: if type(member) == dict: userlist.append(member["_content"]) else: userlist.append(member) elif scope == "USER": # Just a single user userlist.append(scope_value) self.app.log.debug("Found these users:\n %s" % userlist) return userlist
def test_genrequest_batch_invalid_xml(self): """ Create a batchrequest only using the Communication-object, send it and request an invalid request id (xml) """ config = get_config() if config.getboolean("genrequest_test", "enabled"): # Run only if enabled comm = Communication(config.get("genrequest_test", "url")) token = authenticate( config.get("genrequest_test", "url"), config.get("genrequest_test", "account"), config.get("genrequest_test", "preauthkey") ) self.assertNotEqual( token, None, "Cannot authenticate." ) request = comm.gen_request( request_type="xml", token=token, set_batch=True ) self.assertEqual( type(request), RequestXml, "Generated request wasn't an json-request, which should be " "the default." ) request.add_request( "NoOpRequest", { }, "urn:zimbraMail" ) request.add_request( "NoOpRequest", { }, "urn:zimbraMail" ) response = comm.send_request(request) if response.is_fault(): self.fail( "Reponse failed: (%s) %s" % ( response.get_fault_code(), response.get_fault_message() ) ) self.assertIsNone( response.get_response(3), "Querying an invalid requestId didn't return None" )
class Zimbra: """ Classe utilitaire pour l'accès à l'API de Zimbra. """ def __init__( self , cfg ): """ Initialise l'accès à l'API en lisant les paramètres de connexion dans la configuration et en créant l'instance de communication. """ self.url_ = cfg.get( 'bss' , 'zimbra-url' , 'https://webmail.partage.renater.fr/service/soap' ) self.domain_ = cfg.get( 'bss' , 'domain' ) self.dkey_ = cfg.get( 'bss' , 'token' ) self.fake_it_ = cfg.has_flag( 'bss' , 'simulate' ) self.user_ = None self.token_ = None # On lit le timeout depuis la configuration, s'il est défini. Sinon, # on utilise 10s par défaut. timeout_cfg = cfg.get( 'bss' , 'zimbra-timeout' , '10' ) try: timeout_cfg = int( timeout_cfg ) except ValueError as e: Logging( 'zimbra' ).error( 'Paramètre zimbra-timeout invalide' ) raise FatalError( 'Erreur de configuration' , e ) from pythonzimbra.communication import Communication self.comm_ = Communication( self.url_ , timeout = timeout_cfg ) def terminate( self ): """ Termine la session actuelle, s'il y en a une, en envoyant une requête 'EndSession'. """ if self.user_ is None: return Logging( 'zimbra' ).debug( 'Déconnexion de {}'.format( self.user_ ) ) try: self.send_request_( 'Account' , 'EndSession' ) finally: self.user_ = None self.token_ = None def set_user( self , user_name ): """ Tente de se connecter à l'API Zimbra avec un utilisateur donné. Si le nom d'utilisateur n'inclut pas un domaine, il sera rajouté. :param str user_name: le nom d'utilisateur :raises ZimbraError: une erreur de communication s'est produite """ if '@' not in user_name: user_name = '{}@{}'.format( user_name , self.domain_ ) if self.user_ is not None and self.user_ == user_name: return self.terminate( ) # ...Yes dear, you can self-terminate. from pythonzimbra.tools import auth from pythonzimbra.exceptions.auth import AuthenticationFailed Logging( 'zimbra' ).debug( 'Connexion pour {}'.format( user_name ) ) try: ttok = auth.authenticate( self.url_ , user_name , self.dkey_ , raise_on_error = True ) except AuthenticationFailed as e: Logging( 'zimbra' ).error( 'Échec de la connexion: {}'.format( str( e ) ) ) raise ZimbraConnectionError( e ) #FIXME Logging( 'zimbra' ).debug( 'Connexion réussie; jeton: {}'.format( ttok ) ) assert ttok is not None self.user_ = user_name self.token_ = ttok def get_folder( self , path = '/' , uuid = None , recursive = True ): """ Lit les informations d'un dossier (au sens Zimbra du terme) et éventuellement de ses sous-dossiers. :param str path: le chemin absolu du dossier sur lequel on veut \ des informations; sera ignoré si un UUID est spécifié :param str uuid: l'UUID du dossier sur lequel on veut des informations :param bool recursive: les informations des sous-dossiers seront \ également lues si ce paramètre est True :raises ZimbraError: une erreur de communication ou de requête \ s'est produite """ Logging( 'zimbra' ).debug( 'Récupération{} du dossier {}'.format( ' récursive' if recursive else '' , path ) ) folder_spec = dict( ) if uuid is None: folder_spec[ 'path' ] = path else: folder_spec[ 'uuid' ] = uuid ls = self.send_request_( 'Mail' , 'GetFolder' , { 'folder' : folder_spec , 'depth' : -1 if recursive else 0 } ) return ls[ 'folder' ] if 'folder' in ls else None def extract_acl( self , folder_data ): """ Extrait les entrées d'ACL d'un dossier vers un dictionnaire. :param folder_data: les informations d'un dossier :return: un dictionnaire associant à chaque EPPN disposant de droits \ sur un dossier un sous-dictionnaire contenant les permissions \ et l'identifiant de l'entrée dans ses champs 'perm' et 'zid', \ respectivement """ if 'acl' not in folder_data or 'grant' not in folder_data[ 'acl' ]: return dict( ) rv = dict( ) for entry in folder_data[ 'acl' ][ 'grant' ]: if 'd' not in entry or 'perm' not in entry or 'zid' not in entry: continue rv[ entry[ 'd' ] ] = { 'zid' : entry[ 'zid' ] , 'perm' : entry[ 'perm' ] , } return rv def create_folder( self , name , parent_id , folder_type , color = None , url = None , flags = None , others = None ): """ Demande la création d'un dossier. Si le mode simulé est activé, l'appel ne sera pas effectué. :param str name: le nom du dossier à créer :param parent_id: l'identifiant du dossier parent :param str folder_type: le type de dossier à créer (conversation, \ message, contact, appointment, task, wiki, document) :param str color: la couleur, sous la forme d'un code RGB hexadécimal \ ('#RRGGBB') :param str url: l'URL de synchronisation, s'il y en a une :param str flags: les drapeaux Zimbra du dossier :param dict others: un dictionnaire contenant des valeurs \ supplémentaires à associer au dossier :raises ZimbraError: une erreur de communication ou de requête \ s'est produite """ Logging( 'zimbra' ).debug( 'Création{} du dossier {}, parent {}, type {}'.format( ' simulée' if self.fake_it_ else '' , name , parent_id , folder_type ) ) if self.fake_it_: return {} data = { 'name' : name , 'view' : folder_type , 'l' : parent_id , 'fie' : '1' , } if others is not None: data.update( others ) if 'acl' not in data: data[ 'acl' ] = {} if color is not None: data[ 'rgb' ] = color if url is not None: data[ 'url' ] = url if flags is not None: data[ 'f' ] = flags return self.send_request_( 'Mail' , 'CreateFolder' , { 'folder' : data } ) def mount( self , source_eppn , source_id , name , parent_id , folder_type , color = None , flags = None , others = None ): """ Demande le montage d'un dossier. Si le mode simulé est activé, l'appel ne sera pas effectué. :param source_eppn: l'EPPN de l'utilisateur depuis le compte duquel \ on veut monter un dossier :param source_id: l'identifiant Zimbra du dossier à monter :param str name: le nom du dossier à créer :param parent_id: l'identifiant du dossier parent :param str folder_type: le type de dossier à créer (conversation, \ message, contact, appointment, task, wiki, document) :param str color: la couleur, sous la forme d'un code RGB hexadécimal \ ('#RRGGBB') :param str flags: les drapeaux Zimbra du dossier :param dict others: un dictionnaire contenant des valeurs \ supplémentaires à associer au dossier :raises ZimbraError: une erreur de communication ou de requête \ s'est produite """ Logging( 'zimbra' ).debug( ( 'Montage{} du dossier #{} de {} dans le dossier {} ' + '(parent {}, type {})' ).format( ' simulé' if self.fake_it_ else '' , source_id , source_eppn , name , parent_id , folder_type ) ) if self.fake_it_: return {} data = { 'name' : name , 'view' : folder_type , 'l' : parent_id , 'owner' : source_eppn , 'rid' : source_id , } if others is not None: data.update( others ) if color is not None: data[ 'rgb' ] = color if flags is not None: data[ 'f' ] = flags return self.send_request_( 'Mail' , 'CreateMountpoint' , { 'link' : data } ) def move_folder( self , folder_id , to_id ): """ Demande le déplacement d'un dossier. Si le mode simulé est activé, l'appel ne sera pas effectué. :param folder_id: l'identifiant du dossier à déplacer :param to_id: l'identifiant du nouveau dossier parent :raises ZimbraError: une erreur de communication ou de requête \ s'est produite """ Logging( 'zimbra' ).debug( 'Déplacement{} du dossier #{} vers dossier #{}'.format( ' simulé' if self.fake_it_ else '' , folder_id , to_id ) ) if self.fake_it_: return {} return self.send_request_( 'Mail' , 'FolderAction' , { 'action' : { 'op' : 'move' , 'id' : folder_id , 'l' : to_id , } } ) def rename_folder( self , folder_id , name ): """ Demande à l'API de renommer un dossier. Si le mode simulé est activé, l'appel ne sera pas effectué. :param folder_id: l'identifiant du dossier à renommer :param str name: le nouveau nom du dossier :raises ZimbraError: une erreur de communication ou de requête \ s'est produite """ Logging( 'zimbra' ).debug( 'Renommage{} du dossier #{}; nouveau nom "{}"'.format( ' simulé' if self.fake_it_ else '' , folder_id , name ) ) if self.fake_it_: return {} return self.send_request_( 'Mail' , 'FolderAction' , { 'action' : { 'op' : 'rename' , 'id' : folder_id , 'name' : name , } } ) def delete_folder( self , folder_id ): """ Demande à l'API de supprimer un dossier. Si le mode simulé est activé, l'appel ne sera pas effectué. :param folder_id: l'identifiant du dossier à supprimer :raises ZimbraError: une erreur de communication ou de requête \ s'est produite """ Logging( 'zimbra' ).debug( 'Suppression{} du dossier #{}'.format( ' simulée' if self.fake_it_ else '' , folder_id ) ) if self.fake_it_: return {} return self.send_request_( 'Mail' , 'FolderAction' , { 'action' : { 'op' : 'delete' , 'id' : folder_id , } } ) def set_grant( self , folder_id , eppn , perm ): """ Demande à l'API de donner des droits sur un dossier à un utilisateur. :param folder_id: l'identifiant du dossier sur lequel des droits \ doivent être positionnés :param eppn: l'EPPN de l'utilisateur pour lequel des droits doivent \ être positionnés :param perm: les permissions à attribuer à l'utilisateur :raises ZimbraError: une erreur de communication ou de requête \ s'est produite """ Logging( 'zimbra' ).debug( 'Dossier #{}: mise en place des droits "{}" pour {}'.format( folder_id , perm , eppn ) ) if self.fake_it_: return {} return self.send_request_( 'Mail' , 'FolderAction' , { 'action' : { 'op' : 'grant' , 'id' : folder_id , 'grant' : { 'perm' : perm , 'gt' : 'usr' , 'd' : eppn , } , } , } ) def remove_grant( self , folder_id , zid ): """ Demande à l'API de supprimer des droits sur un dossier. :param folder_id: l'identifiant du dossier pour lequel les droits \ doivent être modifiés :param zid: l'identifiant de l'entrée d'ACL à supprimer :raises ZimbraError: une erreur de communication ou de requête \ s'est produite """ Logging( 'zimbra' ).debug( 'Dossier #{}: suppression de l\'entrée ACL {}'.format( folder_id , zid ) ) if self.fake_it_: return {} return self.send_request_( 'Mail' , 'FolderAction' , { 'action': { 'op': '!grant' , 'id': folder_id , 'zid' : zid } } ) def get_data_sources( self ): """ Récupère la liste des sources de données externes associées à un compte Zimbra. :return: le résultat de la requête :raises ZimbraError: une erreur de communication ou de requête \ s'est produite """ Logging( 'zimbra' ).debug( 'Liste des sources de données' ) return self.send_request_( 'Mail' , 'GetDataSources' , {} ) def modify_data_source( self , ds_type , ds_id , **kwargs ): """ Met à jour les paramètres d'une source de données Zimbra. Cette méthode est essentiellement un wrapper autour de la requête SOAP ZimbraMail::ModifyDataSource. Les paramètres supplémentaires seront passés tels quels à l'API Zimbra. :param ds_type: le type de la source de données à modifier :param ds_id: l'identifiant de la source de données à modifier :raises ZimbraError: une erreur de communication ou de requête \ s'est produite """ assert len( kwargs ) != 0 data = dict( kwargs ) data.update({ 'id' : ds_id }) Logging( 'zimbra' ).debug( 'Modification de source de données {}: {}'.format( ds_type , repr( data ) ) ) if self.fake_it_: return self.send_request_( 'Mail' , 'ModifyDataSource' , { ds_type : data } ) def send_request_( self , namespace , request , data = None ): """ Envoie une requête au serveur Zimbra :param str namespace: l'espace de nommage de l'appel, par exemple Mail :param str request: le nom de la requête, par exemple GetFolder :return: la réponse renvoyée par le serveur :raises ZimbraRequestError: une erreur de communication ou de requête \ s'est produite """ assert self.token_ is not None if data is None: data = dict() Logging( 'zimbra.request' ).debug( 'Requête {}.{}( {} )'.format( namespace , request , repr( data ) ) ) req = self.comm_.gen_request( token = self.token_ ) req.add_request( request + 'Request' , data , 'urn:zimbra' + namespace ) response = self.comm_.send_request( req ) if response.is_fault( ): raise ZimbraRequestError( namespace , request , response ) rv = response.get_response( ) return rv[ request + 'Response' ]
def test_genrequest_check_response_batch_xml(self): """ Create a batch-request only using the Communication-object """ config = get_config() if config.getboolean("genrequest_test", "enabled"): # Run only if enabled comm = Communication(config.get("genrequest_test", "url")) token = authenticate( config.get("genrequest_test", "url"), config.get("genrequest_test", "account"), config.get("genrequest_test", "preauthkey") ) self.assertNotEqual( token, None, "Cannot authenticate." ) request = comm.gen_request( request_type="xml", token=token, set_batch=True ) self.assertEqual( type(request), RequestXml, "Generated request wasn't an json-request, which should be " "the default." ) request.add_request( "NoOpRequest", { }, "urn:zimbraMail" ) request.add_request( "GetInfoRequest", { }, "urn:zimbraAccount" ) response = comm.send_request(request) if response.is_fault(): self.fail( "Reponse failed: (%s) %s" % ( response.get_fault_code(), response.get_fault_message() ) ) self.assertEqual( response.get_response(2)["GetInfoResponse"]["name"], config.get("genrequest_test", "account"), "Request returned unexpected response" )
server_url = "https://%s:7071/service/admin/soap" % server_name comm = Communication(server_url) token = auth.authenticate(server_url, admin_account, admin_password, admin_auth=True) users = [] if options.distlist: logging.debug("Retrieving distribution list members from list %s" % ( options.distlist )) get_members_request = comm.gen_request(token=token) get_members_request.add_request( "GetDistributionListRequest", { "dl": { "_content": options.distlist, "by": "name" } }, "urn:zimbraAdmin" ) get_members_response = comm.send_request(get_members_request) if get_members_response.is_fault(): raise Exception(
def test_genrequest_batch_xml(self): """ Create a batch-request only using the Communication-object ( xml-version) """ config = get_config() if config.getboolean("genrequest_test", "enabled"): # Run only if enabled comm = Communication(config.get("genrequest_test", "url")) token = authenticate( config.get("genrequest_test", "url"), config.get("genrequest_test", "account"), config.get("genrequest_test", "preauthkey") ) self.assertNotEqual( token, None, "Cannot authenticate." ) request = comm.gen_request( request_type="xml", token=token, set_batch=True ) self.assertEqual( type(request), RequestXml, "Generated request wasn't an xml-request" ) request.add_request( "NoOpRequest", { }, "urn:zimbraMail" ) request.add_request( "NoOpRequest", { }, "urn:zimbraMail" ) response = comm.send_request(request) if response.is_fault(): self.fail( "Reponse failed: (%s) %s" % ( response.get_fault_code(), response.get_fault_message() ) ) self.assertEqual( response.is_batch(), True, "Batch-request didn't return a Batch response." ) expected_batch = { 'nameToId': { 'NoOpResponse': [ '1', '2' ] }, 'hasFault': False, 'idToName': { '1': 'NoOpResponse', '2': 'NoOpResponse' } } self.assertEqual( response.get_batch(), expected_batch, "Batch-dictionary wasn't expected" )