1
1
import binascii
2
+ import codecs
2
3
import datetime
3
4
import string
4
5
import sys
8
9
from django .forms import fields
9
10
from django .db import models
10
11
from django .conf import settings
11
- from django .utils .encoding import smart_str , force_unicode
12
12
from django .utils .translation import ugettext_lazy as _
13
13
from Crypto import Random
14
14
from Crypto .Random import random
28
28
except :
29
29
import pickle
30
30
31
+ if sys .version_info [0 ] == 3 :
32
+ PYTHON3 = True
33
+ from django .utils .encoding import smart_str , force_text as force_unicode
34
+ else :
35
+ PYTHON3 = False
36
+ from django .utils .encoding import smart_str , force_unicode
37
+
38
+
31
39
class BaseEncryptedField (models .Field ):
32
40
'''This code is based on the djangosnippet #1095
33
41
You can find the original at http://www.djangosnippets.org/snippets/1095/'''
@@ -76,15 +84,20 @@ def __init__(self, *args, **kwargs):
76
84
super (BaseEncryptedField , self ).__init__ (* args , ** kwargs )
77
85
78
86
def _is_encrypted (self , value ):
79
- return isinstance (value , basestring ) and value .startswith (self .prefix )
87
+ if PYTHON3 is True :
88
+ is_enc = isinstance (value , str ) and value .startswith (self .prefix )
89
+ return is_enc
90
+ else :
91
+ return isinstance (value , basestring ) and value .startswith (
92
+ self .prefix )
80
93
81
94
def _get_padding (self , value ):
82
95
# We always want at least 2 chars of padding (including zero byte),
83
96
# so we could have up to block_size + 1 chars.
84
97
mod = (len (value ) + 2 ) % self .cipher .block_size
85
98
return self .cipher .block_size - mod + 2
86
99
87
- def to_python (self , value ):
100
+ def from_db_value (self , value , expression , connection , context ):
88
101
if self ._is_encrypted (value ):
89
102
if self .block_type :
90
103
self .iv = binascii .a2b_hex (value [len (self .prefix ):])[:len (self .iv )]
@@ -96,31 +109,51 @@ def to_python(self, value):
96
109
else :
97
110
decrypt_value = binascii .a2b_hex (value [len (self .prefix ):])
98
111
return force_unicode (
99
- self .cipher .decrypt (decrypt_value ).split ('\0 ' )[0 ]
112
+ self .cipher .decrypt (decrypt_value ).split (b '\0 ' )[0 ]
100
113
)
101
114
return value
102
115
103
116
def get_db_prep_value (self , value , connection = None , prepared = False ):
104
117
if value is None :
105
118
return None
106
119
107
- value = smart_str (value )
120
+ if PYTHON3 is True :
121
+ value = bytes (value .encode ('utf-8' ))
122
+ else :
123
+ value = smart_str (value )
108
124
109
125
if not self ._is_encrypted (value ):
110
126
padding = self ._get_padding (value )
111
127
if padding > 0 :
112
- value += "\0 " + '' .join ([
113
- random .choice (string .printable )
114
- for index in range (padding - 1 )
115
- ])
128
+ if PYTHON3 is True :
129
+ value += bytes ("\0 " .encode ('utf-8' )) + bytes (
130
+ '' .encode ('utf-8' )).join ([
131
+ bytes (random .choice (
132
+ string .printable ).encode ('utf-8' ))
133
+ for index in range (padding - 1 )])
134
+ else :
135
+ value += "\0 " + '' .join ([
136
+ random .choice (string .printable )
137
+ for index in range (padding - 1 )
138
+ ])
116
139
if self .block_type :
117
140
self .cipher = self .cipher_object .new (
118
141
self .secret_key ,
119
142
getattr (self .cipher_object , self .block_type ),
120
143
self .iv )
121
- value = self .prefix + binascii .b2a_hex (self .iv + self .cipher .encrypt (value ))
144
+ if PYTHON3 is True :
145
+ value = self .prefix + binascii .b2a_hex (
146
+ self .iv + self .cipher .encrypt (value )).decode ('utf-8' )
147
+ else :
148
+ value = self .prefix + binascii .b2a_hex (
149
+ self .iv + self .cipher .encrypt (value ))
122
150
else :
123
- value = self .prefix + binascii .b2a_hex (self .cipher .encrypt (value ))
151
+ if PYTHON3 is True :
152
+ value = self .prefix + binascii .b2a_hex (
153
+ self .cipher .encrypt (value )).decode ('utf-8' )
154
+ else :
155
+ value = self .prefix + binascii .b2a_hex (
156
+ self .cipher .encrypt (value ))
124
157
return value
125
158
126
159
def deconstruct (self ):
@@ -136,7 +169,6 @@ def deconstruct(self):
136
169
137
170
138
171
class EncryptedTextField (BaseEncryptedField ):
139
- __metaclass__ = models .SubfieldBase
140
172
141
173
def get_internal_type (self ):
142
174
return 'TextField'
@@ -148,7 +180,6 @@ def formfield(self, **kwargs):
148
180
149
181
150
182
class EncryptedCharField (BaseEncryptedField ):
151
- __metaclass__ = models .SubfieldBase
152
183
153
184
def get_internal_type (self ):
154
185
return "CharField"
@@ -191,6 +222,9 @@ def formfield(self, **kwargs):
191
222
return super (BaseEncryptedDateField , self ).formfield (** defaults )
192
223
193
224
def to_python (self , value ):
225
+ return self .from_db_value (value )
226
+
227
+ def from_db_value (self , value , expression , connection , context ):
194
228
# value is either a date or a string in the format "YYYY:MM:DD"
195
229
196
230
if value in fields .EMPTY_VALUES :
@@ -199,7 +233,8 @@ def to_python(self, value):
199
233
if isinstance (value , self .date_class ):
200
234
date_value = value
201
235
else :
202
- date_text = super (BaseEncryptedDateField , self ).to_python (value )
236
+ date_text = super (BaseEncryptedDateField , self ).from_db_value (
237
+ value , expression , connection , context )
203
238
date_value = self .date_class (* map (int , date_text .split (':' )))
204
239
return date_value
205
240
@@ -218,7 +253,6 @@ def get_db_prep_value(self, value, connection=None, prepared=False):
218
253
219
254
220
255
class EncryptedDateField (BaseEncryptedDateField ):
221
- __metaclass__ = models .SubfieldBase
222
256
form_widget = forms .DateInput
223
257
form_field = forms .DateField
224
258
save_format = "%Y:%m:%d"
@@ -228,7 +262,6 @@ class EncryptedDateField(BaseEncryptedDateField):
228
262
229
263
class EncryptedDateTimeField (BaseEncryptedDateField ):
230
264
# FIXME: This doesn't handle time zones, but Python doesn't really either.
231
- __metaclass__ = models .SubfieldBase
232
265
form_widget = forms .DateTimeInput
233
266
form_field = forms .DateTimeField
234
267
save_format = "%Y:%m:%d:%H:%M:%S:%f"
@@ -248,11 +281,15 @@ def get_internal_type(self):
248
281
return 'CharField'
249
282
250
283
def to_python (self , value ):
284
+ return self .from_db_value (value )
285
+
286
+ def from_db_value (self , value , expression , connection , context ):
251
287
# value is either an int or a string of an integer
252
288
if isinstance (value , self .number_type ) or value == '' :
253
289
number = value
254
290
else :
255
- number_text = super (BaseEncryptedNumberField , self ).to_python (value )
291
+ number_text = super (BaseEncryptedNumberField , self ).from_db_value (
292
+ value , expression , connection , context )
256
293
number = self .number_type (number_text )
257
294
return number
258
295
@@ -267,47 +304,67 @@ def get_db_prep_value(self, value, connection=None, prepared=False):
267
304
268
305
269
306
class EncryptedIntField (BaseEncryptedNumberField ):
270
- __metaclass__ = models .SubfieldBase
271
- max_raw_length = len (str (- sys .maxint - 1 ))
307
+ if PYTHON3 is True :
308
+ max_raw_length = len (str (- sys .maxsize - 1 ))
309
+ else :
310
+ max_raw_length = len (str (- sys .maxint - 1 ))
272
311
number_type = int
273
312
format_string = "%d"
274
313
275
314
276
315
class EncryptedLongField (BaseEncryptedNumberField ):
277
- __metaclass__ = models .SubfieldBase
278
316
max_raw_length = None # no limit
279
- number_type = long
317
+ if PYTHON3 is True :
318
+ number_type = int
319
+ else :
320
+ number_type = long
280
321
format_string = "%d"
281
322
282
323
def get_internal_type (self ):
283
324
return 'TextField'
284
325
285
326
286
327
class EncryptedFloatField (BaseEncryptedNumberField ):
287
- __metaclass__ = models .SubfieldBase
288
328
max_raw_length = 150 # arbitrary, but should be sufficient
289
329
number_type = float
290
330
# If this format is too long for some architectures, change it.
291
331
format_string = "%0.66f"
292
332
293
333
294
334
class PickleField (models .TextField ):
295
- __metaclass__ = models .SubfieldBase
296
-
297
335
editable = False
298
336
serialize = False
299
337
300
338
def get_db_prep_value (self , value , connection = None , prepared = False ):
301
- return pickle .dumps (value )
339
+ if PYTHON3 is True :
340
+ # When PYTHON3, we convert data to base64 to prevent errors when
341
+ # unpickling.
342
+ val = codecs .encode (pickle .dumps (value ), 'base64' ).decode ()
343
+ return val
344
+ else :
345
+ return pickle .dumps (value )
302
346
303
347
def to_python (self , value ):
304
- if not isinstance (value , basestring ):
305
- return value
348
+ return self .from_db_value (value )
349
+
350
+ def from_db_value (self , value , expression , connection , context ):
351
+ if PYTHON3 is True :
352
+ if not isinstance (value , str ):
353
+ return value
354
+ else :
355
+ if not isinstance (value , basestring ):
356
+ return value
306
357
307
358
# Tries to convert unicode objects to string, cause loads pickle from
308
359
# unicode excepts ugly ``KeyError: '\x00'``.
309
360
try :
310
- return pickle .loads (smart_str (value ))
361
+ if PYTHON3 is True :
362
+ # When PYTHON3, data are in base64 to prevent errors when
363
+ # unpickling.
364
+ val = pickle .loads (codecs .decode (value .encode (), "base64" ))
365
+ return val
366
+ else :
367
+ return pickle .loads (smart_str (value ))
311
368
# If pickle could not loads from string it's means that it's Python
312
369
# string saved to PickleField.
313
370
except ValueError :
@@ -317,14 +374,12 @@ def to_python(self, value):
317
374
318
375
319
376
class EncryptedUSPhoneNumberField (BaseEncryptedField ):
320
- __metaclass__ = models .SubfieldBase
321
-
322
377
def get_internal_type (self ):
323
378
return "CharField"
324
379
325
380
def formfield (self , ** kwargs ):
326
381
try :
327
- from localflavor .us .forms import USPhoneNumberField
382
+ from localflavor .us .forms import USPhoneNumberField
328
383
except ImportError :
329
384
from django .contrib .localflavor .us .forms import USPhoneNumberField
330
385
@@ -334,23 +389,21 @@ def formfield(self, **kwargs):
334
389
335
390
336
391
class EncryptedUSSocialSecurityNumberField (BaseEncryptedField ):
337
- __metaclass__ = models .SubfieldBase
338
-
339
392
def get_internal_type (self ):
340
393
return "CharField"
341
394
342
395
def formfield (self , ** kwargs ):
343
396
try :
344
397
from localflavor .us .forms import USSocialSecurityNumberField
345
398
except ImportError :
346
- from django .contrib .localflavor .us .forms import USSocialSecurityNumberField
399
+ from django .contrib .localflavor .us .forms import USSocialSecurityNumberField
347
400
348
401
defaults = {'form_class' : USSocialSecurityNumberField }
349
402
defaults .update (kwargs )
350
403
return super (EncryptedUSSocialSecurityNumberField , self ).formfield (** defaults )
351
404
405
+
352
406
class EncryptedEmailField (BaseEncryptedField ):
353
- __metaclass__ = models .SubfieldBase
354
407
description = _ ("E-mail address" )
355
408
356
409
def get_internal_type (self ):
0 commit comments