lc_ldap/attributs.py
2014-09-22 00:21:54 +02:00

1808 lines
54 KiB
Python
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
""" Définition des classes permettant d'instancier les attributs LDAP. """
#
# Copyright (C) 2010-2013 Cr@ns <roots@crans.org>
# Authors: Antoine Durand-Gasselin <adg@crans.org>
# Nicolas Dandrimont <olasd@crans.org>
# Valentin Samir <samir@crans.org>
# Vincent Le Gallic <legallic@crans.org>
# Pierre-Elliott Bécue <becue@crans.org>
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
# * Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
# * Neither the name of the Cr@ns nor the names of its contributors may
# be used to endorse or promote products derived from this software
# without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT
# HOLDER> BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
import re
import sys
import ssl
import netaddr
import time
import base64
import datetime
import functools
import smtplib
import random
import string
from unicodedata import normalize
from crans_utils import format_tel, format_mac, mailexist, validate_name, ip4_of_rid, ip6_of_mac, fetch_cert_info
from crans_utils import to_generalized_time_format, from_generalized_time_format
import itertools
sys.path.append("/usr/scripts")
import cranslib.deprecated
from gestion import config
from gestion import annuaires_pg
#: Serveur SMTP
smtpserv = "smtp.crans.org"
### Les droits
# en cas de typo, l'appel d'une variable plante, on préfèrera donc les utiliser en lieu et place
# des chaînes de caractères
#: Chaine de caractère des droits nounou
nounou = u"Nounou"
#: Droit cableur
cableur = u"Cableur"
#: Droit apprenti
apprenti = u"Apprenti"
#: Droit trésorier
tresorier = u"Tresorier"
#: Droit bureau
bureau = u"Bureau"
#: Droit imprimeur
imprimeur = u"Imprimeur"
#: Droit modérateur
moderateur = u"Moderateur"
#: Droit multimachine
multimachines = u"Multimachines"
#: Droit Webmaster
webmaster = u"Webmaster"
#: Droit Webradio
webradio = u"Webradio"
#: On peut faire subir des choses à un objet si on est son parent
parent = u"parent"
#: On peut faire subir des choses à un objet si on est cet objet
soi = u"soi"
#: Le responsable d'un club peut lui faire subir des choses
respo = u"responsable"
#: Liste de tous les droits
TOUS_DROITS = [nounou, apprenti, bureau, tresorier, imprimeur, moderateur, multimachines, cableur, webmaster, webradio]
#: Liste des droits forts
DROITS_ELEVES = [nounou, bureau, tresorier]
#: Liste des droits intérmédiaires
DROITS_MOYEN = [apprenti, moderateur]
#: Liste des droits moins sensibles
DROITS_FAIBLES = [cableur, imprimeur, multimachines]
#: Qui a le droit de modifier quels droits
DROITS_SUPERVISEUR = { nounou : TOUS_DROITS,
bureau : DROITS_FAIBLES + [bureau, tresorier],
}
class SingleValueError(ValueError):
"""Erreur levée si on essaye de multivaluer un champ monovalué."""
pass
class UniquenessError(EnvironmentError):
"""Erreur levée si on essaye de créer un objet dont le ``dn`` existe déjà."""
pass
class OptionalError(EnvironmentError):
"""Erreur levée si on essaye de créer un objet sans fournir un attribut obligatoire."""
pass
def attrify(val, attr, conn, Parent=None):
"""Transforme un n'importe quoi en :py:class:`Attr`.
Doit effectuer les normalisations et sanity check si un str ou un
unicode est passé en argument.
Devrait insulter en cas de potentiel problème d'encodage.
"""
if isinstance(val, Attr):
return val
else:
attr_classe = AttributeFactory.get(attr, fallback=Attr)
if not isinstance(val, unicode) and not (attr_classe.python_type and isinstance(val, attr_classe.python_type)):
cranslib.deprecated.usage("attrify ne devrait être appelé qu'avec des unicode (%r)" % val, level=3)
val = val.decode('utf-8')
return attr_classe(val, conn, Parent)
class AttrsList(list):
def __init__(self, Parent, attr, attr_list):
super(AttrsList, self).__init__(attr_list)
self._parent = Parent
self._attr = attr
def _start(self):
if self._parent[self._attr] != self:
raise ValueError("La variable que vous utilisez n'est pas à jour (modifications conccurentes ?)")
def _commit(self):
try:
self._parent[self._attr] = self
finally:
super(AttrsList, self).__init__(self._parent[self._attr])
def __delitem__(self, index):
self._start()
super(AttrsList, self).__delitem__(index)
self._commit()
def __setitem__(self, index, value):
self._start()
super(AttrsList, self).__setitem__(index, value)
self._commit()
def append(self, val):
self._start()
super(AttrsList, self).append(val)
self._commit()
def extend(self, vals):
self._start()
super(AttrsList, self).extend(vals)
self._commit()
def insert(self, pos, val):
self._start()
super(AttrsList, self).insert(pos, val)
self._commit()
def remove(self, val):
self._start()
super(AttrsList, self).remove(val)
self._commit()
def sort(self):
self._start()
super(AttrsList, self).sort()
self._commit()
def pop(self, index=None):
self._start()
ret = super(AttrsList, self).pop(index) if index else super(AttrsList, self).pop()
self._commit()
return ret
def reverse(self):
self._start()
super(AttrsList, self).reverse()
self._commit()
class AttrsDict(dict):
def __init__(self, conn, uldif={}, Parent=None):
super(AttrsDict, self).__init__(uldif)
self._conn = conn
self._parent = Parent
self._iterator = None
def __getitem__(self, attr):
values = super(AttrsDict, self).__getitem__(attr)
if not isinstance(values, list):
values = [values]
output = []
for val in values:
output.append(attrify(val, attr, self._conn, self._parent))
self[attr] = output
return output
def __setitem__(self, attr, values):
# ne devrait par arriver
if not isinstance(values, list):
values = [ values ]
if self._parent.mode in ['w', 'rw']:
if AttributeFactory.get(attr, fallback=Attr).singlevalue and len(values) > 1:
raise SingleValueError("L'attribut %s doit être monovalué." % (attr,))
super(AttrsDict, self).__setitem__(attr, values)
else:
super(AttrsDict, self).__setitem__(attr, values)
def get(self, value, default_value):
try:
return self[value]
except KeyError:
return default_value
def items(self):
return [(key, self[key]) for key in self]
def to_ldif(self):
"""
Transforme le dico en ldif valide au sens openldap.
Ce ldif est celui qui sera transmis à la base.
"""
ldif = {}
for attr, vals in self.items():
ldif[attr] = [ str(val) for val in vals ]
return ldif
class Attr(object):
"""Objet représentant un attribut.
**Paramètres :**
* ``val`` : valeur de l'attribut
* ``ldif`` : objet contenant l'attribut (permet de faire les validations sur l'environnement)
"""
legend = "Human-readable description of attribute"
singlevalue = False
optional = True
conn = None
unique = False
unique_exclue = []
#: Le nom de l'attribut dans le schéma LDAP
ldap_name = None
python_type = None
binary = False
default = None
"""La liste des droits qui suffisent à avoir le droit de modifier la valeur"""
can_modify = [nounou]
"""Qui peut voir l'attribut. Par défaut, les Nounous et les Apprentis
peuvent tout voir. Par transparence, et par utilité, on autorise par
défaut l'adhérent à voir les données le concernant."""
can_view = [nounou, apprenti, soi, parent, respo]
"""Catégorie de l'attribut (pour affichage futur)"""
category = 'other'
def __init__(self, val, conn, Parent):
"""Crée un nouvel objet représentant un attribut."""
self.value = None
self.conn = conn
assert isinstance(val, unicode) or (self.python_type and isinstance(val, self.python_type))
self.parent = Parent
self.parse_value(val)
def __hash__(self):
if not self.parent or self.parent.mode in ['w', 'rw']:
raise TypeError("Mutable structure are not hashable, please use mode = 'ro' to do so")
if hasattr(self.value, "__hash__") and self.value.__hash__ is not None:
return self.value.__hash__()
else:
return str(self).__hash__()
def __getattr__(self, name):
return getattr(self.value, name)
def __ne__(self, obj):
return not self == obj
def __eq__(self, item):
if isinstance(item, self.__class__):
return str(self) == str(item)
elif isinstance(item, Attr) and item.python_type == self.python_type:
return item.value == self.value
elif self.python_type and isinstance(item, self.python_type):
return self.value == item
elif isinstance(item, str):
return str(self) == item
elif isinstance(item, unicode):
return unicode(self) == item
else:
return False
def __nonzero__(self):
if self.value:
return True
else:
return False
def parse_value(self, val):
"""Transforme l'attribut pour travailler avec notre validateur
Le ldif est en dépendance car à certains endroits, il peut servir
(par exemple, pour l'ipv6, ou l'ipv4…"""
self.value = val
def __str__(self):
return self.__unicode__().encode('utf-8')
def __repr__(self):
return str(self.__class__) + " : " + repr(self.value)
def __unicode__(self):
if isinstance(self.value, unicode):
return self.value
else:
return unicode(self.value)
def check_uniqueness(self, liste_exclue):
"""Vérifie l'unicité dans la base de la valeur (``mailAlias``, ``chbre``,
etc...)"""
attr = self.__class__.__name__
if unicode(self) in liste_exclue + self.unique_exclue:
return
if self.unique:
res = self.conn.search(u'%s=%s' % (attr, str(self)))
if res:
raise UniquenessError("%s déjà existant" % attr, [r.dn for r in res])
def is_modifiable(self, liste_droits):
"""
L'attribut est-il modifiable par un des droits dans liste_droits ?
"""
return not set(liste_droits).isdisjoint(self.can_modify)
class AttributeFactory(object):
"""Utilisée pour enregistrer toutes les classes servant à instancier un attribut LDAP.
Elle sert à les récupérer à partir de leur nom LDAP.
Cette classe n'est jamais instanciée.
"""
_classes = {}
@classmethod
def register(cls, name, classe):
"""Enregistre l'association ``name`` -> ``classe``"""
cls._classes[name] = classe
@classmethod
def get(cls, name, fallback=Attr):
"""Retourne la classe qui a ``name`` pour ``ldap_name``.
Si elle n'existe pas, renvoie :py:class:`Attr` (peut être override en précisant ``fallback``)
"""
return cls._classes.get(name, fallback)
def crans_attribute(classe):
"""Pour décorer les classes permettant d'instancier des attributs LDAP,
afin de les enregistrer dans :py:class:`AttributeFactory`.
"""
AttributeFactory.register(classe.ldap_name, classe)
return classe
@crans_attribute
class objectClass(Attr):
singlevalue = False
optional = False
legend = "entité"
ldap_name = "objectClass"
""" Personne ne doit modifier de classe """
can_modify = []
""" Internal purpose (et à fin pédagogique) """
can_view = [nounou, apprenti]
def parse_value(self, val):
if val not in [ 'top', 'organizationalUnit', 'posixAccount', 'shadowAccount',
'proprio', 'adherent', 'club', 'machine', 'machineCrans',
'borneWifi', 'machineWifi', 'machineFixe', 'x509Cert', 'TLSACert',
'baseCert', 'cransAccount', 'service', 'facture', 'freeMid', 'privateKey' ]:
raise ValueError("Pourquoi insérer un objectClass=%r ?" % val)
else:
self.value = unicode(val)
class intAttr(Attr):
python_type = int
def parse_value(self, val):
if self.python_type(val) < 0:
raise ValueError("Valeur entière invalide : %r" % val)
self.value = self.python_type(val)
def __unicode__(self):
return unicode(self.value)
class boolAttr(Attr):
python_type = bool
def parse_value(self, val):
if isinstance(val, self.python_type):
self.value = val
elif val.lower() in [u'true', u'ok']:
self.value = True
elif val.lower() == u'false':
self.value = False
else:
raise ValueError("%r doit être un booléen !" % val)
def __unicode__(self):
return unicode(self.value).upper()
@crans_attribute
class aid(intAttr):
singlevalue = True
optional = True
legend = u"Identifiant de l'adhérent"
category = 'id'
unique = True
ldap_name = "aid"
""" Personne ne devrait modifier un attribut d'identification """
can_modify = []
can_view = [nounou, apprenti, cableur]
def parse_value(self, aid):
self.value = int(aid)
@crans_attribute
class uid(Attr):
singlevalue = True
option = False
legend = u"L'identifiant canonique de l'adhérent"
category = 'perso'
unique = True
can_modify = [nounou]
ldap_name = "uid"
@crans_attribute
class preferedLanguage(Attr):
singlevalue = True
option = True
legend = u"La langue préférée de l'adhérent"
category = 'perso'
unique = False
ldap_name = "preferedLanguage"
@crans_attribute
class nom(Attr):
singlevalue = True
optional = False
legend = "Nom"
category = 'perso'
can_modify = [nounou, cableur]
ldap_name = "nom"
def parse_value(self, nom):
if self.parent != None:
if u'club' in [o.value for o in self.parent['objectClass']]:
self.value = validate_name(nom,"0123456789\[\]")
else:
self.value = validate_name(nom)
else:
self.value = validate_name(nom)
@crans_attribute
class prenom(Attr):
singlevalue = True
optional = False
legend = u"Prénom"
category = 'perso'
can_modify = [nounou, cableur]
ldap_name = "prenom"
def parse_value(self, prenom):
self.value = validate_name(prenom)
@crans_attribute
class compteWiki(Attr):
singlevalue = False
optional = True
legend = u"Compte WiKi"
category = 'perso'
can_modify = [nounou, cableur, soi]
ldap_name = "compteWiki"
#def parse_value(self, compte):
#self.value = validate_name(compte)
# TODO: validate with mdp for user definition here ?
@crans_attribute
class tel(Attr):
singlevalue = True
optional = False
legend = u"Téléphone"
category = 'perso'
can_modify = [soi, nounou, cableur]
ldap_name = "tel"
def parse_value(self, tel):
self.value = format_tel(tel)
if len(self.value) == 0:
raise ValueError("Numéro de téléphone invalide (%r)" % tel)
class yearAttr(intAttr):
singlevalue = False
optional = True
def parse_value(self, annee):
annee = self.python_type(annee)
if annee < 1998:
raise ValueError("Année invalide (%r)" % annee)
self.value = annee
@crans_attribute
class paiement(yearAttr):
legend = u"Paiement"
can_modify = [cableur, nounou, tresorier]
category = 'perso'
ldap_name = "paiement"
@crans_attribute
class carteEtudiant(Attr):
legend = u"Carte d'étudiant"
category = 'perso'
can_modify = [cableur, nounou, tresorier]
ldap_name = "carteEtudiant"
def parse_value(self, data):
self.value = data
@crans_attribute
class derniereConnexion(intAttr):
legend = u"Dernière connexion"
can_modify = [nounou, cableur, soi] # il faut bien pouvoir le modifier pour le mettre à jour
ldap_name = "derniereConnexion"
class generalizedTimeFormat(Attr):
"""Classe définissant un ensemble de données pour manipuler
une donnée de temps suivant la RFC 4517
"""
default = "19700101000000Z"
def __float__(self):
return self._stamp
def __int__(self):
return int(self._stamp)
def __eq__(self, othertime):
if isinstance(self, othertime):
return self._stamp == othertime._stamp
else:
resource = generalizedTimeFormat(othertime, conn=None, Parent=None)
return self._stamp == resource._stamp
def __neq__(self, othertime):
return not (self == othertime)
def __lt__(self, othertime):
if isinstance(othertime, generalizedTimeFormat):
return self._stamp < othertime._stamp
else:
resource = generalizedTimeFormat(othertime, conn=None, Parent=None)
return self._stamp < resource._stamp
def __le__(self, othertime):
return not (self > othertime)
def __ge__(self, othertime):
return not (self < othertime)
def __gt__(self, othertime):
return not (self < othertime) and not (self == othertime)
def parse_value(self, gtf):
if isinstance(gtf, str) or isinstance(gtf, unicode):
if not ('Z' in gtf or '+' in gtf or '-' in gtf):
self._stamp = gtf
self.value = to_generalized_time_format(float(gtf))
else:
self._stamp = from_generalized_time_format(gtf)
self.value = gtf
elif isinstance(gtf, float):
self._stamp = gtf
self.value = to_generalized_time_format(gtf)
@crans_attribute
class debutAdhesion(generalizedTimeFormat):
legend = u"Date de début de l'adhésion"
category = 'Adh'
can_modify = [cableur, nounou]
ldap_name = 'debutAdhesion'
@crans_attribute
class finAdhesion(generalizedTimeFormat):
legend = u"Date de fin de l'adhésion"
category = 'Adh'
can_modify = [cableur, nounou]
ldap_name = 'finAdhesion'
@crans_attribute
class debutConnexion(generalizedTimeFormat):
legend = u"Date de début de la connexion"
category = 'Adh'
can_modify = [cableur, nounou]
ldap_name = 'debutConnexion'
@crans_attribute
class finConnexion(generalizedTimeFormat):
legend = u"Date de fin de la connexion"
category = 'Adh'
can_modify = [cableur, nounou]
ldap_name = 'finConnexion'
@crans_attribute
class mail(Attr):
singlevalue = False
optional = False
unique = True
legend = u"Adresse mail de l'adhérent"
can_modify = [soi, nounou, cableur]
category = 'mail'
ldap_name = "mail"
def is_modifiable(self, liste_droits):
"""
Une adresse mail n'est modifiable que si on a au moins autant de droits
que la personne à qui est l'adresse mail
"""
modifiables = set()
for i in liste_droits:
if i in DROITS_SUPERVISEUR:
modifiables = modifiables.union(DROITS_SUPERVISEUR[i])
modifiables = list(modifiables)
for droit in self.parent.get('droits', []):
if droit not in modifiables and droit in TOUS_DROITS:
return False
return super(mail, self).is_modifiable(liste_droits)
def check_uniqueness(self, liste_exclue):
attr = self.__class__.__name__
if str(self) in liste_exclue:
return
if attr in ["mailAlias", "canonicalAlias", 'mail']:
mail, end = str(self).split('@', 1)
if end.startswith('crans'):
try:
smtp = smtplib.SMTP(smtpserv)
smtp.putcmd("vrfy", mail)
res = smtp.getreply()[0] in [250, 252]
smtp.close()
except:
raise ValueError('Serveur de mail injoignable')
if res:
raise ValueError("Le mail %s est déjà pris." % (str(self)))
else:
check = self.conn.search(u'mail=%s' % mail)
if len(check) >= 1:
raise ValueError("Le mail %s est déjà pris." % (str(self)))
def parse_value(self, mail):
if not re.match(u'^[-_.0-9A-Za-z]+@([A-Za-z0-9]{1}[A-Za-z0-9-_]+[.])+[a-z]{2,6}$', mail):
raise ValueError("%s invalide %r" % (self.legend, mail))
self.value = mail
@crans_attribute
class canonicalAlias(mail):
singlevalue = True
optional = True
unique = True
legend = u"Alias mail canonique"
category = 'mail'
ldap_name = "canonicalAlias"
def parse_value(self, mail):
mail = u".".join([ a.capitalize() for a in mail.split(u'.', 1) ])
super(canonicalAlias, self).parse_value(mail)
@crans_attribute
class mailAlias(mail):
singlevalue = False
optional = True
unique = True
legend = u"Alias mail"
can_modify = [soi, cableur, nounou]
category = 'mail'
ldap_name = "mailAlias"
def parse_value(self, mail):
mail = mail.lower()
super(mailAlias, self).parse_value(mail)
@crans_attribute
class mailExt(mail):
singlevalue = False
optional = True
unique = True
legend = u"Mail de secours"
can_modify = [soi, cableur, nounou]
category = 'mail'
ldap_name = "mailExt"
def parse_value(self, mail):
mail = mail.lower()
# comme on utilise mailExt comme mail de secours si l'utilisateur
# à perdu ses id crans, ça ne sert à rien de mettre ne adresse crans
if mail.endswith("@crans.org"):
raise ValueError("%s ne devrait pas être une adresse crans." % str(self.legend))
super(mailExt, self).parse_value(mail)
@crans_attribute
class mailInvalide(boolAttr):
optional = True
legend = u"Mail invalide"
can_modify = [bureau, nounou]
ldap_name = "mailInvalide"
@crans_attribute
class contourneGreylist(boolAttr):
optionnal = True
legend = u"Contourner la greylist"
category = 'mail'
can_modify = [soi]
ldap_name = "contourneGreylist"
def __unicode__(self):
return u"OK"
@crans_attribute
class etudes(Attr):
singlevalue = False
optional = True
legend = u"Études"
can_modify = [soi, cableur, nounou]
category = 'perso'
ldap_name = "etudes"
@crans_attribute
class chbre(Attr):
singlevalue = True
optional = False
unique = True
unique_exclue = [u'????', u'EXT']
legend = u"Chambre sur le campus"
can_modify = [soi, cableur, nounou]
category = 'perso'
ldap_name = "chbre"
def parse_value(self, chambre):
if self.parent != None and u'club' in [str(o) for o in self.parent['objectClass']]:
if chambre in annuaires_pg.locaux_clubs():
self.value = chambre
else:
raise ValueError("Club devrait etre en XclN, pas en %r" % chambre)
elif chambre in (u"EXT", u"????"):
self.value = chambre
else:
try:
annuaires_pg.chbre_prises(chambre[0], chambre[1:])
except NameError:
import annuaires_pg_test
annuaires_pg_test.chbre_prises(chambre[0], chambre[1:])
self.value = chambre
@crans_attribute
class droits(Attr):
singlevalue = False
optional = True
legend = u"Droits sur les serveurs"
can_modify = [nounou, bureau]
category = 'perso'
ldap_name = "droits"
def parse_value(self, droits):
# if val.lower() not in [i.lower() for i in TOUS_DROITS]:
# raise ValueError("Ces droits n'existent pas (%r)" % val)
self.value = droits.capitalize()
def is_modifiable(self, liste_droits):
"""
Le droit est-il modifiable par un des droits dans liste_droits ?
L'idée étant que pour modifier un droit, il faut avoir un droit plus fort
"""
modifiables = set()
for i in liste_droits:
if i in DROITS_SUPERVISEUR:
modifiables = modifiables.union(DROITS_SUPERVISEUR[i])
modifiables = list(modifiables)
return self.value in modifiables and super(droits, self).is_modifiable(liste_droits)
@crans_attribute
class solde(Attr):
python_type = float
singlevalue = True
optional = True
legend = u"Solde d'impression"
can_modify = [imprimeur, nounou, tresorier]
ldap_name = "solde"
def parse_value(self, solde):
# on évite les dépassements, sauf si on nous dit de ne pas vérifier
#if not (float(solde) >= config.impression.decouvert and float(solde) <= 1024.):
# raise ValueError("Solde invalide: %r" % solde)
self.value = self.python_type(solde)
def __unicode__(self):
return u"%.2f" % self.value
class dnsAttr(Attr):
category = 'dns'
ldap_name = "dnsAttr"
python_type = unicode
def parse_value(self, val):
val = val.lower()
names = val.split('.')
for name in names:
if not re.match('^[a-z0-9](-*[a-z0-9]+)*$', name):
raise ValueError("Nom d'hote invalide %r" % val)
self.value = val
@crans_attribute
class host(dnsAttr):
singlevalue = True
unique = True
optional = False
hname = legend = u"Nom d'hôte"
can_modify = [parent, nounou, cableur]
category = 'base_tech'
ldap_name = "host"
def check_uniqueness(self, liste_exclue):
attr = self.__class__.__name__
if str(self) in liste_exclue:
return
if attr in ["host", "hostAlias"]:
res = self.conn.search(u'(|(host=%s)(hostAlias=%s))' % ((str(self),)*2))
if res:
raise ValueError("Hôte déjà existant", [r.dn for r in res])
@crans_attribute
class hostAlias(host):
singlevalue = False
unique = True
optional = True
legend = u'Alias de nom de machine'
can_modify = [nounou, cableur]
ldap_name = "hostAlias"
@crans_attribute
class macAddress(Attr):
singlevalue = True
optional = False
legend = u"Adresse physique de la carte réseau"
hname = "Adresse MAC"
category = 'base_tech'
can_modify = [parent, nounou, cableur]
ldap_name = "macAddress"
default = u'<automatique>'
def parse_value(self, mac):
mac = mac.lower()
if mac == macAddress.default:
self.value = mac
else:
self.value = format_mac(mac)
def __unicode__(self):
return unicode(self.value).lower()
@crans_attribute
class ipHostNumber(Attr):
singlevalue = True
optional = True
unique = True
legend = u"Adresse IPv4 de la machine"
hname = "IPv4"
category = 'base_tech'
can_modify = [nounou, cableur]
ldap_name = "ipHostNumber"
python_type = netaddr.IPAddress
default = u'<automatique>'
def parse_value(self, ip):
if ip == '<automatique>':
ip = ip4_of_rid(str(self.parent['rid'][0]))
self.value = self.python_type(ip)
def __unicode__(self):
return unicode(self.value)
@crans_attribute
class ip6HostNumber(Attr):
singlevalue = True
optional = True
unique = True
legend = u"Adresse IPv6 de la machine"
hname = "IPv6"
category = 'base_tech'
# beware: parent est nécessaire à la modification des adresses macs par
# les adhérents lambda (see validate_changes)
can_modify = [nounou, cableur, parent]
ldap_name = "ip6HostNumber"
python_type = netaddr.IPAddress
default = u'<automatique>'
def parse_value(self, ip6):
if ip6 == '<automatique>':
ip6 = ip6_of_mac(str(self.parent['macAddress'][0]), int(str(self.parent['rid'][0])))
self.value = self.python_type(ip6)
def __unicode__(self):
return unicode(self.value)
@crans_attribute
class mid(intAttr):
singlevalue = True
optional = False
unique = True
legend = u"Identifiant de machine"
category = 'id'
ldap_name = "mid"
@crans_attribute
class rid(intAttr):
singlevalue = True
optional = True
unique = True
legend = u"Identifiant réseau de machine"
category = 'id'
can_modify = [nounou]
ldap_name = "rid"
def parse_value(self, rid):
rid = self.python_type(rid)
# On veut éviter les rid qui recoupent les ipv4 finissant par
# .0 ou .255
plages = [itertools.chain(*[xrange(plage[0], plage[1]+1) for plage in value]) for (key, value) in config.rid_primaires.iteritems() if ('v6' not in key) and ('special' not in key)]
for plage in plages:
if rid in plage:
if rid % 256 == 0 or rid % 256 == 255:
rid = -1
break
else:
continue
else:
continue
self.value = rid
def __unicode__(self):
return unicode(self.value)
@crans_attribute
class ipsec(Attr):
singlevalue = False
optional = True
legend = u'Clef wifi'
category = 'wifi'
ldap_name = "ipsec"
can_modify = [nounou, parent]
default = u'auto'
def parse_value(self, val):
if len(val) in [10, 22]:
self.value = val
else:
val = u'auto'
if val == u"auto":
self.value = u''.join( random.choice(filter(lambda x: x != 'l' and x != 'o', string.lowercase) + filter(lambda x: x != '1' and x != '0', string.digits)) for i in range(10))
@crans_attribute
class puissance(Attr):
singlevalue = True
optional = True
legend = u"puissance d'émission pour les bornes wifi"
category = 'wifi'
can_modify = [nounou]
ldap_name = "puissance"
@crans_attribute
class canal(intAttr):
singlevalue = True
optional = True
legend = u'Canal d\'émission de la borne'
category = 'wifi'
can_modify = [nounou]
ldap_name = "canal"
@crans_attribute
class hotspot(boolAttr):
singlevalue = True
optional = True
legend = u'Hotspot'
category = 'wifi'
can_modify = [nounou]
ldap_name = "hotspot"
@crans_attribute
class positionBorne(Attr):
legend = u"Position de la borne"
category = "wifi"
can_modify = [nounou]
singlevalue = True
optional = True
ldap_name = "positionBorne"
@crans_attribute
class nvram(Attr):
legend = u"Configuration speciale"
optional = True
can_modify = [nounou]
ldap_name = "nvram"
class portAttr(Attr):
singlevalue = False
optional = True
legend = u'Ouverture de port'
category = 'firewall'
can_modify = [nounou]
def parse_value(self, port):
if ":" in port:
a,b = port.split(":", 1)
if a:
if int(a) <0 or int(a)> 65535:
raise ValueError("Port invalide: %r" % port)
else:
a = 0
if b:
if int(a) <0 or int(a)> 65535:
raise ValueError("Port invalide: %r" % port)
else:
b = 65535
self.value = [int(a), int(b)]
else:
if int(port) <0 or int(port)> 65535:
raise ValueError("Port invalide: %r" % port)
self.value = [int(port)]
def __unicode__(self):
if len(self.value) > 1:
a, b = self.value
a = '' if a == 0 else str(a)
b = '' if b == 65535 else str(b)
return unicode('%s:%s' % (a, b))
else:
return unicode(self.value[0])
@crans_attribute
class portTCPout(portAttr):
legend = u"Port TCP ouvert vers l'extérieur"
ldap_name = "portTCPout"
@crans_attribute
class portTCPin(portAttr):
legend = u"Port TCP ouvert depuis l'extérieur"
ldap_name = "portTCPin"
@crans_attribute
class portUDPout(portAttr):
legend = u"Port UDP ouvert vers l'extérieur"
ldap_name = "portUDPout"
@crans_attribute
class portUDPin(portAttr):
legend = u"Port UDP ouvert depuis l'extérieur"
ldap_name = "portUDPin"
@crans_attribute
class exempt(Attr):
legend = u"Exemption vers une IP"
ldap_name = "exempt"
python_type = netaddr.IPNetwork
def parse_value(self, net):
self.value = self.python_type(net)
def __unicode__(self):
return unicode(self.value)
@crans_attribute
class nombrePrises(intAttr):
legend = u"Nombre de prises ethernet de la machine"
singlevalue = True
optional = True
categoriy = 'base_tech'
can_modify = [nounou]
ldap_name = "nombrePrises"
@crans_attribute
class prise(Attr):
singlevalue = True
optional = True
legend = u"Prise sur laquelle est branchée la machine"
category = 'base_tech'
can_modify = [nounou]
ldap_name = "prise"
@crans_attribute
class cid(intAttr):
singlevalue = True
optional = True
unique = True
legend = u"Identifiant du club"
category = 'id'
ldap_name = "cid"
def parse_value(self, val):
self.value = int(val)
@crans_attribute
class responsable(Attr):
singlevalue = True
optional = True
legend = u"Responsable du club"
category = 'perso'
can_modify = [cableur, nounou]
ldap_name = "responsable"
python_type = int # en vrai, c'est l'aid du responsable
def nonefunction(self, resp):
"""
Just... do... nothing.
L'idée est qu'on initialise self.value à None
dans Attr. Simplement ici, self.value est une property,
donc il faut une fonction pour l'attribution.
"""
pass
def get_respo(self):
"""Méthode spéciale, pour aller avec
property. On génère le respo quand
c'est nécessaire, pas avant."""
if hasattr(self, "_value"):
return self._value
else:
try:
res = self.conn.search(u'aid=%s' % self.__value)[0]
except IndexError:
raise ValueError("get_respo: L'adherent %s n'existe pas ou plus" % (self.__value))
self._value = res
return res
def parse_value(self, resp):
self.__value = resp
value = property(get_respo, nonefunction)
def __unicode__(self):
return unicode(self.__value)
@crans_attribute
class imprimeurClub(Attr):
optional = True
legend = u"Imprimeur du club"
category = "perso"
can_modify = [cableur, nounou]
ldap_name = "imprimeurClub"
def nonefunction(self, imprimeur):
"""
Just... do... nothing.
L'idée est qu'on initialise self.value à None
dans Attr. Simplement ici, self.value est une property,
donc il faut une fonction pour l'attribution.
"""
pass
def get_imprimeur(self):
if hasattr(self, "_value"):
return self._value
else:
try:
res = self.conn.search(u'aid=%s' % int(self.__value))[0]
except IndexError:
raise ValueError("@get_imprimeur(cid=%s) : l'adhérent aid=%r n'existe pas ou plus" % (self.parent['cid'][0], self.__value))
self._value = res
return res
def parse_value(self, imprimeur):
self.__value = imprimeur
value = property(get_imprimeur, nonefunction)
def __unicode__(self):
return unicode(self.value['aid'][0])
@crans_attribute
class blacklist(Attr):
singlevalue = False
optional = True
legend = u"Blackliste"
category = 'info'
can_modify = [nounou]
ldap_name = "blacklist"
python_type = dict
def parse_value(self, blacklist):
if isinstance(blacklist, self.python_type):
if set(blacklist.keys()).issuperset(['debut', 'fin', 'type', 'comm']):
bl_debut, bl_fin, bl_type, bl_comm = (blacklist['debut'], blacklist['fin'], blacklist['type'], blacklist['comm'])
else:
raise ValueError("A blacklist should contain the following keys : 'debut', 'fin', 'type', 'comm'")
else:
try:
bl_debut, bl_fin, bl_type, bl_comm = blacklist.split('$')
except:
print "valeur = %s" % blacklist
raise
now = time.time()
self.value = { 'debut' : bl_debut if bl_debut == '-' else int (bl_debut),
'fin' : bl_fin if bl_fin == '-' else int(bl_fin),
'type' : bl_type,
'comm' : bl_comm,
'actif' : (bl_debut == '-' or int(bl_debut) < now) and (bl_fin == '-' or int(bl_fin) > now) }
def __getitem__(self, attr):
return self.value.__getitem__(attr)
def __setitem__(self, attr, values):
if attr in ['debut', 'fin', 'type', 'comm']:
ret=self.value.__setitem__(attr, values)
self.parse_value(unicode(self))
return ret
else:
raise ValueError("blacklist has no %r" % attr)
def is_actif(self):
return self.value['actif']
def terminer(self):
self.value['fin'] = int(max(self.value['debut'], time.time() - 60))
self.value['actif'] = False
def __unicode__(self):
return u'%(debut)s$%(fin)s$%(type)s$%(comm)s' % self.value
@crans_attribute
class historique(Attr):
"""Un historique est usuellement de la forme JJ/MM/AAAA HH:mm:ss, action comm"""
singlevalue = False
optional = True
legend = u"Historique de l'objet"
category = 'info'
ldap_name = "historique"
# Il faudrait faire quelque chose pour que soi, parent, cableur ne puissent que ajouter de nouvelles lignes
can_modify = [nounou, soi, parent, cableur]
# Thanks to 20-100, on va devoir gérer deux cas
FORMAT = "%d/%m/%Y %H:%M:%S"
FORMAT_OLD = "%d/%m/%Y %H:%M" # ancien binding
def get_datetime(self):
"""Renvoie un objet datetime de la ligne correspondante"""
datepart = self.value.split(',',1)[0]
try:
return datetime.datetime.strptime(datepart, self.FORMAT)
except ValueError:
return datetime.datetime.strptime(datepart, self.FORMAT_OLD)
@crans_attribute
class info(Attr):
singlevalue = False
optional = True
legend = u"Quelques informations"
category = 'info'
can_modify = [nounou, cableur, bureau]
ldap_name = "info"
@crans_attribute
class homepageAlias(Attr):
singlevalue = True
optional = True
legend = u'Un alias pour la page personnelle'
can_modify = [nounou, cableur]
category = 'webpage'
ldap_name = "homepageAlias"
@crans_attribute
class charteMA(boolAttr):
singlevalue = True
optional = True
legend= "Signale si l'adhérent a signé la charte de membres actifs"
can_modify = [nounou, bureau]
category = 'perso'
ldap_name = "charteMA"
@crans_attribute
class homeDirectory(Attr):
singlevalue=True
optional = True
unique = True
can_modify = [nounou, bureau, cableur]
legend="Le chemin du home de l'adhérent"
ldap_name = "homeDirectory"
def is_modifiable(self, liste_droits):
"""
Une adresse mail n'est modifiable que si on a au moins autant de droits
que la personne à qui est l'adresse mail
"""
modifiables = set()
for i in liste_droits:
if i in DROITS_SUPERVISEUR:
modifiables = modifiables.union(DROITS_SUPERVISEUR[i])
modifiables = list(modifiables)
for droit in self.parent.get('droits', []):
if droit not in modifiables and droit in TOUS_DROITS:
return False
return super(homeDirectory, self).is_modifiable(liste_droits)
@crans_attribute
class loginShell(Attr):
singlevalue = True
optional = True
legend = "Le shell de l'adherent"
can_modify = [soi, nounou, cableur]
ldap_name = "loginShell"
def parse_value(self, shell):
#with open('/etc/shells') as f:
# shells = [ l.strip() for l in f.readlines() if not l.startswith('#') ]
shells = config.shells_possibles
if shell not in shells:
raise ValueError("Shell %r invalide.\nLes shells valident sont : %s" % (shell, ', '.join(shells)))
self.value = shell
@crans_attribute
class uidNumber(intAttr):
singlevalue = True
optional = True
unique = True
legend = "L'uid du compte de l'adherent"
category = 'id'
ldap_name = "uidNumber"
@crans_attribute
class gidNumber(intAttr):
singlevalue = True
optional = True
legend = "Le gid du compte de l'adhérent"
category = 'id'
ldap_name = "gidNumber"
@crans_attribute
class gecos(Attr):
singlevalue = True
optional = True
legend = "Le gecos"
category = 'id'
ldap_name = "gecos"
@crans_attribute
class userPassword(Attr):
singlevalue = True
optional = True
legend = "Le mot de passe"
category = ''
ldap_name = "userPassword"
can_modify = [nounou, bureau, cableur]
def is_modifiable(self, liste_droits):
"""
Le mot de passe n'est modifiable que si on a au moins autant de droits
que la personne à qui est le mot de passe
"""
modifiables = set()
for i in liste_droits:
if i in DROITS_SUPERVISEUR:
modifiables = modifiables.union(DROITS_SUPERVISEUR[i])
modifiables = list(modifiables)
for droit in self.parent.get('droits', []):
if droit not in modifiables and droit in TOUS_DROITS:
return False
return super(userPassword, self).is_modifiable(liste_droits)
@crans_attribute
class sshFingerprint(Attr):
singlevalue = False
optional = True
legend = "Clef ssh de la machine"
can_modify = [parent, nounou]
ldap_name = "sshFingerprint"
python_type = dict
def parse_value(self, key):
if isinstance(key, self.python_type):
if set(key.keys()).issuperset(['type', 'key']):
details = [ key['type'], key['key'] ]
if 'comm' in key:
details.append(key['comm'])
else:
raise ValueError("sshFingerprint should have a type dans a key field")
else:
details = key.strip().split()
if len(details)<2:
raise ValueError("Une clef ssh devrait être de la forme : 'format clef commentaire' comme 'ssh-dss AAAAB3NzaC… root@mon-pc' par exemple")
self.value = { 'type' : details[0],
'key' : details[1],
'comm' : ' '.join(details[2:]),
}
if not self.value['type'] in config.sshfs_ralgo.keys():
raise ValueError("Seul les clefs ssh de type %s sont supportées" % ', '.join(config.sshfs_ralgo.keys()))
try:
base64.b64decode(self.value['key'])
except:
raise ValueError("La partie centrale de la clef ssh devrait être du base64 valide")
def __getitem__(self, attr):
return self.value.__getitem__(attr)
def __setitem__(self, attr, values):
if attr in ['comm']:
ret=self.value.__setitem__(attr, values)
self.parse_value(unicode(self))
return ret
else:
raise ValueError("sshFingerprint has no %r editable" % attr)
def __unicode__(self):
return (u'%(type)s %(key)s %(comm)s' % self.value).strip()
@crans_attribute
class gpgFingerprint(Attr):
singlevalue = False
optional = True
unique = True
legend = "Clef gpg d'un adhérent"
can_modify = [soi, nounou]
ldap_name = "gpgFingerprint"
@crans_attribute
class gpgMail(mail):
"""Attribut servant à stocker un mail allant de paire avec
l'un des uid dans la clef gpg pointée par gpgFingerprint"""
singlevalue = False
optional = True
unique = True
legend = "Mail associé à une clef gpg"
can_modify = [soi, nounou]
ldap_name = "gpgMail"
def check_uniqueness(self, liste_exclue):
super(mail, self).check_uniqueness(liste_exclue)
def parse_value(self, mail):
mail = mail.lower()
super(gpgMail, self).parse_value(mail)
@crans_attribute
class cn(Attr):
singlevalue = True
optional = False
category = 'id'
ldap_name = "cn"
@crans_attribute
class dn(Attr):
singlevalue = True
optional = False
unique = True
category = 'id'
ldap_name = "dn"
@crans_attribute
class postalAddress(Attr):
singlevalue = False
optional = True
can_modify = [soi, cableur, nounou, bureau]
legend = u"Adresse"
category = 'perso'
ldap_name = "postalAddress"
@crans_attribute
class controle(Attr):
singlevalue = True
optional = True
legend = u"Contrôle"
can_modify = [tresorier, nounou]
category = 'perso'
ldap_name = "controle"
def parse_value(self, ctrl):
if ctrl not in [u"", u"c", u"p", u"cp", u"pc", u"TRUE", u"FALSE"]:
raise ValueError("Contrôle peut prendre les valeurs [c][p]")
self.value = ctrl
@crans_attribute
class fid(intAttr):
legend = u"Id de facture"
category = 'factures'
optional = False
singlevalue = True
ldap_name = "fid"
@crans_attribute
class modePaiement(Attr):
legend = u"Mode de paiement"
category = 'factures'
optional = False
singlevalue = True
can_modify = [cableur, nounou]
ldap_name = "modePaiement"
def parse_value(self, mode):
if not mode in config.modePaiement:
raise ValueError("%s n'est pas un moyen de paiement accepté. Les moyens accepté sont %s" % ", ".join(config.modePaiement))
self.value = mode
@crans_attribute
class recuPaiement(Attr):
ldap_name = "recuPaiement"
can_modify = [cableur, nounou]
@crans_attribute
class article(Attr):
singlevalue = False
optional = True
legend = u"Articles"
category = 'facture'
can_modify = [cableur, nounou, tresorier]
ldap_name = "article"
python_type = dict
python_type_keys = ['code', 'designation', 'nombre', 'pu']
def parse_value(self, article):
if isinstance(article, self.python_type):
for key in self.python_type_keys:
if key not in article:
raise ValueError("Un article devrait avoir '%s'" % key)
art_code = article['code']
art_designation = article['designation']
art_nombre = article['nombre']
art_pu = article['pu']
else:
art_code, art_designation, art_nombre, art_pu = article.split('~~')
self.value = { 'code' : art_code, # code de l'article (SOLDE, FRAIS, ...)
'designation' : art_designation,
'nombre' : art_nombre,# nombre d'article
'pu' : art_pu, # prix à l'unité
}
def __unicode__(self):
return u'%(code)s~~%(designation)s~~%(nombre)s~~%(pu)s' % self.value
def __getitem__(self, attr):
return self.value.__getitem__(attr)
def __setitem__(self, attr, values):
if attr in self.python_type_keys:
ret = self.value.__setitem__(attr, values)
self.parse_value(unicode(self))
return ret
else:
raise ValueError("article has no %r" % attr)
@crans_attribute
class dnsIpv6(boolAttr):
ldap_name = "dnsIpv6"
legend = "Détermine si l'ipv6 apparait dans le dns"
can_modify = [nounou, parent, cableur]
@crans_attribute
class rewriteMailHeaders(boolAttr):
ldap_name = "rewriteMailHeaders"
@crans_attribute
class machineAlias(boolAttr):
ldap_name = "machineAlias"
@crans_attribute
class issuerCN(Attr):
ldap_name = "issuerCN"
can_modify = [nounou]
legend = "Common Name de l'éméteur du certificat"
@crans_attribute
class serialNumber(Attr):
ldap_name = "serialNumber"
python_type = int
can_modify = [nounou]
legend = "Numéro de série du certificat"
@crans_attribute
class start(intAttr):
ldap_name = "start"
can_modify = [nounou]
legend = "Date de début"
@crans_attribute
class end(intAttr):
ldap_name = "end"
can_modify = [nounou]
legend = "Date de fin"
@crans_attribute
class crlUrl(Attr):
ldap_name = "crlUrl"
optional = True
can_modify = [parent, nounou]
legend = "Adresse de la liste de révocation pour le certificat"
@crans_attribute
class revocked(boolAttr):
ldap_name = "revocked"
singlevalue = True
optional = True
can_modify = [nounou]
legend = "Détermine si le certificat est révoqué"
@crans_attribute
class encrypted(boolAttr):
ldap_name = "encrypted"
singlevalue = True
optional = True
can_modify = [nounou, parent]
legend = "Détermine si une clef privée est chiffrée"
@crans_attribute
class privatekey(Attr):
ldap_name = "privatekey"
python_type = str
can_modify = [parent, nounou]
legend = "Clef privée"
@crans_attribute
class csr(Attr):
ldap_name = "csr"
python_type = str
can_modify = [parent, nounou]
legend = "requête de signature de certificat"
def _format_cert(self, csr):
import OpenSSL
try:
x509 = OpenSSL.crypto.load_certificate_request(OpenSSL.crypto.FILETYPE_PEM, csr)
except:
print csr
raise
data = fetch_cert_info(x509)
return data
def parse_value(self, csr):
if self.parent.mode in ['w', 'rw']:
data = self._format_cert(csr)
self.data = data
for hostname in [data['subject']['CN']] + data['extensions'].get('subjectAltName',[]):
if hostname not in self.parent['hostCert']:
self.parent['hostCert'].append(unicode(hostname))
self.value = csr
@crans_attribute
class certificat(Attr):
ldap_name = "certificat"
binary = True
python_type = str
can_modify = [parent, nounou]
legend = "Certificat"
def __init__(self, *args, **kwargs):
super(certificat, self).__init__(*args, **kwargs)
self.data = None
def _format_cert(self, certificat):
import OpenSSL
try:
if certificat.startswith('-----BEGIN CERTIFICATE-----'):
certificat = ssl.PEM_cert_to_DER_cert(certificat)
x509 = OpenSSL.crypto.load_certificate(OpenSSL.crypto.FILETYPE_ASN1, certificat)
except Exception:
try:
certificat = base64.b64decode(certificat)
x509 = OpenSSL.crypto.load_certificate(OpenSSL.crypto.FILETYPE_ASN1, certificat)
except Exception:
raise ValueError("Format du certificat invalide, est-il bien au format DER ou PEM ?")
data = fetch_cert_info(x509)
return (certificat, data)
def parse_value(self, certificat):
if self.parent.mode in ['w', 'rw']:
(certificat, data) = self._format_cert(certificat)
self.data = data
if not 'x509Cert' in self.parent['objectClass']:
self.parent.x509(unicode(data['issuer']['CN']), data['start'], data['end'], data['serialNumber'])
else:
self.parent['issuerCN'] = unicode(data['issuer']['CN'])
self.parent['start'] = data['start']
self.parent['end'] = data['end']
self.parent['serialNumber'] = data['serialNumber']
for hostname in [data['subject']['CN']] + data['extensions'].get('subjectAltName',[]):
if hostname not in self.parent['hostCert']:
self.parent['hostCert'].append(unicode(hostname))
self.value = certificat
def __getitem__(self, key):
if not self.data:
self.data=self._format_cert(self.value)[1]
return self.data[key]
def pem(self):
return ssl.DER_cert_to_PEM_cert(self.value)
def __unicode__(self):
return unicode(base64.b64encode(self.value))
def __str__(self):
return self.value
@crans_attribute
class certificatUsage(intAttr):
ldap_name = "certificatUsage"
singlevalue = True
can_modify = [parent, nounou]
legend = "Utilisation du certificat pour un enregistrement TLSA"
@crans_attribute
class selector(intAttr):
ldap_name = "selector"
singlevalue = True
can_modify = [parent, nounou]
legend = "Détermine si le certificat est complet ou contient juste la clef publique pour TLSA"
@crans_attribute
class matchingType(intAttr):
ldap_name = "matchingType"
singlevalue = True
can_modify = [parent, nounou]
legend = "Détermine ce que l'on veux mettre dans l'enregistrement TLSA (certificat complet ou hash)"
@crans_attribute
class xid(intAttr):
ldap_name = "xid"
category = 'id'
unique = True
singlevalue = True
can_modify = []
legend = "Identifiant du certificat"
@crans_attribute
class hostCert(dnsAttr):
optional = False
can_modify = [parent, nounou]
ldap_name = "hostCert"
"Nom de domaine utilisé pour le certificat"
def parse_value(self, host):
# Pour les machine crans, on dit ques les nounous
# savent ce qu'elle font (et puis, c'est pratique quand des
# alias migrent vers des vm)
if not "machineCrans" in self.parent.machine()["objectClass"]:
if not host in self.parent.machine()['host'] + self.parent.machine()['hostAlias']:
raise ValueError("hostCert (%s) doit être inclus dans les host et hostAlias de la machine parente : %s ou %s" % (str(host), ', '.join(str(item) for item in self.parent.machine()['host'] + self.parent.machine()['hostAlias'])))
self.value = host
@crans_attribute
class shadowExpire(intAttr):
"""
Durée de validité du mot de passe d'un compte.
On l'utilise en mettant sa valeur à 0 pour désactiver un compte
"""
optinal = True
can_modify = [nounou, bureau]
ldap_name = "shadowExpire"