class Role(models.Model): ALUMNI_ROLES_HRID = ( 'x', 'master', 'phd', 'bachelor', 'executive', 'graduate', 'masterspe', ) system = models.BooleanField(_("system role"), default=False, editable=False) hrid = models.SlugField(_("human-readable identifier"), unique=True) display = UnboundedCharField(_("display name")) class Meta: verbose_name = _("role") verbose_name_plural = _("roles") def __str__(self): return self.hrid @classmethod def get_admin(cls): return cls.objects.get(hrid=ADMIN_ROLE_HRID)
class GoogleAppsPassword(models.Model): """Password for the associated Google Apps account""" user = models.OneToOneField(User, on_delete=models.CASCADE, primary_key=True, related_name='gapps_password', verbose_name=_("user")) password = UnboundedCharField(_("password")) last_update = models.DateTimeField(auto_now=True) class Meta: verbose_name = _("Google Apps password") verbose_name_plural = _("Google Apps passwords")
class Role(models.Model): system = models.BooleanField(_("system role"), default=False, editable=False) hrid = models.SlugField(_("human-readable identifier"), unique=True) display = UnboundedCharField(_("display name"), unique=True) class Meta: verbose_name = _("role") verbose_name_plural = _("roles") def __str__(self): return self.hrid @classmethod def get_admin(cls): return cls.objects.get(hrid=ADMIN_ROLE_HRID)
class User(base_user.AbstractBaseUser): MALE = 'male' FEMALE = 'female' SEX = ( (MALE, _("Male")), (FEMALE, _("Female")), ) uid = models.UUIDField("UUID", default=uuid.uuid4, editable=False) hrid = DottedSlugField( _("username"), unique=True, max_length=255, help_text=_( "Human-readable identifier, usually firstname.lastname.study-year") ) fullname = UnboundedCharField( _("full name"), help_text=_("Name to display to other users")) preferred_name = UnboundedCharField( _("preferred name"), help_text=_("Name used when addressing the user")) firstname = UnboundedCharField(_("first name"), blank=True, null=True) lastname = UnboundedCharField(_("last name"), blank=True, null=True) sex = models.CharField(_("sex"), max_length=6, choices=SEX, blank=True, null=True) main_email = models.EmailField(_("email"), unique=True) roles = models.ManyToManyField(Role, related_name='members', blank=True, verbose_name=_("roles")) axid = models.CharField(_("AX ID"), max_length=20, blank=True, null=True, unique=True, help_text=_("Identification in AX directory")) schoolid = models.CharField( _("School ID"), max_length=20, blank=True, null=True, unique=True, help_text=_("Identification defined by the School")) xorgdb_uid = models.IntegerField( _("Polytechnique.org database user ID"), blank=True, null=True, unique=True, help_text=_("User ID in Polytechnique.org database")) alumnforce_id = models.CharField( _("AlumnForce ID"), max_length=20, blank=True, null=True, unique=True, help_text=_("User ID in ax.polytechnique.org database")) study_year = UnboundedCharField( _("study year"), blank=True, null=True, help_text=_( "Kind and main year of the study ('X1829' means 'entered the school in 1829 " "but 'M2005' means 'graduated in 2005')")) grad_year = models.IntegerField(_("graduation year"), blank=True, null=True, help_text=_("Year of the graduation")) is_active = models.BooleanField(_('active'), default=True, help_text=_('Active user')) birth_date = models.DateField(_("birthdate"), blank=True, null=True) is_dead = models.BooleanField(_("dead"), default=False) death_date = models.DateField(_("death date"), blank=True, null=True) ax_contributor = models.NullBooleanField( _('AX contributor'), help_text=_('Paid a contribution to AX')) axjr_subscriber = models.NullBooleanField( _('J&R subscriber'), help_text=_('Subscribed to La Jaune et la Rouge')) ax_last_synced = models.DateField(_("last sync with AX"), blank=True, null=True) objects = UserManager() class Meta: verbose_name = _("user account") verbose_name_plural = _("user accounts") # base_user.AbstractBaseUser bridge EMAIL_FIELD = 'main_email' USERNAME_FIELD = 'hrid' REQUIRED_FIELDS = ['fullname', 'preferred_name', 'main_email'] def get_username(self): return self.hrid def get_full_name(self): return self.fullname def get_short_name(self): return self.preferred_name @property def is_staff(self): """Staff members are defined by the admin role""" return self.roles.filter(hrid=ADMIN_ROLE_HRID).count() > 0 @property def email(self): """Hack to work around a bug in django-oidc-provider""" field_name = self.get_email_field_name() return getattr(self, field_name) def has_module_perms(self, app_label): # staff members have every right if self.is_staff: return True # FIXME implement permissions for other user kinds return False def has_perm(self, perm, obj=None): # staff members have every right if self.is_staff: return True # FIXME implement permissions for other user kinds return False def is_x_alumni(self): """The user is an alumni of Ecole Polytechnique (not an external account)""" return self.roles.filter(hrid__in=Role.ALUMNI_ROLES_HRID).exists() def clean(self): # If the death date is filled, the user is dead if self.death_date is not None and not self.is_dead: self.is_dead = True # Make sure the human-readable identifier is in lowercase if self.hrid != self.hrid.lower(): raise ValidationError({ 'hrid': ValidationError(_("Enter a human-readable ID in lowercase.")), }) # Make sure the email address is in lowercase if self.main_email != self.main_email.lower(): raise ValidationError({ 'main_email': ValidationError(_("Enter an email address in lowercase.")), })
class AuthGroupeXClient(models.Model): privkey = models.CharField( _("private key"), unique=True, max_length=40, help_text=_("Secret key shared with the client")) name = UnboundedCharField(_("name"), unique=True, help_text=_("Name of the client")) data_fields = UnboundedCharField( _("data fields"), help_text=_("Fields requested by the client")) return_urls = UnboundedCharField( _("return URLs"), help_text=_("Regular expression pattern of the allowed return URLs")) last_used = models.DateField( _("last used"), null=True, help_text=_("Date of the last use of this client")) allow_xnet = models.BooleanField( _("allow xnet"), default=False, help_text=_( "Allow account with type 'xnet' (external) to log in to this client" )) objects = AuthGroupeXClientManager() class Meta: verbose_name = _("AuthGroupeX client") verbose_name_plural = _("AuthGroupeX clients") def __str__(self): return self.name def match_return_url(self, url): """Match the given url against the pattern for return URLS""" pattern = self.return_urls.strip() # PHP's preg_match uses a special character at the beginning and the end # of the pattern if pattern and pattern[0] == pattern[-1] == '#': pattern = pattern[1:-1] return re.match(pattern, url) def match_signed_challenge(self, gpex_challenge, gpex_pass): """Verify that the challenge has been signed with the right key""" try: computed_pass = hashlib.md5( (gpex_challenge + self.privkey).encode('ascii')).hexdigest() except UnicodeEncodeError: return return compare_digest(computed_pass, gpex_pass) def add_response_data(self, ext_url_params, user, gpex_challenge, gpex_group): """Add user response data into the returned parameters""" try: hashed_val = ('1' + gpex_challenge + self.privkey).encode('ascii') except UnicodeEncodeError: return False for data_field in self.data_fields.split(','): val = None if data_field == 'perms': val = 'admin' if user.is_staff else 'user' elif data_field == 'forlife': val = user.hrid elif data_field in ('prenom', 'firstname'): val = user.firstname elif data_field in ('nom', 'lastname'): val = user.lastname elif data_field == 'sex': val = user.sex elif data_field == 'matricule_ax': val = user.axid elif data_field == 'matricule': val = user.schoolid elif data_field == 'uid': val = str(user.xorgdb_uid or '') elif data_field == 'username': val = user.main_email elif data_field in ('promo', 'entry_year'): val = user.study_year while val and not '0' <= val[0] <= '9': val = val[1:] elif data_field == 'full_promo': val = user.study_year elif data_field in ('promo_sortie', 'grad_year'): val = str(user.grad_year) elif data_field == 'grpauth' and gpex_group: try: grp_membership = user.groups.get( group__shortname=gpex_group) val = grp_membership.perms if val == 'member': val = 'membre' except ObjectDoesNotExist: pass if not val: val = '' ext_url_params[data_field] = val try: hashed_val += val.encode('utf-8') except UnicodeEncodeError: return False # Sign the values auth = hashlib.md5(hashed_val + b'1').hexdigest() ext_url_params['auth'] = auth return True
class User(base_user.AbstractBaseUser): MALE = 'male' FEMALE = 'female' SEX = ( (MALE, _("Male")), (FEMALE, _("Female")), ) uid = models.UUIDField("UUID", default=uuid.uuid4, editable=False) hrid = DottedSlugField( _("username"), unique=True, max_length=255, help_text=_( "Human-readable identifier, usually firstname.lastname.study-year") ) fullname = UnboundedCharField( _("full name"), help_text=_("Name to display to other users")) preferred_name = UnboundedCharField( _("preferred name"), help_text=_("Name used when addressing the user")) firstname = UnboundedCharField(_("first name"), null=True) lastname = UnboundedCharField(_("last name"), null=True) sex = models.CharField(_("sex"), max_length=6, choices=SEX, null=True) main_email = models.EmailField(_("email"), unique=True) roles = models.ManyToManyField(Role, related_name='members', blank=True, verbose_name=_("roles")) axid = UnboundedCharField(_("AX ID"), blank=True, null=True, help_text=_("Identification in AX directory")) schoolid = UnboundedCharField( _("School ID"), blank=True, null=True, unique=True, help_text=_("Identification defined by the School")) xorgdb_uid = models.IntegerField( _("Polytechnique.org database user ID"), null=True, unique=True, help_text=_("User ID in Polytechnique.org database")) study_year = UnboundedCharField( _("study year"), blank=True, null=True, help_text= _("Kind and main year of the study ('X1829' means 'entered the school in 1829 " + "but 'M2005' means 'graduated in 2005')")) grad_year = models.IntegerField(_("graduation year"), blank=True, null=True, help_text=_("Year of the graduation")) objects = UserManager() class Meta: verbose_name = _("user account") verbose_name_plural = _("user accounts") # base_user.AbstractBaseUser bridge EMAIL_FIELD = 'main_email' USERNAME_FIELD = 'hrid' REQUIRED_FIELDS = ['fullname', 'preferred_name', 'main_email'] def get_username(self): return self.hrid def get_full_name(self): return self.fullname def get_short_name(self): return self.preferred_name @property def is_staff(self): """Staff members are defined by the admin role""" return self.roles.filter(hrid=ADMIN_ROLE_HRID).count() > 0 @property def email(self): """Hack to work around a bug in django-oidc-provider""" field_name = self.get_email_field_name() return getattr(self, field_name) def has_module_perms(self, app_label): # staff members have every right if self.is_staff: return True # FIXME implement permissions for other user kinds return False def has_perm(self, perm, obj=None): # staff members have every right if self.is_staff: return True # FIXME implement permissions for other user kinds return False