[Zope3-dev] Mail delivery failed: returning message to sender

Mail Delivery System Mailer-Daemon at python.org
Tue Feb 17 09:03:28 EST 2004


This message was created automatically by mail delivery software.

A message that you sent could not be delivered to one or more of its
recipients. This is a permanent error. The following address(es) failed:

  corey at streamreel.net
    SMTP error from remote mailer after RCPT TO:<corey at streamreel.net>:
    host iris2.directnic.com [204.251.10.82]: 550 5.7.1 No such recipient

------ This is a copy of the message, including all the headers. ------

Return-path: <zope3-dev at zope.org>
Received: from cache1.zope.org ([12.155.117.38])
	by mail.python.org with esmtp (Exim 4.22)
	id 1At5nx-0007Nm-Lb; Tue, 17 Feb 2004 09:02:29 -0500
From: zope3-dev at zope.org (srichter)
Reply-To: zope3-dev at zope.org
To: ;
Subject: [NewPrincipalSourcePlugins] first draft (generated from LaTeX file)
Message-ID: <20040217090229EST at dev.zope.org>
X-BeenThere: zope3-dev at zope.org
X-Zwiki-Version: 0.21.1
Precedence: bulk
List-Id:  <zope3-dev at zope.org>
List-Post: <mailto:zope3-dev at zope.org>
List-Subscribe: <http://dev.zope.org/Wikis/DevSite/Projects/ComponentArchitecture/NewPrincipalSourcePlugins/subscribeform>
List-Unsubscribe: <http://dev.zope.org/Wikis/DevSite/Projects/ComponentArchitecture/NewPrincipalSourcePlugins/subscribeform>
List-Archive: <http://dev.zope.org/Wikis/DevSite/Projects/ComponentArchitecture/NewPrincipalSourcePlugins>
List-Help: <http://dev.zope.org/Wikis/DevSite/Projects/ComponentArchitecture>
Date: Tue, 17 Feb 2004 09:02:29 -0500
X-Spam-Status: OK (default 0.000)

++added:
  

??changed: - IsWorkInProgress - mainly an outline - - Author IsDraft

  Authors

??changed: - Difficulty - - SprinterLevel - - Skills - - - Basic
understanding of the Zope 3 Component Architecture - - - Understand
the purpose and differences between permissions, roles and -
principals. - - - Basic knowledge about the Authentication Service.
Optional. Difficulty

    Sprinter Level  

  Skills

    - You should have a basic understanding of the Zope 3 component
      architecture.

    - It is necessary to understand the purpose and differences
      between permissions, roles and principals.

    - Basic knowledge about the Authentication Service. Optional.  


??changed:
-    Many systems provide their own mechanisms for authentication. Examples
-    include /etc/passwd, NIS, Radius and relational databases. For a generic
-    platform like Zope it is therefore critically necessary to provide
-    facilities to connect to these external authentication sources. 
        Many systems provide their own mechanisms for authentication.
    Examples include '/etc/passwd' , NIS, Radius and relational
    databases. For a generic platform like Zope it is critically necessary to
    provide facilities to connect to these external authentication sources.

??changed: - register it with the Authentication Service. register it
with the Authentication Service .


??changed:
-    - while one can become very fancy in implementing a feature-rich principal
-      source implementation, we are concentrating here on the most simple
-      case. The exercises point out many of the improvements that can be done
-      later.
-
-    - It will be our goal to create a file-based principal source, so that it
-      could read a /etc/passwd-like file (it will not be able to read passwd
-      files, since we do not know whether everyone has the crypt module
-      installed on her/his machine)
-
-    - therefore, component should be able to read and parse files of the
-      format (users are seperated by a newline character)::
-
-        login:password:title:other_stuff
-
-    - An 'IPrincipalSource' component promises to implement three simple
-      methods:
-
-      o getPrincipal(id) - This method gets a particular principal by its id,
-[388 more lines...]
    While one can become very fancy in implementing a feature-rich
    principal source implementation, we are concentrating on the most simple
    case here. The exercises point out many of the improvements that can be
    done during later development.

    The goal of this recipe is to create a file-based principal source,
    so that it could read a '/etc/passwd' -like file (it will not
    actually be able to read passwd files, since we do not know whether
    everyone has the 'crypt' module installed on her/his machine). The
    format of the file that we want to be able to read and parse is (users
    are seperated by a newline character)::

      login:password:title:other_stuff
        Let's now turn to the principal source. A component implementing
    'IPrincipalSource' promises to provide three simple methods:  

    -'getPrincipal(id)' - This method gets a particular principal by
      its id, which is unique for this particular source. If no principal
      with the supplied id is found, a 'NotFoundError' is raised.

    -'getPrincipals(name)' - This method returns a list of principals
      whose login somehow contain the substring specified in 'name' .
      If 'name' is an empty string, all principals of this source are
      returned.

    -'authenticate(login, password)' - This method is required by the
      'ILoginPasswordPrincipalSource' interface and provides
      authentication for the provided principal. There are other ways to
      implement authentication for these principals, but they add unnecessary
      complexity. 'None' is returned, if no match is made.  


    The next step is to provide an implementation of 'IPrincipalSource' 
    for password files. Create a new sub-package called 'passwdauth' in
    the 'zope.products.demo' package. Now the first step is to define
    the interfaces, as usual.

    
  Defining the interface


    "What interface do we need?", you might wonder. In order for a
    file-based principle source plugin to provide principals, we need to know
    the file that contains the data and knowing about this file is certainly
    part of the API for this plugin. So we want to create a specific
    interface that contains a filename. If we make this attribute a schema
    field, we can even use the interface/schema to create autogenerated add
    and edit forms.

    In the 'passwdauth' directory add a 'interfaces.py' file and add the
    following contents::

      01 from zope.schema import TextLine
      02 from zope.app.i18n import ZopeMessageIDFactory as _
      04 from zope.app.interfaces.services.pluggableauth import IPrincipalSource
      06 class IFileBasedPrincipalSource(IPrincipalSource):
      07     """Describes file-based principal sources."""
      09     filename = TextLine(
      10         title = _(u'File Name'),
      11         description=_(u'File name of the data file.'),
      12         default = u'/etc/passwd')

    o Line 1: Usual imports of the TextLine field for the filename
      property.

    o Line 2: This is the typical I18n boilerplate (not much though);
      all text strings wrapped by the underscore function will be
      internationalized, or in other terms localizable.

    o Line 4-5: Our file-based principal source is still of type 'IPrincipalSource' 
      , so let's make it the base interface.

    o Line 10-14: Typical internationalized text line field
      declaration, making '/etc/passwd' the default value (even though
      the product will not work with this file due to the crypt issue). You
      might want to add a different default, also based on the operating
      system you are on.  


    
  Writing the tests


    The next step is to write some unit tests that assure that the file
    parser does its job right. But first we need to develop a small data file
    with which we can test the plugin with. Create a file called 'passwd.sample' 
    and add the following two principal entries::

      01 foo1:bar1:Foo Bar 1
      02 foo2:bar2:Foo Bar 2

    Now we have a user with login 'foo1' and one known as 'foo2' ,
    having 'bar1' and 'bar2' as passwords, respectively.

    In the following test code We will only test the aforementioned
    three methods of the principal source. The file reading code is not
    seperately checked, sinde it will be well tested through the other tests.

    Create a 'tests.py' file and add the code below.::

      01 import os
      02 from zope.products.demo import passwdauth
      03 from zope.exceptions import NotFoundError
      04 from unittest import TestCase, TestSuite, main, makeSuite
      06 class PasswdPrincipalSourceTest(TestCase):
      08     def setUp(self):
      09         dir = os.path.split(passwdauth.__file__)[0]
      10         self.source = passwdauth.PasswdPrincipalSource(
      11             os.path.join(dir, 'passwd.sample'))
      13     def test_getPrincipal(self):
      14         self.assertEqual(self.source.getPrincipal('\t\tfoo1').password, 'bar1')
      15         self.assertEqual(self.source.getPrincipal('\t\tfoo2').password, 'bar2')
      16         self.assertRaises(NotFoundError, self.source.getPrincipal, '\t\tfoo')
      18     def test_getPrincipals(self):
      19         self.assertEqual(len(self.source.getPrincipals('foo')), 2)
      20         self.assertEqual(len(self.source.getPrincipals('')), 2)
      21         self.assertEqual(len(self.source.getPrincipals('2')), 1)
      23     def test_authenticate(self):
      24         self.assertEqual(self.source.authenticate('foo1', 'bar1').id, 'foo1')
      25         self.assertEqual(self.source.authenticate('foo1', 'bar'), None)
      26         self.assertEqual(self.source.authenticate('foo', 'bar'), None)
      28 def test_suite():
      29     return TestSuite((
      30         makeSuite(PasswdPrincipalSourceTest),
      31         ))
      33 if __name__=='__main__':
      34     main(defaultTest='test_suite')

    o Line 1, 2, 9-11: The reason we imported 'os' and the 'tests' 
      package itself, was to be able to get to the directory of the code as
      seen in line 9. Once we have the directory it is easy to build up the
      data file path and initialize the principal source (line 10-11).

    o Line 13-16: Test the 'getPrincipal(id)' method. The last test
      checks that the correct error is thrown in case of a failure.

    o Line 18-21: The test for 'getPrincipals(name)' mainly tests that
      the resulting user list is correctly filtered based on the 'name' 
      parameter value.

    o Line 23-26: The authentication test concentrates on checking
      that really only a valid login name and password pair receives a
      positive authentication by returning the principal object.

    o Line 28-34: This is the usual test boiler plate.  


    You can now run the tests either using Zope's 'test.py' test runner
    or by executing the script directly; the latter method requires the
    Python path to be set correctly to 'ZOPE3/src' .

    
  Implementing the plugin


    The implementation of the plugin should be straight forward and bear
    no surprises. The tests already express all the necessary semantics. We
    only have not discussed the data structure of the principal itself yet.
    For it we can reuse the 'SimplePrincipal' , which is a basic 'IUser' 
    implementation that contains all the data fields 'IUserSchemafied' )
    relevant to a principal: id, login (username), password, title and
    description.

    Note that in Zope 3 the principal knows absolutely nothing about its
    roles, permissions or anything else about security. This information is
    handled by other components of the system and is subject to policy
    settings. Now we are ready to realize the principal source. In the '_' 
    file of the 'passwdauth' package we add the following
    implementation::

      01 import os
      02 from persistence import Persistent
      03 from zope.app.container.contained import Contained
      04 from zope.app.interfaces.services.pluggableauth import \
      05      ILoginPasswordPrincipalSource
      06 from zope.app.location import locate
      07 from zope.app.services.pluggableauth import SimplePrincipal
      08 from zope.exceptions import NotFoundError
      09 from zope.interface import implements
      10 from interfaces import IFileBasedPrincipalSource
      12 class PasswdPrincipalSource(Contained, Persistent):
      13     """A Principal Source for /etc/passwd-like files."""
      15     implements(ILoginPasswordPrincipalSource, 
      16                IFileBasedPrincipalSource)
      18     def __init__(self, filename=''):
      19         self.filename = filename
      21     def readPrincipals(self):
      22         if not os.path.exists(self.filename):
      23             return []
      24         file = open(self.filename, 'r')
      25         principals = []
      26         for line in file.readlines():
      27             if line.strip() != '':
      28                 user_info = line.strip().split(':', 3)
      29                 p = SimplePrincipal(*user_info)
[226 more lines...]

??changed: - - Exercise 2: The current implementation requires the
passwords to be - plain text, which is of course a huge security risk.
Implement a version - of the plugin that can handle plain text,
crypt-based and SHA - passwords. Implement a setting in the user
interface that let's the user - select one of the encryption types. -
- - Exercise 3: Implement a version of the plugin that provides a
write - interface, i.e. you should be able to add, edit and delete -
Exercise 2: The current implementation requires the passwords to be
plain text, which is of course a huge security risk. Implement a
version of the plugin that can handle plain text, crypt-based and SHA
passwords. Implement a setting in the user interface that let's the
user select one of the encryption types.

    - Exercise 3: Implement a version of the plugin that provides a
      write interface, i.e. you should be able to add, edit and delete

??changed: - - Exercise 4: It is very limiting to require a special
file format for - the principal data. It would be useful to develop a
method that allows - the user to specify the file format. Implement
this feature and provide - an intuitive user interface for this
feature. (This is a tough one; feel - free to make some assumptions to
solve the problem.) - - - Exercise 5: Reading in the user data for
every single authentication - call is quiet expensive, so it would be
helpful to implement some - caching features. This can be done in two
ways: (1) Use the caching - framework to implement a cached version of
the source or (2) save the - list of principals in a volatile
attribute (i.e. _v_principals) and - check for every call whether the
file had been modified since the last - time it was read. - Exercise
4: It is very limiting to require a special file format for the
principal data. It would be useful to develop a method that allows the
user to specify the file format. Implement this feature and provide an
intuitive user interface for this feature. (This is a tough one; feel
free to make some assumptions to solve the problem.)

    - Exercise 5: Reading in the user data for every single
      authentication call is quiet expensive, so it would be helpful to
      implement some caching features. This can be done in two ways: (1) Use
      the caching framework to implement a cached version of the source or
      (2) save the list of principals in a volatile attribute (i.e. '_' 
      ) and check for every call whether the file had been modified since the
      last time it was read.  
--
forwarded from http://dev.zope.org/Wikis/DevSite/Projects/ComponentArchitecture/NewPrincipalSourcePlugins



More information about the Zope3-dev mailing list