models.py 61.2 KB
Newer Older
1
# -*- mode: python; coding: utf-8 -*-
chirac's avatar
chirac committed
2 3 4
# Re2o est un logiciel d'administration développé initiallement au rezometz.
# Il  se veut agnostique au réseau considéré, de manière à être installable
# en quelques clics.
5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
#
# Copyright © 2017  Gabriel Détraz
# Copyright © 2017  Goulven Kermarec
# Copyright © 2017  Augustin Lemesle
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License along
# with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
chirac's avatar
chirac committed
23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44
"""
Models de l'application users.

On défini ici des models django classiques:
- users, qui hérite de l'abstract base user de django. Permet de définit
un utilisateur du site (login, passwd, chambre, adresse, etc)
- les whiteslist
- les bannissements
- les établissements d'enseignement (school)
- les droits (right et listright)
- les utilisateurs de service (pour connexion automatique)

On défini aussi des models qui héritent de django-ldapdb :
- ldapuser
- ldapgroup
- ldapserviceuser

Ces utilisateurs ldap sont synchronisés à partir des objets
models sql classiques. Seuls certains champs essentiels sont
dupliqués.
"""

45

46 47
from __future__ import unicode_literals

chirac's avatar
chirac committed
48 49 50
import re
import uuid
import datetime
51
import sys
chirac's avatar
chirac committed
52

lhark's avatar
lhark committed
53
from django.db import models
54
from django.db.models import Q
55
from django import forms
56
from django.db.models.signals import post_save, post_delete, m2m_changed
57
from django.dispatch import receiver
58
from django.utils.functional import cached_property
chirac's avatar
chirac committed
59
from django.template import Context, loader
60 61
from django.core.mail import send_mail
from django.core.urlresolvers import reverse
chirac's avatar
chirac committed
62 63
from django.db import transaction
from django.utils import timezone
64 65
from django.contrib.auth.models import (
    AbstractBaseUser,
66
    BaseUserManager,
67 68
    PermissionsMixin,
    Group
69
)
chirac's avatar
chirac committed
70
from django.core.validators import RegexValidator
71
import traceback
72 73
from reversion import revisions as reversion

74 75 76
import ldapdb.models
import ldapdb.models.fields

chirac's avatar
chirac committed
77
from re2o.settings import LDAP, GID_RANGES, UID_RANGES
78
from re2o.login import hashNT
79
from re2o.field_permissions import FieldPermissionModelMixin
80
from re2o.mixins import AclMixin, RevMixin
lhark's avatar
lhark committed
81

chibrac's avatar
chibrac committed
82
from cotisations.models import Cotisation, Facture, Paiement, Vente
83
from machines.models import Domain, Interface, Machine, regen
chirac's avatar
chirac committed
84 85
from preferences.models import GeneralOption, AssoOption, OptionalUser
from preferences.models import OptionalMachine, MailMessageOption
86

chirac's avatar
chirac committed
87

chirac's avatar
chirac committed
88
# Utilitaires généraux
chirac's avatar
chirac committed
89

90 91

def linux_user_check(login):
chirac's avatar
chirac committed
92
    """ Validation du pseudo pour respecter les contraintes unix"""
93
    UNIX_LOGIN_PATTERN = re.compile("^[a-zA-Z0-9-]*[$]?$")
94 95 96 97
    return UNIX_LOGIN_PATTERN.match(login)


def linux_user_validator(login):
chirac's avatar
chirac committed
98
    """ Retourne une erreur de validation si le login ne respecte
chirac's avatar
chirac committed
99
    pas les contraintes unix (maj, min, chiffres ou tiret)"""
100
    if not linux_user_check(login):
101
        raise forms.ValidationError(
chirac's avatar
chirac committed
102 103
            ", ce pseudo ('%(label)s') contient des carractères interdits",
            params={'label': login},
chirac's avatar
chirac committed
104 105
        )

chirac's avatar
chirac committed
106

107
def get_fresh_user_uid():
chirac's avatar
chirac committed
108
    """ Renvoie le plus petit uid non pris. Fonction très paresseuse """
chirac's avatar
chirac committed
109 110 111 112
    uids = list(range(
        int(min(UID_RANGES['users'])),
        int(max(UID_RANGES['users']))
    ))
113
    try:
114
        used_uids = list(User.objects.values_list('uid_number', flat=True))
115 116
    except:
        used_uids = []
chirac's avatar
chirac committed
117
    free_uids = [id for id in uids if id not in used_uids]
118 119
    return min(free_uids)

chirac's avatar
chirac committed
120

121
def get_fresh_gid():
chirac's avatar
chirac committed
122
    """ Renvoie le plus petit gid libre  """
chirac's avatar
chirac committed
123 124 125 126
    gids = list(range(
        int(min(GID_RANGES['posix'])),
        int(max(GID_RANGES['posix']))
    ))
127
    used_gids = list(ListRight.objects.values_list('gid', flat=True))
chirac's avatar
chirac committed
128
    free_gids = [id for id in gids if id not in used_gids]
129
    return min(free_gids)
130

chirac's avatar
chirac committed
131

132
class UserManager(BaseUserManager):
chirac's avatar
chirac committed
133 134 135 136 137
    """User manager basique de django"""
    def _create_user(
            self,
            pseudo,
            surname,
138
            email,
chirac's avatar
chirac committed
139 140 141
            password=None,
            su=False
    ):
142 143 144 145
        if not pseudo:
            raise ValueError('Users must have an username')

        if not linux_user_check(pseudo):
146
            raise ValueError('Username shall only contain [a-z0-9-]')
147

148
        user = Adherent(
149 150
            pseudo=pseudo,
            surname=surname,
151
            name=surname,
detraz's avatar
detraz committed
152
            email=self.normalize_email(email),
153 154 155 156
        )

        user.set_password(password)
        if su:
157
            user.is_superuser = True
158
        user.save(using=self._db)
159 160
        return user

161
    def create_user(self, pseudo, surname, email, password=None):
162 163 164 165
        """
        Creates and saves a User with the given pseudo, name, surname, email,
        and password.
        """
166
        return self._create_user(pseudo, surname, email, password, False)
167

168
    def create_superuser(self, pseudo, surname, email, password):
169 170 171 172
        """
        Creates and saves a superuser with the given pseudo, name, surname,
        email, and password.
        """
173
        return self._create_user(pseudo, surname, email, password, True)
174

175 176 177

class User(RevMixin, FieldPermissionModelMixin, AbstractBaseUser,
           PermissionsMixin, AclMixin):
chirac's avatar
chirac committed
178 179 180
    """ Definition de l'utilisateur de base.
    Champs principaux : name, surnname, pseudo, email, room, password
    Herite du django BaseUser et du système d'auth django"""
Gabriel Detraz's avatar
Gabriel Detraz committed
181
    PRETTY_NAME = "Utilisateurs (clubs et adhérents)"
lhark's avatar
lhark committed
182
    STATE_ACTIVE = 0
chirac's avatar
chirac committed
183 184
    STATE_DISABLED = 1
    STATE_ARCHIVE = 2
lhark's avatar
lhark committed
185
    STATES = (
chirac's avatar
chirac committed
186 187 188 189
        (0, 'STATE_ACTIVE'),
        (1, 'STATE_DISABLED'),
        (2, 'STATE_ARCHIVE'),
    )
lhark's avatar
lhark committed
190 191

    surname = models.CharField(max_length=255)
chirac's avatar
chirac committed
192 193 194 195 196 197
    pseudo = models.CharField(
        max_length=32,
        unique=True,
        help_text="Doit contenir uniquement des lettres, chiffres, ou tirets",
        validators=[linux_user_validator]
    )
198 199
    email = models.EmailField()
    local_email_redirect = models.BooleanField(
200
        default=False,
201
        help_text="Whether or not to redirect the local email messages to the main email."
202
    )
203
    local_email_enabled = models.BooleanField(
204
        default=False,
205
        help_text="Wether or not to enable the local email account."
206
    )
chirac's avatar
chirac committed
207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223
    school = models.ForeignKey(
        'School',
        on_delete=models.PROTECT,
        null=True,
        blank=True
    )
    shell = models.ForeignKey(
        'ListShell',
        on_delete=models.PROTECT,
        null=True,
        blank=True
    )
    comment = models.CharField(
        help_text="Commentaire, promo",
        max_length=255,
        blank=True
    )
lhark's avatar
lhark committed
224
    pwd_ntlm = models.CharField(max_length=255)
225
    state = models.IntegerField(choices=STATES, default=STATE_ACTIVE)
226
    registered = models.DateTimeField(auto_now_add=True)
227
    telephone = models.CharField(max_length=15, blank=True, null=True)
228 229 230 231
    uid_number = models.PositiveIntegerField(
        default=get_fresh_user_uid,
        unique=True
    )
232 233 234 235 236
    rezo_rez_uid = models.PositiveIntegerField(
        unique=True,
        blank=True,
        null=True
    )
lhark's avatar
lhark committed
237

238
    USERNAME_FIELD = 'pseudo'
239
    REQUIRED_FIELDS = ['surname', 'email']
240 241 242

    objects = UserManager()

243 244
    class Meta:
        permissions = (
245 246
            ("change_user_password",
             "Peut changer le mot de passe d'un user"),
247 248 249
            ("change_user_state", "Peut éditer l'etat d'un user"),
            ("change_user_force", "Peut forcer un déménagement"),
            ("change_user_shell", "Peut éditer le shell d'un user"),
250 251 252 253 254 255 256
            ("change_user_groups",
             "Peut éditer les groupes d'un user ! Permission critique"),
            ("change_all_users",
             "Peut éditer tous les users, y compris ceux dotés de droits. "
             "Superdroit"),
            ("view_user",
             "Peut voir un objet user quelquonque"),
257
        )
258

259 260 261 262 263 264 265 266
    @cached_property
    def name(self):
        """Si il s'agit d'un adhérent, on renvoie le prénom"""
        if self.is_class_adherent:
            return self.adherent.name
        else:
            return ''

267 268 269 270 271 272 273 274 275 276
    @cached_property
    def room(self):
        """Alias vers room """
        if self.is_class_adherent:
            return self.adherent.room
        elif self.is_class_club:
            return self.club.room
        else:
            raise NotImplementedError("Type inconnu")

277 278 279 280 281 282 283 284 285 286 287 288 289 290
    @cached_property
    def get_mail_addresses(self):
        if self.local_email_enabled:
            return self.emailaddress_set.all()
        return None

    @cached_property
    def get_mail(self):
        """Return the mail address choosen by the user"""
        if not OptionalUser.get_cached_value('local_email_accounts_enabled') or not self.local_email_enabled or self.local_email_redirect:
            return str(self.email)
        else:
            return str(self.emailaddress_set.get(local_part=self.pseudo))

291 292 293 294
    @cached_property
    def class_name(self):
        """Renvoie si il s'agit d'un adhérent ou d'un club"""
        if hasattr(self, 'adherent'):
Gabriel Detraz's avatar
Gabriel Detraz committed
295
            return "Adherent"
296 297 298 299 300
        elif hasattr(self, 'club'):
            return "Club"
        else:
            raise NotImplementedError("Type inconnu")

301 302 303 304 305
    @cached_property
    def gid_number(self):
        """renvoie le gid par défaut des users"""
        return int(LDAP['user_gid'])

306 307
    @cached_property
    def is_class_club(self):
308 309
        """ Returns True if the object is a Club (subclassing User) """
        # TODO : change to isinstance (cleaner)
310 311 312 313
        return hasattr(self, 'club')

    @cached_property
    def is_class_adherent(self):
314 315
        """ Returns True if the object is a Adherent (subclassing User) """
        # TODO : change to isinstance (cleaner)
316 317
        return hasattr(self, 'adherent')

318 319
    @property
    def is_active(self):
chirac's avatar
chirac committed
320
        """ Renvoie si l'user est à l'état actif"""
321 322 323 324
        return self.state == self.STATE_ACTIVE

    @property
    def is_staff(self):
chirac's avatar
chirac committed
325
        """ Fonction de base django, renvoie si l'user est admin"""
326 327 328 329
        return self.is_admin

    @property
    def is_admin(self):
chirac's avatar
chirac committed
330
        """ Renvoie si l'user est admin"""
331
        admin, _ = Group.objects.get_or_create(name="admin")
332
        return self.is_superuser or admin in self.groups.all()
333 334

    def get_full_name(self):
chirac's avatar
chirac committed
335
        """ Renvoie le nom complet de l'user formaté nom/prénom"""
336 337 338 339 340
        name = self.name
        if name:
            return '%s %s' % (name, self.surname)
        else:
            return self.surname
341 342

    def get_short_name(self):
chirac's avatar
chirac committed
343
        """ Renvoie seulement le nom"""
344
        return self.surname
345

346 347 348 349 350 351
    @property
    def get_shell(self):
        """ A utiliser de préférence, prend le shell par défaut
        si il n'est pas défini"""
        return self.shell or OptionalUser.get_cached_value('shell_default')

352 353 354 355 356 357 358 359
    @cached_property
    def get_shadow_expire(self):
        """Return the shadow_expire value for the user"""
        if self.state == self.STATE_DISABLED:
            return str(0)
        else:
            return None

360
    def end_adhesion(self):
chirac's avatar
chirac committed
361 362
        """ Renvoie la date de fin d'adhésion d'un user. Examine les objets
        cotisation"""
chirac's avatar
chirac committed
363 364 365 366 367 368
        date_max = Cotisation.objects.filter(
            vente__in=Vente.objects.filter(
                facture__in=Facture.objects.filter(
                    user=self
                ).exclude(valid=False)
            )
369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384
        ).filter(
            Q(type_cotisation='All') | Q(type_cotisation='Adhesion')
        ).aggregate(models.Max('date_end'))['date_end__max']
        return date_max

    def end_connexion(self):
        """ Renvoie la date de fin de connexion d'un user. Examine les objets
        cotisation"""
        date_max = Cotisation.objects.filter(
            vente__in=Vente.objects.filter(
                facture__in=Facture.objects.filter(
                    user=self
                ).exclude(valid=False)
            )
        ).filter(
            Q(type_cotisation='All') | Q(type_cotisation='Connexion')
chirac's avatar
chirac committed
385
        ).aggregate(models.Max('date_end'))['date_end__max']
386 387 388
        return date_max

    def is_adherent(self):
chirac's avatar
chirac committed
389 390
        """ Renvoie True si l'user est adhérent : si
        self.end_adhesion()>now"""
391
        end = self.end_adhesion()
392 393
        if not end:
            return False
394
        elif end < timezone.now():
395 396 397 398
            return False
        else:
            return True

399 400 401 402 403 404
    def is_connected(self):
        """ Renvoie True si l'user est adhérent : si
        self.end_adhesion()>now et end_connexion>now"""
        end = self.end_connexion()
        if not end:
            return False
405
        elif end < timezone.now():
406 407 408 409
            return False
        else:
            return self.is_adherent()

410 411
    def end_ban(self):
        """ Renvoie la date de fin de ban d'un user, False sinon """
chirac's avatar
chirac committed
412 413 414
        date_max = Ban.objects.filter(
            user=self
        ).aggregate(models.Max('date_end'))['date_end__max']
415 416 417
        return date_max

    def end_whitelist(self):
418
        """ Renvoie la date de fin de whitelist d'un user, False sinon """
chirac's avatar
chirac committed
419 420 421
        date_max = Whitelist.objects.filter(
            user=self
        ).aggregate(models.Max('date_end'))['date_end__max']
422 423 424 425
        return date_max

    def is_ban(self):
        """ Renvoie si un user est banni ou non """
426
        end = self.end_ban()
427 428
        if not end:
            return False
429
        elif end < timezone.now():
430 431 432 433 434 435
            return False
        else:
            return True

    def is_whitelisted(self):
        """ Renvoie si un user est whitelisté ou non """
436
        end = self.end_whitelist()
437 438
        if not end:
            return False
439
        elif end < timezone.now():
440 441 442 443 444 445
            return False
        else:
            return True

    def has_access(self):
        """ Renvoie si un utilisateur a accès à internet """
446 447 448
        return (self.state == User.STATE_ACTIVE and
                not self.is_ban() and
                (self.is_connected() or self.is_whitelisted()))
449

450 451
    def end_access(self):
        """ Renvoie la date de fin normale d'accès (adhésion ou whiteliste)"""
452
        if not self.end_connexion():
453
            if not self.end_whitelist():
454 455
                return None
            else:
456
                return self.end_whitelist()
457
        else:
458
            if not self.end_whitelist():
459
                return self.end_connexion()
chirac's avatar
chirac committed
460
            else:
461
                return max(self.end_connexion(), self.end_whitelist())
462

chibrac's avatar
chibrac committed
463 464
    @cached_property
    def solde(self):
465
        """ Renvoie le solde d'un user.
chirac's avatar
chirac committed
466
        Somme les crédits de solde et retire les débit payés par solde"""
467
        solde_objects = Paiement.objects.filter(is_balance=True)
468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489
        somme_debit = Vente.objects.filter(
            facture__in=Facture.objects.filter(
                user=self,
                paiement__in=solde_objects,
                valid=True
            )
        ).aggregate(
            total=models.Sum(
                models.F('prix')*models.F('number'),
                output_field=models.FloatField()
            )
        )['total'] or 0
        somme_credit = Vente.objects.filter(
            facture__in=Facture.objects.filter(user=self, valid=True),
            name="solde"
        ).aggregate(
            total=models.Sum(
                models.F('prix')*models.F('number'),
                output_field=models.FloatField()
            )
        )['total'] or 0
        return somme_credit - somme_debit
chibrac's avatar
chibrac committed
490

chirac's avatar
chirac committed
491
    def user_interfaces(self, active=True):
chirac's avatar
chirac committed
492 493 494 495 496
        """ Renvoie toutes les interfaces dont les machines appartiennent à
        self. Par defaut ne prend que les interfaces actives"""
        return Interface.objects.filter(
            machine__in=Machine.objects.filter(user=self, active=active)
        ).select_related('domain__extension')
497

498 499 500 501 502 503 504 505 506 507 508
    def assign_ips(self):
        """ Assign une ipv4 aux machines d'un user """
        interfaces = self.user_interfaces()
        for interface in interfaces:
            if not interface.ipv4:
                with transaction.atomic(), reversion.create_revision():
                    interface.assign_ipv4()
                    reversion.set_comment("Assignation ipv4")
                    interface.save()

    def unassign_ips(self):
chirac's avatar
chirac committed
509
        """ Désassigne les ipv4 aux machines de l'user"""
510 511 512 513 514 515 516 517
        interfaces = self.user_interfaces()
        for interface in interfaces:
            with transaction.atomic(), reversion.create_revision():
                interface.unassign_ipv4()
                reversion.set_comment("Désassignation ipv4")
                interface.save()

    def archive(self):
chirac's avatar
chirac committed
518
        """ Filling the user; no more active"""
519 520 521
        self.unassign_ips()

    def unarchive(self):
chirac's avatar
chirac committed
522
        """Unfilling the user"""
523
        self.assign_ips()
Gabriel Detraz's avatar
Gabriel Detraz committed
524 525 526 527 528 529 530

    def state_sync(self):
        """Archive, or unarchive, if the user was not active/or archived before"""
        if self.__original_state != self.STATE_ACTIVE and self.state == self.STATE_ACTIVE:
            self.unarchive()
        elif self.__original_state != self.STATE_ARCHIVE and self.state == self.STATE_ARCHIVE:
            self.archive()
531

532 533
    def ldap_sync(self, base=True, access_refresh=True, mac_refresh=True,
                  group_refresh=False):
chirac's avatar
chirac committed
534 535 536 537 538 539
        """ Synchronisation du ldap. Synchronise dans le ldap les attributs de
        self
        Options : base : synchronise tous les attributs de base - nom, prenom,
        mail, password, shell, home
        access_refresh : synchronise le dialup_access notant si l'user a accès
        aux services
540
        mac_refresh : synchronise les machines de l'user
541
        group_refresh : synchronise les group de l'user
542
        Si l'instance n'existe pas, on crée le ldapuser correspondant"""
543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588
        if sys.version_info[0] >= 3:
            self.refresh_from_db()
            try:
                user_ldap = LdapUser.objects.get(uidNumber=self.uid_number)
            except LdapUser.DoesNotExist:
                user_ldap = LdapUser(uidNumber=self.uid_number)
                base = True
                access_refresh = True
                mac_refresh = True
            if base:
                user_ldap.name = self.pseudo
                user_ldap.sn = self.pseudo
                user_ldap.dialupAccess = str(self.has_access())
                user_ldap.home_directory = '/home/' + self.pseudo
                user_ldap.mail = self.get_mail
                user_ldap.given_name = self.surname.lower() + '_'\
                    + self.name.lower()[:3]
                user_ldap.gid = LDAP['user_gid']
                if '{SSHA}' in self.password or '{SMD5}' in self.password:
                    # We remove the extra $ added at import from ldap
                    user_ldap.user_password = self.password[:6] + self.password[7:]
                elif '{crypt}' in self.password:
                    # depending on the length, we need to remove or not a $
                    if len(self.password)==41:
                        user_ldap.user_password = self.password
                    else: 
                        user_ldap.user_password = self.password[:7] + self.password[8:]

                user_ldap.sambat_nt_password = self.pwd_ntlm.upper()
                if self.get_shell:
                    user_ldap.login_shell = str(self.get_shell)
                user_ldap.shadowexpire = self.get_shadow_expire
            if access_refresh:
                user_ldap.dialupAccess = str(self.has_access())
            if mac_refresh:
                user_ldap.macs = [str(mac) for mac in Interface.objects.filter(
                    machine__user=self
                ).values_list('mac_address', flat=True).distinct()]
            if group_refresh:
                # Need to refresh all groups because we don't know which groups
                # were updated during edition of groups and the user may no longer
                # be part of the updated group (case of group removal)
                for group in Group.objects.all():
                    if hasattr(group, 'listright'):
                        group.listright.ldap_sync()
            user_ldap.save()
589 590

    def ldap_del(self):
chirac's avatar
chirac committed
591
        """ Supprime la version ldap de l'user"""
592 593 594 595 596 597
        try:
            user_ldap = LdapUser.objects.get(name=self.pseudo)
            user_ldap.delete()
        except LdapUser.DoesNotExist:
            pass

598 599
    def notif_inscription(self):
        """ Prend en argument un objet user, envoie un mail de bienvenue """
chirac's avatar
chirac committed
600 601 602 603
        template = loader.get_template('users/email_welcome')
        mailmessageoptions, _created = MailMessageOption\
            .objects.get_or_create()
        context = Context({
604
            'nom': self.get_full_name(),
605 606
            'asso_name': AssoOption.get_cached_value('name'),
            'asso_email': AssoOption.get_cached_value('contact'),
chirac's avatar
chirac committed
607 608 609
            'welcome_mail_fr': mailmessageoptions.welcome_mail_fr,
            'welcome_mail_en': mailmessageoptions.welcome_mail_en,
            'pseudo': self.pseudo,
610
        })
611 612 613 614 615 616 617 618 619
        send_mail(
            'Bienvenue au %(name)s / Welcome to %(name)s' % {
                'name': AssoOption.get_cached_value('name')
                },
            '',
            GeneralOption.get_cached_value('email_from'),
            [self.email],
            html_message=template.render(context)
        )
620 621 622
        return

    def reset_passwd_mail(self, request):
chirac's avatar
chirac committed
623 624
        """ Prend en argument un request, envoie un mail de
        réinitialisation de mot de pass """
625 626 627 628
        req = Request()
        req.type = Request.PASSWD
        req.user = self
        req.save()
chirac's avatar
chirac committed
629 630
        template = loader.get_template('users/email_passwd_request')
        context = {
631
            'name': req.user.get_full_name(),
632 633
            'asso': AssoOption.get_cached_value('name'),
            'asso_mail': AssoOption.get_cached_value('contact'),
634
            'site_name': GeneralOption.get_cached_value('site_name'),
635
            'url': request.build_absolute_uri(
636 637 638 639 640 641
                reverse('users:process', kwargs={'token': req.token})
            ),
            'expire_in': str(
                GeneralOption.get_cached_value('req_expire_hrs')
            ) + ' heures',
        }
642
        send_mail(
643 644
            'Changement de mot de passe du %(name)s / Password renewal for '
            '%(name)s' % {'name': AssoOption.get_cached_value('name')},
645 646 647 648 649
            template.render(context),
            GeneralOption.get_cached_value('email_from'),
            [req.user.email],
            fail_silently=False
        )
650 651
        return

652
    def autoregister_machine(self, mac_address, nas_type):
chirac's avatar
chirac committed
653 654
        """ Fonction appellée par freeradius. Enregistre la mac pour
        une machine inconnue sur le compte de l'user"""
chirac's avatar
chirac committed
655
        all_interfaces = self.user_interfaces(active=False)
656
        if all_interfaces.count() > OptionalMachine.get_cached_value(
657 658
                'max_lambdauser_interfaces'
            ):
659
            return False, "Maximum de machines enregistrees atteinte"
660
        if not nas_type:
chirac's avatar
chirac committed
661 662
            return False, "Re2o ne sait pas à quel machinetype affecter cette\
            machine"
663
        machine_type_cible = nas_type.machine_type
664 665 666 667 668
        try:
            machine_parent = Machine()
            machine_parent.user = self
            interface_cible = Interface()
            interface_cible.mac_address = mac_address
669
            interface_cible.type = machine_type_cible
670 671 672
            interface_cible.clean()
            machine_parent.clean()
            domain = Domain()
673
            domain.name = self.get_next_domain_name()
674 675
            domain.interface_parent = interface_cible
            domain.clean()
676 677 678 679 680 681
            machine_parent.save()
            interface_cible.machine = machine_parent
            interface_cible.save()
            domain.interface_parent = interface_cible
            domain.clean()
            domain.save()
682
            self.notif_auto_newmachine(interface_cible)
chirac's avatar
chirac committed
683
        except Exception as error:
684
            return False,  traceback.format_exc()
685
        return interface_cible, "Ok"
686

687 688 689 690 691 692
    def notif_auto_newmachine(self, interface):
        """Notification mail lorsque une machine est automatiquement
        ajoutée par le radius"""
        template = loader.get_template('users/email_auto_newmachine')
        context = Context({
            'nom': self.get_full_name(),
693
            'mac_address': interface.mac_address,
694
            'asso_name': AssoOption.get_cached_value('name'),
695
            'interface_name': interface.domain,
696
            'asso_email': AssoOption.get_cached_value('contact'),
697 698 699 700 701
            'pseudo': self.pseudo,
        })
        send_mail(
            "Ajout automatique d'une machine / New machine autoregistered",
            '',
702
            GeneralOption.get_cached_value('email_from'),
703 704 705 706 707
            [self.email],
            html_message=template.render(context)
        )
        return

708
    def set_password(self, password):
chirac's avatar
chirac committed
709
        """ A utiliser de préférence, set le password en hash courrant et
chirac's avatar
chirac committed
710
        dans la version ntlm"""
711
        super().set_password(password)
712 713 714
        self.pwd_ntlm = hashNT(password)
        return

715
    @cached_property
716
    def email_address(self):
717 718
        if (OptionalUser.get_cached_value('local_email_accounts_enabled')
                and self.local_email_enabled):
719 720
            return self.emailaddress_set.all()
        return EMailAddress.objects.none()
721

722 723 724
    def get_next_domain_name(self):
        """Look for an available name for a new interface for
        this user by trying "pseudo0", "pseudo1", "pseudo2", ...
chirac's avatar
chirac committed
725 726 727

        Recherche un nom disponible, pour une machine. Doit-être
        unique, concatène le nom, le pseudo et le numero de machine
728 729 730
        """

        def simple_pseudo():
chirac's avatar
chirac committed
731
            """Renvoie le pseudo sans underscore (compat dns)"""
732 733
            return self.pseudo.replace('_', '-').lower()

chirac's avatar
chirac committed
734 735 736
        def composed_pseudo(name):
            """Renvoie le resultat de simplepseudo et rajoute le nom"""
            return simple_pseudo() + str(name)
737 738

        num = 0
chirac's avatar
chirac committed
739
        while Domain.objects.filter(name=composed_pseudo(num)):
740 741 742
            num += 1
        return composed_pseudo(num)

743 744
    def can_edit(self, user_request, *_args, **_kwargs):
        """Check if a user can edit a user object.
745 746 747 748

        :param self: The user which is to be edited.
        :param user_request: The user who requests to edit self.
        :return: a message and a boolean which is True if self is a club and
749 750
            user_request one of its member, or if user_request is self, or if
            user_request has the 'cableur' right.
751
        """
752
        if self.is_class_club and user_request.is_class_adherent:
753 754 755
            if (self == user_request or
                    user_request.has_perm('users.change_user') or
                    user_request.adherent in self.club.administrators.all()):
756 757 758
                return True, None
            else:
                return False, u"Vous n'avez pas le droit d'éditer ce club"
759
        else:
760 761 762 763 764 765
            if self == user_request:
                return True, None
            elif user_request.has_perm('users.change_all_users'):
                return True, None
            elif user_request.has_perm('users.change_user'):
                if self.groups.filter(listright__critical=True):
766 767
                    return False, (u"Utilisateurs avec droits critiques, ne "
                                   "peut etre édité")
768
                elif self == AssoOption.get_cached_value('utilisateur_asso'):
769 770
                    return False, (u"Impossible d'éditer l'utilisateur asso "
                                   "sans droit change_all_users")
771 772 773
                else:
                    return True, None
            elif user_request.has_perm('users.change_all_users'):
774 775
                return True, None
            else:
776 777
                return False, (u"Vous ne pouvez éditer un autre utilisateur "
                               "que vous même")
778

779 780 781 782 783 784 785 786 787
    def can_change_password(self, user_request, *_args, **_kwargs):
        """Check if a user can change a user's password

        :param self: The user which is to be edited
        :param user_request: The user who request to edit self
        :returns: a message and a boolean which is True if self is a club
            and user_request one of it's admins, or if user_request is self,
            or if user_request has the right to change other's password
        """
788
        if self.is_class_club and user_request.is_class_adherent:
789 790 791
            if (self == user_request or
                    user_request.has_perm('users.change_user_password') or
                    user_request.adherent in self.club.administrators.all()):
792 793 794 795
                return True, None
            else:
                return False, u"Vous n'avez pas le droit d'éditer ce club"
        else:
796 797 798 799
            if (self == user_request or
                    user_request.has_perm('users.change_user_groups')):
                # Peut éditer les groupes d'un user,
                # c'est un privilège élevé, True
800
                return True, None
801 802
            elif (user_request.has_perm('users.change_user') and
                  not self.groups.all()):
803 804
                return True, None
            else:
805 806
                return False, (u"Vous ne pouvez éditer un autre utilisateur "
                               "que vous même")
807

808 809 810 811
    def check_selfpasswd(self, user_request, *_args, **_kwargs):
        """ Returns (True, None) if user_request is self, else returns
        (False, None)
        """
812 813
        return user_request == self, None

814
    @staticmethod
815 816 817 818 819 820 821
    def can_change_state(user_request, *_args, **_kwargs):
        """ Check if a user can change a state

        :param user_request: The user who request
        :returns: a message and a boolean which is True if the user has
        the right to change a state
        """
822 823 824 825
        return (
            user_request.has_perm('users.change_user_state'),
            "Droit requis pour changer l'état"
        )
826

827
    @staticmethod
828 829 830 831 832 833 834
    def can_change_shell(user_request, *_args, **_kwargs):
        """ Check if a user can change a shell

        :param user_request: The user who request
        :returns: a message and a boolean which is True if the user has
        the right to change a shell
        """
835 836 837 838
        return (
            user_request.has_perm('users.change_user_shell'),
            "Droit requis pour changer le shell"
        )
839

840
    @staticmethod
841 842
    def can_change_local_email_redirect(user_request, *_args, **_kwargs):
        """ Check if a user can change local_email_redirect.
843 844 845 846 847 848

        :param user_request: The user who request
        :returns: a message and a boolean which is True if the user has
        the right to change a redirection
        """
        return (
849
            OptionalUser.get_cached_value('local_email_accounts_enabled'),
850 851 852 853
            "La gestion des comptes mails doit être activée"
        )

    @staticmethod
854
    def can_change_local_email_enabled(user_request, *_args, **_kwargs):
855 856 857 858 859 860 861
        """ Check if a user can change internal address .

        :param user_request: The user who request
        :returns: a message and a boolean which is True if the user has
        the right to change internal address
        """
        return (
862
            OptionalUser.get_cached_value('local_email_accounts_enabled'),
863
            "La gestion des comptes mails doit être activée"
864
        )
865

866
    @staticmethod
867 868 869 870 871 872 873
    def can_change_force(user_request, *_args, **_kwargs):
        """ Check if a user can change a force

        :param user_request: The user who request
        :returns: a message and a boolean which is True if the user has
        the right to change a force
        """
874 875 876 877
        return (
            user_request.has_perm('users.change_user_force'),
            "Droit requis pour forcer le déménagement"
        )
878 879

    @staticmethod
880 881 882 883 884 885 886
    def can_change_groups(user_request, *_args, **_kwargs):
        """ Check if a user can change a group

        :param user_request: The user who request
        :returns: a message and a boolean which is True if the user has
        the right to change a group
        """
887 888 889 890
        return (
            user_request.has_perm('users.change_user_groups'),
            "Droit requis pour éditer les groupes de l'user"
        )
891

Levy--Falk Hugo's avatar
Levy--Falk Hugo committed
892 893 894 895 896 897 898 899 900 901 902 903
    @staticmethod
    def can_change_is_superuser(user_request, *_args, **_kwargs):
        """ Check if an user can change a is_superuser flag

        :param user_request: The user who request
        :returns: a message and a boolean which is True if permission is granted.
        """
        return (
            user_request.is_superuser,
            "Droit superuser requis pour éditer le flag superuser"
        )

904
    def can_view(self, user_request, *_args, **_kwargs):
905 906 907 908 909
        """Check if an user can view an user object.

        :param self: The targeted user.
        :param user_request: The user who ask for viewing the target.
        :return: A boolean telling if the acces is granted and an explanation
910
            text
911
        """
912
        if self.is_class_club and user_request.is_class_adherent:
913 914 915 916
            if (self == user_request or
                    user_request.has_perm('users.view_user') or
                    user_request.adherent in self.club.administrators.all() or
                    user_request.adherent in self.club.members.all()):
917 918 919
                return True, None
            else:
                return False, u"Vous n'avez pas le droit de voir ce club"
920
        else:
921 922
            if (self == user_request or
                    user_request.has_perm('users.view_user')):
923 924
                return True, None
            else:
925 926
                return False, (u"Vous ne pouvez voir un autre utilisateur "
                               "que vous même")
927

928 929
    @staticmethod
    def can_view_all(user_request, *_args, **_kwargs):
930 931 932
        """Check if an user can access to the list of every user objects

        :param user_request: The user who wants to view the list.
933 934
        :return: True if the user can view the list and an explanation
            message.
935
        """
936 937 938 939
        return (
            user_request.has_perm('users.view_user'),
            u"Vous n'avez pas accès à la liste des utilisateurs."
        )
940

941
    def can_delete(self, user_request, *_args, **_kwargs):
942 943 944 945
        """Check if an user can delete an user object.

        :param self: The user who is to be deleted.
        :param user_request: The user who requests deletion.
946 947
        :return: True if user_request has the right 'bureau', and a
            message.
948
        """
949 950 951 952
        return (
            user_request.has_perm('users.delete_user'),
            u"Vous ne pouvez pas supprimer cet utilisateur."
        )
953

954 955 956
    def __init__(self, *args, **kwargs):
        super(User, self).__init__(*args, **kwargs)
        self.field_permissions = {
957 958 959
            'shell': self.can_change_shell,
            'force': self.can_change_force,
            'selfpasswd': self.check_selfpasswd,
960 961
            'local_email_redirect': self.can_change_local_email_redirect,
            'local_email_enabled' : self.can_change_local_email_enabled,
962
        }
Gabriel Detraz's avatar
Gabriel Detraz committed
963
        self.__original_state = self.state
964

965 966 967
    def clean(self, *args, **kwargs):
        """Check if this pseudo is already used by any mailalias.
        Better than raising an error in post-save and catching it"""
968
        if (EMailAddress.objects
969 970 971
                .filter(local_part=self.pseudo)
                .exclude(user=self)):
            raise ValidationError("This pseudo is already in use.")
972

973
    def __str__(self):
974
        return self.pseudo
lhark's avatar
lhark committed
975

976

977
class Adherent(User):
978 979
    """ A class representing a member (it's a user with special
    informations) """
Gabriel Detraz's avatar
Gabriel Detraz committed
980
    PRETTY_NAME = "Adhérents"
981
    name = models.CharField(max_length=255)
982 983 984 985 986 987
    room = models.OneToOneField(
        'topologie.Room',
        on_delete=models.PROTECT,
        blank=True,
        null=True
    )
988

989 990
    @classmethod
    def get_instance(cls, adherentid, *_args, **_kwargs):
991
        """Try to find an instance of `Adherent` with the given id.
992

993 994 995
        :param adherentid: The id of the adherent we are looking for.
        :return: An adherent.
        """
996
        return cls.objects.get(pk=adherentid)
997

998 999
    @staticmethod
    def can_create(user_request, *_args, **_kwargs):
1000 1001 1002 1003
        """Check if an user can create an user object.

        :param user_request: The user who wants to create a user object.
        :return: a message and a boolean which is True if the user can create
1004
            a user or if the `options.all_can_create` is set.
1005
        """
1006 1007
        if (not user_request.is_authenticated and
                not OptionalUser.get_cached_value('self_adhesion')):
1008 1009
            return False, None
        else:
1010 1011
            if (OptionalUser.get_cached_value('all_can_create_adherent') or
                    OptionalUser.get_cached_value('self_adhesion')):
1012 1013
                return True, None
            else:
1014 1015 1016 1017
                return (
                    user_request.has_perm('users.add_user'),
                    u"Vous n'avez pas le droit de créer un utilisateur"
                )
1018

1019

1020
class Club(User):
1021 1022
    """ A class representing a club (it is considered as a user
    with special informations) """
Gabriel Detraz's avatar
Gabriel Detraz committed
1023
    PRETTY_NAME = "Clubs"
1024 1025 1026 1027 1028 1029
    room = models.ForeignKey(
        'topologie.Room',
        on_delete=models.PROTECT,
        blank=True,
        null=True
    )
1030 1031 1032 1033 1034 1035 1036 1037 1038 1039
    administrators = models.ManyToManyField(
        blank=True,
        to='users.Adherent',
        related_name='club_administrator'
    )
    members = models.ManyToManyField(
        blank=True,
        to='users.Adherent',
        related_name='club_members'
    )
1040
    mailing = models.BooleanField(
1041
        default=False
1042
    )
1043

1044 1045
    @staticmethod
    def can_create(user_request, *_args, **_kwargs):
1046 1047 1048 1049
        """Check if an user can create an user object.

        :param user_request: The user who wants to create a user object.
        :return: a message and a boolean which is True if the user can create
1050
            an user or if the `options.all_can_create` is set.
1051 1052 1053 1054 1055 1056 1057
        """
        if not user_request.is_authenticated:
            return False, None
        else:
            if OptionalUser.get_cached_value('all_can_create_club'):
                return True, None
            else:
1058 1059 1060 1061
                return (
                    user_request.has_perm('users.add_user'),
                    u"Vous n'avez pas le droit de créer un club"
                )
1062

1063 1064
    @staticmethod
    def can_view_all(user_request, *_args, **_kwargs):
1065 1066 1067
        """Check if an user can access to the list of every user objects

        :param user_request: The user who wants to view the list.
1068 1069
        :return: True if the user can view the list and an explanation
            message.
1070
        """
1071
        if user_request.has_perm('users.view_user'):
1072
            return True, None
1073 1074 1075 1076
        if (hasattr(user_request, 'is_class_adherent') and
                user_request.is_class_adherent):
            if (user_request.adherent.club_administrator.all() or
                    user_request.adherent.club_members.all()):
1077 1078 1079
                return True, None
        return False, u"Vous n'avez pas accès à la liste des utilisateurs."

1080 1081
    @classmethod
    def get_instance(cls, clubid, *_args, **_kwargs):
1082
        """Try to find an instance of `Club` with the given id.
1083

1084 1085 1086
        :param clubid: The id of the adherent we are looking for.
        :return: A club.
        """
1087
        return cls.objects.get(pk=clubid)
1088

1089

1090 1091
@receiver(post_save, sender=Adherent)
@receiver(post_save, sender=Club)
1092
@receiver(post_save, sender=User)
1093
def user_post_save(**kwargs):
chirac's avatar
chirac committed
1094
    """ Synchronisation post_save : envoie le mail de bienvenue si creation
1095
    Synchronise le pseudo, en créant un alias mail correspondant
chirac's avatar
chirac committed
1096
    Synchronise le ldap"""
1097
    is_created = kwargs['created']
1098
    user = kwargs['instance']
1099
    EMailAddress.objects.get_or_create(local_part=user.pseudo, user=user)
1100 1101
    if is_created:
        user.notif_inscription()
Gabriel Detraz's avatar
Gabriel Detraz committed
1102
    user.state_sync()
1103 1104 1105 1106 1107 1108
    user.ldap_sync(
        base=True,
        access_refresh=True,
        mac_refresh=False,
        group_refresh=True
    )
1109
    regen('mailing')
1110

chirac's avatar
chirac committed
1111

1112 1113 1114 1115 1116 1117 1118 1119 1120 1121
@receiver(m2m_changed, sender=User.groups.through)
def user_group_relation_changed(**kwargs):
    action = kwargs['action']
    if action in ('post_add', 'post_remove', 'post_clear'):
        user = kwargs['instance']
        user.ldap_sync(base=False,
                       access_refresh=False,
                       mac_refresh=False,
                       group_refresh=True)

1122 1123
@receiver(post_delete, sender=Adherent)
@receiver(post_delete, sender=Club)
1124
@receiver(post_delete, sender=User)
1125
def user_post_delete(**kwargs):
chirac's avatar
chirac committed
1126
    """Post delete d'un user, on supprime son instance ldap"""
1127
    user = kwargs['instance']
1128
    user.ldap_del()
1129
    regen('mailing')
1130

1131

1132
class ServiceUser(RevMixin, AclMixin, AbstractBaseUser):
chirac's avatar
chirac committed
1133
    """ Classe des users daemons, règle leurs accès au ldap"""
1134 1135
    readonly = 'readonly'
    ACCESS = (
chirac's avatar
chirac committed
1136 1137 1138 1139
        ('auth', 'auth'),
        ('readonly', 'readonly'),
        ('usermgmt', 'usermgmt'),
    )
1140

1141
    PRETTY_NAME = "Utilisateurs de service"
chirac's avatar
chirac committed
1142

chirac's avatar
chirac committed
1143 1144 1145 1146 1147 1148 1149 1150 1151 1152 1153 1154 1155 1156 1157 1158
    pseudo = models.CharField(
        max_length=32,
        unique=True,
        help_text="Doit contenir uniquement des lettres, chiffres, ou tirets",
        validators=[linux_user_validator]
    )
    access_group = models.CharField(
        choices=ACCESS,
        default=readonly,
        max_length=32
    )
    comment = models.CharField(
        help_text="Commentaire",
        max_length=255,
        blank=True
    )
chirac's avatar
chirac committed
1159 1160 1161 1162

    USERNAME_FIELD = 'pseudo'
    objects = UserManager()

1163 1164 1165 1166 1167
    class Meta:
        permissions = (
            ("view_serviceuser", "Peut voir un objet serviceuser"),
        )

1168 1169 1170 1171 1172 1173 1174 1175
    def get_full_name(self):
        """ Renvoie le nom complet du serviceUser formaté nom/prénom"""
        return "ServiceUser <{name}>".format(name=self.pseudo)

    def get_short_name(self):
        """ Renvoie seulement le nom"""
        return self.pseudo

chirac's avatar
chirac committed
1176
    def ldap_sync(self):
chirac's avatar
chirac committed
1177
        """ Synchronisation du ServiceUser dans sa version ldap"""
chirac's avatar
chirac committed
1178 1179 1180 1181
        try:
            user_ldap = LdapServiceUser.objects.get(name=self.pseudo)
        except LdapServiceUser.DoesNotExist:
            user_ldap = LdapServiceUser(name=self.pseudo)
1182
        user_ldap.user_password = self.password[:6] + self.password[7:]
chirac's avatar
chirac committed
1183
        user_ldap.save()
1184
        self.serviceuser_group_sync()
chirac's avatar
chirac committed
1185 1186

    def ldap_del(self):
chirac's avatar
chirac committed
1187
        """Suppression de l'instance ldap d'un service user"""
chirac's avatar
chirac committed
1188 1189 1190 1191 1192
        try:
            user_ldap = LdapServiceUser.objects.get(name=self.pseudo)
            user_ldap.delete()
        except LdapUser.DoesNotExist:
            pass
1193 1194 1195
        self.serviceuser_group_sync()

    def serviceuser_group_sync(self):
chirac's avatar
chirac committed
1196
        """Synchronise le groupe et les droits de groupe dans le ldap"""
1197 1198 1199 1200
        try:
            group = LdapServiceUserGroup.objects.get(name=self.access_group)
        except:
            group = LdapServiceUserGroup(name=self.access_group)
chirac's avatar
chirac committed
1201 1202 1203 1204
        group.members = list(LdapServiceUser.objects.filter(
            name__in=[user.pseudo for user in ServiceUser.objects.filter(
                access_group=self.access_group
            )]).values_list('dn', flat=True))
1205
        group.save()
chirac's avatar
chirac committed
1206

1207 1208
    def __str__(self):
        return self.pseudo
chirac's avatar
chirac committed
1209

1210

chirac's avatar
chirac committed
1211
@receiver(post_save, sender=ServiceUser)
1212
def service_user_post_save(**kwargs):
chirac's avatar
chirac committed
1213
    """ Synchronise un service user ldap après modification django"""
chirac's avatar
chirac committed
1214
    service_user = kwargs['instance']
1215
    service_user.ldap_sync()
chirac's avatar
chirac committed
1216

chirac's avatar
chirac committed
1217

chirac's avatar
chirac committed
1218
@receiver(post_delete, sender=ServiceUser)
1219
def service_user_post_delete(**kwargs):
chirac's avatar
chirac committed
1220
    """ Supprime un service user ldap après suppression django"""
chirac's avatar
chirac committed
1221
    service_user = kwargs['instance']
1222
    service_user.ldap_del()
chirac's avatar
chirac committed
1223

chirac's avatar
chirac committed
1224

1225
class School(RevMixin, AclMixin, models.Model):
chirac's avatar
chirac committed
1226
    """ Etablissement d'enseignement"""
Gabriel Detraz's avatar
Gabriel Detraz committed
1227
    PRETTY_NAME = "Établissements enregistrés"
1228

lhark's avatar
lhark committed
1229 1230
    name = models.CharField(max_length=255)

1231 1232 1233 1234 1235
    class Meta:
        permissions = (
            ("view_school", "Peut voir un objet school"),
        )

1236 1237 1238
    def __str__(self):
        return self.name

1239

1240
class ListRight(RevMixin, AclMixin, Group):
chirac's avatar
chirac committed
1241 1242
    """ Ensemble des droits existants. Chaque droit crée un groupe
    ldap synchronisé, avec gid.
chirac's avatar
chirac committed
1243
    Permet de gérer facilement les accès serveurs et autres
chirac's avatar
chirac committed
1244 1245
    La clef de recherche est le gid, pour cette raison là
    il n'est plus modifiable après creation"""
1246 1247
    PRETTY_NAME = "Liste des droits existants"

1248
    unix_name = models.CharField(
chirac's avatar
chirac committed
1249 1250 1251 1252
        max_length=255,
        unique=True,
        validators=[RegexValidator(
            '^[a-z]+$',
1253 1254
            message=("Les groupes unix ne peuvent contenir que des lettres "
                     "minuscules")
chirac's avatar
chirac committed
1255 1256
        )]
    )
1257
    gid = models.PositiveIntegerField(unique=True, null=True)
1258
    critical = models.BooleanField(default=False)
chirac's avatar
chirac committed
1259 1260 1261 1262 1263
    details = models.CharField(
        help_text="Description",
        max_length=255,
        blank=True
    )
1264

1265 1266 1267 1268 1269
    class Meta:
        permissions = (
            ("view_listright", "Peut voir un objet Group/ListRight"),
        )

1270
    def __str__(self):
1271
        return self.name
1272

1273
    def ldap_sync(self):
chirac's avatar
chirac committed
1274
        """Sychronise les groups ldap avec le model listright coté django"""
1275 1276 1277 1278
        try:
            group_ldap = LdapUserGroup.objects.get(gid=self.gid)
        except LdapUserGroup.DoesNotExist:
            group_ldap = LdapUserGroup(gid=self.gid)
1279
        group_ldap.name = self.unix_name
Gabriel Detraz's avatar