From 43cc3b40b0e7e30c710996e4b5f43ce76e73bb7a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emmanuel=20Beno=C3=AEt?= Date: Mon, 18 Jun 2018 11:52:07 +0200 Subject: [PATCH 1/7] =?UTF-8?q?Groupes=20-=20Mod=C3=A8le,=20lecture=20sur?= =?UTF-8?q?=20serveur?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Ajouté models.Group qui permet de stocker les informations concernant les groupes * Ajouté services.GroupService, qui devrait implémenter à terme les commandes sur les groupes. Pour l'instant seules les commandes de lecture (GetAllGroups, GetGroup, GetSendAsGroup) sont implémentées. * Ajouté support pour les commandes ci-dessus au client en ligne de commande. --- cli-bss.py | 72 +++++++++- lib_Partage_BSS/models/Group.py | 161 +++++++++++++++++++++++ lib_Partage_BSS/services/GroupService.py | 66 ++++++++++ 3 files changed, 298 insertions(+), 1 deletion(-) create mode 100644 lib_Partage_BSS/models/Group.py create mode 100644 lib_Partage_BSS/services/GroupService.py diff --git a/cli-bss.py b/cli-bss.py index 554eb71..4375197 100755 --- a/cli-bss.py +++ b/cli-bss.py @@ -8,7 +8,8 @@ from lib_Partage_BSS.exceptions import ServiceException, NameException from lib_Partage_BSS.models.Account import Account, importJsonAccount -from lib_Partage_BSS.services import AccountService +from lib_Partage_BSS.models.Group import Group +from lib_Partage_BSS.services import AccountService , GroupService from lib_Partage_BSS.models.COS import COS from lib_Partage_BSS.services import COSService from lib_Partage_BSS.services.BSSConnexionService import BSSConnexion @@ -46,6 +47,9 @@ parser.add_argument('--userPassword', metavar='{ssha}HpqRjlh1WEha+6or95YkqA', help="empreinte du mot de passe utilisateur") parser.add_argument('--asJson', action='store_const', const=True, help="option pour exporter un compte au format JSON") parser.add_argument('--jsonData', metavar='/tmp/myAccount.json', type=argparse.FileType('r'), help="fichier contenant des données JSON") +parser.add_argument( '--fullData' , action = 'store_true' , + help = '''Récupérer toutes les informations dans les requêtes sur les + groupes. Attention, c'est lent.''' ) group = parser.add_argument_group('Opérations implémentées :') group.add_argument('--getAccount', action='store_const', const=True, help="rechercher un compte") @@ -65,6 +69,13 @@ group.add_argument('--modifyAccountAliases', action='store_const', const=True, help="positionne une liste d'aliases pour un compte (supprime des aliases existants si non mentionnés)") group.add_argument('--getCos', action='store_const', const=True, help="rechercher une classe de service") group.add_argument('--getAllCos', action='store_const', const=True, help="rechercher toutes les classes de service du domaine") +group.add_argument( '--getAllGroups', action = 'store_true' , + help = 'Afficher la liste des groupes et listes de distribution' ) +group.add_argument( '--getGroup' , action = 'store_true' , + help = 'Rechercher un groupe / une liste de distribution' ) +group.add_argument( '--getSendAsGroup' , action = 'store_true' , + help = '''Lister les l’ensemble des comptes pouvant utiliser l’adresse + mail du groupe en adresse d’expédition.''' ) args = vars(parser.parse_args()) @@ -365,5 +376,64 @@ print("Classe de service %s :" % cos.name) print(cos.showAttr()) +elif args[ 'getAllGroups' ]: + data = { + 'domain' : args[ 'domain' ] , + 'limit' : args[ 'limit' ] , + } + + try: + all_groups = GroupService.getAllGroups( **data ) + except Exception as err: + print( "Echec d'exécution : {}".format( repr( err ) ) ) + sys.exit( 2 ) + + print( "{} groupes retournés".format( len( all_groups ) ) ) + if 'fullData' in args and args[ 'fullData' ]: + print( 'Récupération des informations complètes...' ) + try: + all_groups = [ GroupService.getGroup( g.name , full_info = True ) + for g in all_groups ] + except Exception as err: + print( "Echec d'exécution : {}".format( repr( err ) ) ) + sys.exit( 2 ) + + print( ) + for group in all_groups: + print( "Groupe {} : ".format( group.name ) ) + print( group.showAttr( ) ) + print( ) + +elif args[ 'getGroup' ]: + try: + if not args[ 'email' ]: + raise Exception( "Argument 'email' manquant" ) + data = { 'name' : args[ 'email' ] } + if 'fullData' in args: + data[ 'full_info' ] = args[ 'fullData' ] + group = GroupService.getGroup( **data ) + except Exception as err: + print( "Echec d'exécution : {}".format( repr( err ) ) ) + sys.exit( 2 ) + if group is None: + print( "Groupe {} non trouvé".format( args[ 'email' ] ) ) + else: + print( group.showAttr( ) ) + +elif args[ 'getSendAsGroup' ]: + try: + if not args[ 'email' ]: + raise Exception( "Argument 'email' manquant" ) + senders = GroupService.getSendAsGroup( args[ 'email' ] ) + except Exception as err: + print( "Echec d'exécution : {}".format( repr( err ) ) ) + sys.exit( 2 ) + if senders is None: + print( "Groupe {} non trouvé".format( args[ 'email' ] ) ) + elif not senders: + print( "Pas d'utilisateurs autorisés" ) + else: + print( "Utilisateurs autorisés: {}".format( ', '.join( senders ) ) ) + else: print("Aucune opération à exécuter") diff --git a/lib_Partage_BSS/models/Group.py b/lib_Partage_BSS/models/Group.py new file mode 100644 index 0000000..8b242ee --- /dev/null +++ b/lib_Partage_BSS/models/Group.py @@ -0,0 +1,161 @@ +# -*-coding:utf-8 -* +import json + +from lib_Partage_BSS import utils +from lib_Partage_BSS.exceptions.NameException import NameException +from lib_Partage_BSS.models.GlobalModel import GlobalModel + + +class Group( GlobalModel ): + + ATTRIBUTES = ( + 'description' , 'displayName' , + 'zimbraDistributionListSendShareMessageToNewMembers' , + 'zimbraHideInGal' , 'zimbraMailStatus' , 'zimbraNotes' + ) + + def __init__( self , name = None ): + if name is not None and not ( isinstance( name , str ) + and utils.checkIsMailAddress( name ) ): + raise TypeError + GlobalModel.__init__( self , name ) + for a in Group.ATTRIBUTES: + setattr( self , '_{}'.format( a ) , None ) + self._members = set( ) + self._senders = set( ) + self._aliases = set( ) + + #--------------------------------------------------------------------------- + + @staticmethod + def _get_set( output , data , name , sub = None ): + if name not in data: return + od = data[ name ] + if sub is None: + sub = name + if sub in od: + if isinstance( od[ sub ] , str ): + output.add( od[ sub ] ) + else: + output.update( od[ sub ] ) + + @staticmethod + def _from_bool( value , true_value , false_value , xform ): + if value is None: + return None + v = None + if isinstance( value , str ) and xform( value ) in ( true_value , + false_value ): + v = xform( value ) + elif isinstance( value , bool ): + v = true_value if value else false_value + if v is None: + raise TypeError + return v + + #--------------------------------------------------------------------------- + + @staticmethod + def from_bss( data ): + try: + group = Group( data[ 'name' ] ) + except TypeError: + raise NameException( "L'adresse mail {} n'est pas valide".format( + data[ 'name' ] ) ) + for a in Group.ATTRIBUTES: + if a in data: + setattr( group , a , data[ a ] ) + Group._get_set( group._members , data , 'members' , 'member' ) + Group._get_set( group._aliases , data , 'zimbraMailAlias' ) + return group + + def senders_from_bss( self , data ): + self._senders.clear( ) + Group._get_set( self._senders , data , 'accounts' , 'account' ) + return self.senders + + #--------------------------------------------------------------------------- + + @property + def members( self ): + return sorted( self._members ) + + @property + def senders( self ): + return sorted( self._senders ) + + @property + def aliases( self ): + return sorted( self._aliases ) + + #--------------------------------------------------------------------------- + + @property + def description( self ): + return self._description + + @description.setter + def description( self , value ): + if isinstance( value , str ) or value is None: + self._description = value + else: + raise TypeError + + #--------------------------------------------------------------------------- + + @property + def displayName( self ): + return self._displayName + + @displayName.setter + def displayName( self , value ): + if isinstance( value , str ) or value is None: + self._displayName = value + else: + raise TypeError + + #--------------------------------------------------------------------------- + + @property + def zimbraDistributionListSendShareMessageToNewMembers( self ): + return self._zimbraDistributionListSendShareMessageToNewMembers + + @zimbraDistributionListSendShareMessageToNewMembers.setter + def zimbraDistributionListSendShareMessageToNewMembers( self , value ): + v = Group._from_bool( value , 'TRUE' , 'FALSE' , lambda x : x.upper( ) ) + self._zimbraDistributionListSendShareMessageToNewMembers = v + + #--------------------------------------------------------------------------- + + @property + def zimbraHideInGal( self ): + return self._zimbraHideInGal + + @zimbraHideInGal.setter + def zimbraHideInGal( self , value ): + v = Group._from_bool( value , 'TRUE' , 'FALSE' , lambda x : x.upper( ) ) + self._zimbraHideInGal = v + + #--------------------------------------------------------------------------- + + @property + def zimbraMailStatus( self ): + return self._zimbraMailStatus == 'enabled' + + @zimbraMailStatus.setter + def zimbraMailStatus( self , value ): + self._zimbraMailStatus = Group._from_bool( value , + 'enabled' , 'disabled' , lambda x : x.lower( ) ) + + #--------------------------------------------------------------------------- + + @property + def zimbraNotes( self ): + return self._zimbraNotes + + @zimbraNotes.setter + def zimbraNotes( self , value ): + if isinstance( value , str ) or value is None: + self._zimbraNotes = value + else: + raise TypeError diff --git a/lib_Partage_BSS/services/GroupService.py b/lib_Partage_BSS/services/GroupService.py new file mode 100644 index 0000000..a1d3088 --- /dev/null +++ b/lib_Partage_BSS/services/GroupService.py @@ -0,0 +1,66 @@ +# -*-coding:utf-8 -* +from lib_Partage_BSS.models.Group import Group +from .GlobalService import callMethod +from lib_Partage_BSS import services , utils +from lib_Partage_BSS.exceptions import ( NameException , DomainException , + ServiceException ) + + +def getAllGroups( domain , limit = 100 , offset = 0 ): + data = { + 'limit' : limit , + 'offset' : offset , + } + + response = callMethod( domain , 'GetAllGroups' , data ) + if not utils.checkResponseStatus( response[ 'status' ] ): + raise ServiceException( response[ 'status' ] , response[ 'message' ] ) + + if len( response[ 'groups' ] ) <= 1: + return [] + + groups = response[ 'groups' ][ 'group' ] + if isinstance( groups , list ): + return [ Group.from_bss( e ) for e in groups ] + return Group.from_bss( groups ) + + +def getGroup( name , full_info = False ): + if not utils.checkIsMailAddress( name ): + raise NameException( "L'adresse mail {} n'est pas valide".format( + name ) ) + + data = { 'name' : name } + domain = services.extractDomain( name ) + response = callMethod( domain , 'GetGroup' , data ) + + if utils.checkResponseStatus( response[ 'status' ] ): + group = Group.from_bss( response[ 'group' ] ) + if full_info: + getSendAsGroup( group ) + return group + if 'no such distribution list' in response[ 'message' ]: + return None + raise ServiceException( response[ 'status' ] , response[ 'message' ] ) + + +def getSendAsGroup( name_or_group ): + if isinstance( name_or_group , Group ): + name = name_or_group.name + group = name_or_group + else: + name = name_or_group + if not utils.checkIsMailAddress( name ): + raise NameException( "L'adresse mail {} n'est pas valide".format( + name ) ) + group = Group( name ) + + data = { 'name' : name } + domain = services.extractDomain( name ) + response = callMethod( domain , 'GetSendAsGroup' , data ) + + if utils.checkResponseStatus( response[ 'status' ] ): + return group.senders_from_bss( response ) + if 'no such distribution list' in response[ 'message' ]: + return None + raise ServiceException( response[ 'status' ] , response[ 'message' ] ) From 29d27caa6c83a7c0b806936de6785b29631a49c7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emmanuel=20Beno=C3=AEt?= Date: Mon, 18 Jun 2018 16:33:26 +0200 Subject: [PATCH 2/7] =?UTF-8?q?Op=C3=A9rations=20de=20modification=20sur?= =?UTF-8?q?=20les=20groupes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Group: ajouté méthodes diverses pour les conversions vers/depuis différents formats (par exemple format utilisé pour BSS ou JSON) * GroupService: création / suppression de groupe, modifications sur les membres, les aliases et les utilisateurs autorisés * cli-bss: support des diverses opérations ajoutées --- cli-bss.py | 203 +++++++++++++++++++++++ lib_Partage_BSS/models/Group.py | 88 +++++++++- lib_Partage_BSS/services/GroupService.py | 159 ++++++++++++++++++ 3 files changed, 446 insertions(+), 4 deletions(-) diff --git a/cli-bss.py b/cli-bss.py index ac7f952..89c77d3 100755 --- a/cli-bss.py +++ b/cli-bss.py @@ -56,6 +56,13 @@ parser.add_argument('--field' , '-f' , action='append' , nargs=2 , metavar=('name','value') , help="nom et valeur d'un champ du compte") +parser.add_argument( '--member' , action = 'append' , + metavar = 'example@example.org' , + help = '''Membre(s) d'un groupe ou d'une liste de distribution.''' ) +parser.add_argument( '--sender' , action = 'append' , + metavar = 'example@example.org' , + help = '''Compte autorisé à utiliser l'adresse mail d'un groupe ou d'une + liste de distribution en adresse d'expédition.''' ) group = parser.add_argument_group('Opérations implémentées :') group.add_argument('--getAccount', action='store_const', const=True, help="rechercher un compte") @@ -78,6 +85,7 @@ group.add_argument('--modifyAccountAliases', action='store_const', const=True, help="positionne une liste d'aliases pour un compte (supprime des aliases existants si non mentionnés)") group.add_argument('--getCos', action='store_const', const=True, help="rechercher une classe de service") group.add_argument('--getAllCos', action='store_const', const=True, help="rechercher toutes les classes de service du domaine") +# Requêtes sur les groupes group.add_argument( '--getAllGroups', action = 'store_true' , help = 'Afficher la liste des groupes et listes de distribution' ) group.add_argument( '--getGroup' , action = 'store_true' , @@ -85,6 +93,37 @@ group.add_argument( '--getSendAsGroup' , action = 'store_true' , help = '''Lister les l’ensemble des comptes pouvant utiliser l’adresse mail du groupe en adresse d’expédition.''' ) +# Opérations sur les groupes +group.add_argument( '--createGroup' , action = 'store_true' , + help = '''Créer un groupe / une liste de distribution.''' ) +group.add_argument( '--createGroupExt', action = 'store_true' , + help = '''Créer un groupe / une liste de distribution en spécifiant les + paramètres via -f ou --jsonData''' ) +group.add_argument( '--deleteGroup' , action = 'store_true' , + help = '''Supprimer un groupe / une liste de distribution.''' ) +group.add_argument( '--addGroupAlias' , action = 'store_true' , + help = '''Ajoute des alias à un groupe / une liste de distribution.''' ) +group.add_argument( '--removeGroupAlias' , action = 'store_true' , + help = '''Supprime des alias à un groupe / une liste de distribution.''' ) +group.add_argument( '--setGroupAliases' , action = 'store_true' , + help = '''Modifie les alias à un groupe / une liste de distribution.''' ) +group.add_argument( '--addGroupMember' , action = 'store_true' , + help = '''Ajoute des membres à un groupe / une liste de distribution.''' ) +group.add_argument( '--removeGroupMember' , action = 'store_true' , + help = '''Supprime des membres d'un groupe / d'une liste de + distribution.''' ) +group.add_argument( '--setGroupMembers' , action = 'store_true' , + help = '''Modifie les membres d'un groupe / d'une liste de + distribution.''' ) +group.add_argument( '--addGroupSender' , action = 'store_true' , + help = '''Ajoute des autorisations d'utilisation de l'adresse du groupe ou + de la liste de distribution par des comptes.''' ) +group.add_argument( '--removeGroupSender' , action = 'store_true' , + help = '''Supprime des autorisations d'utilisation de l'adresse du groupe ou + de la liste de distribution par des comptes.''' ) +group.add_argument( '--setGroupSenders' , action = 'store_true' , + help = '''Modifie les autorisations d'utilisation de l'adresse du groupe ou + de la liste de distribution par des comptes.''' ) args = vars(parser.parse_args()) @@ -471,5 +510,169 @@ else: print( "Utilisateurs autorisés: {}".format( ', '.join( senders ) ) ) +elif args[ 'createGroup' ]: + try: + if not args[ 'email' ]: + raise Exception( "Argument 'email' manquant" ) + GroupService.createGroup( args[ 'email' ] ) + group = GroupService.getGroup( args[ 'email' ] , full_info = True ) + except Exception as err: + raise err + print( "Echec d'exécution : {}".format( repr( err ) ) ) + sys.exit( 2 ) + print( group.showAttr( ) ) + +elif args[ 'createGroupExt' ]: + try: + # Objet du compte, éventuellement lu depuis un fichier JSON + if args[ 'jsonData' ]: + group = Group.from_json( args[ 'jsonData' ] , is_file = True ) + else: + group = Group( ) + + if args[ 'field' ]: + group.from_dict({ + arg[ 0 ] : arg[ 1 ] + for arg in args[ 'field' ] + } , allow_name = True ) + + GroupService.createGroup( group ) + group = GroupService.getGroup( group.name , full_info = True ) + except Exception as err: + raise err + print( "Echec d'exécution : {}".format( repr( err ) ) ) + sys.exit( 2 ) + print( group.showAttr( ) ) + +elif args[ 'deleteGroup' ]: + try: + if not args[ 'email' ]: + raise Exception( "Argument 'email' manquant" ) + GroupService.deleteGroup( args[ 'email' ] ) + except Exception as err: + print( "Echec d'exécution : {}".format( repr( err ) ) ) + sys.exit( 2 ) + print( "Groupe {} supprimé".format( args[ 'email' ] ) ) + +elif args[ 'addGroupAlias' ]: + try: + if not args[ 'email' ]: + raise Exception( "Argument 'email' manquant" ) + if not args[ 'alias' ]: + raise Exception( "Argument 'alias' manquant" ) + GroupService.addGroupAliases( args[ 'email' ] , args[ 'alias' ] ) + group = GroupService.getGroup( args[ 'email' ] , full_info = True ) + except Exception as err: + print( "Echec d'exécution : {}".format( repr( err ) ) ) + sys.exit( 2 ) + print( group.showAttr( ) ) + +elif args[ 'removeGroupAlias' ]: + try: + if not args[ 'email' ]: + raise Exception( "Argument 'email' manquant" ) + if not args[ 'alias' ]: + raise Exception( "Argument 'alias' manquant" ) + GroupService.removeGroupAliases( args[ 'email' ] , args[ 'alias' ] ) + group = GroupService.getGroup( args[ 'email' ] , full_info = True ) + except Exception as err: + print( "Echec d'exécution : {}".format( repr( err ) ) ) + sys.exit( 2 ) + print( group.showAttr( ) ) + +elif args[ 'setGroupAliases' ]: + try: + if not args[ 'email' ]: + raise Exception( "Argument 'email' manquant" ) + if not args[ 'alias' ]: + args[ 'alias' ] = ( ) + GroupService.updateGroupAliases( args[ 'email' ] , args[ 'alias' ] ) + group = GroupService.getGroup( args[ 'email' ] , full_info = True ) + except Exception as err: + raise err + print( "Echec d'exécution : {}".format( repr( err ) ) ) + sys.exit( 2 ) + print( group.showAttr( ) ) + +elif args[ 'addGroupMember' ]: + try: + if not args[ 'email' ]: + raise Exception( "Argument 'email' manquant" ) + if not args[ 'member' ]: + raise Exception( "Argument 'member' manquant" ) + GroupService.addGroupMembers( args[ 'email' ] , args[ 'member' ] ) + group = GroupService.getGroup( args[ 'email' ] , full_info = True ) + except Exception as err: + print( "Echec d'exécution : {}".format( repr( err ) ) ) + sys.exit( 2 ) + print( group.showAttr( ) ) + +elif args[ 'removeGroupMember' ]: + try: + if not args[ 'email' ]: + raise Exception( "Argument 'email' manquant" ) + if not args[ 'member' ]: + raise Exception( "Argument 'member' manquant" ) + GroupService.removeGroupMembers( args[ 'email' ] , args[ 'member' ] ) + group = GroupService.getGroup( args[ 'email' ] , full_info = True ) + except Exception as err: + print( "Echec d'exécution : {}".format( repr( err ) ) ) + sys.exit( 2 ) + print( group.showAttr( ) ) + +elif args[ 'setGroupMembers' ]: + try: + if not args[ 'email' ]: + raise Exception( "Argument 'email' manquant" ) + if not args[ 'member' ]: + args[ 'member' ] = ( ) + GroupService.updateGroupMembers( args[ 'email' ] , args[ 'member' ] ) + group = GroupService.getGroup( args[ 'email' ] , full_info = True ) + except Exception as err: + raise err + print( "Echec d'exécution : {}".format( repr( err ) ) ) + sys.exit( 2 ) + print( group.showAttr( ) ) + +elif args[ 'addGroupSender' ]: + try: + if not args[ 'email' ]: + raise Exception( "Argument 'email' manquant" ) + if not args[ 'sender' ]: + raise Exception( "Argument 'sender' manquant" ) + GroupService.addGroupSenders( args[ 'email' ] , args[ 'sender' ] ) + group = GroupService.getGroup( args[ 'email' ] , full_info = True ) + except Exception as err: + print( "Echec d'exécution : {}".format( repr( err ) ) ) + sys.exit( 2 ) + print( group.showAttr( ) ) + +elif args[ 'removeGroupSender' ]: + try: + if not args[ 'email' ]: + raise Exception( "Argument 'email' manquant" ) + if not args[ 'sender' ]: + raise Exception( "Argument 'sender' manquant" ) + GroupService.removeGroupSenders( args[ 'email' ] , args[ 'sender' ] ) + group = GroupService.getGroup( args[ 'email' ] , full_info = True ) + except Exception as err: + print( "Echec d'exécution : {}".format( repr( err ) ) ) + sys.exit( 2 ) + print( group.showAttr( ) ) + +elif args[ 'setGroupSenders' ]: + try: + if not args[ 'email' ]: + raise Exception( "Argument 'email' manquant" ) + if not args[ 'sender' ]: + args[ 'sender' ] = ( ) + GroupService.updateGroupSenders( args[ 'email' ] , args[ 'sender' ] ) + group = GroupService.getGroup( args[ 'email' ] , full_info = True ) + except Exception as err: + raise err + print( "Echec d'exécution : {}".format( repr( err ) ) ) + sys.exit( 2 ) + print( group.showAttr( ) ) + else: print("Aucune opération à exécuter") diff --git a/lib_Partage_BSS/models/Group.py b/lib_Partage_BSS/models/Group.py index 8b242ee..032a16b 100644 --- a/lib_Partage_BSS/models/Group.py +++ b/lib_Partage_BSS/models/Group.py @@ -14,6 +14,9 @@ class Group( GlobalModel ): 'zimbraHideInGal' , 'zimbraMailStatus' , 'zimbraNotes' ) + SETS = ( 'members' , 'senders' , 'aliases' ) + + def __init__( self , name = None ): if name is not None and not ( isinstance( name , str ) and utils.checkIsMailAddress( name ) ): @@ -62,32 +65,109 @@ def from_bss( data ): except TypeError: raise NameException( "L'adresse mail {} n'est pas valide".format( data[ 'name' ] ) ) - for a in Group.ATTRIBUTES: - if a in data: - setattr( group , a , data[ a ] ) + group.from_dict( data ) Group._get_set( group._members , data , 'members' , 'member' ) Group._get_set( group._aliases , data , 'zimbraMailAlias' ) return group + def from_dict( self , data , allow_name = False ): + attrs = ( + ( 'name' , *Group.ATTRIBUTES ) if allow_name + else Group.ATTRIBUTES + ) + for a in attrs: + if a in data: + setattr( self , a , data[ a ] ) + def senders_from_bss( self , data ): self._senders.clear( ) Group._get_set( self._senders , data , 'accounts' , 'account' ) return self.senders + def to_bss( self ): + rv = { } + for a in ( 'name' , *Group.ATTRIBUTES ): + value = getattr( self , a ) + if value is not None: + rv[ a ] = value + return rv + + #--------------------------------------------------------------------------- + + @staticmethod + def from_json( source , is_file = False ): + if is_file: + if isinstance( source , str ): + with open( source ) as json_file: + data = json.load( json_file ) + else: + data = json.load( source ) + else: + data = json.loads( source ) + return Group.from_json_record( data ) + + @staticmethod + def from_json_record( record ): + group = Group( record[ 'name' ] if 'name' in record else None ) + for a in Group.ATTRIBUTES: + if a in record: + setattr( group , a , record[ a ] ) + for s in Group.SETS: + if s in record: + getattr( group , '_{}'.format( s ) ).update( record[ s ] ) + return group + + def to_json_record( self ): + rv = { + a : getattr( self , a ) + for a in ( 'name' , *Group.ATTRIBUTES ) + if getattr( self , a ) is not None + } + rv.update({ + s : list( getattr( self , '_{}'.format( s ) ) ) + for s in Group.SETS + if getattr( self , '_{}'.format( s ) ) + }) + return rv + #--------------------------------------------------------------------------- @property def members( self ): return sorted( self._members ) + @property + def members_set( self ): + return self._members + + @property + def has_members( self ): + return bool( self._members ) + @property def senders( self ): return sorted( self._senders ) + @property + def senders_set( self ): + return self._senders + + @property + def has_senders( self ): + return bool( self._senders ) + @property def aliases( self ): return sorted( self._aliases ) + @property + def aliases_set( self ): + return self._aliases + + @property + def has_aliases( self ): + return bool( self._aliases ) + #--------------------------------------------------------------------------- @property @@ -140,7 +220,7 @@ def zimbraHideInGal( self , value ): @property def zimbraMailStatus( self ): - return self._zimbraMailStatus == 'enabled' + return self._zimbraMailStatus @zimbraMailStatus.setter def zimbraMailStatus( self , value ): diff --git a/lib_Partage_BSS/services/GroupService.py b/lib_Partage_BSS/services/GroupService.py index a1d3088..3b0db7c 100644 --- a/lib_Partage_BSS/services/GroupService.py +++ b/lib_Partage_BSS/services/GroupService.py @@ -6,6 +6,10 @@ ServiceException ) +#------------------------------------------------------------------------------- +# Opérations d'interrogation + + def getAllGroups( domain , limit = 100 , offset = 0 ): data = { 'limit' : limit , @@ -64,3 +68,158 @@ def getSendAsGroup( name_or_group ): if 'no such distribution list' in response[ 'message' ]: return None raise ServiceException( response[ 'status' ] , response[ 'message' ] ) + + +#------------------------------------------------------------------------------- +# Création & suppression + +def createGroup( name_or_group ): + is_group = isinstance( name_or_group , Group ) + if is_group: + if name_or_group.name is None: + raise NameException( "L'adresse mail n'est pas renseignée" ) + data = name_or_group.to_bss( ) + else: + if not isinstance( name_or_group , str ): + raise TypeError + data = { 'name' : name_or_group } + + if not utils.checkIsMailAddress( data[ 'name' ] ): + raise NameException( "L'adresse mail {} n'est pas valide".format( n ) ) + + domain = services.extractDomain( data[ 'name' ] ) + response = callMethod( domain , 'CreateGroup' , data ) + if not utils.checkResponseStatus( response[ 'status' ] ): + raise ServiceException( response[ 'status' ] , response[ 'message' ] ) + + if not is_group: + return + if name_or_group.has_aliases: + addGroupAliases( name_or_group.name , name_or_group.aliases ) + if name_or_group.has_members: + addGroupMembers( name_or_group.name , name_or_group.members ) + if name_or_group.has_senders: + addGroupSenders( name_or_group.name , name_or_group.senders ) + +def deleteGroup( name_or_group ): + if isinstance( name_or_group , Group ): + if name_or_group.name is None: + raise NameException( "L'adresse mail n'est pas renseignée" ) + data = { 'name' : name_or_group.name } + else: + if not isinstance( name_or_group , str ): + raise TypeError + data = { 'name' : name_or_group } + + if not utils.checkIsMailAddress( data[ 'name' ] ): + raise NameException( "L'adresse mail {} n'est pas valide".format( n ) ) + + domain = services.extractDomain( data[ 'name' ] ) + response = callMethod( domain , 'DeleteGroup' , data ) + if not utils.checkResponseStatus( response[ 'status' ] ): + raise ServiceException( response[ 'status' ] , response[ 'message' ] ) + + +#------------------------------------------------------------------------------- +# Opération de modification + +def modifyGroup( group ): + response = callMethod( services.extractDomain( group.name ) , + 'ModifyGroup' , group.to_bss( ) ) + if not utils.checkResponseStatus( response[ 'status' ] ): + raise ServiceException( response[ 'status' ] , response[ 'message' ] ) + +#------------------------------------------------------------------------------- + +def _group_set_op( name_or_group , entries , f_name , op_name ): + if isinstance( entries , str ): + entries = [ entries ] + else: + entries = list( entries ) + + if isinstance( name_or_group , Group ): + name = name_or_group.name + else: + name = name_or_group + + for n in ( name , *entries ): + if not utils.checkIsMailAddress( n ): + raise NameException( + "L'adresse mail {} n'est pas valide".format( n ) ) + + domain = services.extractDomain( name ) + if '[]' in f_name: + entries = [entries] + + for entry in entries: + data = { + 'name' : name , + f_name : entry , + } + print( domain , op_name , repr( data ) ) + response = callMethod( domain , op_name , data ) + if not utils.checkResponseStatus( response[ 'status' ] ): + raise ServiceException( response[ 'status' ] , + response[ 'message' ] ) + +def _group_diff_op( name_or_group , new_values , a_name , f_name , + add_op_name , rem_op_name ): + if isinstance( name_or_group , Group ): + group = name_or_group + else: + group = getGroup( name_or_group , full_info = 'senders' == a_name ) + + if isinstance( new_values , str ): + nv_set = set( ( new_values , ) ) + else: + nv_set = set( new_values ) + grp_set = getattr( group , '{}_set'.format( a_name ) ) + + rem = grp_set - nv_set + if rem: _group_set_op( group , rem , f_name , rem_op_name ) + + add = nv_set - grp_set + if add: _group_set_op( group , add , f_name , add_op_name ) + + +#------------------------------------------------------------------------------- + +def addGroupAliases( name_or_group , aliases ): + _group_set_op( name_or_group , aliases , + 'alias' , 'AddDistributionListAlias' ) + +def removeGroupAliases( name_or_group , aliases ): + _group_set_op( name_or_group , aliases , + 'alias' , 'RemoveDistributionListAlias' ) + +def updateGroupAliases( name_or_group , new_aliases ): + _group_diff_op( name_or_group , new_aliases , 'aliases' , 'alias' , + 'AddDistributionListAlias' , 'RemoveDistributionListAlias' ) + +#------------------------------------------------------------------------------- + +def addGroupMembers( name_or_group , members ): + _group_set_op( name_or_group , members , + 'members[]' , 'AddGroupMembers' ) + +def removeGroupMembers( name_or_group , members ): + _group_set_op( name_or_group , members , + 'members[]' , 'RemoveGroupMembers' ) + +def updateGroupMembers( name_or_group , new_members ): + _group_diff_op( name_or_group , new_members , 'members' , 'members[]' , + 'AddGroupMembers' , 'RemoveGroupMembers' ) + +#------------------------------------------------------------------------------- + +def addGroupSenders( name_or_group , senders ): + _group_set_op( name_or_group , senders , + 'account' , 'AddSendAsGroup' ) + +def removeGroupSenders( name_or_group , senders ): + _group_set_op( name_or_group , senders , + 'account' , 'DeleteSendAsGroup' ) + +def updateGroupSenders( name_or_group , new_senders ): + _group_diff_op( name_or_group , new_senders , 'senders' , 'account' , + 'AddSendAsGroup' , 'DeleteSendAsGroup' ) From f50e0a389e5e6e6bbc64f975f9d19fb1d14d6bbf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emmanuel=20Beno=C3=AEt?= Date: Mon, 18 Jun 2018 17:28:14 +0200 Subject: [PATCH 3/7] models.Group - Documentation --- lib_Partage_BSS/models/Group.py | 175 ++++++++++++++++++++++++++++++-- 1 file changed, 165 insertions(+), 10 deletions(-) diff --git a/lib_Partage_BSS/models/Group.py b/lib_Partage_BSS/models/Group.py index 032a16b..8f7abc3 100644 --- a/lib_Partage_BSS/models/Group.py +++ b/lib_Partage_BSS/models/Group.py @@ -7,21 +7,43 @@ class Group( GlobalModel ): - + """ + Classe représentant un groupe ou une liste de distribution dans Partage. + + :ivar _description: description du groupe ou de la liste + :ivar _displayName: nom d'affichage du groupe + :ivar _zimbraDistributionListSendShareMessageToNewMembers: détermine \ + si la liste des partages devrait être envoyée par mail aux \ + nouveaux membres + :ivar _zimbraHideInGal: masquer dans la GAL ? + :ivar _zimbraMailStatus: ce groupe peut-il recevoir du mail? Si oui, \ + il s'agit d'une liste de distribution. + :ivar _zimbraNotes: notes concernant le groupe ou la liste. + :ivar _members: l'ensemble des adresses des membres du groupe ou \ + de la liste + :ivar _senders: l'ensemble des comptes autorisés à envoyer du mail en \ + utilisant la liste comme adresse d'expédition + :ivar _aliases: l'ensemble des alias de la liste + """ + + # Attributs utilisés dans {Create,Modify}Account ATTRIBUTES = ( 'description' , 'displayName' , 'zimbraDistributionListSendShareMessageToNewMembers' , 'zimbraHideInGal' , 'zimbraMailStatus' , 'zimbraNotes' ) + # Attributs synthétiques sous la forme d'ensembles SETS = ( 'members' , 'senders' , 'aliases' ) - def __init__( self , name = None ): - if name is not None and not ( isinstance( name , str ) - and utils.checkIsMailAddress( name ) ): + if name is not None and not isinstance( name , str ): raise TypeError + if name is not None and not utils.checkIsMailAddress( name ): + raise NameException( "Adresse mail {} invalide".format( name ) ) + GlobalModel.__init__( self , name ) + for a in Group.ATTRIBUTES: setattr( self , '_{}'.format( a ) , None ) self._members = set( ) @@ -32,6 +54,16 @@ def __init__( self , name = None ): @staticmethod def _get_set( output , data , name , sub = None ): + """ + Récupère les données correspondant à un attribut de type ensemble depuis + la réponse du serveur BSS. + + :param output: l'instance à mettre à jour + :param data: les données reçues du serveur + :param name: le nom du champ contenant la liste + :param sub: le nom des éléments de la liste, s'ils diffèrent du nom \ + de celle-ci + """ if name not in data: return od = data[ name ] if sub is None: @@ -44,6 +76,21 @@ def _get_set( output , data , name , sub = None ): @staticmethod def _from_bool( value , true_value , false_value , xform ): + """ + Vérifie et retourne la valeur à utiliser pour un champ 'booléen' mais + encodé sous la forme de chaînes. + + :param value: la nouvelle valeur du champ + :param true_value: la chaîne correspondant à une valeur vraie + :param false_value: la chaîne correspondant à une valeur fausse + :param xform: une fonction qui transforme la chaîne d'entrée si \ + nécessaire + + :raises TypeError: la valeur n'est ni une chaîne ni un booléen, ou \ + sa valeur ne correspond pas à l'une des chaînes indiquées + + :return: la nouvelle valeur du champ + """ if value is None: return None v = None @@ -60,17 +107,38 @@ def _from_bool( value , true_value , false_value , xform ): @staticmethod def from_bss( data ): - try: - group = Group( data[ 'name' ] ) - except TypeError: - raise NameException( "L'adresse mail {} n'est pas valide".format( - data[ 'name' ] ) ) + """ + Crée une instance en se basant sur des données reçues du serveur + Partage, soit via GetGroup soit via GetAllGroups. Dans le premier cas, + tous les champs à l'exception de la liste des utilisateurs autorisés à + expédier avec l'adresse du groupe seront mis à jour. + + :param data: les données du compte reçues depuis le serveur Partage + + :raises TypeError: un champ n'a pas le format attendu + + :return: l'instance de Group créée, avec ses champs renseignés + """ + group = Group( data[ 'name' ] ) group.from_dict( data ) Group._get_set( group._members , data , 'members' , 'member' ) Group._get_set( group._aliases , data , 'zimbraMailAlias' ) return group def from_dict( self , data , allow_name = False ): + """ + Met à jour les champs d'une instance à partir d'un dictionnaire. Seuls + les attributs, et optionellement le nom, peuvent être modifiés par cette + méthode. + + :param data: le dictionnaire à partir duquel on veut mettre à jour les \ + données + :param allow_name: permettre la modification du champ 'name' à partir \ + du dictionnaire; si False, une entrée 'name' dans le \ + dictionnaire sera ignorée + + :raises TypeError: un champ n'a pas le format attendu + """ attrs = ( ( 'name' , *Group.ATTRIBUTES ) if allow_name else Group.ATTRIBUTES @@ -80,11 +148,25 @@ def from_dict( self , data , allow_name = False ): setattr( self , a , data[ a ] ) def senders_from_bss( self , data ): + """ + Remplace la liste des utilisateurs autorisés à expédier avec l'adresse + de ce groupe à partir de données fournies par le serveur Partage. + + :param data: les données reçues du serveur Partage + + :return: l'ensemble des adresses autorisées + """ self._senders.clear( ) Group._get_set( self._senders , data , 'accounts' , 'account' ) return self.senders def to_bss( self ): + """ + Génère un dictionnaire pouvant être utilisé pour créer ou modifier un + groupe sur le serveur. + + :return: le dictionnaire contenant les attributs + """ rv = { } for a in ( 'name' , *Group.ATTRIBUTES ): value = getattr( self , a ) @@ -96,6 +178,25 @@ def to_bss( self ): @staticmethod def from_json( source , is_file = False ): + """ + Génère une instance à partir de données au format JSON. + + :param source: la source des données à partir desquelles on doit créer \ + une instance. Il peut s'agir de source JSON ou bien d'un \ + fichier, en fonction de la valeur du paramètre is_file. Dans \ + le second cas, on peut passer aussi bien le chemin du fichier \ + qu'une instance (par exemple de file) permettant le chargement \ + du JSON. + :param is_file: un booléen qui indique si le paramètre précédent est \ + un fichier (True) ou du source JSON (False). + + :raises TypeError: si certains des champs ont des types invalides + :raises NameException: si l'adresse contenue dans le champ name, ou \ + l'une des adresses de membres, l'un des alias ou l'une des \ + entrées d'autorisation sont invalides + + :return: l'instance créée + """ if is_file: if isinstance( source , str ): with open( source ) as json_file: @@ -108,16 +209,43 @@ def from_json( source , is_file = False ): @staticmethod def from_json_record( record ): + """ + Génère une instance à partir de données JSON décodées dans un + dictionnaire Python. + + :param record: le dictionnaire dans lequel les information ont été \ + décodées + + :raises TypeError: si certains des champs ont des types invalides + :raises NameException: si l'adresse contenue dans le champ name, ou \ + l'une des adresses de membres, l'un des alias ou l'une des \ + entrées d'autorisation sont invalides + + :return: l'instance créée + """ group = Group( record[ 'name' ] if 'name' in record else None ) for a in Group.ATTRIBUTES: if a in record: setattr( group , a , record[ a ] ) for s in Group.SETS: if s in record: - getattr( group , '_{}'.format( s ) ).update( record[ s ] ) + bad_addr = set([ a for a in record[ s ] + if not utils.checkIsMailAddress( a ) ]) + if not bad_addr: + getattr( group , '_{}'.format( s ) ).update( record[ s ] ) + continue + raise NameException( "Adresse(s) mail {} invalide(s)".format( + ', '.join( bad_addr ) ) ) return group def to_json_record( self ): + """ + Génère les données (sous la forme d'un dictionnaire Python) pour un + enregistrement JSON décrivant l'instance. + + :return: un dictionnaire contenant les champs appropriés pour \ + sauvegarde au format JSON + """ rv = { a : getattr( self , a ) for a in ( 'name' , *Group.ATTRIBUTES ) @@ -134,38 +262,65 @@ def to_json_record( self ): @property def members( self ): + """ + La liste des membres, triée par ordre alphabétique. + """ return sorted( self._members ) @property def members_set( self ): + """ + L'ensemble des membres, modifiable. + """ return self._members @property def has_members( self ): + """ + La présence, ou non, de membres dans le groupe + """ return bool( self._members ) @property def senders( self ): + """ + La liste des expéditeurs autorisés, triée par ordre alphabétique. + """ return sorted( self._senders ) @property def senders_set( self ): + """ + L'ensemble des expéditeurs autorisés, modifiable. + """ return self._senders @property def has_senders( self ): + """ + La présence, ou non, d'expéditeurs autorisés + """ return bool( self._senders ) @property def aliases( self ): + """ + La liste des alias du groupe, triée par ordre alphabétique. + """ return sorted( self._aliases ) @property def aliases_set( self ): + """ + L'ensemble des alias du groupe, modifiable + """ return self._aliases @property def has_aliases( self ): + """ + La présence, ou non, d'alias pour ce groupe + """ return bool( self._aliases ) #--------------------------------------------------------------------------- From d23b04c499bb7a6107f6853cfe3cf2604891b059 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emmanuel=20Beno=C3=AEt?= Date: Mon, 18 Jun 2018 17:48:44 +0200 Subject: [PATCH 4/7] services.GroupService - Documentation --- lib_Partage_BSS/services/GroupService.py | 129 ++++++++++++++++++++++- 1 file changed, 127 insertions(+), 2 deletions(-) diff --git a/lib_Partage_BSS/services/GroupService.py b/lib_Partage_BSS/services/GroupService.py index 3b0db7c..36aa7d0 100644 --- a/lib_Partage_BSS/services/GroupService.py +++ b/lib_Partage_BSS/services/GroupService.py @@ -1,4 +1,9 @@ # -*-coding:utf-8 -* +""" +Module contenant les méthodes permettant d'appeler les services de l'API BSS +concernant les groupes et listes de distribution. +""" + from lib_Partage_BSS.models.Group import Group from .GlobalService import callMethod from lib_Partage_BSS import services , utils @@ -9,8 +14,19 @@ #------------------------------------------------------------------------------- # Opérations d'interrogation - def getAllGroups( domain , limit = 100 , offset = 0 ): + """ + Lit la liste de tous les groupes depuis le serveur Partage. + + :param domain: le domaine de la recherche + :param limit: le nombre maximal d'entrées à renvoyer + :param offset: l'index du premier élément à renvoyer + + :raises ServiceException: la requête vers l'API a echoué + :raises DomainException: le domaine n'est pas valide + + :return: la liste des groupes, sous la forme d'instances du modèle. + """ data = { 'limit' : limit , 'offset' : offset , @@ -30,6 +46,20 @@ def getAllGroups( domain , limit = 100 , offset = 0 ): def getGroup( name , full_info = False ): + """ + Lit les informations concernant un groupe. + + :param name: l'adresse mail du groupe ou de la liste de distribution + :param full_info: une requête additionnelle permettant de lister \ + les expéditeurs autorisés doit-elle être effectuée ? + + :raises NameException: l'adresse de groupe spécifiée est incorrecte + :raises ServiceException: la requête vers l'API a echoué + :raises DomainException: le domaine n'est pas valide + + :return: le groupe, sous la forme d'une instance du modèle, ou bien None \ + si aucun groupe ne correspond au nom spécifié + """ if not utils.checkIsMailAddress( name ): raise NameException( "L'adresse mail {} n'est pas valide".format( name ) ) @@ -49,6 +79,21 @@ def getGroup( name , full_info = False ): def getSendAsGroup( name_or_group ): + """ + Lit la liste des utilisateurs autorisés à expédier du mail en utilisant + l'adresse d'un groupe comme expéditeur. + + :param name_or_group: l'adresse d'un groupe ou l'instance correspondant au \ + groupe pour lequel on veut lire la liste des autorisations. Si une \ + instance est passée, son champ 'senders' sera mis à jour + + :raises NameException: l'adresse de groupe spécifiée est incorrecte + :raises ServiceException: la requête vers l'API a echoué + :raises DomainException: le domaine n'est pas valide + + :return: l'ensemble des utilisateurs autorisés, ou None si le groupe n'a \ + pas été trouvé + """ if isinstance( name_or_group , Group ): name = name_or_group.name group = name_or_group @@ -74,6 +119,24 @@ def getSendAsGroup( name_or_group ): # Création & suppression def createGroup( name_or_group ): + """ + Crée un groupe ou une liste de distribution en se basant sur une instance du + modèle, ou simplement en utilisant un nom. + + Si une instance est utilisée et que des alias, des membres ou des + autorisations d'expéditions sont présents dans l'instance, ils seront + ajoutés au groupe sur Partage après sa création. + + :param name_or_group: le nom du groupe à créer, ou bien une instance du \ + modèle contenant les informations initiales au sujet du groupe. + + :raises TypeError: si le paramètre n'est ni un nom ni une instance du modèle + :raises NameError: si l'adresse du groupe à créer est invalide, ou si \ + l'une des autres informations de ce type (alias, membres, \ + autorisation) est incorrecte + :raises ServiceException: la requête vers l'API a echoué + :raises DomainException: le domaine n'est pas valide + """ is_group = isinstance( name_or_group , Group ) if is_group: if name_or_group.name is None: @@ -102,6 +165,16 @@ def createGroup( name_or_group ): addGroupSenders( name_or_group.name , name_or_group.senders ) def deleteGroup( name_or_group ): + """ + Supprime un groupe. + + :param name_or_group: le nom du groupe, ou bien l'instance de modèle \ + correspondante. + + :raises NameException: l'adresse de groupe spécifiée est incorrecte + :raises ServiceException: la requête vers l'API a echoué + :raises DomainException: le domaine n'est pas valide + """ if isinstance( name_or_group , Group ): if name_or_group.name is None: raise NameException( "L'adresse mail n'est pas renseignée" ) @@ -124,6 +197,17 @@ def deleteGroup( name_or_group ): # Opération de modification def modifyGroup( group ): + """ + Modifie les informations concernant un groupe. Les membres, alias et + autorisations ne sont pas affectés. + + :param group: l'instance du modèle contenant les nouvelles informations \ + ainsi que l'adresse du groupe à modifier + + :raises NameException: l'adresse de groupe spécifiée est incorrecte + :raises ServiceException: la requête vers l'API a echoué + :raises DomainException: le domaine n'est pas valide + """ response = callMethod( services.extractDomain( group.name ) , 'ModifyGroup' , group.to_bss( ) ) if not utils.checkResponseStatus( response[ 'status' ] ): @@ -132,6 +216,24 @@ def modifyGroup( group ): #------------------------------------------------------------------------------- def _group_set_op( name_or_group , entries , f_name , op_name ): + """ + Fonction interne utilisée pour effectuer les ajouts ou suppressions sur les + listes de membres, d'alias et d'autorisations d'expédition d'un groupe. + + :param name_or_group: le nom du groupe à modifier, ou l'instance du modèle \ + correspondante + :param entries: l'entrée ou les entrées à ajouter ou supprimer + :param f_name: le nom du champ tel qu'il doit être envoyé à l'API BSS; si \ + le nom finit par '[]', toutes les informations seront envoyées \ + en un seul appel + :param op_name: le nom de la méthode distante à utiliser + + :raises TypeError: le groupe n'est ni un nom, ni une instance de modèle + :raises NameException: l'adresse de groupe ou l'une des entrées est \ + incorrecte + :raises ServiceException: la requête vers l'API a echoué + :raises DomainException: le domaine n'est pas valide + """ if isinstance( entries , str ): entries = [ entries ] else: @@ -140,6 +242,8 @@ def _group_set_op( name_or_group , entries , f_name , op_name ): if isinstance( name_or_group , Group ): name = name_or_group.name else: + if not isinstance( name_or_group , str ): + raise TypeError name = name_or_group for n in ( name , *entries ): @@ -156,7 +260,6 @@ def _group_set_op( name_or_group , entries , f_name , op_name ): 'name' : name , f_name : entry , } - print( domain , op_name , repr( data ) ) response = callMethod( domain , op_name , data ) if not utils.checkResponseStatus( response[ 'status' ] ): raise ServiceException( response[ 'status' ] , @@ -164,6 +267,28 @@ def _group_set_op( name_or_group , entries , f_name , op_name ): def _group_diff_op( name_or_group , new_values , a_name , f_name , add_op_name , rem_op_name ): + """ + Fonction interne utilisée pour effectuer les modifications sur les listes de + membres, d'alias et d'autorisations d'expédition d'un groupe. + + :param name_or_group: le nom du groupe à modifier, ou l'instance du modèle \ + correspondante; si seul le nom est passé, getGroup() sera appelée + :param new_values: la nouvelle liste des entrées pour le champ concerné + :param a_name: le nom du champ dans l'instance de modèle + :param f_name: le nom du champ tel qu'il doit être envoyé à l'API BSS; si \ + le nom finit par '[]', toutes les informations seront envoyées \ + en un seul appel + :param add_op_name: le nom de la méthode distante à utiliser pour ajouter \ + des entrées + :param rem_op_name: le nom de la méthode distante à utiliser pour \ + supprimer des entrées + + :raises TypeError: le groupe n'est ni un nom, ni une instance de modèle + :raises NameException: l'adresse de groupe ou l'une des entrées est \ + incorrecte + :raises ServiceException: la requête vers l'API a echoué + :raises DomainException: le domaine n'est pas valide + """ if isinstance( name_or_group , Group ): group = name_or_group else: From 689295ba6ca2a6e24f03ed561d3e518694b02223 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emmanuel=20Beno=C3=AEt?= Date: Tue, 19 Jun 2018 09:10:41 +0200 Subject: [PATCH 5/7] =?UTF-8?q?services.GroupService=20-=20Termin=C3=A9=20?= =?UTF-8?q?documentation?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib_Partage_BSS/services/GroupService.py | 74 ++++++++++++++++++++++++ 1 file changed, 74 insertions(+) diff --git a/lib_Partage_BSS/services/GroupService.py b/lib_Partage_BSS/services/GroupService.py index 36aa7d0..01df598 100644 --- a/lib_Partage_BSS/services/GroupService.py +++ b/lib_Partage_BSS/services/GroupService.py @@ -310,41 +310,115 @@ def _group_diff_op( name_or_group , new_values , a_name , f_name , #------------------------------------------------------------------------------- def addGroupAliases( name_or_group , aliases ): + """ + Ajoute des alias à un groupe ou une liste de distribution. + + :param name_or_group: le nom du groupe, ou l'instance de modèle \ + correspondante + :param aliases: un alias sous la forme d'une chaîne, ou une collection \ + d'alias + """ _group_set_op( name_or_group , aliases , 'alias' , 'AddDistributionListAlias' ) def removeGroupAliases( name_or_group , aliases ): + """ + Supprime des alias d'un groupe ou d'une liste de distribution. + + :param name_or_group: le nom du groupe, ou l'instance de modèle \ + correspondante + :param aliases: un alias sous la forme d'une chaîne, ou une collection \ + d'alias + """ _group_set_op( name_or_group , aliases , 'alias' , 'RemoveDistributionListAlias' ) def updateGroupAliases( name_or_group , new_aliases ): + """ + Met à jour les alias d'un groupe ou d'une liste de distribution. + + :param name_or_group: le nom du groupe, ou l'instance de modèle \ + correspondante + :param aliases: un alias sous la forme d'une chaîne, ou une collection \ + d'alias + """ _group_diff_op( name_or_group , new_aliases , 'aliases' , 'alias' , 'AddDistributionListAlias' , 'RemoveDistributionListAlias' ) #------------------------------------------------------------------------------- def addGroupMembers( name_or_group , members ): + """ + Ajoute des membres à un groupe ou une liste de distribution. + + :param name_or_group: le nom du groupe, ou l'instance de modèle \ + correspondante + :param members: un membre sous la forme d'une chaîne, ou une collection \ + de membres + """ _group_set_op( name_or_group , members , 'members[]' , 'AddGroupMembers' ) def removeGroupMembers( name_or_group , members ): + """ + Supprime des membres d'un groupe ou d'une liste de distribution. + + :param name_or_group: le nom du groupe, ou l'instance de modèle \ + correspondante + :param members: un membre sous la forme d'une chaîne, ou une collection \ + de membres + """ _group_set_op( name_or_group , members , 'members[]' , 'RemoveGroupMembers' ) def updateGroupMembers( name_or_group , new_members ): + """ + Met à jour les membres d'un groupe ou d'une liste de distribution. + + :param name_or_group: le nom du groupe, ou l'instance de modèle \ + correspondante + :param members: un membre sous la forme d'une chaîne, ou une collection \ + de membres + """ _group_diff_op( name_or_group , new_members , 'members' , 'members[]' , 'AddGroupMembers' , 'RemoveGroupMembers' ) #------------------------------------------------------------------------------- def addGroupSenders( name_or_group , senders ): + """ + Ajoute des utilisateurs autorisés à un groupe ou une liste de distribution. + + :param name_or_group: le nom du groupe, ou l'instance de modèle \ + correspondante + :param members: un utilisateur autorisé sous la forme d'une chaîne, ou une \ + collection d'utilisateurs autorisés + """ _group_set_op( name_or_group , senders , 'account' , 'AddSendAsGroup' ) def removeGroupSenders( name_or_group , senders ): + """ + Supprime des utilisateurs autorisés d'un groupe ou d'une liste de + distribution. + + :param name_or_group: le nom du groupe, ou l'instance de modèle \ + correspondante + :param members: un utilisateur autorisé sous la forme d'une chaîne, ou une \ + collection d'utilisateurs autorisés + """ _group_set_op( name_or_group , senders , 'account' , 'DeleteSendAsGroup' ) def updateGroupSenders( name_or_group , new_senders ): + """ + Met à jour les utilisateurs autorisés d'un groupe ou d'une liste de + distribution. + + :param name_or_group: le nom du groupe, ou l'instance de modèle \ + correspondante + :param members: un utilisateur autorisé sous la forme d'une chaîne, ou une \ + collection d'utilisateurs autorisés + """ _group_diff_op( name_or_group , new_senders , 'senders' , 'account' , 'AddSendAsGroup' , 'DeleteSendAsGroup' ) From bf2af15a76686d6eeda58fd2f9d6e8b92af35279 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olivier=20Sala=C3=BCn?= Date: Mon, 25 Jun 2018 16:09:02 +0200 Subject: [PATCH 6/7] =?UTF-8?q?./cli-bss.sh=20--getAllGroups=20=C3=A9choua?= =?UTF-8?q?it=20s'il=20n'existe=20qu'un=20seul=20groupe=20avec=20l'erreur?= =?UTF-8?q?=20"AttributeError:=20'collections.OrderedDict'=20object=20has?= =?UTF-8?q?=20no=20attribute=20'name'".=20En=20effet=20la=20biblioth=C3=A8?= =?UTF-8?q?que=20lorsqu'on=20ne=20re=C3=A7oit=20qu'un=20seul=20=C3=A9l?= =?UTF-8?q?=C3=A9ment=20=20la=20biblioth=C3=A8que=20xmljson=20ne=20?= =?UTF-8?q?le=20retourne=20pas=20sous=20forme=20de=20List=20python.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib_Partage_BSS/services/GroupService.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/lib_Partage_BSS/services/GroupService.py b/lib_Partage_BSS/services/GroupService.py index 01df598..d76c7ae 100644 --- a/lib_Partage_BSS/services/GroupService.py +++ b/lib_Partage_BSS/services/GroupService.py @@ -36,13 +36,16 @@ def getAllGroups( domain , limit = 100 , offset = 0 ): if not utils.checkResponseStatus( response[ 'status' ] ): raise ServiceException( response[ 'status' ] , response[ 'message' ] ) - if len( response[ 'groups' ] ) <= 1: + # Attention : xmljson retourne un dictionnaire contenant une entrée également par attribut XML + # Donc il y a une entrée pour l'attribut "type" de "" + if len( response[ 'groups' ] ) == 1: return [] groups = response[ 'groups' ][ 'group' ] if isinstance( groups , list ): return [ Group.from_bss( e ) for e in groups ] - return Group.from_bss( groups ) + else: + return [ Group.from_bss(groups) ] def getGroup( name , full_info = False ): From d948ab487b93240401c53c7b1b3deabea9553c66 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olivier=20Sala=C3=BCn?= Date: Tue, 3 Jul 2018 15:38:54 +0200 Subject: [PATCH 7/7] Ajout exemples d'appels pour les nouvelles fonctions de gestion des groupes --- README.md | 19 ++++++++++++++++++- cli-bss.py | 20 +++++++++++++++++++- 2 files changed, 37 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 6d4d349..2734cdb 100644 --- a/README.md +++ b/README.md @@ -48,9 +48,9 @@ Les arguments `--domain` et `--domainKey` doivent être fournis pour chaque appe Exemples d'appel : ``` ./cli-bss.py --domain=x.fr --domainKey=yourKey --getAccount --email=user@x.fr -./cli-bss.py --domain=x.fr --domainKey=yourKey --getAccount --email=user@x.fr ./cli-bss.py --domain=x.fr --domainKey=yourKey --getAllAccounts --limit=200 --ldapQuery='mail=u*' ./cli-bss.py --domain=x.fr --domainKey=yourKey --createAccount --email=user@x.fr --cosId=yourCos --userPassword={SSHA}yourHash +./cli-bss.py --domain=x.fr --domainKey=yourKey --createAccountExt -f name user@x.fr -f zimbraHideInGal oui --userPassword={SSHA}someHash ./cli-bss.py --domain=x.fr --domainKey=yourKey --deleteAccount --email=user@x.fr ./cli-bss.py --domain=x.fr --domainKey=yourKey --modifyPassword --email=user@x.fr --userPassword={SSHA}yourHash ./cli-bss.py --domain=x.fr --domainKey=yourKey --lockAccount --email=user@x.fr @@ -65,6 +65,23 @@ Exemples d'appel : ./cli-bss.py --domain=x.fr --domainKey=yourKey --modifyAccountAliases --email=user@x.fr --alias=alias3@x.fr --alias=alias4@x.fr ./cli-bss.py --domain=x.fr --domainKey=yourKey --getCos --cosName=etu_s_xx ./cli-bss.py --domain=x.fr --domainKey=yourKey --getAllCos +./cli-bss.py --domain=x.fr --domainKey=yourKey --getAllGroups +./cli-bss.py --domain=x.fr --domainKey=yourKey --getGroup --email=testgroup1@x.fr +./cli-bss.py --domain=x.fr --domainKey=yourKey --getGroup --email=testgroup1@x.fr --fullData +./cli-bss.py --domain=x.fr --domainKey=yourKey --getSendAsGroup --email=testgroup1@x.fr +./cli-bss.py --domain=x.fr --domainKey=yourKey ---createGroup --email=testgroup2@x.fr +./cli-bss.py --domain=x.fr --domainKey=yourKey --createGroupExt -f name testgroup4@x.fr -f displayName 'Groupe 4' -f zimbraMailStatus disabled +./cli-bss.py --domain=x.fr --domainKey=yourKey --createGroupExt --jsonData=/tmp/data.json +./cli-bss.py --domain=x.fr --domainKey=yourKey --deleteGroup --email=testgroup6@x.fr +./cli-bss.py --domain=x.fr --domainKey=yourKey --addGroupAlias --email=testgroup4@x.fr --alias=alias@x.fr +./cli-bss.py --domain=x.fr --domainKey=yourKey --removeGroupAlias --email=testgroup4@x.fr --alias=alias@x.fr +./cli-bss.py --domain=x.fr --domainKey=yourKey --setGroupAlias --email=testgroup4@x.fr --alias=alias2@x.fr --alias=alias3@x.fr +./cli-bss.py --domain=x.fr --domainKey=yourKey --addGroupMember --email=testgroup1@x.fr --member=member01@x.fr +./cli-bss.py --domain=x.fr --domainKey=yourKey --removeGroupMember --email=testgroup1@x.fr --member=member01@x.fr +./cli-bss.py --domain=x.fr --domainKey=yourKey --setGroupMember --email=testgroup1@x.fr --member=member01@x.fr --member=member02@x.fr +./cli-bss.py --domain=x.fr --domainKey=yourKey --addGroupSender --email=testgroup1@x.fr --sender=sender03@x.fr +./cli-bss.py --domain=x.fr --domainKey=yourKey --removeGroupSender --email=testgroup1@x.fr --sender=sender03@x.fr +./cli-bss.py --domain=x.fr --domainKey=yourKey --setGroupSender --email=testgroup1@x.fr --sender=sender03@x.fr --sender=sender05@x.fr ``` ## License diff --git a/cli-bss.py b/cli-bss.py index 89c77d3..6755545 100755 --- a/cli-bss.py +++ b/cli-bss.py @@ -35,7 +35,25 @@ "./cli-bss.py --domain=x.fr --domainKey=yourKey --removeAccountAlias --email=user@x.fr --alias=alias1@x.fr --alias=alias2@x.fr\n" + \ "./cli-bss.py --domain=x.fr --domainKey=yourKey --modifyAccountAliases --email=user@x.fr --alias=alias3@x.fr --alias=alias4@x.fr\n" + \ "./cli-bss.py --domain=x.fr --domainKey=yourKey --getCos --cosName=etu_s_xx\n" + \ - "./cli-bss.py --domain=x.fr --domainKey=yourKey --getAllCos\n" + "./cli-bss.py --domain=x.fr --domainKey=yourKey --getAllCos\n" + \ + "./cli-bss.py --domain=x.fr --domainKey=yourKey --getAllGroups\n" + \ + "./cli-bss.py --domain=x.fr --domainKey=yourKey --getGroup --email=testgroup1@x.fr\n" + \ + "./cli-bss.py --domain=x.fr --domainKey=yourKey --getGroup --email=testgroup1@x.fr --fullData\n" + \ + "./cli-bss.py --domain=x.fr --domainKey=yourKey --getSendAsGroup --email=testgroup1@x.fr\n" + \ + "./cli-bss.py --domain=x.fr --domainKey=yourKey ---createGroup --email=testgroup2@x.fr\n" + \ + "./cli-bss.py --domain=x.fr --domainKey=yourKey --createGroupExt -f name testgroup4@x.fr -f displayName 'Groupe 4' -f zimbraMailStatus disabled\n" + \ + "./cli-bss.py --domain=x.fr --domainKey=yourKey --createGroupExt --jsonData=/tmp/data.json\n" + \ + "./cli-bss.py --domain=x.fr --domainKey=yourKey --deleteGroup --email=testgroup6@x.fr\n" + \ + "./cli-bss.py --domain=x.fr --domainKey=yourKey --addGroupAlias --email=testgroup4@x.fr --alias=alias@x.fr\n" + \ + "./cli-bss.py --domain=x.fr --domainKey=yourKey --removeGroupAlias --email=testgroup4@x.fr --alias=alias@x.fr\n" + \ + "./cli-bss.py --domain=x.fr --domainKey=yourKey --setGroupAlias --email=testgroup4@x.fr --alias=alias2@x.fr --alias=alias3@x.fr\n" + \ + "./cli-bss.py --domain=x.fr --domainKey=yourKey --addGroupMember --email=testgroup1@x.fr --member=member01@x.fr\n" + \ + "./cli-bss.py --domain=x.fr --domainKey=yourKey --removeGroupMember --email=testgroup1@x.fr --member=member01@x.fr\n" + \ + "./cli-bss.py --domain=x.fr --domainKey=yourKey --setGroupMember --email=testgroup1@x.fr --member=member01@x.fr --member=member02@x.fr\n" + \ + "./cli-bss.py --domain=x.fr --domainKey=yourKey --addGroupSender --email=testgroup1@x.fr --sender=sender03@x.fr\n" + \ + "./cli-bss.py --domain=x.fr --domainKey=yourKey --removeGroupSender --email=testgroup1@x.fr --sender=sender03@x.fr\n" + \ + "./cli-bss.py --domain=x.fr --domainKey=yourKey --setGroupSender --email=testgroup1@x.fr --sender=sender03@x.fr --sender=sender05@x.fr\n" + parser = argparse.ArgumentParser(description="Client en ligne de commande pour l'API BSS Partage", epilog=epilog, formatter_class=argparse.RawDescriptionHelpFormatter) parser.add_argument('--domain', required=True, metavar='mondomaine.fr', help="domaine cible sur le serveur Partage") parser.add_argument('--domainKey', required=True, metavar="6b7ead4bd425836e8c", help="clé du domaine cible")