Changeset 3258

Show
Ignore:
Timestamp:
05/26/07 08:35:08 (1 year ago)
Author:
mscott
Message:
Merging branch for #53.

- Add `HashedPassword` field type and failing test for hashing unicode
  strings.

- Make `HashedValue` field accept unicode values -- they are encoded to
  utf8 strings before hashing and comparison operations.

- Deprecate `Password` field and show deprecation warning.
 
- Alter tests to no longer use `Password` field except when checking
  for deprecation warning of its usage.  Tests that previously used
  `Password` now use `HashedPassword`.
 
- Remove `schevo.icon.schema` and `schevo.identity` packages, as
  schema importing is no longer supported.  Also removed references to
  those from entry points in ``setup.py`` by entirely removing the
  `schevo.schema_export` section.
 
- Documentation about `HashedPassword`

- Documentation about deprecation of `Password` field type.

Files:

Legend:

Unmodified
Added
Removed
Modified
Copied
Moved
  • trunk/Schevo/doc/SchevoSchemaDefinition.txt

    r3233 r3258  
    486486 
    487487 
    488 HashedValue field type 
    489 ...................... 
     488HashedValue field types 
     489....................... 
    490490 
    491491A **HashedValue** field, upon having its value set, will store a 
    492492one-way hash of that value. 
    493493 
    494 You can use the `compare(value)` method of a `HashedValue` field to 
    495 see if the hash matches a value, but it is not possible to retrieve 
    496 the original value from the one-way hash. 
     494Use the `compare(value)` method of a `HashedValue` field instance to 
     495see if the hash matches a value. 
     496 
     497It is not possible to retrieve the original value from the one-way 
     498hash. 
     499 
     500**HashedPassword** fields are a convenience class to show that a field 
     501specifically stores hashed values of passwords.   
    497502 
    498503This is useful for storing information about a user's password in a 
     
    522527field, but that it is a multiple-line field. 
    523528 
    524 **Password** fields are used to store plaintext Unicode strings.  An 
    525 application may assume that the contents of a `Password` field should 
    526 not be displayed as plaintext.  **NOTE:** `Password` fields are not 
    527 necessarily secure.  You may wish to use the `HashedValue field type`_ 
    528 instead. 
    529  
    530529 
    531530Blob fields 
     
    577576Custom field types 
    578577.................. 
     578 
     579 
     580Deprecated field types 
     581...................... 
     582 
     583Deprecated field types are those field types that are available in 
     584Schevo in order to support legacy schemata, but are no longer 
     585recommended for use. 
     586 
     587If you have a database schema that uses any of these field types, you 
     588should use a different field type instead. 
     589 
     590When you use a deprecated field type, Schevo will give a Python 
     591`DeprecationWarning` about such use to encourage you to use a 
     592different field type. 
     593 
     594The following types of fields are deprecated: 
     595 
     596- **Password**: Use one of the `HashedValue field types`_ instead. 
     597 
     598  If you wish to store a plain-text password in your schema, you can 
     599  do one of the following: 
     600 
     601  * Use the `Unicode` field type:: 
     602 
     603        class Foo(E.Entity): 
     604            password = f.unicode() 
     605 
     606  * Create a custom `PlaintextPassword` field type:: 
     607 
     608        class PlaintextPassword(F.Unicode): 
     609            pass 
     610 
     611        class Foo(E.Entity): 
     612            password = f.plaintext_password() 
    579613 
    580614 
  • trunk/Schevo/schevo/field.py

    r3236 r3258  
    190190    was = None 
    191191 
     192    _deprecated_class = False 
     193    _deprecated_class_see_also = None 
    192194    _name = None 
    193195 
     
    479481 
    480482class HashedValue(Field): 
    481     """Field which stores a value as a one-way hash. 
    482  
    483     Useful for storing passwords. 
     483    """Field that stores a value as a one-way hash. 
    484484 
    485485    When you assign or set the value of this field, it stores a 
     
    487487    value itself.  To see if another plaintext value 'matches' the 
    488488    stored hash, use the compare() method. 
     489 
     490    When a unicode value, as opposed to a string value, is given to 
     491    hash or to compare to an existing hash, it will be encoded to a 
     492    UTF-8 encoded string before the hash or comparison operation 
     493    occurs. 
    489494 
    490495    hash_header: The value that is prepended to all hashed values, to 
     
    525530        if value is UNASSIGNED: 
    526531            return value 
    527         if value.startswith(self.hash_header): 
     532        if isinstance(value, str) and value.startswith(self.hash_header): 
    528533            # Short-circuit if the value is already hashed. 
    529534            return value 
    530535        else: 
     536            if isinstance(value, unicode): 
     537                value = value.encode('utf8') 
    531538            return self.hash_encode(value) 
    532539 
     
    540547        algorithm. 
    541548        """ 
     549        if isinstance(value, unicode): 
     550            value = value.encode('utf8') 
    542551        header_len = len(self.hash_header) 
    543552        salt = hashed_value[header_len:header_len+12] 
     
    563572 
    564573 
     574class HashedPassword(HashedValue): 
     575    """Field that stores a password as a one-way hash.""" 
     576 
     577 
     578# -------------------------------------------------------------------- 
     579 
     580 
    565581class String(Field): 
    566582    """String field class. 
     
    642658 
    643659    data_type = unicode 
    644  
    645  
    646 class Password(Unicode): 
    647     """Password field class. 
    648  
    649     Intended to designate a unicode field as something that stores a 
    650     plaintext string, but whose value shouldn't be exposed in a UI. 
    651     """ 
    652  
    653     data_type = unicode 
    654  
    655     def __unicode__(self): 
    656         v = self.get() 
    657         if v is UNASSIGNED: 
    658             return Field.__unicode__(self) 
    659         else: 
    660             return u'(Hidden)' 
    661  
    662     def reversible(self, value=None): 
    663         return u'' 
    664660 
    665661 
     
    15611557 
    15621558 
     1559# -------------------------------------------------------------------- 
     1560 
     1561 
     1562class Password(Unicode): 
     1563    """Password field class. 
     1564 
     1565    Intended to designate a unicode field as something that stores a 
     1566    plaintext string, but whose value shouldn't be exposed in a UI. 
     1567    """ 
     1568 
     1569    data_type = unicode 
     1570 
     1571    _deprecated_class = True 
     1572    _deprecated_class_see_also = 'http://schevo.org/wiki/SchevoSchemaDefinition' 
     1573 
     1574    def __unicode__(self): 
     1575        v = self.get() 
     1576        if v is UNASSIGNED: 
     1577            return Field.__unicode__(self) 
     1578        else: 
     1579            return u'(Hidden)' 
     1580 
     1581    def reversible(self, value=None): 
     1582        return u'' 
     1583 
     1584 
    15631585optimize.bind_all(sys.modules[__name__])  # Last line of module. 
    15641586 
  • trunk/Schevo/schevo/fieldspec.py

    r3112 r3258  
    167167                ) 
    168168            warn(msg, DeprecationWarning, 2) 
     169        # Warn about class deprecation if this class is deprecated. 
     170        if _Field._deprecated_class: 
     171            msg = ( 
     172                "%r is a deprecated field type.  " 
     173                'See %s for more information.' 
     174                % (self.__class__.__name__, _Field._deprecated_class_see_also) 
     175                ) 
     176            warn(msg, DeprecationWarning, 2) 
    169177 
    170178    def __call__(self, fn): 
  • trunk/Schevo/schevo/icon/__init__.py

    r1877 r3258  
    1414# Needs to be imported right away. 
    1515import schevo.entity 
    16 import schevo.icon.schema 
    1716 
    1817 
  • trunk/Schevo/setup.py

    r3112 r3258  
    114114    db = schevo.script.db:start 
    115115    shell = schevo.script.shell:start 
    116  
    117     [schevo.schema_export] 
    118     icon=schevo.icon.schema 
    119     identity=schevo.identity.schema 
    120116    """, 
    121117    ) 
  • trunk/Schevo/tests/test_calculated_field_unicode.py

    r3112 r3258  
    1515    class Thing(E.Entity): 
    1616        image = f.image() 
    17         password = f.password() 
     17        password = f.hashed_password() 
    1818        @f.image() 
    1919        def calc_image(self): 
    2020            return self.image 
    21         @f.password() 
     21        @f.hashed_password() 
    2222        def calc_password(self): 
    2323            return self.password 
     
    3131        # Unicode reprs of fields on thing itself. 
    3232        assert unicode(thing.f.image) == u'(Binary data)' 
    33         assert unicode(thing.f.password) == u'(Hidden)' 
     33        assert unicode(thing.f.password) == u'(Encrypted)' 
    3434        assert unicode(thing.f.calc_image) == u'(Binary data)' 
    35         assert unicode(thing.f.calc_password) == u'(Hidden)' 
     35        assert unicode(thing.f.calc_password) == u'(Encrypted)' 
    3636        # Unicode reprs of fields on thing's default view. 
    3737        thing_view = thing.v.default() 
    3838        assert unicode(thing_view.f.image) == u'(Binary data)' 
    39         assert unicode(thing_view.f.password) == u'(Hidden)' 
     39        assert unicode(thing_view.f.password) == u'(Encrypted)' 
    4040        assert unicode(thing_view.f.calc_image) == u'(Binary data)' 
    41         assert unicode(thing_view.f.calc_password) == u'(Hidden)' 
     41        assert unicode(thing_view.f.calc_password) == u'(Encrypted)' 
    4242 
    4343 
  • trunk/Schevo/tests/test_field.py

    r3112 r3258  
    531531 
    532532 
    533 class TestPassword(object): 
     533class TestHashedPassword(object): 
    534534 
    535535    def test_unicode_representation(self): 
    536         f = field.Password(None, None) 
     536        f = field.HashedPassword(None, None) 
    537537        f.set('some-password') 
    538         assert unicode(f) == u'(Hidden)' 
     538        assert unicode(f) == u'(Encrypted)' 
     539 
     540    def test_unicode_values(self): 
     541        f = field.HashedPassword(None, None) 
     542        value = u'some-unicode-password-\ucafe' 
     543        f.set(value) 
     544        assert f.compare(value) 
     545 
     546 
     547class TestPasswordIsDeprecated(object): 
     548 
     549    """ 
     550    Change `showwarning` to log to a list instead of to stderr, so we 
     551    can test the deprecation warning below:: 
     552 
     553        >>> import warnings 
     554        >>> old_showwarning = warnings.showwarning 
     555        >>> captured_warnings = [] 
     556        >>> def showwarning(message, category, filename, lineno, file=None): 
     557        ...     captured_warnings.append((message, category, filename, lineno)) 
     558        >>> warnings.showwarning = showwarning 
     559 
     560    Create a schema that has a `password` field class:: 
     561 
     562        >>> body = ''' 
     563        ...     class Foo(E.Entity): 
     564        ... 
     565        ...         p = f.password() 
     566        ...     ''' 
     567 
     568    When using the schema, a deprecation warning is given for the `p` 
     569    field definition:: 
     570 
     571        >>> from schevo.test import DocTest 
     572        >>> len_before = len(captured_warnings) 
     573        >>> t = DocTest(body) 
     574        >>> len_after = len(captured_warnings) 
     575        >>> len_after - len_before 
     576        1 
     577        >>> message, category, filename, lineno = captured_warnings[-1] 
     578        >>> print str(message)  #doctest: +ELLIPSIS 
     579        'password' is a deprecated field type. ... 
     580 
     581    The line number that the warning is on appears to be line four 
     582    above, but since a two-line header is prepended to the body during 
     583    unit testing, it's actually line six that the warning occurs at:: 
     584 
     585        >>> lineno 
     586        6 
     587 
     588    Place the old `showwarning` function back into the `warnings` 
     589    module:: 
     590 
     591        >>> warnings.showwarning = old_showwarning 
     592 
     593    """ 
    539594 
    540595