[Checkins] SVN: Products.PluggableAuthService/trunk/ Factored out 'filter' logic into separate classes

Tres Seaver tseaver at palladion.com
Mon Dec 3 01:25:51 EST 2007


Log message for revision 82087:
  Factored out 'filter' logic into separate classes
  
  o Added filters for 'startswith' test and (if the IPy module is present)
    IP-range tests.
  

Changed:
  U   Products.PluggableAuthService/trunk/Products/PluggableAuthService/doc/CHANGES.txt
  U   Products.PluggableAuthService/trunk/Products/PluggableAuthService/plugins/DomainAuthHelper.py
  U   Products.PluggableAuthService/trunk/Products/PluggableAuthService/plugins/tests/test_DomainAuthHelper.py
  U   Products.PluggableAuthService/trunk/Products/PluggableAuthService/plugins/tests/test_exportimport.py
  U   Products.PluggableAuthService/trunk/setup.py

-=-
Modified: Products.PluggableAuthService/trunk/Products/PluggableAuthService/doc/CHANGES.txt
===================================================================
--- Products.PluggableAuthService/trunk/Products/PluggableAuthService/doc/CHANGES.txt	2007-12-03 00:18:13 UTC (rev 82086)
+++ Products.PluggableAuthService/trunk/Products/PluggableAuthService/doc/CHANGES.txt	2007-12-03 06:25:49 UTC (rev 82087)
@@ -4,9 +4,34 @@
 
     Features Added
 
-    Bugs Fixed
-  
+      - Factored out 'filter' logic into separate classes;  added filters
+        for 'startswith' test and (if the IPy module is present) IP-range
+        tests.
 
+  PluggableAuthService 1.5.2 (2007/11/28)
+
+    Bugs fixed
+
+      - DomainAuthHelper plugin:  fix glitch for plugins which have never
+        configured any "default" policy:  'authenticateCredentials' and
+        'getRolesForPrincipal' would raise ValueError.
+        (http://www.zope.org/Collectors/PAS/59)
+
+  PluggableAuthService 1.5.1 (2007/09/11)
+
+    Bugs fixed
+
+      - PluggableAuthService._verifyUser: changed to use exact_match to the 
+        enumerator, otherwise a user with login 'foobar' might get returned 
+        by _verifyUser for a query for login='foo' because the enumerator 
+        happened to return 'foobar' first in the results.
+
+    Others
+
+      - Add a test for manage_zmi_logout and replace a call to isImplementedBy
+        with providedBy.
+        (http://www.zope.org/Collectors/PAS/58)
+
   PluggableAuthService 1.5 (06/17/2007)
 
     Features Added

Modified: Products.PluggableAuthService/trunk/Products/PluggableAuthService/plugins/DomainAuthHelper.py
===================================================================
--- Products.PluggableAuthService/trunk/Products/PluggableAuthService/plugins/DomainAuthHelper.py	2007-12-03 00:18:13 UTC (rev 82086)
+++ Products.PluggableAuthService/trunk/Products/PluggableAuthService/plugins/DomainAuthHelper.py	2007-12-03 06:25:49 UTC (rev 82087)
@@ -19,8 +19,17 @@
 __version__ = '$Revision$'[11:-2]
 
 # General Python imports
-import socket, os, time, copy, re
+import socket
+import os
+import time
+import copy
+import re
 
+try:
+    from IPy import IP
+except ImportError:
+    IP = None
+
 # General Zope imports
 from BTrees.OOBTree import OOBTree
 from Globals import InitializeClass
@@ -45,10 +54,60 @@
     """ Marker interface.
     """
 
-_MATCH_EQUALS = 'equals'
-_MATCH_ENDSWITH = 'endswith'
-_MATCH_REGEX = 'regex'
+class EqualsFilter:
 
+    def __init__(self, matchstring):
+        self.match_string = matchstring
+
+    def __call__(self, candidate):
+        return candidate == self.match_string
+
+class StartsWithFilter:
+
+    def __init__(self, matchstring):
+        self.match_string = matchstring
+
+    def __call__(self, candidate):
+        return candidate.startswith(self.match_string)
+
+class EndsWithFilter:
+
+    def __init__(self, matchstring):
+        self.match_string = matchstring
+
+    def __call__(self, candidate):
+        return candidate.endswith(self.match_string)
+
+class RegexFilter:
+
+    def __init__(self, matchstring):
+        self.regex = re.compile(matchstring)
+
+    def __call__(self, candidate):
+        return self.regex.match(candidate)
+
+_MATCH_TYPE_FILTERS = {
+    'equals': EqualsFilter,
+    'startswith': EndsWithFilter,
+    'endswith': EndsWithFilter,
+    'regex': RegexFilter,
+}
+
+if IP is not None:
+    class IPFilter:
+
+        def __init__(self, matchstring):
+            self.ip = IP(matchstring)
+
+        def __call__(self, candidate):
+            try:
+                c_ip = IP(candidate)
+            except ValueError:
+                return False
+            return c_ip in self.ip
+
+    _MATCH_TYPE_FILTERS['ip'] = IPFilter
+
 manage_addDomainAuthHelperForm = PageTemplateFile(
     'www/daAdd', globals(), __name__='manage_addDomainAuthHelperForm' )
 
@@ -194,16 +253,13 @@
         for match_info in all_info:
             m = []
             m_type = match_info['match_type']
-            m_real = match_info['match_real']
+            m_string = match_info['match_string']
+            filter = match_info.get('match_filter')
 
-            if m_type == _MATCH_EQUALS:
-                m = [match_info for x in candidates if x == m_real]
-            elif m_type == _MATCH_ENDSWITH:
-                m = [match_info for x in candidates if x.endswith(m_real)]
-            elif m_type == _MATCH_REGEX:
-                m = [match_info for x in candidates if m_real.search(x)]
+            if filter is None:  # legacy data
+                filter = _MATCH_TYPE_FILTERS[m_type](m_string)
 
-            matches.extend(m)
+            matches.extend([match_info for x in candidates if filter(x)])
 
         return tuple(matches)
 
@@ -211,7 +267,7 @@
     security.declareProtected(manage_users, 'listMatchTypes')
     def listMatchTypes(self):
         """ Return a sequence of possible match types """
-        return (_MATCH_EQUALS, _MATCH_ENDSWITH, _MATCH_REGEX)
+        return _MATCH_TYPE_FILTERS.keys()
 
 
     security.declareProtected(manage_users, 'listMappingsForUser')
@@ -243,18 +299,18 @@
         """ Add a mapping for a user """
         msg = ''
 
-        if match_type not in (_MATCH_EQUALS, _MATCH_ENDSWITH, _MATCH_REGEX):
+        try:
+            filter = _MATCH_TYPE_FILTERS[match_type](match_string)
+        except KeyError:
             msg = 'Unknown match type %s' % match_type
+        except re.error:
+            msg = 'Invalid regular expression %s' % match_string
+        except ValueError, e:
+            msg = 'Invalid match string %s (%s)' % (match_string, e)
 
         if not match_string:
             msg = 'No match string specified'
 
-        if match_type == _MATCH_REGEX:
-            try:
-                re.compile(match_string, re.IGNORECASE)
-            except re.error:
-                msg = 'Invalid regular expression %s' % match_string
-
         if msg:
             if REQUEST is not None:
                 return self.manage_map(manage_tabs_message=msg)
@@ -262,14 +318,10 @@
             raise ValueError, msg
 
         record = self._domain_map.get(user_id, [])
-        if match_type == _MATCH_REGEX:
-            real_match = re.compile(match_string, re.IGNORECASE)
-        else:
-            real_match = match_string
 
         match = { 'match_type' : match_type
                 , 'match_string' : match_string
-                , 'match_real' : real_match
+                , 'match_filter' : filter
                 , 'match_id' : '%s_%s' % (user_id, str(time.time()))
                 , 'username' : user_id or username or 'Remote User'
                 , 'roles' : roles

Modified: Products.PluggableAuthService/trunk/Products/PluggableAuthService/plugins/tests/test_DomainAuthHelper.py
===================================================================
--- Products.PluggableAuthService/trunk/Products/PluggableAuthService/plugins/tests/test_DomainAuthHelper.py	2007-12-03 00:18:13 UTC (rev 82086)
+++ Products.PluggableAuthService/trunk/Products/PluggableAuthService/plugins/tests/test_DomainAuthHelper.py	2007-12-03 06:25:49 UTC (rev 82087)
@@ -14,6 +14,11 @@
 ##############################################################################
 import unittest
 
+try:
+    from IPy import IP
+except ImportError:
+    IP = None
+
 from Products.PluggableAuthService.tests.conformance \
      import IExtractionPlugin_conformance
 from Products.PluggableAuthService.tests.conformance \
@@ -121,22 +126,16 @@
         self.assertEqual(helper.authenticateCredentials(creds), (None, None))
 
     def test_authenticateCredentials_w_mapping_known_remote_host(self):
-        from Products.PluggableAuthService.plugins.DomainAuthHelper \
-            import _MATCH_EQUALS
-
         creds = {'login': 'qux', 'remote_host': 'bam'}
         helper = self._makeOne()
-        helper.manage_addMapping(match_type=_MATCH_EQUALS, match_string='bam')
+        helper.manage_addMapping(match_type='equals', match_string='bam')
 
         self.assertEqual(helper.authenticateCredentials(creds), ('qux', 'qux'))
 
     def test_authenticateCredentials_w_mapping_known_remote_addr(self):
-        from Products.PluggableAuthService.plugins.DomainAuthHelper \
-            import _MATCH_ENDSWITH
-
         creds = {'login': 'qux', 'remote_address': 'baz'}
         helper = self._makeOne()
-        helper.manage_addMapping(match_type=_MATCH_ENDSWITH,
+        helper.manage_addMapping(match_type='endswith',
                                  match_string='z',
                                  username='foo',
                                 )
@@ -144,12 +143,9 @@
         self.assertEqual(helper.authenticateCredentials(creds), ('qux', 'qux'))
 
     def test_authenticateCredentials_w_mapping_no_login_known_remote_host(self):
-        from Products.PluggableAuthService.plugins.DomainAuthHelper \
-            import _MATCH_EQUALS
-
         creds = {'remote_host': 'baz'}
         helper = self._makeOne()
-        helper.manage_addMapping(match_type=_MATCH_EQUALS,
+        helper.manage_addMapping(match_type='equals',
                                  match_string='baz',
                                  username='foo',
                                 )
@@ -158,12 +154,138 @@
 
     # TODO  add tests for getRolesForPrincipal, etc.
 
+class EqualsFilterTests(unittest.TestCase):
 
+    def _getTargetClass(self):
+        from Products.PluggableAuthService.plugins.DomainAuthHelper \
+            import EqualsFilter
+        return EqualsFilter
+
+    def _makeOne(self, matchstring):
+        return self._getTargetClass()(matchstring)
+
+    def test_hit(self):
+        filter = self._makeOne('hitme')
+        self.failUnless(filter('hitme'))
+
+    def test_miss(self):
+        filter = self._makeOne('hitme')
+        self.failIf(filter('miss'))
+
+class StartsWithFilterTests(unittest.TestCase):
+
+    def _getTargetClass(self):
+        from Products.PluggableAuthService.plugins.DomainAuthHelper \
+            import StartsWithFilter
+        return StartsWithFilter
+
+    def _makeOne(self, matchstring):
+        return self._getTargetClass()(matchstring)
+
+    def test_hit_exact(self):
+        filter = self._makeOne('hitme')
+        self.failUnless(filter('hitme'))
+
+    def test_hit_prefix(self):
+        filter = self._makeOne('hit')
+        self.failUnless(filter('hitme'))
+
+    def test_miss(self):
+        filter = self._makeOne('hitme')
+        self.failIf(filter('miss'))
+
+class EndsWithFilterTests(unittest.TestCase):
+
+    def _getTargetClass(self):
+        from Products.PluggableAuthService.plugins.DomainAuthHelper \
+            import EndsWithFilter
+        return EndsWithFilter
+
+    def _makeOne(self, matchstring):
+        return self._getTargetClass()(matchstring)
+
+    def test_hit_exact(self):
+        filter = self._makeOne('hitme')
+        self.failUnless(filter('hitme'))
+
+    def test_hit_suffix(self):
+        filter = self._makeOne('tme')
+        self.failUnless(filter('hitme'))
+
+    def test_miss(self):
+        filter = self._makeOne('hitme')
+        self.failIf(filter('miss'))
+
+class RegexFilterTests(unittest.TestCase):
+
+    def _getTargetClass(self):
+        from Products.PluggableAuthService.plugins.DomainAuthHelper \
+            import RegexFilter
+        return RegexFilter
+
+    def _makeOne(self, matchstring):
+        return self._getTargetClass()(matchstring)
+
+    def test_hit_exact(self):
+        filter = self._makeOne('^hitme$')
+        self.failUnless(filter('hitme'))
+
+    def test_hit_pattern(self):
+        filter = self._makeOne('^h.*tme$')
+        self.failUnless(filter('hitme'))
+
+    def test_miss(self):
+        filter = self._makeOne('^hitme$')
+        self.failIf(filter('miss'))
+
+class IPFilterTests(unittest.TestCase):
+
+    def _getTargetClass(self):
+        from Products.PluggableAuthService.plugins.DomainAuthHelper \
+            import IPFilter
+        return IPFilter
+
+    def _makeOne(self, matchstring):
+        return self._getTargetClass()(matchstring)
+
+    def test_hit_exact(self):
+        filter = self._makeOne('192.168.1.24')
+        self.failUnless(filter('192.168.1.24'))
+
+    def test_miss_exact(self):
+        filter = self._makeOne('192.168.1.24')
+        self.failIf(filter('192.168.1.13'))
+
+    def test_hit_prefix(self):
+        filter = self._makeOne('192.168.1.0/24')
+        self.failUnless(filter('192.168.1.13'))
+
+    def test_miss_prefix(self):
+        filter = self._makeOne('192.168.1.0/24')
+        self.failIf(filter('192.168.0.13'))
+
+    def test_hit_range(self):
+        filter = self._makeOne('192.168.1.0-192.168.1.255')
+        self.failUnless(filter('192.168.1.13'))
+
+    def test_miss_range(self):
+        filter = self._makeOne('192.168.1.0-192.168.1.255')
+        self.failIf(filter('192.168.0.13'))
+
 if __name__ == "__main__":
     unittest.main()
 
 def test_suite():
-    return unittest.TestSuite((
+    tests = (
         unittest.makeSuite( DomainAuthHelperTests ),
-        ))
+        unittest.makeSuite( EqualsFilterTests ),
+        unittest.makeSuite( StartsWithFilterTests ),
+        unittest.makeSuite( EndsWithFilterTests ),
+        unittest.makeSuite( RegexFilterTests ),
+        )
 
+    if IP is not None:
+        tests += (unittest.makeSuite( IPFilterTests ),)
+
+    return unittest.TestSuite(tests)
+

Modified: Products.PluggableAuthService/trunk/Products/PluggableAuthService/plugins/tests/test_exportimport.py
===================================================================
--- Products.PluggableAuthService/trunk/Products/PluggableAuthService/plugins/tests/test_exportimport.py	2007-12-03 00:18:13 UTC (rev 82086)
+++ Products.PluggableAuthService/trunk/Products/PluggableAuthService/plugins/tests/test_exportimport.py	2007-12-03 06:25:49 UTC (rev 82087)
@@ -704,6 +704,8 @@
             self.assertEqual( content_type, 'text/xml' )
 
         def test_import_empty(self):
+            from Products.PluggableAuthService.plugins.DomainAuthHelper \
+                import EqualsFilter
             TITLE = 'With Map'
             USER_ID = 'some_user_id'
             DOMAIN = 'host.example.com'
@@ -733,7 +735,9 @@
             match = match_list[0]
             self.assertEqual(match['username'], USER_ID)
             self.assertEqual(match['match_string'], DOMAIN)
-            self.assertEqual(match['match_real'], DOMAIN)
+            filter = match['match_filter']
+            self.failUnless(isinstance(filter, EqualsFilter))
+            self.assertEquals(filter.match_string, DOMAIN)
             self.assertEqual(match['match_type'], 'equals')
             self.assertEqual(len(match['roles']), len(ROLES))
             for role in ROLES:

Modified: Products.PluggableAuthService/trunk/setup.py
===================================================================
--- Products.PluggableAuthService/trunk/setup.py	2007-12-03 00:18:13 UTC (rev 82086)
+++ Products.PluggableAuthService/trunk/setup.py	2007-12-03 06:25:49 UTC (rev 82087)
@@ -49,6 +49,8 @@
       namespace_packages=['Products'],
       zip_safe=False,
       #install_requires=['Zope >= 2.10']
+      extras_require={'ip_range': ['IPy'],
+                     },
       entry_points="""
       [zope2.initialize]
       Products.PluggableAuthService = Products.PluggableAuthService:initialize



More information about the Checkins mailing list