class DemoMigrationModel(models.Model): """A model to demonstrate and test migrations from a regular django field to an EncryptedField, regular django field to a SearchField and also an EncryptedField to a SearchField. See the 'migrations' directory and tests.""" data = models.CharField(max_length=10, default="hi") info = models.CharField(max_length=10, default="") # Used to demo migrating 'data' to an EncryptedField encrypted_data = fields.EncryptedCharField(max_length=20, default="hi") # Used to demo migrating 'info' to a SearchField (with associated EncryptedField): encrypted_info = fields.EncryptedCharField(max_length=20, default="") searchable_encrypted_info = fields.SearchField( hash_key="abc", encrypted_field_name="encrypted_info") # Used to demo migrating 'encrypted_data' to a SearchField: _encrypted_data = fields.EncryptedCharField(max_length=20, default="hi") searchable_encrypted_data = fields.SearchField( hash_key="abcd", encrypted_field_name="_encrypted_data") # New fields with defaults added 'later' in a different migration. _default_date = fields.EncryptedDateField(default=datetime.date.today) searchable_default_date = fields.SearchField( hash_key="123abc", encrypted_field_name="_default_date") _default_number = fields.EncryptedPositiveSmallIntegerField(default=1) searchable_default_number = fields.SearchField( hash_key="123abc", encrypted_field_name="_default_number") _default_char = fields.EncryptedCharField(default="foo default", max_length=20) searchable_default_char = fields.SearchField( hash_key="123abc", encrypted_field_name="_default_char") default_encrypted_char = fields.EncryptedCharField(max_length=20, default="encrypted hi") def __str__(self): return self.info
class SpotifyToken(Model): """ Describe a spotify token. Self explenatory. """ _access = fields.EncryptedCharField(max_length=256) access = fields.SearchField(hash_key=HASH_KEY, encrypted_field_name='_access') _refresh = fields.EncryptedCharField(max_length=256) refresh = fields.SearchField(hash_key=HASH_KEY, encrypted_field_name='_refresh') expiration = DateTimeField()
class CreditCard(models.Model): exp_date = models.DateField(auto_now=False, auto_now_add=False) holder = models.CharField(max_length=70, blank=False, default="") number_data = fields.EncryptedCharField(max_length=255, default="", blank=False) number = fields.SearchField( hash_key= "f164ec6bd6fbc4aef5647abc15199da0f9badcc1d2127bde2087ae0d794a9a0b", encrypted_field_name="number_data") cvv = models.IntegerField(blank=True) brand = models.CharField(max_length=70, blank=True, default="")
class Key(models.Model): """Model, representing keys for code panel in service. Fields: id (int): Internal read-only identifier. code (str): Generated code. See key_validator and function hash_code from signals.py. Should hashes before saving. hash_code (str): Read-only hashed code. See function hash_code from signals.py lock (Lock): Required. Lock for which access is given. access_start (Datetime): Required. Time when access to lock starts. access_stop (Datetime): Required. Time when access to lock ends. """ id = models.BigAutoField('id', primary_key=True) _code_data = fields.EncryptedPositiveIntegerField( validators=[key_validator], null=False) code = fields.SearchField(hash_key=settings.KEY_HASH, encrypted_field_name='_code_data', editable=False) code_secure = models.TextField(editable=True, blank=True, null=False) hash_code = models.CharField('hash_code', max_length=256, unique=False, blank=True) lock = models.ForeignKey(Lock, models.CASCADE, 'lock_code', null=False, verbose_name='lock_id', db_index=True) access_start = models.DateTimeField('access_start') access_stop = models.DateTimeField('access_stop') created_manually = models.BooleanField(default=False) is_master = models.BooleanField(default=False) created_at = models.DateTimeField(auto_now_add=True) updated_at = models.DateTimeField(auto_now_add=True) class Meta: managed = True db_table = 'register_key' @classmethod def get_instance_by_hash_id(cls, hash_id): """Finds key by hashed uuid. Args: hash_id (str): given hashed code. Returns: Key: object having that code. """ return cls.objects.get(hash_code=hash_id.lower())
class Card(models.Model): """Model, representing RFID cards in service. Fields: id (int): Internal read-only identifier. card_id (str): Identifier of the physical RFID card. See card_validator. Should hashes before saving. hash_id (str): Read-only hashed card_id. See function hash_card_id from signals.py is_master (bool): Required. Indicates, does it master key. lock (Lock): Required. Lock for which access is given. """ id = models.BigAutoField('id', primary_key=True) _card_data = fields.EncryptedCharField(validators=[card_validator], null=False, max_length=9) card_id = fields.SearchField(hash_key=settings.CARD_HASH, encrypted_field_name='_card_data', editable=False) hash_id = models.CharField('hash_id', max_length=256, unique=False, editable=False) is_master = models.BooleanField('is_master', null=False, default=False) lock = models.ForeignKey(Lock, models.CASCADE, 'lock_key', null=False, verbose_name='lock_id', db_index=True) created_at = models.DateTimeField(auto_now_add=True) updated_at = models.DateTimeField(auto_now_add=True) class Meta: managed = True db_table = 'register_card' @classmethod def get_instance_by_hash_id(cls, hash_id): """Finds card by hashed card_id. Args: hash_id (str): given hashed card_id. Returns: Card: object having that card_id. """ return cls.objects.get(hash_id=hash_id.lower())
class User(AbstractUser): """Example of replacing 'username' with Search and Encryption fields. Note we add the unique constraint to the SearchField.""" username_validator = UnicodeUsernameValidator() _username = fields.EncryptedCharField( max_length=150, validators=[username_validator], ) username = fields.SearchField( hash_key="abc123", encrypted_field_name="_username", unique=True, error_messages={ "unique": "Custom error message for already exists.", }, ) USERNAME_FIELD = "username"
class SearchCharWithDefault(models.Model): value = fields.EncryptedCharField(max_length=25, default="foo") search = fields.SearchField(hash_key="abc123", encrypted_field_name="value")
def test_must_have_hash_key(): with pytest.raises(ImproperlyConfigured): fields.SearchField(hash_key=None, encrypted_field_name="value")
class SearchPosInt(models.Model): value = fields.EncryptedPositiveIntegerField() search = fields.SearchField(hash_key="abc123", encrypted_field_name="value")
class SearchDateTime(models.Model): value = fields.EncryptedDateTimeField() search = fields.SearchField(hash_key="abc123", encrypted_field_name="value")
class SearchEmail(models.Model): value = fields.EncryptedEmailField() search = fields.SearchField(hash_key="abc123", encrypted_field_name="value")
class SearchInt(models.Model): value = fields.EncryptedIntegerField( validators=[validators.MaxValueValidator(10)]) search = fields.SearchField(hash_key="abc123", encrypted_field_name="value")
def test_encrypted_field_name_is_string(): with pytest.raises(ImproperlyConfigured): fields.SearchField(hash_key="aa", encrypted_field_name=1)
class SearchChar(models.Model): value = fields.EncryptedCharField(max_length=25) search = fields.SearchField(hash_key="abc123", encrypted_field_name="value")
def test_default_not_allowed(): with pytest.raises(ImproperlyConfigured): fields.SearchField(hash_key="aa", encrypted_field_name="v", default="foo")
class DemoModel(models.Model): """Note that all regulare kwargs are added to EncryptedFields and not SearchFields. Eg 'default=', 'null=', 'blank='. Also note that we declare the SearchField *after* its EncryptedField. This is only important when using DRF ModelSerializers, but never the less should be the standard way of doing it.""" _email_data = fields.EncryptedEmailField() email = fields.SearchField(hash_key="123abc", encrypted_field_name="_email_data") _name_data = fields.EncryptedCharField( max_length=10, blank=True, null=True, help_text="This field is not required.") name = fields.SearchField(hash_key="123abc", encrypted_field_name="_name_data") _date_data = fields.EncryptedDateField() date = fields.SearchField(hash_key="123abc", encrypted_field_name="_date_data") date_2 = fields.EncryptedDateField( blank=True, null=True, help_text="This field is just encrypted and is not required.", ) _number_data = fields.EncryptedPositiveSmallIntegerField() number = fields.SearchField(hash_key="123abc", encrypted_field_name="_number_data") _text_data = fields.EncryptedTextField( help_text="A text area. Not typically used with a SearchField.") text = fields.SearchField(hash_key="123abc", encrypted_field_name="_text_data") info = fields.EncryptedCharField( blank=True, null=False, max_length=20, help_text= "Char field, required at db level, without a default and blank=True", ) # Examples with defaults _default_date_data = fields.EncryptedDateField(default=datetime.date.today) default_date = fields.SearchField( hash_key="123abc", encrypted_field_name="_default_date_data") _default_number_data = fields.EncryptedPositiveSmallIntegerField(default=1) default_number = fields.SearchField( hash_key="123abc", encrypted_field_name="_default_number_data") _default_char_data = fields.EncryptedCharField(default="foo default", max_length=20) default_char = fields.SearchField( hash_key="123abc", encrypted_field_name="_default_char_data") # typical use case created_at = fields.EncryptedDateTimeField(auto_now_add=True) updated_at = fields.EncryptedDateTimeField(auto_now=True) def __str__(self): return f"{self.pk}: {self.name}" def get_absolute_url(self): return reverse("demomodel-list")
class SearchDateWithDefault(models.Model): value = fields.EncryptedDateField(default=datetime.date.today) search = fields.SearchField(hash_key="abc123", encrypted_field_name="value")
def test_index_by_default(): f = fields.SearchField(hash_key="aa", encrypted_field_name="v") assert f.db_index is True
def test_db_index_allowed(): f = fields.SearchField(hash_key="aa", encrypted_field_name="v", db_index=False) assert f.db_index is False
def test_primary_key_not_allowed(): with pytest.raises(ImproperlyConfigured): fields.SearchField(primary_key=True, hash_key="a", encrypted_field_name="value")
def test_must_have_encrypted_field_name(): with pytest.raises(ImproperlyConfigured): fields.SearchField(hash_key="aa", encrypted_field_name=None)