Exemple #1
0
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)
Exemple #2
0
    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()))
Exemple #4
0
    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'])
Exemple #6
0
    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
            )
Exemple #8
0
    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"
            )
Exemple #9
0
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)
Exemple #10
0
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()
Exemple #14
0
    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
Exemple #15
0
    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
Exemple #16
0
    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' ]
Exemple #18
0
    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(
Exemple #20
0
    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"
            )